Bug 1449055 Convert formautofill to a webextension r=MattN,kmag
☠☠ backed out by e01f02f3bb21 ☠ ☠
authorAndrew Swan <aswan@mozilla.com>
Sat, 28 Jul 2018 14:06:07 -0700
changeset 431348 ecda6532c852b3f225fce998e3ce83ad157760e6
parent 431347 515c80e08cb90de324300c000b50d0d69b608951
child 431349 644216b0ad9b5f9e1e9e8f1f930d4db1c6e717a1
push id34437
push userebalazs@mozilla.com
push dateTue, 14 Aug 2018 09:31:09 +0000
treeherdermozilla-central@914b3b370ad0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, kmag
bugs1449055
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1449055 Convert formautofill to a webextension r=MattN,kmag
browser/extensions/formautofill/FormAutofillStorage.jsm
browser/extensions/formautofill/api.js
browser/extensions/formautofill/background.js
browser/extensions/formautofill/bootstrap.js
browser/extensions/formautofill/content/editAddress.xhtml
browser/extensions/formautofill/content/editCreditCard.xhtml
browser/extensions/formautofill/install.rdf.in
browser/extensions/formautofill/jar.mn
browser/extensions/formautofill/manifest.json
browser/extensions/formautofill/moz.build
browser/extensions/formautofill/schema.json
browser/extensions/formautofill/skin/shared/autocomplete-item-shared.css
browser/extensions/formautofill/skin/shared/autocomplete-item.css
browser/extensions/formautofill/skin/shared/editDialog-shared.css
browser/extensions/formautofill/skin/shared/editDialog.css
browser/extensions/formautofill/test/unit/head.js
browser/extensions/formautofill/test/unit/test_activeStatus.js
browser/extensions/formautofill/test/unit/test_addressDataLoader.js
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
browser/extensions/formautofill/test/unit/test_collectFormFields.js
browser/extensions/formautofill/test/unit/test_createRecords.js
browser/extensions/formautofill/test/unit/test_creditCardRecords.js
browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
browser/extensions/formautofill/test/unit/test_findLabelElements.js
browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
browser/extensions/formautofill/test/unit/test_getCategoriesFromFieldNames.js
browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
browser/extensions/formautofill/test/unit/test_getInfo.js
browser/extensions/formautofill/test/unit/test_getRecords.js
browser/extensions/formautofill/test/unit/test_isAvailable.js
browser/extensions/formautofill/test/unit/test_isCJKName.js
browser/extensions/formautofill/test/unit/test_isFieldEligibleForAutofill.js
browser/extensions/formautofill/test/unit/test_markAsAutofillField.js
browser/extensions/formautofill/test/unit/test_masterPassword.js
browser/extensions/formautofill/test/unit/test_migrateRecords.js
browser/extensions/formautofill/test/unit/test_nameUtils.js
browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
browser/extensions/formautofill/test/unit/test_parseAddressFormat.js
browser/extensions/formautofill/test/unit/test_phoneNumber.js
browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
browser/extensions/formautofill/test/unit/test_savedFieldNames.js
browser/extensions/formautofill/test/unit/test_storage_remove.js
browser/extensions/formautofill/test/unit/test_storage_tombstones.js
browser/extensions/formautofill/test/unit/test_sync.js
browser/extensions/formautofill/test/unit/test_toOneLineAddress.js
browser/extensions/formautofill/test/unit/test_transformFields.js
testing/modules/FileTestUtils.jsm
toolkit/content/widgets.css
--- a/browser/extensions/formautofill/FormAutofillStorage.jsm
+++ b/browser/extensions/formautofill/FormAutofillStorage.jsm
@@ -1785,13 +1785,17 @@ FormAutofillStorage.prototype = {
     }
     return data;
   },
 
   // For test only.
   _saveImmediately() {
     return this._store._save();
   },
+
+  _finalize() {
+    return this._store.finalize();
+  },
 };
 
 // The singleton exposed by this module.
 this.formAutofillStorage = new FormAutofillStorage(
   OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME));
rename from browser/extensions/formautofill/bootstrap.js
rename to browser/extensions/formautofill/api.js
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/api.js
@@ -1,25 +1,22 @@
 /* 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/. */
 
 "use strict";
 
-/* exported startup, shutdown, install, uninstall */
+/* globals ExtensionAPI */
 
 const STYLESHEET_URI = "chrome://formautofill/content/formautofill.css";
 const CACHED_STYLESHEETS = new WeakMap();
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
-ChromeUtils.defineModuleGetter(this, "AddonManagerPrivate",
-                               "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofill",
                                "resource://formautofill/FormAutofill.jsm");
 ChromeUtils.defineModuleGetter(this, "formAutofillParent",
                                "resource://formautofill/FormAutofillParent.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "resProto",
                                    "@mozilla.org/network/protocol;1?name=resource",
                                    "nsISubstitutingProtocolHandler");
@@ -45,23 +42,16 @@ function onMaybeOpenPopup(evt) {
   if (CACHED_STYLESHEETS.has(domWindow)) {
     // This window already has autofill stylesheets.
     return;
   }
 
   insertStyleSheet(domWindow, STYLESHEET_URI);
 }
 
-function addUpgradeListener(instanceID) {
-  AddonManager.addUpgradeListener(instanceID, upgrade => {
-    // don't install the upgrade by doing nothing here.
-    // The upgrade will be installed upon next restart.
-  });
-}
-
 function isAvailable() {
   let availablePref = Services.prefs.getCharPref("extensions.formautofill.available");
   if (availablePref == "on") {
     return true;
   } else if (availablePref == "detect") {
     let locale = Services.locale.getRequestedLocale();
     let region = Services.prefs.getCharPref("browser.search.region", "");
     let supportedCountries = Services.prefs.getCharPref("extensions.formautofill.supportedCountries")
@@ -70,90 +60,86 @@ function isAvailable() {
         Services.locale.isAppLocaleRTL) {
       return false;
     }
     return locale == "en-US" && supportedCountries.includes(region);
   }
   return false;
 }
 
