From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001
From: "Matt A. Tobin" <mattatobin@localhost.localdomain>
Date: Fri, 2 Feb 2018 04:16:08 -0500
Subject: Add m-esr52 at 52.6.0

---
 toolkit/components/formautofill/FormAutofill.jsm   |  85 +++++++
 .../formautofill/FormAutofillContentService.js     | 272 +++++++++++++++++++++
 .../formautofill/FormAutofillIntegration.jsm       |  62 +++++
 .../components/formautofill/FormAutofillStartup.js |  64 +++++
 .../formautofill/content/RequestAutocompleteUI.jsm |  58 +++++
 .../formautofill/content/requestAutocomplete.js    |  85 +++++++
 .../formautofill/content/requestAutocomplete.xhtml |  31 +++
 .../components/formautofill/formautofill.manifest  |   7 +
 toolkit/components/formautofill/jar.mn             |   8 +
 toolkit/components/formautofill/moz.build          |  46 ++++
 .../formautofill/nsIFormAutofillContentService.idl |  46 ++++
 .../formautofill/test/browser/.eslintrc.js         |   7 +
 .../formautofill/test/browser/browser.ini          |  10 +
 .../test/browser/browser_infrastructure.js         |  48 ++++
 .../test/browser/browser_ui_requestAutocomplete.js |  48 ++++
 .../components/formautofill/test/browser/head.js   |  18 ++
 .../components/formautofill/test/browser/loader.js |  38 +++
 .../formautofill/test/chrome/.eslintrc.js          |   7 +
 .../components/formautofill/test/chrome/chrome.ini |  17 ++
 .../components/formautofill/test/chrome/head.js    |  15 ++
 .../components/formautofill/test/chrome/loader.js  | 116 +++++++++
 .../formautofill/test/chrome/loader_parent.js      |  77 ++++++
 .../test/chrome/test_infrastructure.html           |   8 +
 .../test/chrome/test_infrastructure.js             |  61 +++++
 .../chrome/test_requestAutocomplete_cancel.html    |   9 +
 .../test/chrome/test_requestAutocomplete_cancel.js |  26 ++
 .../components/formautofill/test/head_common.js    | 245 +++++++++++++++++++
 .../components/formautofill/test/loader_common.js  | 120 +++++++++
 .../formautofill/test/xpcshell/.eslintrc.js        |   7 +
 .../components/formautofill/test/xpcshell/head.js  |  23 ++
 .../formautofill/test/xpcshell/loader.js           |  46 ++++
 .../test/xpcshell/test_infrastructure.js           |  48 ++++
 .../formautofill/test/xpcshell/test_integration.js |  72 ++++++
 .../formautofill/test/xpcshell/xpcshell.ini        |  12 +
 34 files changed, 1842 insertions(+)
 create mode 100644 toolkit/components/formautofill/FormAutofill.jsm
 create mode 100644 toolkit/components/formautofill/FormAutofillContentService.js
 create mode 100644 toolkit/components/formautofill/FormAutofillIntegration.jsm
 create mode 100644 toolkit/components/formautofill/FormAutofillStartup.js
 create mode 100644 toolkit/components/formautofill/content/RequestAutocompleteUI.jsm
 create mode 100644 toolkit/components/formautofill/content/requestAutocomplete.js
 create mode 100644 toolkit/components/formautofill/content/requestAutocomplete.xhtml
 create mode 100644 toolkit/components/formautofill/formautofill.manifest
 create mode 100644 toolkit/components/formautofill/jar.mn
 create mode 100644 toolkit/components/formautofill/moz.build
 create mode 100644 toolkit/components/formautofill/nsIFormAutofillContentService.idl
 create mode 100644 toolkit/components/formautofill/test/browser/.eslintrc.js
 create mode 100644 toolkit/components/formautofill/test/browser/browser.ini
 create mode 100644 toolkit/components/formautofill/test/browser/browser_infrastructure.js
 create mode 100644 toolkit/components/formautofill/test/browser/browser_ui_requestAutocomplete.js
 create mode 100644 toolkit/components/formautofill/test/browser/head.js
 create mode 100644 toolkit/components/formautofill/test/browser/loader.js
 create mode 100644 toolkit/components/formautofill/test/chrome/.eslintrc.js
 create mode 100644 toolkit/components/formautofill/test/chrome/chrome.ini
 create mode 100644 toolkit/components/formautofill/test/chrome/head.js
 create mode 100644 toolkit/components/formautofill/test/chrome/loader.js
 create mode 100644 toolkit/components/formautofill/test/chrome/loader_parent.js
 create mode 100644 toolkit/components/formautofill/test/chrome/test_infrastructure.html
 create mode 100644 toolkit/components/formautofill/test/chrome/test_infrastructure.js
 create mode 100644 toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.html
 create mode 100644 toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.js
 create mode 100644 toolkit/components/formautofill/test/head_common.js
 create mode 100644 toolkit/components/formautofill/test/loader_common.js
 create mode 100644 toolkit/components/formautofill/test/xpcshell/.eslintrc.js
 create mode 100644 toolkit/components/formautofill/test/xpcshell/head.js
 create mode 100644 toolkit/components/formautofill/test/xpcshell/loader.js
 create mode 100644 toolkit/components/formautofill/test/xpcshell/test_infrastructure.js
 create mode 100644 toolkit/components/formautofill/test/xpcshell/test_integration.js
 create mode 100644 toolkit/components/formautofill/test/xpcshell/xpcshell.ini

(limited to 'toolkit/components/formautofill')

