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 828924 ecda6532c852b3f225fce998e3ce83ad157760e6
parent 828923 515c80e08cb90de324300c000b50d0d69b608951
child 828925 644216b0ad9b5f9e1e9e8f1f930d4db1c6e717a1
push id118727
push userbmo:rbartlensky@mozilla.com
push dateTue, 14 Aug 2018 10:49:39 +0000
reviewersMattN, kmag
bugs1449055
milestone63.0a1
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");