-function startup(data) {
-  // We have to do this before actually determining if we're enabled, since
-  // there are scripts inside of the core browser code that depend on the
-  // FormAutofill JSMs being registered.
-  resProto.setSubstitution(RESOURCE_HOST,
-                           Services.io.newURI("chrome/res/", null, data.resourceURI));
+this.formautofill = class extends ExtensionAPI {
+  onStartup() {
+    // We have to do this before actually determining if we're enabled, since
+    // there are scripts inside of the core browser code that depend on the
+    // FormAutofill JSMs being registered.
+    let uri = Services.io.newURI("chrome/res/", null, this.extension.rootURI);
+    resProto.setSubstitution(RESOURCE_HOST, uri);
+
+    let aomStartup = Cc["@mozilla.org/addons/addon-manager-startup;1"]
+                                 .getService(Ci.amIAddonManagerStartup);
+    const manifestURI = Services.io.newURI("manifest.json", null, this.extension.rootURI);
+    this.chromeHandle = aomStartup.registerChrome(manifestURI, [
+      ["content", "formautofill", "chrome/content/"],
+      ["locale", "formautofill", "en-US", "en-US/locale/en-US/"],
+    ]);
+
+    if (!isAvailable()) {
+      Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
+      // reset the sync related prefs incase the feature was previously available
+      // but isn't now.
+      Services.prefs.clearUserPref("services.sync.engine.addresses.available");
+      Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
+      Services.telemetry.scalarSet("formautofill.availability", false);
+      return;
+    }
 
-  if (!isAvailable()) {
-    Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
-    // reset the sync related prefs incase the feature was previously available
-    // but isn't now.
-    Services.prefs.clearUserPref("services.sync.engine.addresses.available");
-    Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
-    Services.telemetry.scalarSet("formautofill.availability", false);
-    return;
-  }
+    // This pref is used for web contents to detect the autocomplete feature.
+    // When it's true, "element.autocomplete" will return tokens we currently
+    // support -- otherwise it'll return an empty string.
+    Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
+    Services.telemetry.scalarSet("formautofill.availability", true);
 
-  if (data.hasOwnProperty("instanceID") && data.instanceID) {
-    if (AddonManagerPrivate.isDBLoaded()) {
-      addUpgradeListener(data.instanceID);
+    // This pref determines whether the "addresses"/"creditcards" sync engine is
+    // available (ie, whether it is shown in any UI etc) - it *does not* determine
+    // whether the engine is actually enabled or not.
+    Services.prefs.setBoolPref("services.sync.engine.addresses.available", true);
+    if (FormAutofill.isAutofillCreditCardsAvailable) {
+      Services.prefs.setBoolPref("services.sync.engine.creditcards.available", true);
     } else {
-      // Wait for the extension database to be loaded so we don't cause its init.
-      Services.obs.addObserver(function xpiDatabaseLoaded() {
-        Services.obs.removeObserver(xpiDatabaseLoaded, "xpi-database-loaded");
-        addUpgradeListener(data.instanceID);
-      }, "xpi-database-loaded");
+      Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
     }
-  } else {
-    throw Error("no instanceID passed to bootstrap startup");
+
+    // Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
+    Services.mm.addMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
+
+    formAutofillParent.init().catch(Cu.reportError);
+    /* eslint-disable no-unused-vars */
+    Services.ppmm.loadProcessScript("data:,new " + function() {
+      ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
+    }, true);
+    /* eslint-enable no-unused-vars */
+    Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
   }
 
-  // This pref is used for web contents to detect the autocomplete feature.
-  // When it's true, "element.autocomplete" will return tokens we currently
-  // support -- otherwise it'll return an empty string.
-  Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
-  Services.telemetry.scalarSet("formautofill.availability", true);
+  onShutdown() {
+    resProto.setSubstitution(RESOURCE_HOST, null);
 
-  // This pref determines whether the "addresses"/"creditcards" sync engine is
-  // available (ie, whether it is shown in any UI etc) - it *does not* determine
-  // whether the engine is actually enabled or not.
-  Services.prefs.setBoolPref("services.sync.engine.addresses.available", true);
-  if (FormAutofill.isAutofillCreditCardsAvailable) {
-    Services.prefs.setBoolPref("services.sync.engine.creditcards.available", true);
-  } else {
-    Services.prefs.clearUserPref("services.sync.engine.creditcards.available");
-  }
+    this.chromeHandle.destruct();
+    this.chromeHandle = null;
 
-  // Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
-  Services.mm.addMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
+    Services.mm.removeMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
 
-  formAutofillParent.init().catch(Cu.reportError);
-  /* eslint-disable no-unused-vars */
-  Services.ppmm.loadProcessScript("data:,new " + function() {
-    ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
-  }, true);
-  /* eslint-enable no-unused-vars */
-  Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
-}
-
-function shutdown() {
-  resProto.setSubstitution(RESOURCE_HOST, null);
+    let enumerator = Services.wm.getEnumerator("navigator:browser");
+    while (enumerator.hasMoreElements()) {
+      let win = enumerator.getNext();
+      let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+      let cachedStyleSheets = CACHED_STYLESHEETS.get(domWindow);
 
-  Services.mm.removeMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
+      if (!cachedStyleSheets) {
+        continue;
+      }
 
-  let enumerator = Services.wm.getEnumerator("navigator:browser");
-  while (enumerator.hasMoreElements()) {
-    let win = enumerator.getNext();
-    let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-    let cachedStyleSheets = CACHED_STYLESHEETS.get(domWindow);
-
-    if (!cachedStyleSheets) {
-      continue;
-    }
-
-    while (cachedStyleSheets.length !== 0) {
-      cachedStyleSheets.pop().remove();
+      while (cachedStyleSheets.length !== 0) {
+        cachedStyleSheets.pop().remove();
+      }
     }
   }
-}
-
-function install() {}
-function uninstall() {}
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/background.js
@@ -0,0 +1,8 @@
+/* eslint-env webextensions */
+
+"use strict";
+
+browser.runtime.onUpdateAvailable.addListener(details => {
+  // By listening to but ignoring this event, any updates will
+  // be delayed until the next browser restart.
+});
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -4,19 +4,19 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html [
   <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
   %globalDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <title data-localization="addNewAddressTitle"/>
-  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
-  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css"/>
-  <link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
+  <link rel="stylesheet" href="resource://formautofill/editDialog-shared.css"/>
+  <link rel="stylesheet" href="resource://formautofill/editAddress.css"/>
+  <link rel="stylesheet" href="resource://formautofill/editDialog.css"/>
   <script src="chrome://formautofill/content/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" autocomplete="off">
     <div>
       <div id="name-container">
--- a/browser/extensions/formautofill/content/editCreditCard.xhtml
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -4,19 +4,19 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html [
   <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
   %globalDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <title data-localization="addNewCreditCardTitle"/>
-  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
-  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editCreditCard.css"/>
-  <link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
+  <link rel="stylesheet" href="resource://formautofill/editDialog-shared.css"/>
+  <link rel="stylesheet" href="resource://formautofill/editCreditCard.css"/>
+  <link rel="stylesheet" href="resource://formautofill/editDialog.css"/>
   <script src="chrome://formautofill/content/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" autocomplete="off">
     <label>
       <span data-localization="cardNumber"/>
--- a/browser/extensions/formautofill/jar.mn
+++ b/browser/extensions/formautofill/jar.mn
@@ -1,18 +1,11 @@
 # 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/.
 
 [features/formautofill@mozilla.org] chrome.jar:
-% resource formautofill %res/
   res/ (*.jsm)
   res/phonenumberutils/ (phonenumberutils/*.jsm)
   res/addressmetadata/ (addressmetadata/*)
-
-% content formautofill %content/
-  content/ (content/*)
+  res/ (skin/shared/*)
 
-% skin formautofill classic/1.0 %skin/linux/ os=LikeUnix
-% skin formautofill classic/1.0 %skin/osx/ os=Darwin
-% skin formautofill classic/1.0 %skin/windows/ os=WINNT
-% skin formautofill-shared classic/1.0 %skin/shared/
-  skin/  (skin/*)
+  content/ (content/*)
rename from browser/extensions/formautofill/install.rdf.in
rename to browser/extensions/formautofill/manifest.json
--- a/browser/extensions/formautofill/install.rdf.in
+++ b/browser/extensions/formautofill/manifest.json
@@ -1,32 +1,26 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
+{
+  "manifest_version": 2,
+  "name": "Form Autofill",
+  "version": "1.0",
 
-#filter substitution
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+  "applications": {
+    "gecko": {
+      "id": "formautofill@mozilla.org"
+    }
+  },
 
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>formautofill@mozilla.org</em:id>
-    <em:version>1.0</em:version>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-    <em:multiprocessCompatible>true</em:multiprocessCompatible>
+  "background": {
+    "scripts": ["background.js"]
+  },
 
-    <!-- Target Application this extension can install into,
-        with minimum and maximum supported versions. -->
-    <em:targetApplication>
-      <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
-        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <!-- Front End MetaData -->
-    <em:name>Form Autofill</em:name>
-    <em:description>Autofill forms with saved profiles</em:description>
-  </Description>
-</RDF>
+  "experiment_apis": {
+    "formautofill": {
+      "schema": "schema.json",
+      "parent": {
+        "scopes": ["addon_parent"],
+        "script": "api.js",
+        "events": ["startup"]
+      }
+    }
+  }
+}
--- a/browser/extensions/formautofill/moz.build
+++ b/browser/extensions/formautofill/moz.build
@@ -5,22 +5,37 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
 
 DIRS += ['locales']
 
 FINAL_TARGET_FILES.features['formautofill@mozilla.org'] += [
-  'bootstrap.js'
+  'api.js',
+  'background.js',
+  'manifest.json',
+  'schema.json',
 ]
 
-FINAL_TARGET_PP_FILES.features['formautofill@mozilla.org'] += [
-  'install.rdf.in'
-]
+if CONFIG['OS_ARCH'] == 'Linux':
+    FINAL_TARGET_FILES.features['formautofill@mozilla.org'].chrome.res += [
+        'skin/linux/autocomplete-item.css',
+        'skin/linux/editDialog.css',
+    ]
+elif CONFIG['OS_ARCH'] == 'Darwin':
+    FINAL_TARGET_FILES.features['formautofill@mozilla.org'].chrome.res += [
+        'skin/osx/autocomplete-item.css',
+        'skin/osx/editDialog.css',
+    ]
+elif CONFIG['OS_ARCH'] == 'WINNT':
+    FINAL_TARGET_FILES.features['formautofill@mozilla.org'].chrome.res += [
+        'skin/windows/autocomplete-item.css',
+        'skin/windows/editDialog.css',
+    ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
 
 JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/schema.json
@@ -0,0 +1,1 @@
+[]
rename from browser/extensions/formautofill/skin/shared/autocomplete-item.css
rename to browser/extensions/formautofill/skin/shared/autocomplete-item-shared.css
rename from browser/extensions/formautofill/skin/shared/editDialog.css
rename to browser/extensions/formautofill/skin/shared/editDialog-shared.css
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -3,72 +3,93 @@
  */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");
 ChromeUtils.import("resource://gre/modules/FormLikeFactory.jsm");
+ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm");
 ChromeUtils.import("resource://testing-common/FileTestUtils.jsm");
 ChromeUtils.import("resource://testing-common/MockDocument.jsm");
 ChromeUtils.import("resource://testing-common/TestUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "DownloadPaths",
                                "resource://gre/modules/DownloadPaths.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "resProto",
-                                   "@mozilla.org/network/protocol;1?name=resource",
-                                   "nsISubstitutingProtocolHandler");
+ChromeUtils.defineModuleGetter(this, "ExtensionParent",
+                               "resource://gre/modules/ExtensionParent.jsm");
 
 do_get_profile();
 
 // ================================================
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/releases/v2.3.2/
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
 /* globals sinon */
 // ================================================
 
-// Load our bootstrap extension manifest so we can access our chrome/resource URIs.
 const EXTENSION_ID = "formautofill@mozilla.org";
-let extensionDir = Services.dirsvc.get("GreD", Ci.nsIFile);
-extensionDir.append("browser");
-extensionDir.append("features");
-extensionDir.append(EXTENSION_ID);
-let bootstrapFile = extensionDir.clone();
-bootstrapFile.append("bootstrap.js");
-let bootstrapURI = Services.io.newFileURI(bootstrapFile).spec;
-// If the unpacked extension doesn't exist, use the packed version.
-if (!extensionDir.exists()) {
-  extensionDir = extensionDir.parent;
-  extensionDir.append(EXTENSION_ID + ".xpi");
-  let jarURI = Services.io.newFileURI(extensionDir);
-  bootstrapURI = "jar:" + jarURI.spec + "!/bootstrap.js";
+
+ExtensionTestUtils.init(this);
+
+async function loadExtension() {
+  ExtensionTestUtils.startAddonManager();
+
+  let extensionPath = Services.dirsvc.get("GreD", Ci.nsIFile);
+  extensionPath.append("browser");
+  extensionPath.append("features");
+  extensionPath.append(EXTENSION_ID);
+
+  if (!extensionPath.exists()) {
+    extensionPath.leafName = `${EXTENSION_ID}.xpi`;
+  }
+
+  let startupPromise = new Promise(resolve => {
+    const {apiManager} = ExtensionParent;
+    function onReady(event, extension) {
+      if (extension.id == EXTENSION_ID) {
+        apiManager.off("ready", onReady);
+        resolve();
+      }
+    }
+
+    apiManager.on("ready", onReady);
+  });
+
+  await AddonManager.installTemporaryAddon(extensionPath);
+  await startupPromise;
 }
-Components.manager.addBootstrappedManifestLocation(extensionDir);
-
-let resURI = Services.io.newURI("chrome/res/", null, Services.io.newURI(bootstrapURI));
-resProto.setSubstitution("formautofill", resURI);
 
 // Returns a reference to a temporary file that is guaranteed not to exist and
 // is cleaned up later. See FileTestUtils.getTempFile for details.
 function getTempFile(leafName) {
   return FileTestUtils.getTempFile(leafName);
 }
 
 async function initProfileStorage(fileName, records, collectionName = "addresses") {
   let {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
   let path = getTempFile(fileName).path;
   let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
+  // AddonTestUtils inserts its own directory provider that manages TmpD.
+  // It removes that directory at shutdown, which races with shutdown
+  // handing in JSONFile/DeferredTask (which is used by FormAutofillStorage).
+  // Avoid the race by explicitly finalizing any formautofill JSONFile
+  // instances created manually by individual tests when the test finishes.
+  registerCleanupFunction(function finalizeAutofillStorage() {
+    return profileStorage._finalize();
+  });
+
   if (!records || !Array.isArray(records)) {
     return profileStorage;
   }
 
   let onChanged = TestUtils.topicObserved(
     "formautofill-storage-changed",
     (subject, data) =>
       data == "add" &&
@@ -94,19 +115,21 @@ function verifySectionFieldDetails(secti
       let expectedField = expectedSectionInfo[fieldIndex];
       delete field._reason;
       delete field.elementWeakRef;
       Assert.deepEqual(field, expectedField);
     });
   });
 }
 
-function runHeuristicsTest(patterns, fixturePathPrefix) {
-  ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
-  ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+async function runHeuristicsTest(patterns, fixturePathPrefix) {
+  add_task(async function setup() {
+    ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
+    ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+  });
 
   patterns.forEach(testPattern => {
     add_task(async function() {
       info("Starting test fixture: " + testPattern.fixturePath);
       let file = do_get_file(fixturePathPrefix + testPattern.fixturePath);
       let doc = MockDocument.createTestDocumentFromFile("http://localhost:8080/test/", file);
 
       let forms = [];
@@ -189,9 +212,11 @@ add_task(async function head_initialize(
   // Clean up after every test.
   registerCleanupFunction(function head_cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.available");
     Services.prefs.clearUserPref("extensions.formautofill.creditCards.available");
     Services.prefs.clearUserPref("extensions.formautofill.heuristics.enabled");
     Services.prefs.clearUserPref("extensions.formautofill.section.enabled");
     Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
   });
+
+  await loadExtension();
 });
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -1,15 +1,19 @@
 /*
  * Test for status handling in Form Autofill Parent.
  */
 
 "use strict";
 
-let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {});
+let FormAutofillParent;
+
+add_task(async function setup() {
+  ({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {}));
+});
 
 add_task(async function test_activeStatus_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.spy(formAutofillParent, "_updateStatus");
 
   // Default status is null before initialization
   Assert.equal(formAutofillParent._active, null);
   Assert.equal(Services.ppmm.initialProcessData.autofillEnabled, undefined);
--- a/browser/extensions/formautofill/test/unit/test_addressDataLoader.js
+++ b/browser/extensions/formautofill/test/unit/test_addressDataLoader.js
@@ -1,27 +1,29 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
-
 const SUPPORT_COUNTRIES_TESTCASES = [
   {
     country: "US",
     properties: ["languages", "alternative_names", "sub_keys", "sub_names"],
   },
   {
     country: "CA",
     properties: ["languages", "name", "sub_keys", "sub_names"],
   },
   {
     country: "DE",
     properties: ["name"],
   },
 ];
 
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+});
+
 add_task(async function test_initalState() {
   // addressData should not exist
   Assert.equal(AddressDataLoader._addressData, undefined);
   // Verify _dataLoaded state
   Assert.equal(AddressDataLoader._dataLoaded.country, false);
   Assert.equal(AddressDataLoader._dataLoaded.level1.size, 0);
 });
 
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -1,17 +1,20 @@
 /*
  * Test for form auto fill content helper fill all inputs function.
  */
 /* eslint-disable mozilla/no-arbitrary-setTimeout */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
-let {MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {});
+let MasterPassword;
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
+  ({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
+});
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><select id="country"></select>
                <input id='email'><input id="tel"></form>`,
     focusedInputId: "given-name",
--- a/browser/extensions/formautofill/test/unit/test_collectFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -1,15 +1,17 @@
 /*
  * Test for form auto fill content helper collectFormFields functions.
  */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
+});
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><select id="country"></select>
                <input id='email'><input id="phone"></form>`,
     sections: [
--- a/browser/extensions/formautofill/test/unit/test_createRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_createRecords.js
@@ -1,15 +1,17 @@
 /*
  * Test for the normalization of records created by FormAutofillHandler.
  */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
+add_task(async function seutp() {
+  ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
+});
 
 const TESTCASES = [
   {
     description: "Don't contain a field whose length of value is greater than 200",
     document: `<form>
                 <input id="given-name" autocomplete="given-name">
                 <input id="organization" autocomplete="organization">
                 <input id="address-level1" autocomplete="address-level1">
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -1,19 +1,23 @@
 /**
  * Tests FormAutofillStorage object with creditCards records.
  */
 
 "use strict";
 
-const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
 ChromeUtils.defineModuleGetter(this, "Preferences",
                                "resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
 
+let FormAutofillStorage;
+add_task(async function setup() {
+  ({FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {}));
+});
+
 const TEST_STORE_FILE_NAME = "test-credit-card.json";
 const COLLECTION_NAME = "creditCards";
 
 const TEST_CREDIT_CARD_1 = {
   "cc-name": "John Doe",
   "cc-number": "4929001587121045",
   "cc-exp-month": 4,
   "cc-exp-year": 2017,
--- a/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
+++ b/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
@@ -1,11 +1,13 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
+add_task(async function() {
+  ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
+});
 
 const TESTCASES = [
   {
     description: "A label element contains one input element.",
     document: `<label id="typeA"> label type A
                  <!-- This comment should not be extracted. -->
                  <input type="text">
                  <script>FOO</script>
--- a/browser/extensions/formautofill/test/unit/test_findLabelElements.js
+++ b/browser/extensions/formautofill/test/unit/test_findLabelElements.js
@@ -1,11 +1,13 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
+add_task(async function() {
+  ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
+});
 
 const TESTCASES = [
   {
     description: "Input contains in a label element.",
     document: `<form>
                  <label id="labelA"> label type A
                    <input id="typeA" type="text">
                  </label>
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -1,15 +1,17 @@
 /*
  * Test for form auto fill content helper fill all inputs function.
  */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
+add_task(async function() {
+  ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
+});
 
 const DEFAULT_ADDRESS_RECORD = {
   "guid": "123",
   "street-address": "2 Harrison St\nline2\nline3",
   "address-line1": "2 Harrison St",
   "address-line2": "line2",
   "address-line3": "line3",
   "address-level1": "CA",
--- a/browser/extensions/formautofill/test/unit/test_getCategoriesFromFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_getCategoriesFromFieldNames.js
@@ -1,12 +1,14 @@
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+add_task(async function() {
+  ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+});
 
 add_task(async function test_isAddressField_isCreditCardField() {
   const TEST_CASES = {
     "given-name": {
       isAddressField: true,
       isCreditCardField: false,
     },
     "organization": {
--- a/browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
+++ b/browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
@@ -1,11 +1,13 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
+add_task(async function() {
+  ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
+});
 
 const TESTCASES = [
   {
     description: "Form containing 5 fields with autocomplete attribute.",
     document: `<form id="form1">
                  <input id="street-addr" autocomplete="street-address">
                  <input id="city" autocomplete="address-level2">
                  <select id="country" autocomplete="country"></select>
--- a/browser/extensions/formautofill/test/unit/test_getInfo.js
+++ b/browser/extensions/formautofill/test/unit/test_getInfo.js
@@ -1,11 +1,13 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
+add_task(async function() {
+  ChromeUtils.import("resource://formautofill/FormAutofillHeuristics.jsm");
+});
 
 const TESTCASES = [
   {
     description: "Input element in a label element",
     document: `<form>
                  <label> E-Mail
                    <input id="targetElement" type="text">
                  </label>
--- a/browser/extensions/formautofill/test/unit/test_getRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_getRecords.js
@@ -1,18 +1,22 @@
 /*
  * Test for make sure getRecords can retrieve right collection from storage.
  */
 
 "use strict";
 
-let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {});
-ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
 ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
 
+let FormAutofillParent;
+add_task(async function setup() {
+  ({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {}));
+  ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
+});
+
 const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
   "address-level2": "Cambridge",
   "address-level1": "MA",
--- a/browser/extensions/formautofill/test/unit/test_isAvailable.js
+++ b/browser/extensions/formautofill/test/unit/test_isAvailable.js
@@ -1,32 +1,37 @@
 /**
  * Test enabling the feature in specific locales and regions.
  */
 
 "use strict";
 
-// Load bootstrap.js into a sandbox to be able to test `isAvailable`
-let sandbox = {};
-Services.scriptloader.loadSubScript(bootstrapURI, sandbox, "utf-8");
-info("bootstrapURI: " + bootstrapURI);
+const DOM_ENABLED_PREF = "dom.forms.autocomplete.formautofill";
 
 add_task(async function test_defaultTestEnvironment() {
-  Assert.ok(sandbox.isAvailable());
+  Assert.ok(Services.prefs.getBoolPref(DOM_ENABLED_PREF));
 });
 
 add_task(async function test_unsupportedRegion() {
   Services.prefs.setCharPref("extensions.formautofill.available", "detect");
   Services.prefs.setCharPref("browser.search.region", "ZZ");
   registerCleanupFunction(function cleanupRegion() {
     Services.prefs.clearUserPref("browser.search.region");
   });
-  Assert.ok(!sandbox.isAvailable());
+
+  let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+  await addon.reload();
+
+  Assert.ok(!Services.prefs.getBoolPref(DOM_ENABLED_PREF));
 });
 
 add_task(async function test_supportedRegion() {
   Services.prefs.setCharPref("extensions.formautofill.available", "detect");
   Services.prefs.setCharPref("browser.search.region", "US");
   registerCleanupFunction(function cleanupRegion() {
     Services.prefs.clearUserPref("browser.search.region");
   });
-  Assert.ok(sandbox.isAvailable());
+
+  let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+  await addon.reload();
+
+  Assert.ok(Services.prefs.getBoolPref(DOM_ENABLED_PREF));
 });
--- a/browser/extensions/formautofill/test/unit/test_isCJKName.js
+++ b/browser/extensions/formautofill/test/unit/test_isCJKName.js
@@ -1,15 +1,17 @@
 /**
  * Tests the "isCJKName" function of FormAutofillNameUtils object.
  */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillNameUtils.jsm");
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillNameUtils.jsm");
+});
 
 // Test cases is initially copied from
 // https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_data_util_unittest.cc
 const TESTCASES = [
   {
     // Non-CJK language with only ASCII characters.
     fullName: "Homer Jay Simpson",
     expectedResult: false,
--- a/browser/extensions/formautofill/test/unit/test_isFieldEligibleForAutofill.js
+++ b/browser/extensions/formautofill/test/unit/test_isFieldEligibleForAutofill.js
@@ -1,11 +1,13 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+add_task(async function seutp() {
+  ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+});
 
 const TESTCASES = [
   {
     document: `<input id="targetElement" type="text">`,
     fieldId: "targetElement",
     expectedResult: true,
   },
   {
--- a/browser/extensions/formautofill/test/unit/test_markAsAutofillField.js
+++ b/browser/extensions/formautofill/test/unit/test_markAsAutofillField.js
@@ -1,12 +1,10 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
-
 const TESTCASES = [
   {
     description: "Form containing 8 fields with autocomplete attribute.",
     document: `<form>
                  <input id="given-name" autocomplete="given-name">
                  <input id="additional-name" autocomplete="additional-name">
                  <input id="family-name" autocomplete="family-name">
                  <input id="street-addr" autocomplete="street-address">
@@ -56,19 +54,24 @@ const TESTCASES = [
       "country",
       "email",
       "tel",
     ],
   },
 ];
 
 let markedFieldId = [];
-FormAutofillContent._markAsAutofillField = function(field) {
-  markedFieldId.push(field.id);
-};
+
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
+
+  FormAutofillContent._markAsAutofillField = function(field) {
+    markedFieldId.push(field.id);
+  };
+});
 
 TESTCASES.forEach(testcase => {
   add_task(async function() {
     info("Starting testcase: " + testcase.description);
 
     markedFieldId = [];
 
     let doc = MockDocument.createTestDocument(
--- a/browser/extensions/formautofill/test/unit/test_masterPassword.js
+++ b/browser/extensions/formautofill/test/unit/test_masterPassword.js
@@ -1,16 +1,20 @@
 /**
  * Tests of MasterPassword.jsm
  */
 
 "use strict";
 const {MockRegistrar} =
   ChromeUtils.import("resource://testing-common/MockRegistrar.jsm", {});
-let {MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {});
+
+let MasterPassword;
+add_task(async function setup() {
+  ({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
+});
 
 const TESTCASES = [{
   description: "With master password set",
   masterPassword: "fakemp",
   mpEnabled: true,
 },
 {
   description: "Without master password set",
@@ -62,20 +66,20 @@ do_get_profile();
 let windowWatcherCID =
   MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
                          gWindowWatcher);
 registerCleanupFunction(() => {
   MockRegistrar.unregister(windowWatcherCID);
 });
 
 TESTCASES.forEach(testcase => {
-  let token = MasterPassword._token;
-
   add_task(async function test_encrypt_decrypt() {
     info("Starting testcase: " + testcase.description);
+
+    let token = MasterPassword._token;
     token.initPassword(testcase.masterPassword);
 
     // Test only: Force the token login without asking for master password
     token.login(/* force */ false);
     Assert.equal(testcase.mpEnabled, token.isLoggedIn(), "Token should now be logged into");
     Assert.equal(MasterPassword.isEnabled, testcase.mpEnabled);
 
     let testText = "test string";
--- a/browser/extensions/formautofill/test/unit/test_migrateRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_migrateRecords.js
@@ -1,15 +1,19 @@
 /**
  * Tests the migration algorithm in profileStorage.
  */
 
 "use strict";
 
-const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
+let FormAutofillStorage;
+
+add_task(async function setup() {
+  ({FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {}));
+});
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const ADDRESS_SCHEMA_VERSION = 1;
 const CREDIT_CARD_SCHEMA_VERSION = 1;
 
 const ADDRESS_TESTCASES = [
   {
--- a/browser/extensions/formautofill/test/unit/test_nameUtils.js
+++ b/browser/extensions/formautofill/test/unit/test_nameUtils.js
@@ -1,15 +1,17 @@
 /**
  * Tests FormAutofillNameUtils object.
  */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillNameUtils.jsm");
+add_task(async function() {
+  ChromeUtils.import("resource://formautofill/FormAutofillNameUtils.jsm");
+});
 
 // Test cases initially copied from
 // https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_data_util_unittest.cc
 const TESTCASES = [
   {
     description: "Full name including given, middle and family names",
     fullName: "Homer Jay Simpson",
     nameParts: {
--- a/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
+++ b/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
@@ -1,11 +1,13 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillContent.jsm");
+});
 
 const MOCK_DOC = MockDocument.createTestDocument("http://localhost:8080/test/",
                    `<form id="form1">
                       <input id="street-addr" autocomplete="street-address">
                       <select id="address-level1" autocomplete="address-level1">
                         <option value=""></option>
                         <option value="AL">Alabama</option>
                         <option value="AK">Alaska</option>
--- a/browser/extensions/formautofill/test/unit/test_parseAddressFormat.js
+++ b/browser/extensions/formautofill/test/unit/test_parseAddressFormat.js
@@ -1,11 +1,13 @@
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+});
 
 add_task(async function test_parseAddressFormat() {
   const TEST_CASES = [
     {
       fmt: "%N%n%O%n%A%n%C, %S %Z", // US
       parsed: [
         {fieldId: "name", newLine: true},
         {fieldId: "organization", newLine: true},
--- a/browser/extensions/formautofill/test/unit/test_phoneNumber.js
+++ b/browser/extensions/formautofill/test/unit/test_phoneNumber.js
@@ -1,16 +1,18 @@
 /**
  * Tests PhoneNumber.jsm and PhoneNumberNormalizer.jsm.
  */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/phonenumberutils/PhoneNumber.jsm");
-ChromeUtils.import("resource://formautofill/phonenumberutils/PhoneNumberNormalizer.jsm");
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/phonenumberutils/PhoneNumber.jsm");
+  ChromeUtils.import("resource://formautofill/phonenumberutils/PhoneNumberNormalizer.jsm");
+});
 
 function IsPlain(dial, expected) {
   let result = PhoneNumber.IsPlain(dial);
   Assert.equal(result, expected);
 }
 
 function Normalize(dial, expected) {
   let result = PhoneNumberNormalizer.Normalize(dial);
--- a/browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
+++ b/browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
@@ -1,13 +1,15 @@
 "use strict";
 
 /* global AddressResult, CreditCardResult */
-// eslint-disable-next-line no-unused-vars
-ChromeUtils.import("resource://formautofill/ProfileAutoCompleteResult.jsm");
+add_task(async function setup() {
+  // eslint-disable-next-line no-unused-vars
+  ChromeUtils.import("resource://formautofill/ProfileAutoCompleteResult.jsm");
+});
 
 let matchingProfiles = [{
   guid: "test-guid-1",
   "given-name": "Timothy",
   "family-name": "Berners-Lee",
   name: "Timothy Berners-Lee",
   organization: "Sesame Street",
   "street-address": "123 Sesame Street.",
@@ -336,25 +338,25 @@ let creditCardTestCases = [{
   fieldName: "",
   expected: {
     searchResult: Ci.nsIAutoCompleteResult.RESULT_FAILURE,
     defaultIndex: 0,
     items: [],
   },
 }];
 
-let testSets = [{
-  collectionConstructor: AddressResult,
-  testCases: addressTestCases,
-}, {
-  collectionConstructor: CreditCardResult,
-  testCases: creditCardTestCases,
-}];
+add_task(async function test_all_patterns() {
+  let testSets = [{
+    collectionConstructor: AddressResult,
+    testCases: addressTestCases,
+  }, {
+    collectionConstructor: CreditCardResult,
+    testCases: creditCardTestCases,
+  }];
 
-add_task(async function test_all_patterns() {
   testSets.forEach(({collectionConstructor, testCases}) => {
     testCases.forEach(testCase => {
       info("Starting testcase: " + testCase.description);
       let actual = new collectionConstructor(testCase.searchString,
                                              testCase.fieldName,
                                              testCase.allFieldNames,
                                              testCase.matchingProfiles,
                                              testCase.options);
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -1,15 +1,19 @@
 /*
  * Test for keeping the valid fields information in initialProcessData.
  */
 
 "use strict";
 
-let {FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {});
+let FormAutofillParent;
+
+add_task(async function setup() {
+  ({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {}));
+});
 
 add_task(async function test_profileSavedFieldNames_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
 
   await formAutofillParent.init();
   await formAutofillParent.formAutofillStorage.initialize();
   Assert.equal(formAutofillParent._updateSavedFieldNames.called, true);
--- a/browser/extensions/formautofill/test/unit/test_storage_remove.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_remove.js
@@ -1,15 +1,18 @@
 /**
  * Tests removing all address/creditcard records.
  */
 
 "use strict";
 
-const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
+let FormAutofillStorage;
+add_task(async function setup() {
+  ({FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {}));
+});
 
 const TEST_STORE_FILE_NAME = "test-tombstones.json";
 
 const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
--- a/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
@@ -1,15 +1,18 @@
 /**
  * Tests tombstones in address/creditcard records.
  */
 
 "use strict";
 
-const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
+let FormAutofillStorage;
+add_task(async function setup() {
+  ({FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {}));
+});
 
 const TEST_STORE_FILE_NAME = "test-tombstones.json";
 
 const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
--- a/browser/extensions/formautofill/test/unit/test_sync.js
+++ b/browser/extensions/formautofill/test/unit/test_sync.js
@@ -8,18 +8,21 @@
 /* import-globals-from ../../../../../services/sync/tests/unit/head_http_server.js */
 
 "use strict";
 
 ChromeUtils.import("resource://services-sync/service.js");
 ChromeUtils.import("resource://services-sync/constants.js");
 ChromeUtils.import("resource://testing-common/services/sync/utils.js");
 
-let {sanitizeStorageObject, AutofillRecord, AddressesEngine} =
-  ChromeUtils.import("resource://formautofill/FormAutofillSync.jsm", {});
+let sanitizeStorageObject, AutofillRecord, AddressesEngine;
+add_task(async function() {
+  ({sanitizeStorageObject, AutofillRecord, AddressesEngine} =
+    ChromeUtils.import("resource://formautofill/FormAutofillSync.jsm", {}));
+});
 
 
 Services.prefs.setCharPref("extensions.formautofill.loglevel", "Trace");
 initTestLogging("Trace");
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const TEST_PROFILE_1 = {
--- a/browser/extensions/formautofill/test/unit/test_toOneLineAddress.js
+++ b/browser/extensions/formautofill/test/unit/test_toOneLineAddress.js
@@ -1,12 +1,14 @@
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+add_task(async function setup() {
+  ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+});
 
 add_task(async function test_getCategoriesFromFieldNames() {
   const TEST_CASES = [
     {
       strings: ["A", "B", "C", "D"],
       expectedValue: "A B C D",
     },
     {
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -1,15 +1,18 @@
 /**
  * Tests the transform algorithm in profileStorage.
  */
 
 "use strict";
 
-const {FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
+let FormAutofillStorage;
+add_task(async function setup() {
+  ({FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {}));
+});
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const ADDRESS_COMPUTE_TESTCASES = [
   // Name
   {
     description: "Has split names",
     address: {
@@ -896,16 +899,18 @@ add_task(async function test_computeAddr
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.addresses.add(testcase.address);
     let address = profileStorage.addresses.get(guid);
     do_check_record_matches(testcase.expectedResult, address);
 
     profileStorage.addresses.remove(guid);
   });
+
+  await profileStorage._finalize();
 });
 
 add_task(async function test_normalizeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
@@ -913,16 +918,18 @@ add_task(async function test_normalizeAd
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.addresses.add(testcase.address);
     let address = profileStorage.addresses.get(guid);
     do_check_record_matches(testcase.expectedResult, address);
 
     profileStorage.addresses.remove(guid);
   });
+
+  await profileStorage._finalize();
 });
 
 add_task(async function test_computeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
@@ -930,16 +937,18 @@ add_task(async function test_computeCred
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.creditCards.add(testcase.creditCard);
     let creditCard = profileStorage.creditCards.get(guid);
     do_check_record_matches(testcase.expectedResult, creditCard);
 
     profileStorage.creditCards.remove(guid);
   });
+
+  await profileStorage._finalize();
 });
 
 add_task(async function test_normalizeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
@@ -947,9 +956,11 @@ add_task(async function test_normalizeCr
     info("Verify testcase: " + testcase.description);
 
     let guid = profileStorage.creditCards.add(testcase.creditCard);
     let creditCard = profileStorage.creditCards.get(guid, {rawData: true});
     do_check_record_matches(testcase.expectedResult, creditCard);
 
     profileStorage.creditCards.remove(guid);
   });
+
+  await profileStorage._finalize();
 });
--- a/testing/modules/FileTestUtils.jsm
+++ b/testing/modules/FileTestUtils.jsm
@@ -113,16 +113,21 @@ XPCOMUtils.defineLazyGetter(FileTestUtil
     let dir = FileUtils.getFile("TmpD", ["testdir-" + randomNumber]);
     dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
     AsyncShutdown.profileBeforeChange.addBlocker("Removing test files",
       async () => {
         // Remove the files we know about first.
         for (let path of gPathsToRemove) {
           await this.tolerantRemove(path);
         }
+
+        if (!(await OS.File.exists(dir.path))) {
+          return;
+        }
+
         // Detect any extra files, like the ".part" files of downloads.
         let iterator = new OS.File.DirectoryIterator(dir.path);
         try {
           await iterator.forEach(entry => this.tolerantRemove(entry.path,
                                                               entry.isDir));
         } finally {
           iterator.close();
         }
--- a/toolkit/content/widgets.css
+++ b/toolkit/content/widgets.css
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* ===== widgets.css =====================================================
    == Styles ported from XBL <resources>, loaded by "global.css".
    ======================================================================= */
 
 @import url("chrome://global/content/autocomplete.css");
 @import url("chrome://global/skin/autocomplete.css");
-@import url("chrome://formautofill-shared/skin/autocomplete-item.css");
-@import url("chrome://formautofill/skin/autocomplete-item.css");
+@import url("resource://formautofill/autocomplete-item-shared.css");
+@import url("resource://formautofill/autocomplete-item.css");
 @import url("chrome://global/skin/dialog.css");
 @import url("chrome://global/skin/dropmarker.css");
 @import url("chrome://global/skin/groupbox.css");
 @import url("chrome://global/skin/menu.css");
 @import url("chrome://global/skin/menulist.css");
 @import url("chrome://global/skin/notification.css");
 @import url("chrome://global/skin/popup.css");
 @import url("chrome://global/skin/progressmeter.css");