diff --git a/toolkit/components/formautofill/FormAutofill.jsm b/toolkit/components/formautofill/FormAutofill.jsm
new file mode 100644
index 000000000..aae3a956c
--- /dev/null
+++ b/toolkit/components/formautofill/FormAutofill.jsm
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Main module handling references to objects living in the main process.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "FormAutofill",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Integration.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+/**
+ * Main module handling references to objects living in the main process.
+ */
+this.FormAutofill = {
+  /**
+   * Registers new overrides for the FormAutofillIntegration methods.  Example:
+   *
+   *   FormAutofill.registerIntegration(base => ({
+   *     createRequestAutocompleteUI: Task.async(function* () {
+   *       yield base.createRequestAutocompleteUI.apply(this, arguments);
+   *     }),
+   *   }));
+   *
+   * @param aIntegrationFn
+   *        Function returning an object defining the methods that should be
+   *        overridden.  Its only parameter is an object that contains the base
+   *        implementation of all the available methods.
+   *
+   * @note The integration function is called every time the list of registered
+   *       integration functions changes.  Thus, it should not have any side
+   *       effects or do any other initialization.
+   */
+  registerIntegration(aIntegrationFn) {
+    Integration.formAutofill.register(aIntegrationFn);
+  },
+
+  /**
+   * Removes a previously registered FormAutofillIntegration override.
+   *
+   * Overrides don't usually need to be unregistered, unless they are added by a
+   * restartless add-on, in which case they should be unregistered when the
+   * add-on is disabled or uninstalled.
+   *
+   * @param aIntegrationFn
+   *        This must be the same function object passed to registerIntegration.
+   */
+  unregisterIntegration(aIntegrationFn) {
+    Integration.formAutofill.unregister(aIntegrationFn);
+  },
+
+  /**
+   * Processes a requestAutocomplete message asynchronously.
+   *
+   * @param aData
+   *        Provided to FormAutofillIntegration.createRequestAutocompleteUI.
+   *
+   * @return {Promise}
+   * @resolves Structured data received from the requestAutocomplete UI.
+   */
+  processRequestAutocomplete: Task.async(function* (aData) {
+    let ui = yield FormAutofill.integration.createRequestAutocompleteUI(aData);
+    return yield ui.show();
+  }),
+};
+
+/**
+ * Dynamically generated object implementing the FormAutofillIntegration
+ * methods.  Platform-specific code and add-ons can override methods of this
+ * object using the registerIntegration method.
+ */
+Integration.formAutofill.defineModuleGetter(
+  this.FormAutofill,
+  "integration",
+  "resource://gre/modules/FormAutofillIntegration.jsm",
+  "FormAutofillIntegration"
+);
diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js
new file mode 100644
index 000000000..ee8e978ad
--- /dev/null
+++ b/toolkit/components/formautofill/FormAutofillContentService.js
@@ -0,0 +1,272 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implements a service used by DOM content to request Form Autofill, in
+ * particular when the requestAutocomplete method of Form objects is invoked.
+ *
+ * See the nsIFormAutofillContentService documentation for details.
+ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofill",
+                                  "resource://gre/modules/FormAutofill.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+/**
+ * Handles requestAutocomplete for a DOM Form element.
+ */
+function FormHandler(aForm, aWindow) {
+  this.form = aForm;
+  this.window = aWindow;
+
+  this.fieldDetails = [];
+}
+
+FormHandler.prototype = {
+  /**
+   * DOM Form element to which this object is attached.
+   */
+  form: null,
+
+  /**
+   * nsIDOMWindow to which this object is attached.
+   */
+  window: null,
+
+  /**
+   * Array of collected data about relevant form fields.  Each item is an object
+   * storing the identifying details of the field and a reference to the
+   * originally associated element from the form.
+   *
+   * The "section", "addressType", "contactType", and "fieldName" values are
+   * used to identify the exact field when the serializable data is received
+   * from the requestAutocomplete user interface.  There cannot be multiple
+   * fields which have the same exact combination of these values.
+   *
+   * A direct reference to the associated element cannot be sent to the user
+   * interface because processing may be done in the parent process.
+   */
+  fieldDetails: null,
+
+  /**
+   * Handles requestAutocomplete and generates the DOM events when finished.
+   */
+  handleRequestAutocomplete: Task.async(function* () {
+    // Start processing the request asynchronously.  At the end, the "reason"
+    // variable will contain the outcome of the operation, where an empty
+    // string indicates that an unexpected exception occurred.
+    let reason = "";
+    try {
+      reason = yield this.promiseRequestAutocomplete();
+    } catch (ex) {
+      Cu.reportError(ex);
+    }
+
+    // The type of event depends on whether this is a success condition.
+    let event = (reason == "success")
+                ? new this.window.Event("autocomplete", { bubbles: true })
+                : new this.window.AutocompleteErrorEvent("autocompleteerror",
+                                                         { bubbles: true,
+                                                           reason: reason });
+    yield this.waitForTick();
+    this.form.dispatchEvent(event);
+  }),
+
+  /**
+   * Handles requestAutocomplete and returns the outcome when finished.
+   *
+   * @return {Promise}
+   * @resolves The "reason" value indicating the outcome of the
+   *           requestAutocomplete operation, including "success" if the
+   *           operation completed successfully.
+   */
+  promiseRequestAutocomplete: Task.async(function* () {
+    let data = this.collectFormFields();
+    if (!data) {
+      return "disabled";
+    }
+
+    // Access the frame message manager of the window starting the request.
+    let rootDocShell = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIDocShell)
+                                  .sameTypeRootTreeItem
+                                  .QueryInterface(Ci.nsIDocShell);
+    let frameMM = rootDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIContentFrameMessageManager);
+
+    // We need to set up a temporary message listener for our result before we
+    // send the request to the parent process.  At present, there is no check
+    // for reentrancy (bug 1020459), thus it is possible that we'll receive a
+    // message for a different request, but this will not be normally allowed.
+    let promiseRequestAutocompleteResult = new Promise((resolve, reject) => {
+      frameMM.addMessageListener("FormAutofill:RequestAutocompleteResult",
+        function onResult(aMessage) {
+          frameMM.removeMessageListener(
+                        "FormAutofill:RequestAutocompleteResult", onResult);
+          // Exceptions in the parent process are serialized and propagated in
+          // the response message that we received.
+          if ("exception" in aMessage.data) {
+            reject(aMessage.data.exception);
+          } else {
+            resolve(aMessage.data);
+          }
+        });
+    });
+
+    // Send the message to the parent process, and wait for the result.  This
+    // will throw an exception if one occurred in the parent process.
+    frameMM.sendAsyncMessage("FormAutofill:RequestAutocomplete", data);
+    let result = yield promiseRequestAutocompleteResult;
+    if (result.canceled) {
+      return "cancel";
+    }
+
+    this.autofillFormFields(result);
+
+    return "success";
+  }),
+
+  /**
+   * Returns information from the form about fields that can be autofilled, and
+   * populates the fieldDetails array on this object accordingly.
+   *
+   * @returns Serializable data structure that can be sent to the user
+   *          interface, or null if the operation failed because the constraints
+   *          on the allowed fields were not honored.
+   */
+  collectFormFields: function () {
+    let autofillData = {
+      sections: [],
+    };
+
+    for (let element of this.form.elements) {
+      // Query the interface and exclude elements that cannot be autocompleted.
+      if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
+        continue;
+      }
+
+      // Exclude elements to which no autocomplete field has been assigned.
+      let info = element.getAutocompleteInfo();
+      if (!info.fieldName || ["on", "off"].indexOf(info.fieldName) != -1) {
+        continue;
+      }
+
+      // Store the association between the field metadata and the element.
+      if (this.fieldDetails.some(f => f.section == info.section &&
+                                      f.addressType == info.addressType &&
+                                      f.contactType == info.contactType &&
+                                      f.fieldName == info.fieldName)) {
+        // A field with the same identifier already exists.
+        return null;
+      }
+      this.fieldDetails.push({
+        section: info.section,
+        addressType: info.addressType,
+        contactType: info.contactType,
+        fieldName: info.fieldName,
+        element: element,
+      });
+
+      // The first level is the custom section.
+      let section = autofillData.sections
+                                .find(s => s.name == info.section);
+      if (!section) {
+        section = {
+          name: info.section,
+          addressSections: [],
+        };
+        autofillData.sections.push(section);
+      }
+
+      // The second level is the address section.
+      let addressSection = section.addressSections
+                                  .find(s => s.addressType == info.addressType);
+      if (!addressSection) {
+        addressSection = {
+          addressType: info.addressType,
+          fields: [],
+        };
+        section.addressSections.push(addressSection);
+      }
+
+      // The third level contains all the fields within the section.
+      let field = {
+        fieldName: info.fieldName,
+        contactType: info.contactType,
+      };
+      addressSection.fields.push(field);
+    }
+
+    return autofillData;
+  },
+
+  /**
+   * Processes form fields that can be autofilled, and populates them with the
+   * data provided by RequestAutocompleteUI.
+   *
+   * @param aAutofillResult
+   *        Data returned by the user interface.
+   *        {
+   *          fields: [
+   *            section: Value originally provided to the user interface.
+   *            addressType: Value originally provided to the user interface.
+   *            contactType: Value originally provided to the user interface.
+   *            fieldName: Value originally provided to the user interface.
+   *            value: String with which the field should be updated.
+   *          ],
+   *        }
+   */
+  autofillFormFields: function (aAutofillResult) {
+    for (let field of aAutofillResult.fields) {
+      // Get the field details, if it was processed by the user interface.
+      let fieldDetail = this.fieldDetails
+                            .find(f => f.section == field.section &&
+                                       f.addressType == field.addressType &&
+                                       f.contactType == field.contactType &&
+                                       f.fieldName == field.fieldName);
+      if (!fieldDetail) {
+        continue;
+      }
+
+      fieldDetail.element.value = field.value;
+    }
+  },
+
+  /**
+   * Waits for one tick of the event loop before resolving the returned promise.
+   */
+  waitForTick: function () {
+    return new Promise(function (resolve) {
+      Services.tm.currentThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+    });
+  },
+};
+
+/**
+ * Implements a service used by DOM content to request Form Autofill, in
+ * particular when the requestAutocomplete method of Form objects is invoked.
+ */
+function FormAutofillContentService() {
+}
+
+FormAutofillContentService.prototype = {
+  classID: Components.ID("{ed9c2c3c-3f86-4ae5-8e31-10f71b0f19e6}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormAutofillContentService]),
+
+  // nsIFormAutofillContentService
+  requestAutocomplete: function (aForm, aWindow) {
+    new FormHandler(aForm, aWindow).handleRequestAutocomplete()
+                                   .catch(Cu.reportError);
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormAutofillContentService]);
diff --git a/toolkit/components/formautofill/FormAutofillIntegration.jsm b/toolkit/components/formautofill/FormAutofillIntegration.jsm
new file mode 100644
index 000000000..4b838a6ab
--- /dev/null
+++ b/toolkit/components/formautofill/FormAutofillIntegration.jsm
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This module defines the default implementation of platform-specific functions
+ * that can be overridden by the host application and by add-ons.
+ *
+ * This module should not be imported directly, but the "integration" getter of
+ * the FormAutofill module should be used to get a reference to the currently
+ * defined implementations of the methods.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "FormAutofillIntegration",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RequestAutocompleteUI",
+                                  "resource://gre/modules/RequestAutocompleteUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+/**
+ * This module defines the default implementation of platform-specific functions
+ * that can be overridden by the host application and by add-ons.
+ */
+this.FormAutofillIntegration = {
+  /**
+   * Creates a new RequestAutocompleteUI object.
+   *
+   * @param aAutofillData
+   *        Provides the initial data required to display the user interface.
+   *        {
+   *          sections: [{
+   *            name: User-specified section name, or empty string.
+   *            addressSections: [{
+   *              addressType: "shipping", "billing", or empty string.
+   *              fields: [{
+   *                fieldName: Type of information requested, like "email".
+   *                contactType: For example "work", "home", or empty string.
+   *              }],
+   *            }],
+   *          }],
+   *        }
+   *
+   * @return {Promise}
+   * @resolves The newly created RequestAutocompleteUI object.
+   * @rejects JavaScript exception.
+   */
+  createRequestAutocompleteUI: Task.async(function* (aAutofillData) {
+    return new RequestAutocompleteUI(aAutofillData);
+  }),
+};
diff --git a/toolkit/components/formautofill/FormAutofillStartup.js b/toolkit/components/formautofill/FormAutofillStartup.js
new file mode 100644
index 000000000..92887f872
--- /dev/null
+++ b/toolkit/components/formautofill/FormAutofillStartup.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Handles startup in the parent process.
+ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofill",
+                                  "resource://gre/modules/FormAutofill.jsm");
+
+/**
+ * Handles startup in the parent process.
+ */
+function FormAutofillStartup() {
+}
+
+FormAutofillStartup.prototype = {
+  classID: Components.ID("{51c95b3d-7431-467b-8d50-383f158ce9e5}"),
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIFrameMessageListener,
+    Ci.nsIObserver,
+    Ci.nsISupportsWeakReference,
+  ]),
+
+  // nsIObserver
+  observe: function (aSubject, aTopic, aData) {
+    // This method is called by the "profile-after-change" category on startup,
+    // which is called before any web page loads.  At this time, we need to
+    // register a global message listener in the parent process preemptively,
+    // because we can receive requests from child processes at any time.  For
+    // performance reasons, we use this object as a message listener, so that we
+    // don't have to load the FormAutoFill module at startup.
+    let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+                     .getService(Ci.nsIMessageListenerManager);
+    globalMM.addMessageListener("FormAutofill:RequestAutocomplete", this);
+  },
+
+  // nsIFrameMessageListener
+  receiveMessage: function (aMessage) {
+    // Process the "FormAutofill:RequestAutocomplete" message.  Any exception
+    // raised in the parent process is caught and serialized into the reply
+    // message that is sent to the requesting child process.
+    FormAutofill.processRequestAutocomplete(aMessage.data)
+      .catch(ex => { return { exception: ex } })
+      .then(result => {
+        // The browser message manager in the parent will send the reply to the
+        // associated frame message manager in the child.
+        let browserMM = aMessage.target.messageManager;
+        browserMM.sendAsyncMessage("FormAutofill:RequestAutocompleteResult",
+                                   result);
+      })
+      .catch(Cu.reportError);
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormAutofillStartup]);
diff --git a/toolkit/components/formautofill/content/RequestAutocompleteUI.jsm b/toolkit/components/formautofill/content/RequestAutocompleteUI.jsm
new file mode 100644
index 000000000..74c4834ba
--- /dev/null
+++ b/toolkit/components/formautofill/content/RequestAutocompleteUI.jsm
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Handles the requestAutocomplete user interface.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "RequestAutocompleteUI",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+/**
+ * Handles the requestAutocomplete user interface.
+ */
+this.RequestAutocompleteUI = function (aAutofillData) {
+  this._autofillData = aAutofillData;
+}
+
+this.RequestAutocompleteUI.prototype = {
+  _autofillData: null,
+
+  show: Task.async(function* () {
+    // Create a new promise and store the function that will resolve it.  This
+    // will be called by the UI once the selection has been made.
+    let resolveFn;
+    let uiPromise = new Promise(resolve => resolveFn = resolve);
+
+    // Wrap the callback function so that it survives XPCOM.
+    let args = {
+      resolveFn: resolveFn,
+      autofillData: this._autofillData,
+    };
+    args.wrappedJSObject = args;
+
+    // Open the window providing the function to call when it closes.
+    Services.ww.openWindow(null,
+                           "chrome://formautofill/content/requestAutocomplete.xhtml",
+                           "Toolkit:RequestAutocomplete",
+                           "chrome,dialog=no,resizable",
+                           args);
+
+    // Wait for the window to be closed and the operation confirmed.
+    return yield uiPromise;
+  }),
+};
diff --git a/toolkit/components/formautofill/content/requestAutocomplete.js b/toolkit/components/formautofill/content/requestAutocomplete.js
new file mode 100644
index 000000000..47d500964
--- /dev/null
+++ b/toolkit/components/formautofill/content/requestAutocomplete.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of "requestAutocomplete.xhtml".
+ */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+const RequestAutocompleteDialog = {
+  resolveFn: null,
+  autofillData: null,
+
+  onLoad: function () {
+    Task.spawn(function* () {
+      let args = window.arguments[0].wrappedJSObject;
+      this.resolveFn = args.resolveFn;
+      this.autofillData = args.autofillData;
+
+      window.sizeToContent();
+
+      Services.obs.notifyObservers(window,
+                                   "formautofill-window-initialized", "");
+    }.bind(this)).catch(Cu.reportError);
+  },
+
+  onAccept: function () {
+    // TODO: Replace with autofill storage module (bug 1018304).
+    const dummyDB = {
+      "": {
+        "name": "Mozzy La",
+        "street-address": "331 E Evelyn Ave",
+        "address-level2": "Mountain View",
+        "address-level1": "CA",
+        "country": "US",
+        "postal-code": "94041",
+        "email": "email@example.org",
+      }
+    };
+
+    let result = { fields: [] };
+    for (let section of this.autofillData.sections) {
+      for (let addressSection of section.addressSections) {
+        let addressType = addressSection.addressType;
+        if (!(addressType in dummyDB)) {
+          continue;
+        }
+
+        for (let field of addressSection.fields) {
+          let fieldName = field.fieldName;
+          if (!(fieldName in dummyDB[addressType])) {
+            continue;
+          }
+
+          result.fields.push({
+            section: section.name,
+            addressType: addressType,
+            contactType: field.contactType,
+            fieldName: field.fieldName,
+            value: dummyDB[addressType][fieldName],
+          });
+        }
+      }
+    }
+
+    window.close();
+    this.resolveFn(result);
+  },
+
+  onCancel: function () {
+    window.close();
+    this.resolveFn({ canceled: true });
+  },
+};
diff --git a/toolkit/components/formautofill/content/requestAutocomplete.xhtml b/toolkit/components/formautofill/content/requestAutocomplete.xhtml
new file mode 100644
index 000000000..269e55bd6
--- /dev/null
+++ b/toolkit/components/formautofill/content/requestAutocomplete.xhtml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % requestAutocompleteDTD SYSTEM "chrome://formautofill/locale/requestAutocomplete.dtd">
+  %requestAutocompleteDTD;
+  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
+  %globalDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>requestAutocomplete demo window</title>
+    <link rel="stylesheet"
+          href="chrome://mozapps/skin/formautofill/requestAutocomplete.css" />
+    <script type="application/javascript;version=1.7"
+            src="chrome://formautofill/content/requestAutocomplete.js" />
+  </head>
+  <body dir="&locale.dir;" onload="RequestAutocompleteDialog.onLoad();">
+    <h1>requestAutocomplete</h1>
+    <p>This is a demo window.</p>
+    <input id="accept" type="button" value="(OK)"
+           onclick="RequestAutocompleteDialog.onAccept();" />
+    <input id="cancel" type="button" value="(Cancel)"
+           onclick="RequestAutocompleteDialog.onCancel();" />
+  </body>
+</html>
diff --git a/toolkit/components/formautofill/formautofill.manifest b/toolkit/components/formautofill/formautofill.manifest
new file mode 100644
index 000000000..880972edc
--- /dev/null
+++ b/toolkit/components/formautofill/formautofill.manifest
@@ -0,0 +1,7 @@
+component {ed9c2c3c-3f86-4ae5-8e31-10f71b0f19e6} FormAutofillContentService.js
+contract @mozilla.org/formautofill/content-service;1 {ed9c2c3c-3f86-4ae5-8e31-10f71b0f19e6}
+component {51c95b3d-7431-467b-8d50-383f158ce9e5} FormAutofillStartup.js
+contract @mozilla.org/formautofill/startup;1 {51c95b3d-7431-467b-8d50-383f158ce9e5}
+#ifdef NIGHTLY_BUILD
+category profile-after-change FormAutofillStartup @mozilla.org/formautofill/startup;1
+#endif
diff --git a/toolkit/components/formautofill/jar.mn b/toolkit/components/formautofill/jar.mn
new file mode 100644
index 000000000..ebe869b58
--- /dev/null
+++ b/toolkit/components/formautofill/jar.mn
@@ -0,0 +1,8 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+% content formautofill %content/formautofill/
+  content/formautofill/requestAutocomplete.js      (content/requestAutocomplete.js)
+  content/formautofill/requestAutocomplete.xhtml   (content/requestAutocomplete.xhtml)
diff --git a/toolkit/components/formautofill/moz.build b/toolkit/components/formautofill/moz.build
new file mode 100644
index 000000000..2c2179f81
--- /dev/null
+++ b/toolkit/components/formautofill/moz.build
@@ -0,0 +1,46 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['NIGHTLY_BUILD']:
+    BROWSER_CHROME_MANIFESTS += [
+        'test/browser/browser.ini',
+    ]
+
+    MOCHITEST_CHROME_MANIFESTS += [
+        'test/chrome/chrome.ini',
+    ]
+
+XPCSHELL_TESTS_MANIFESTS += [
+    'test/xpcshell/xpcshell.ini',
+]
+
+XPIDL_SOURCES += [
+    'nsIFormAutofillContentService.idl',
+]
+
+XPIDL_MODULE = 'toolkit_formautofill'
+
+EXTRA_COMPONENTS += [
+    'FormAutofillContentService.js',
+    'FormAutofillStartup.js',
+]
+
+EXTRA_PP_COMPONENTS += [
+    'formautofill.manifest',
+]
+
+EXTRA_JS_MODULES += [
+    'content/RequestAutocompleteUI.jsm',
+    'FormAutofill.jsm',
+    'FormAutofillIntegration.jsm',
+]
+
+JAR_MANIFESTS += [
+    'jar.mn',
+]
+
+with Files('**'):
+    BUG_COMPONENT = ('Toolkit', 'Form Manager')
diff --git a/toolkit/components/formautofill/nsIFormAutofillContentService.idl b/toolkit/components/formautofill/nsIFormAutofillContentService.idl
new file mode 100644
index 000000000..300645e74
--- /dev/null
+++ b/toolkit/components/formautofill/nsIFormAutofillContentService.idl
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMHTMLFormElement;
+interface nsIDOMWindow;
+
+/**
+ * Defines a service used by DOM content to request Form Autofill, in particular
+ * when the requestAutocomplete method of Form objects is invoked.
+ *
+ * This service lives in the process that hosts the requesting DOM content.
+ * This means that, in a multi-process (e10s) environment, there can be an
+ * instance of the service for each content process, in addition to an instance
+ * for the chrome process.
+ *
+ * @remarks The service implementation uses a child-side message manager to
+ *          communicate with a parent-side message manager living in the chrome
+ *          process, where most of the processing is located.
+ */
+[scriptable, uuid(1db29340-99df-4845-9102-0c5d281b2fe8)]
+interface nsIFormAutofillContentService : nsISupports
+{
+  /**
+   * Invoked by the requestAutocomplete method of the DOM Form object.
+   *
+   * The application is expected to display a user interface asking for the
+   * details that are relevant to the form being filled in.  The application
+   * should use the "autocomplete" attributes on the input elements as hints
+   * about which type of information is being requested.
+   *
+   * The processing will result in either an "autocomplete" simple DOM Event or
+   * an AutocompleteErrorEvent being fired on the form.
+   *
+   * @param aForm
+   *        The form on which the requestAutocomplete method was invoked.
+   * @param aWindow
+   *        The window where the form is located.  This must be specified even
+   *        for elements that are not in a document, and is used to generate the
+   *        DOM events resulting from the operation.
+   */
+  void requestAutocomplete(in nsIDOMHTMLFormElement aForm,
+                           in nsIDOMWindow aWindow);
+};
diff --git a/toolkit/components/formautofill/test/browser/.eslintrc.js b/toolkit/components/formautofill/test/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/toolkit/components/formautofill/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/mochitest/browser.eslintrc.js"
+  ]
+};
diff --git a/toolkit/components/formautofill/test/browser/browser.ini b/toolkit/components/formautofill/test/browser/browser.ini
new file mode 100644
index 000000000..dff9c3381
--- /dev/null
+++ b/toolkit/components/formautofill/test/browser/browser.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+# The following files starting with ".." are installed in the current folder.
+support-files =
+  ../head_common.js
+  ../loader_common.js
+  head.js
+  loader.js
+
+[browser_infrastructure.js]
+[browser_ui_requestAutocomplete.js]
diff --git a/toolkit/components/formautofill/test/browser/browser_infrastructure.js b/toolkit/components/formautofill/test/browser/browser_infrastructure.js
new file mode 100644
index 000000000..af27cfdb5
--- /dev/null
+++ b/toolkit/components/formautofill/test/browser/browser_infrastructure.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the local testing infrastructure.
+ */
+
+"use strict";
+
+/**
+ * Tests the truth assertion function.
+ */
+add_task(function* test_assert_truth() {
+  Assert.ok(1 != 2);
+});
+
+/**
+ * Tests the equality assertion function.
+ */
+add_task(function* test_assert_equality() {
+  Assert.equal(1 + 1, 2);
+});
+
+/**
+ * Uses some of the utility functions provided by the framework.
+ */
+add_task(function* test_utility_functions() {
+  // The "print" function is useful to log information that is not known before.
+  let randomString = "R" + Math.floor(Math.random() * 10);
+  Output.print("The random contents will be '" + randomString + "'.");
+
+  // Create the text file with the random contents.
+  let path = yield TestUtils.getTempFile("test-infrastructure.txt");
+  yield OS.File.writeAtomic(path, new TextEncoder().encode(randomString));
+
+  // Test a few utility functions.
+  yield TestUtils.waitForTick();
+  yield TestUtils.waitMs(50);
+
+  let promiseMyNotification = TestUtils.waitForNotification("my-topic");
+  Services.obs.notifyObservers(null, "my-topic", "");
+  yield promiseMyNotification;
+
+  // Check the file size.  The file will be deleted automatically later.
+  Assert.equal((yield OS.File.stat(path)).size, randomString.length);
+});
+
+add_task(terminationTaskFn);
diff --git a/toolkit/components/formautofill/test/browser/browser_ui_requestAutocomplete.js b/toolkit/components/formautofill/test/browser/browser_ui_requestAutocomplete.js
new file mode 100644
index 000000000..2a7b58f12
--- /dev/null
+++ b/toolkit/components/formautofill/test/browser/browser_ui_requestAutocomplete.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the requestAutocomplete user interface.
+ */
+
+"use strict";
+
+/**
+ * Open the requestAutocomplete UI and test that selecting a profile results in
+ * the correct data being sent back to the opener.
+ */
+add_task(function* test_select_profile() {
+  // Request an e-mail address.
+  let { uiWindow, promiseResult } = yield FormAutofillTest.showUI(
+                                          TestData.requestEmailOnly);
+
+  // Accept the dialog.
+  let acceptButton = uiWindow.document.getElementById("accept");
+  EventUtils.synthesizeMouseAtCenter(acceptButton, {}, uiWindow);
+
+  let result = yield promiseResult;
+  Assert.equal(result.fields.length, 1);
+  Assert.equal(result.fields[0].section, "");
+  Assert.equal(result.fields[0].addressType, "");
+  Assert.equal(result.fields[0].contactType, "");
+  Assert.equal(result.fields[0].fieldName, "email");
+  Assert.equal(result.fields[0].value, "email@example.org");
+});
+
+/**
+ * Open the requestAutocomplete UI and cancel the dialog.
+ */
+add_task(function* test_cancel() {
+  // Request an e-mail address.
+  let { uiWindow, promiseResult } = yield FormAutofillTest.showUI(
+                                          TestData.requestEmailOnly);
+
+  // Cancel the dialog.
+  let acceptButton = uiWindow.document.getElementById("cancel");
+  EventUtils.synthesizeMouseAtCenter(acceptButton, {}, uiWindow);
+
+  let result = yield promiseResult;
+  Assert.ok(result.canceled);
+});
+
+add_task(terminationTaskFn);
diff --git a/toolkit/components/formautofill/test/browser/head.js b/toolkit/components/formautofill/test/browser/head.js
new file mode 100644
index 000000000..882f3fd5e
--- /dev/null
+++ b/toolkit/components/formautofill/test/browser/head.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Initialization specific to Form Autofill mochitest-browser tests.
+ */
+
+"use strict";
+
+// We cannot start initialization from "loader.js" like we do in the xpcshell
+// and mochitest-chrome frameworks, thus we load the script here.
+Services.scriptloader.loadSubScript(getRootDirectory(gTestPath) + "loader.js",
+                                    this);
+
+// The testing framework is fully initialized at this point, you can add
+// mochitest-browser specific test initialization here.  If you need shared
+// functions or initialization that are not specific to mochitest-browser,
+// consider adding them to "head_common.js" in the parent folder instead.
diff --git a/toolkit/components/formautofill/test/browser/loader.js b/toolkit/components/formautofill/test/browser/loader.js
new file mode 100644
index 000000000..bfd5b9ee0
--- /dev/null
+++ b/toolkit/components/formautofill/test/browser/loader.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Infrastructure for the mochitest-browser tests located in this folder.
+ *
+ * See "loader_common.js" in the parent folder for a general overview.
+ *
+ * Unless you are adding new features to the framework, you shouldn't have to
+ * modify this file.  Use "head_common.js" or "head.js" for shared code.
+ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(getRootDirectory(gTestPath) +
+                                    "loader_common.js", this);
+
+// Define output functions so they look the same across all frameworks.
+var Output = {
+  print: info,
+};
+
+// Define assertion functions so they look the same across all frameworks.
+var Assert = {
+  ok: _mochitestAssert.ok,
+  equal: _mochitestAssert.equal,
+};
+
+// Define task registration functions, see description in "loader_common.js".
+var add_task_in_parent_process = add_task;
+var add_task_in_child_process = function () {};
+var add_task_in_both_processes = add_task;
+
+Services.scriptloader.loadSubScript(getRootDirectory(gTestPath) +
+                                    "head_common.js", this);
+
+// Reminder: unless you are adding new features to the framework, you shouldn't
+// have to modify this file.  Use "head_common.js" or "head.js" for shared code.
diff --git a/toolkit/components/formautofill/test/chrome/.eslintrc.js b/toolkit/components/formautofill/test/chrome/.eslintrc.js
new file mode 100644
index 000000000..8c0f4f574
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/mochitest/chrome.eslintrc.js"
+  ]
+};
diff --git a/toolkit/components/formautofill/test/chrome/chrome.ini b/toolkit/components/formautofill/test/chrome/chrome.ini
new file mode 100644
index 000000000..67b7869af
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/chrome.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+# The following files starting with ".." are installed in the current folder.
+support-files =
+  ../head_common.js
+  ../loader_common.js
+  head.js
+  test_infrastructure.js
+  test_requestAutocomplete_cancel.js
+  loader_parent.js
+  loader.js
+
+# For each test defined below, the associated JavaScript file must be declared
+# in the list above.  This is required because a "support-files" declaration on
+# the individual test would override the global list instead of adding entries.
+
+[test_infrastructure.html]
+[test_requestAutocomplete_cancel.html]
diff --git a/toolkit/components/formautofill/test/chrome/head.js b/toolkit/components/formautofill/test/chrome/head.js
new file mode 100644
index 000000000..4110d5e7c
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/head.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Initialization specific to Form Autofill mochitest-chrome tests.
+ *
+ * This file is loaded by "loader.js".
+ */
+
+"use strict";
+
+// The testing framework is fully initialized at this point, you can add
+// mochitest-chrome specific test initialization here.  If you need shared
+// functions or initialization that are not specific to mochitest-chrome,
+// consider adding them to "head_common.js" in the parent folder instead.
diff --git a/toolkit/components/formautofill/test/chrome/loader.js b/toolkit/components/formautofill/test/chrome/loader.js
new file mode 100644
index 000000000..25b0e6ea3
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/loader.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Infrastructure for the mochitest-chrome tests located in this folder.
+ *
+ * See "loader_common.js" in the parent folder for a general overview.
+ *
+ * Unless you are adding new features to the framework, you shouldn't have to
+ * modify this file.  Use "head_common.js" or "head.js" for shared code.
+ */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+
+Services.scriptloader.loadSubScript(
+  "chrome://mochikit/content/tests/SimpleTest/SimpleTest.js", this);
+
+var sharedUrl = SimpleTest.getTestFileURL("loader_common.js");
+Services.scriptloader.loadSubScript(sharedUrl, this);
+
+var parentScript = SpecialPowers.loadChromeScript(
+                                 SimpleTest.getTestFileURL("loader_parent.js"));
+
+// Replace the extension of the loaded HTML file with ".js"
+var testUrl = location.href.replace(/\.\w+$/, ".js");
+
+// Start loading the test script in the parent process.
+var promiseParentInitFinished = new Promise(function (resolve) {
+  parentScript.addMessageListener("finish_load_in_parent", resolve);
+});
+parentScript.sendAsyncMessage("start_load_in_parent", { testUrl: testUrl });
+
+// Define output functions so they look the same across all frameworks.
+var Output = {
+  print: info,
+};
+
+// Define assertion functions so they look the same across all frameworks.
+var Assert = {
+  ok: _mochitestAssert.ok,
+  equal: _mochitestAssert.equal,
+};
+
+var executeSoon = SimpleTest.executeSoon;
+
+var gTestTasks = [];
+
+// Define task registration functions, see description in "loader_common.js".
+function add_task(taskFn) {
+  gTestTasks.push([taskFn, "content", taskFn.name]);
+}
+function add_task_in_parent_process(taskFn, taskIdOverride) {
+  let taskId = taskIdOverride || getTaskId(Components.stack.caller);
+  gTestTasks.push([taskFn, "parent", taskId]);
+}
+function add_task_in_both_processes(taskFn) {
+  // We need to define a task ID based on our direct caller.
+  add_task_in_parent_process(taskFn, getTaskId(Components.stack.caller));
+  add_task(taskFn);
+}
+var add_task_in_child_process = add_task;
+
+window.addEventListener("load", function onLoad() {
+  window.removeEventListener("load", onLoad);
+
+  Task.spawn(function* () {
+    try {
+      for (let [taskFn, taskType, taskId] of gTestTasks) {
+        if (taskType == "content") {
+          // This is a normal task executed in the current process.
+          info("Running " + taskFn.name);
+          yield Task.spawn(taskFn);
+        } else {
+          // This is a task executed in the parent process.
+          info("Running task in parent process: " + taskFn.name);
+          let promiseFinished = new Promise(function (resolve) {
+            parentScript.addMessageListener("finish_task_" + taskId, resolve);
+          });
+          parentScript.sendAsyncMessage("start_task_" + taskId);
+          yield promiseFinished;
+          info("Finished task in parent process: " + taskFn.name);
+        }
+      }
+    } catch (ex) {
+      ok(false, ex);
+    }
+
+    SimpleTest.finish();
+  });
+});
+
+// Wait for the test script to be loaded in the parent process.  This means that
+// test tasks are registered and ready, but have not been executed yet.
+add_task(function* wait_loading_in_parent_process() {
+  yield promiseParentInitFinished;
+});
+
+var headUrl = SimpleTest.getTestFileURL("head_common.js");
+Services.scriptloader.loadSubScript(headUrl, this);
+
+Output.print("Loading test file: " + testUrl);
+Services.scriptloader.loadSubScript(testUrl, this);
+
+// Register the execution of termination tasks after all other tasks.
+add_task(terminationTaskFn);
+add_task_in_parent_process(terminationTaskFn, terminationTaskFn.name);
+
+SimpleTest.waitForExplicitFinish();
+
+// Reminder: unless you are adding new features to the framework, you shouldn't
+// have to modify this file.  Use "head_common.js" or "head.js" for shared code.
diff --git a/toolkit/components/formautofill/test/chrome/loader_parent.js b/toolkit/components/formautofill/test/chrome/loader_parent.js
new file mode 100644
index 000000000..bf823218e
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/loader_parent.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Infrastructure for the mochitest-chrome tests located in this folder, always
+ * executed in the parent process.
+ *
+ * See "loader_common.js" in the parent folder for a general overview.
+ *
+ * Unless you are adding new features to the framework, you shouldn't have to
+ * modify this file.  Use "head_common.js" or "head.js" for shared code.
+ */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+
+var sharedUrl = "chrome://mochitests/content/chrome/" +
+                "toolkit/components/formautofill/test/chrome/loader_common.js";
+Services.scriptloader.loadSubScript(sharedUrl, this);
+
+// Define output functions so they look the same across all frameworks.  Since
+// we don't have an output function available here, we report as TEST-PASS.
+var Output = {
+  print: message => assert.ok(true, message),
+};
+
+// Define assertion functions so they look the same across all frameworks.
+var Assert = {
+  ok: assert.ok,
+  equal: assert.equal,
+};
+
+// Define task registration functions, see description in "loader_common.js".
+function add_task_in_parent_process(taskFn, taskIdOverride) {
+  let taskId = taskIdOverride || getTaskId(Components.stack.caller);
+  Output.print("Registering in the parent process: " + taskId);
+  addMessageListener("start_task_" + taskId, function () {
+    Task.spawn(function* () {
+      try {
+        Output.print("Running in the parent process " + taskId);
+        yield Task.spawn(taskFn);
+      } catch (ex) {
+        assert.ok(false, ex);
+      }
+
+      sendAsyncMessage("finish_task_" + taskId, {});
+    });
+  });
+}
+var add_task = function () {};
+var add_task_in_child_process = function () {};
+var add_task_in_both_processes = add_task_in_parent_process;
+
+// We need to wait for the child process to send us the path of the test file
+// to load before we can actually start loading it.
+var context = this;
+addMessageListener("start_load_in_parent", function (message) {
+  Output.print("Starting loading infrastructure in parent process.");
+  let headUrl = "chrome://mochitests/content/chrome/" +
+                "toolkit/components/formautofill/test/chrome/head_common.js";
+  Services.scriptloader.loadSubScript(headUrl, context);
+
+  Services.scriptloader.loadSubScript(message.testUrl, context);
+
+  // Register the execution of termination tasks after all other tasks.
+  add_task_in_parent_process(terminationTaskFn, terminationTaskFn.name);
+
+  Output.print("Finished loading infrastructure in parent process.");
+  sendAsyncMessage("finish_load_in_parent", {});
+});
+
+// Reminder: unless you are adding new features to the framework, you shouldn't
+// have to modify this file.  Use "head_common.js" or "head.js" for shared code.
diff --git a/toolkit/components/formautofill/test/chrome/test_infrastructure.html b/toolkit/components/formautofill/test/chrome/test_infrastructure.html
new file mode 100644
index 000000000..54f417f77
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/test_infrastructure.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
+<script type="application/javascript;version=1.7" src="loader.js"></script>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<p id="paragraph">Paragraph contents.</p>
+
+</body></html>
diff --git a/toolkit/components/formautofill/test/chrome/test_infrastructure.js b/toolkit/components/formautofill/test/chrome/test_infrastructure.js
new file mode 100644
index 000000000..c3b0b43ff
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/test_infrastructure.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the local testing infrastructure.
+ */
+
+"use strict";
+
+/**
+ * Tests the truth assertion function.
+ */
+add_task(function* test_assert_truth() {
+  Assert.ok(1 != 2);
+});
+
+/**
+ * Tests the equality assertion function.
+ */
+add_task(function* test_assert_equality() {
+  Assert.equal(1 + 1, 2);
+});
+
+/**
+ * Uses some of the utility functions provided by the framework.
+ */
+add_task(function* test_utility_functions() {
+  // The "print" function is useful to log information that is not known before.
+  let randomString = "R" + Math.floor(Math.random() * 10);
+  Output.print("The random contents will be '" + randomString + "'.");
+
+  // Create the text file with the random contents.
+  let path = yield TestUtils.getTempFile("test-infrastructure.txt");
+  yield OS.File.writeAtomic(path, new TextEncoder().encode(randomString));
+
+  // Test a few utility functions.
+  yield TestUtils.waitForTick();
+  yield TestUtils.waitMs(50);
+
+  let promiseMyNotification = TestUtils.waitForNotification("my-topic");
+  Services.obs.notifyObservers(null, "my-topic", "");
+  yield promiseMyNotification;
+
+  // Check the file size.  The file will be deleted automatically later.
+  Assert.equal((yield OS.File.stat(path)).size, randomString.length);
+});
+
+/**
+ * This type of test has access to the content declared above.
+ */
+add_task(function* test_content() {
+  Assert.equal($("paragraph").innerHTML, "Paragraph contents.");
+
+  let promiseMyEvent = TestUtils.waitForEvent($("paragraph"), "MyEvent");
+
+  let event = document.createEvent("CustomEvent");
+  event.initCustomEvent("MyEvent", true, false, {});
+  $("paragraph").dispatchEvent(event);
+
+  yield promiseMyEvent;
+});
diff --git a/toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.html b/toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.html
new file mode 100644
index 000000000..8ae7ffd4b
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
+<script type="application/javascript;version=1.7" src="loader.js"></script>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<form id="form">
+</form>
+
+</body></html>
diff --git a/toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.js b/toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.js
new file mode 100644
index 000000000..1ee12bd9a
--- /dev/null
+++ b/toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the response sent when requestAutocomplete is canceled by the user.
+ */
+
+"use strict";
+
+/**
+ * The requestAutocomplete UI will not be displayed during these tests.
+ */
+add_task_in_parent_process(function* test_cancel_init() {
+  FormAutofillTest.requestAutocompleteResponse = { canceled: true };
+});
+
+/**
+ * Tests the case where the feature is canceled.
+ */
+add_task(function* test_cancel() {
+  let promise = TestUtils.waitForEvent($("form"), "autocompleteerror");
+  $("form").requestAutocomplete();
+  let errorEvent = yield promise;
+
+  Assert.equal(errorEvent.reason, "cancel");
+});
diff --git a/toolkit/components/formautofill/test/head_common.js b/toolkit/components/formautofill/test/head_common.js
new file mode 100644
index 000000000..82b87e4a6
--- /dev/null
+++ b/toolkit/components/formautofill/test/head_common.js
@@ -0,0 +1,245 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Initialization of Form Autofill tests shared between all frameworks.
+ *
+ * A copy of this file is installed in each of the framework subfolders, this
+ * means it becomes a sibling of the test files in the final layout.  This is
+ * determined by how manifest "support-files" installation works.
+ */
+
+"use strict";
+
+// The requestAutocomplete framework is available at this point, you can add
+// mochitest-chrome specific test initialization here.  If you need shared
+// functions or initialization that are not specific to mochitest-chrome,
+// consider adding them to "head_common.js" in the parent folder instead.
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
+                                  "resource://gre/modules/DownloadPaths.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofill",
+                                  "resource://gre/modules/FormAutofill.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
+
+/* --- Global helpers --- */
+
+// Some of these functions are already implemented in other parts of the source
+// tree, see bug 946708 about sharing more code.
+
+var TestUtils = {
+  /**
+   * Waits for at least one tick of the event loop.  This means that all pending
+   * events at the time of this call will have been processed.  Other events may
+   * be processed before the returned promise is resolved.
+   *
+   * @return {Promise}
+   * @resolves When pending events have been processed.
+   * @rejects Never.
+   */
+  waitForTick: function () {
+    return new Promise(resolve => executeSoon(resolve));
+  },
+
+  /**
+   * Waits for the specified timeout.
+   *
+   * @param aTimeMs
+   *        Minimum time to wait from the moment of this call, in milliseconds.
+   *        The actual wait may be longer, due to system timer resolution and
+   *        pending events being processed before the promise is resolved.
+   *
+   * @return {Promise}
+   * @resolves When the specified time has passed.
+   * @rejects Never.
+   */
+  waitMs: function (aTimeMs) {
+    return new Promise(resolve => setTimeout(resolve, aTimeMs));
+  },
+
+  /**
+   * Allows waiting for an observer notification once.
+   *
+   * @param aTopic
+   *        Notification topic to observe.
+   *
+   * @return {Promise}
+   * @resolves The array [aSubject, aData] from the observed notification.
+   * @rejects Never.
+   */
+  waitForNotification: function (aTopic) {
+    Output.print("Waiting for notification: '" + aTopic + "'.");
+
+    return new Promise(resolve => Services.obs.addObserver(
+      function observe(aSubject, aTopic, aData) {
+        Services.obs.removeObserver(observe, aTopic);
+        resolve([aSubject, aData]);
+      }, aTopic, false));
+  },
+
+  /**
+   * Waits for a DOM event on the specified target.
+   *
+   * @param aTarget
+   *        The DOM EventTarget on which addEventListener should be called.
+   * @param aEventName
+   *        String with the name of the event.
+   * @param aUseCapture
+   *        This parameter is passed to the addEventListener call.
+   *
+   * @return {Promise}
+   * @resolves The arguments from the observed event.
+   * @rejects Never.
+   */
+  waitForEvent: function (aTarget, aEventName, aUseCapture = false) {
+    Output.print("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
+
+    return new Promise(resolve => aTarget.addEventListener(aEventName,
+      function onEvent(...aArgs) {
+        aTarget.removeEventListener(aEventName, onEvent, aUseCapture);
+        resolve(...aArgs);
+      }, aUseCapture));
+  },
+
+  // While the previous test file should have deleted all the temporary files it
+  // used, on Windows these might still be pending deletion on the physical file
+  // system.  Thus, start from a new base number every time, to make a collision
+  // with a file that is still pending deletion highly unlikely.
+  _fileCounter: Math.floor(Math.random() * 1000000),
+
+  /**
+   * Returns a reference to a temporary file, that is guaranteed not to exist,
+   * and to have never been created before.
+   *
+   * @param aLeafName
+   *        Suggested leaf name for the file to be created.
+   *
+   * @return {Promise}
+   * @resolves Path of a non-existent file in a temporary directory.
+   *
+   * @note It is not enough to delete the file if it exists, or to delete the
+   *       file after calling nsIFile.createUnique, because on Windows the
+   *       delete operation in the file system may still be pending, preventing
+   *       a new file with the same name to be created.
+   */
+  getTempFile: Task.async(function* (aLeafName) {
+    // Prepend a serial number to the extension in the suggested leaf name.
+    let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
+    let leafName = base + "-" + this._fileCounter + ext;
+    this._fileCounter++;
+
+    // Get a file reference under the temporary directory for this test file.
+    let path = OS.Path.join(OS.Constants.Path.tmpDir, leafName);
+    Assert.ok(!(yield OS.File.exists(path)));
+
+    // Ensure the file is deleted whe the test terminates.
+    add_termination_task(function* () {
+      if (yield OS.File.exists(path)) {
+        yield OS.File.remove(path);
+      }
+    });
+
+    return path;
+  }),
+};
+
+/* --- Local helpers --- */
+
+var FormAutofillTest = {
+  /**
+   * Stores the response that the next call to the mock requestAutocomplete UI
+   * will return to the requester, or null to enable displaying the default UI.
+   */
+  requestAutocompleteResponse: null,
+
+  /**
+   * Displays the requestAutocomplete user interface using the specified data.
+   *
+   * @param aFormAutofillData
+   *        Serializable object containing the set of requested fields.
+   *
+   * @return {Promise}
+   * @resolves An object with the following properties:
+   *           {
+   *             uiWindow: Reference to the initialized window.
+   *             promiseResult: Promise resolved by the UI when it closes.
+   *           }
+   */
+  showUI: Task.async(function* (aFormAutofillData) {
+    Output.print("Opening UI with data: " + JSON.stringify(aFormAutofillData));
+
+    // Wait for the initialization event before opening the window.
+    let promiseUIWindow =
+        TestUtils.waitForNotification("formautofill-window-initialized");
+    let ui = yield FormAutofill.integration.createRequestAutocompleteUI(
+                                                         aFormAutofillData);
+    let promiseResult = ui.show();
+
+    // The window is the subject of the observer notification.
+    return {
+      uiWindow: (yield promiseUIWindow)[0],
+      promiseResult: promiseResult,
+    };
+  }),
+};
+
+var TestData = {
+  /**
+   * Autofill UI request for the e-mail field only.
+   */
+  get requestEmailOnly() {
+    return {
+      sections: [{
+        name: "",
+        addressSections: [{
+          addressType: "",
+          fields: [{
+            fieldName: "email",
+            contactType: "",
+          }],
+        }],
+      }],
+    };
+  },
+};
+
+/* --- Initialization and termination functions common to all tests --- */
+
+add_task_in_parent_process(function* () {
+  // If required, we return a mock response instead of displaying the UI.
+  let mockIntegrationFn = base => ({
+    createRequestAutocompleteUI: Task.async(function* () {
+      // Call the base method to display the UI if override is not requested.
+      if (FormAutofillTest.requestAutocompleteResponse === null) {
+        return yield base.createRequestAutocompleteUI.apply(this, arguments);
+      }
+
+      // Return a mock RequestAutocompleteUI object.
+      return {
+        show: Task.async(function* () {
+          let response = FormAutofillTest.requestAutocompleteResponse;
+          Output.print("Mock UI response: " + JSON.stringify(response));
+          return response;
+        }),
+      };
+    }),
+  });
+
+  FormAutofill.registerIntegration(mockIntegrationFn);
+  add_termination_task(function* () {
+    FormAutofill.unregisterIntegration(mockIntegrationFn);
+  });
+});
+
+add_task_in_both_processes(function* () {
+  // We must manually enable the feature while testing.
+  Services.prefs.setBoolPref("dom.forms.requestAutocomplete", true);
+  add_termination_task(function* () {
+    Services.prefs.clearUserPref("dom.forms.requestAutocomplete");
+  });
+});
diff --git a/toolkit/components/formautofill/test/loader_common.js b/toolkit/components/formautofill/test/loader_common.js
new file mode 100644
index 000000000..340586b65
--- /dev/null
+++ b/toolkit/components/formautofill/test/loader_common.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Infrastructure common to the test frameworks located in subfolders.
+ *
+ * A copy of this file is installed in each of the framework subfolders, this
+ * means it becomes a sibling of the test files in the final layout.  This is
+ * determined by how manifest "support-files" installation works.
+ *
+ * Unless you are adding new features to the framework, you shouldn't have to
+ * modify this file.  Use "head_common.js" or the "head.js" file of each
+ * framework for shared code.
+ */
+
+"use strict";
+
+/*
+ * --------------------
+ *  FRAMEWORK OVERVIEW
+ * --------------------
+ *
+ * This framework is designed in such a way that test can be written in similar
+ * ways in the xpcshell, mochitest-chrome, and mochitest-browser frameworks,
+ * both when tests are running in the parent process or in a content process.
+ *
+ * There are some basic self-documenting assertion and output functions:
+ *
+ * Assert.ok(actualValue);
+ * Assert.is(actualValue, expectedValue);
+ * Output.print(string);
+ *
+ * Test cases and initialization functions are declared in shared head files
+ * ("head_common.js" and "head.js") as well as individual test files.  When
+ * tests run in an Elecrolysis (e10s) environment, they are executed in both
+ * processes at first.  Normally, at this point only the registration of test
+ * cases happen.  When everything has finished loading, tests are started and
+ * appropriately synchronized between processes.
+ *
+ * Tests can be declared using the add_task syntax:
+ *
+ * add_task(function* test_something () { ... });
+ *   This adds a test either in the parent process or child process:
+ *     - Parent: xpcshell, mochitest-chrome --disable-e10s, mochitest-browser
+ *     - Child: mochitest-chrome with e10s
+ *   In the future, these might run in the child process for "xpcshell".
+ *
+ * add_task_in_parent_process(function* test_something () { ... });
+ *   This test runs in the parent process, but the child process will wait for
+ *   its completion before continuing with the next task.  This wait currently
+ *   happens only in mochitest-chrome with e10s, in other frameworks that run
+ *   only in the parent process this is the same as a normal add_task.
+ *
+ * add_task_in_child_process(function* test_something () { ... });
+ *   This test runs only in the child process.  This means that the test is not
+ *   run unless this is an e10s test, currently mochitest-chrome with e10s.
+ *
+ * add_task_in_both_processes(function* test_something () { ... });
+ *   Useful for initialization that must be done both in the parent and the
+ *   child, like setting preferences.
+ *
+ * add_termination_task(function* () { ... });
+ *   Registers a new asynchronous termination task.  This is executed after all
+ *   test cases in the file finished, and always in the same process where the
+ *   termination task is registered.
+ */
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+var gTerminationTasks = [];
+
+/**
+ * None of the testing frameworks support asynchronous termination functions, so
+ * this task must be registered later, after the other "add_task" calls.
+ *
+ * Even xpcshell doesn't support calling "add_task" in the "tail.js" file,
+ * because it registers the task but does not wait for its termination,
+ * potentially leading to intermittent failures in subsequent tests.
+ */
+function* terminationTaskFn() {
+  for (let taskFn of gTerminationTasks) {
+    try {
+      yield Task.spawn(taskFn);
+    } catch (ex) {
+      Output.print(ex);
+      Assert.ok(false);
+    }
+  }
+}
+
+function add_termination_task(taskFn) {
+  gTerminationTasks.push(taskFn);
+}
+
+/**
+ * Returns a unique identifier used for synchronizing the given test task
+ * between the parent and child processes.
+ */
+function getTaskId(stackFrame) {
+  return stackFrame.filename + ":" + stackFrame.lineNumber;
+}
+
+// This is a shared helper for mochitest-chrome and mochitest-browser.
+var _mochitestAssert = {
+  ok: function (actual) {
+    let stack = Components.stack.caller;
+    ok(actual, "[" + stack.name + " : " + stack.lineNumber + "] " + actual +
+               " == true");
+  },
+  equal: function (actual, expected) {
+    let stack = Components.stack.caller;
+    is(actual, expected, "[" + stack.name + " : " + stack.lineNumber + "] " +
+               actual + " == " + expected);
+  },
+};
+
+// Reminder: unless you are adding new features to the framework, you shouldn't
+// have to modify this file.  Use "head_common.js" or "head.js" for shared code.
diff --git a/toolkit/components/formautofill/test/xpcshell/.eslintrc.js b/toolkit/components/formautofill/test/xpcshell/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/toolkit/components/formautofill/test/xpcshell/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+  ]
+};
diff --git a/toolkit/components/formautofill/test/xpcshell/head.js b/toolkit/components/formautofill/test/xpcshell/head.js
new file mode 100644
index 000000000..1cee023f2
--- /dev/null
+++ b/toolkit/components/formautofill/test/xpcshell/head.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Initialization specific to Form Autofill xpcshell tests.
+ *
+ * This file is loaded by "loader.js".
+ */
+
+"use strict";
+
+// The testing framework is fully initialized at this point, you can add
+// xpcshell specific test initialization here.  If you need shared functions or
+// initialization that are not specific to xpcshell, consider adding them to
+// "head_common.js" in the parent folder instead.
+
+add_task_in_parent_process(function* test_xpcshell_initialize_profile() {
+  // We need to send the profile-after-change notification manually to the
+  // startup component to ensure it has been initialized.
+  Cc["@mozilla.org/formautofill/startup;1"]
+    .getService(Ci.nsIObserver)
+    .observe(null, "profile-after-change", "");
+});
diff --git a/toolkit/components/formautofill/test/xpcshell/loader.js b/toolkit/components/formautofill/test/xpcshell/loader.js
new file mode 100644
index 000000000..449989c8a
--- /dev/null
+++ b/toolkit/components/formautofill/test/xpcshell/loader.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Infrastructure for the xpcshell tests located in this folder.
+ *
+ * See "loader_common.js" in the parent folder for a general overview.
+ *
+ * Unless you are adding new features to the framework, you shouldn't have to
+ * modify this file.  Use "head_common.js" or "head.js" for shared code.
+ */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+
+Services.scriptloader.loadSubScript(
+  Services.io.newFileURI(do_get_file("loader_common.js")).spec, this);
+
+// Define output functions so they look the same across all frameworks.
+var Output = {
+  print: do_print,
+};
+
+var executeSoon = do_execute_soon;
+var setTimeout = (fn, delay) => do_timeout(delay, fn);
+
+// Define task registration functions, see description in "loader_common.js".
+var add_task_in_parent_process = add_task;
+var add_task_in_child_process = function () {};
+var add_task_in_both_processes = add_task;
+
+Services.scriptloader.loadSubScript(
+  Services.io.newFileURI(do_get_file("head_common.js")).spec, this);
+
+// Tests are always run asynchronously and with the profile loaded.
+function run_test() {
+  do_get_profile();
+  run_next_test();
+}
+
+// Reminder: unless you are adding new features to the framework, you shouldn't
+// have to modify this file.  Use "head_common.js" or "head.js" for shared code.
diff --git a/toolkit/components/formautofill/test/xpcshell/test_infrastructure.js b/toolkit/components/formautofill/test/xpcshell/test_infrastructure.js
new file mode 100644
index 000000000..af27cfdb5
--- /dev/null
+++ b/toolkit/components/formautofill/test/xpcshell/test_infrastructure.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the local testing infrastructure.
+ */
+
+"use strict";
+
+/**
+ * Tests the truth assertion function.
+ */
+add_task(function* test_assert_truth() {
+  Assert.ok(1 != 2);
+});
+
+/**
+ * Tests the equality assertion function.
+ */
+add_task(function* test_assert_equality() {
+  Assert.equal(1 + 1, 2);
+});
+
+/**
+ * Uses some of the utility functions provided by the framework.
+ */
+add_task(function* test_utility_functions() {
+  // The "print" function is useful to log information that is not known before.
+  let randomString = "R" + Math.floor(Math.random() * 10);
+  Output.print("The random contents will be '" + randomString + "'.");
+
+  // Create the text file with the random contents.
+  let path = yield TestUtils.getTempFile("test-infrastructure.txt");
+  yield OS.File.writeAtomic(path, new TextEncoder().encode(randomString));
+
+  // Test a few utility functions.
+  yield TestUtils.waitForTick();
+  yield TestUtils.waitMs(50);
+
+  let promiseMyNotification = TestUtils.waitForNotification("my-topic");
+  Services.obs.notifyObservers(null, "my-topic", "");
+  yield promiseMyNotification;
+
+  // Check the file size.  The file will be deleted automatically later.
+  Assert.equal((yield OS.File.stat(path)).size, randomString.length);
+});
+
+add_task(terminationTaskFn);
diff --git a/toolkit/components/formautofill/test/xpcshell/test_integration.js b/toolkit/components/formautofill/test/xpcshell/test_integration.js
new file mode 100644
index 000000000..7707f3880
--- /dev/null
+++ b/toolkit/components/formautofill/test/xpcshell/test_integration.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests overriding the FormAutofillIntegration module functions.
+ */
+
+"use strict";
+
+/**
+ * The requestAutocomplete UI will not be displayed during these tests.
+ */
+add_task_in_parent_process(function* test_initialize() {
+  FormAutofillTest.requestAutocompleteResponse = { canceled: true };
+});
+
+/**
+ * Registers and unregisters an integration override function.
+ */
+add_task(function* test_integration_override() {
+  let overrideCalled = false;
+
+  let newIntegrationFn = base => ({
+    createRequestAutocompleteUI: Task.async(function* () {
+      overrideCalled = true;
+      return yield base.createRequestAutocompleteUI.apply(this, arguments);
+    }),
+  });
+
+  FormAutofill.registerIntegration(newIntegrationFn);
+  try {
+    let ui = yield FormAutofill.integration.createRequestAutocompleteUI({});
+    let result = yield ui.show();
+    Assert.ok(result.canceled);
+  } finally {
+    FormAutofill.unregisterIntegration(newIntegrationFn);
+  }
+
+  Assert.ok(overrideCalled);
+});
+
+/**
+ * Registers an integration override function that throws an exception, and
+ * ensures that this does not block other functions from being registered.
+ */
+add_task(function* test_integration_override_error() {
+  let overrideCalled = false;
+
+  let errorIntegrationFn = base => { throw "Expected error." };
+
+  let newIntegrationFn = base => ({
+    createRequestAutocompleteUI: Task.async(function* () {
+      overrideCalled = true;
+      return yield base.createRequestAutocompleteUI.apply(this, arguments);
+    }),
+  });
+
+  FormAutofill.registerIntegration(errorIntegrationFn);
+  FormAutofill.registerIntegration(newIntegrationFn);
+  try {
+    let ui = yield FormAutofill.integration.createRequestAutocompleteUI({});
+    let result = yield ui.show();
+    Assert.ok(result.canceled);
+  } finally {
+    FormAutofill.unregisterIntegration(errorIntegrationFn);
+    FormAutofill.unregisterIntegration(newIntegrationFn);
+  }
+
+  Assert.ok(overrideCalled);
+});
+
+add_task(terminationTaskFn);
diff --git a/toolkit/components/formautofill/test/xpcshell/xpcshell.ini b/toolkit/components/formautofill/test/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..711c03399
--- /dev/null
+++ b/toolkit/components/formautofill/test/xpcshell/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+head = loader.js head.js
+tail =
+skip-if = toolkit == 'android'
+# The following files starting with ".." are installed in the current folder.
+# However, they cannot be referenced directly in the "head" directive above.
+support-files =
+  ../head_common.js
+  ../loader_common.js
+
+[test_infrastructure.js]
+[test_integration.js]
-- 
cgit v1.2.3