Bug 1525762: Part 1a - Add maybeInstallBuiltinAddon method. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Fri, 08 Feb 2019 12:32:49 -0800
changeset 466961 cf6d3341f350209bc140849f073867608b55c51a
parent 466923 9d74f5279e4f223d07fdfddbbe2340f39c6f7a53
child 466962 40936b75fd328853be677891f47dcff4ba85b2c0
push id35789
push userbtara@mozilla.com
push dateSun, 31 Mar 2019 09:00:52 +0000
treeherdermozilla-central@c06dfc552c64 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1525762
milestone68.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 1525762: Part 1a - Add maybeInstallBuiltinAddon method. r=aswan This allows us to install built-in themes at startup only when they're new, or have changed, without requiring special work from callers, and in particular without requiring loading the add-on database when no changes are required.
toolkit/components/extensions/ExtensionXPCShellUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
toolkit/components/extensions/test/xpcshell/test_ext_management.js
toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js
toolkit/components/extensions/test/xpcshell/test_ext_startup_perf.js
toolkit/components/extensions/test/xpcshell/test_ext_storage.js
toolkit/components/extensions/test/xpcshell/test_ext_storage_content.js
toolkit/components/extensions/test/xpcshell/test_ext_storage_managed.js
toolkit/components/extensions/test/xpcshell/test_ext_storage_managed_policy.js
toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -775,35 +775,28 @@ var ExtensionTestUtils = {
       return Promise.all(Array.from(this.fetchScopes.values(),
                                     promise => promise.then(scope => scope.close())));
     });
   },
 
   addonManagerStarted: false,
 
   mockAppInfo() {
-    const {updateAppInfo} = ChromeUtils.import("resource://testing-common/AppInfo.jsm");
-    updateAppInfo({
-      ID: "xpcshell@tests.mozilla.org",
-      name: "XPCShell",
-      version: "48",
-      platformVersion: "48",
-    });
+    AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org",
+                                 "XPCShell", "48", "48");
   },
 
   startAddonManager() {
     if (this.addonManagerStarted) {
       return;
     }
     this.addonManagerStarted = true;
     this.mockAppInfo();
 
-    let manager = Cc["@mozilla.org/addons/integration;1"].getService(Ci.nsIObserver)
-                                                         .QueryInterface(Ci.nsITimerCallback);
-    manager.observe(null, "addons-startup", null);
+    return AddonTestUtils.promiseStartupManager();
   },
 
   loadExtension(data) {
     if (data.useAddonManager) {
       // If we're using incognitoOverride, we'll need to ensure
       // an ID is available before generating the XPI.
       if (data.incognitoOverride) {
         ExtensionTestCommon.setExtensionID(data);
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
@@ -8,16 +8,18 @@ function waitForPrefChange(pref) {
       Services.prefs.removeObserver(pref, observeChange);
       resolve();
     }
 
     Services.prefs.addObserver(pref, observeChange);
   });
 }
 
+AddonTestUtils.init(this);
+
 add_task(async function startup() {
   await ExtensionTestUtils.startAddonManager();
 });
 
 add_task(async function test_contextualIdentities_without_permissions() {
   function background() {
     browser.test.assertTrue(!browser.contextualIdentities,
                             "contextualIdentities API is not available when the contextualIdentities permission is not required");
--- a/toolkit/components/extensions/test/xpcshell/test_ext_management.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_management.js
@@ -1,12 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+AddonTestUtils.init(this);
+
 add_task(async function setup() {
   await ExtensionTestUtils.startAddonManager();
 });
 
 add_task(async function test_management_getAll() {
   const id1 = "get_all_test1@tests.mozilla.com";
   const id2 = "get_all_test2@tests.mozilla.com";
 
--- a/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js
@@ -34,16 +34,18 @@ let promptService = {
   _response: null,
   QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptService]),
   confirmEx: function(...args) {
     this._confirmExArgs = args;
     return this._response;
   },
 };
 
+AddonTestUtils.init(this);
+
 add_task(async function setup() {
   let fakePromptService = MockRegistrar.register("@mozilla.org/embedcomp/prompt-service;1", promptService);
   registerCleanupFunction(() => {
     MockRegistrar.unregister(fakePromptService);
   });
   await ExtensionTestUtils.startAddonManager();
 });
 
--- a/toolkit/components/extensions/test/xpcshell/test_ext_startup_perf.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_startup_perf.js
@@ -20,16 +20,18 @@ const STARTUP_MODULES = [
 ];
 
 if (!Services.prefs.getBoolPref("extensions.webextensions.remote")) {
   STARTUP_MODULES.push(
     "resource://gre/modules/ExtensionChild.jsm",
     "resource://gre/modules/ExtensionPageChild.jsm");
 }
 
+AddonTestUtils.init(this);
+
 // Tests that only the minimal set of API scripts and modules are loaded at
 // startup for a simple extension.
 add_task(async function test_loaded_scripts() {
   await ExtensionTestUtils.startAddonManager();
 
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
     background() {},
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage.js
@@ -2,16 +2,18 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "ExtensionStorageIDB",
                                "resource://gre/modules/ExtensionStorageIDB.jsm");
 
 const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled";
 
+AddonTestUtils.init(this);
+
 add_task(async function setup() {
   await ExtensionTestUtils.startAddonManager();
 });
 
 /**
  * Utility function to ensure that all supported APIs for getting are
  * tested.
  *
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_content.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_content.js
@@ -247,16 +247,18 @@ async function test_contentscript_storag
 
   extension.sendMessage(`test-${storageType}`);
   await extension.awaitMessage("test-finished");
 
   await extension.unload();
   await contentPage.close();
 }
 
+AddonTestUtils.init(this);
+
 add_task(async function setup() {
   await ExtensionTestUtils.startAddonManager();
 });
 
 add_task(async function test_contentscript_storage_sync() {
   return runWithPrefs([[STORAGE_SYNC_PREF, true]],
                       () => test_contentscript_storage("sync"));
 });
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_managed.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_managed.js
@@ -14,16 +14,18 @@ const MANIFEST = {
     str: "hello",
     obj: {
       a: [2, 3],
       b: true,
     },
   },
 };
 
+AddonTestUtils.init(this);
+
 add_task(async function setup() {
   await ExtensionTestUtils.startAddonManager();
 
   let tmpDir = FileUtils.getDir("TmpD", ["native-manifests"]);
   tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
   let dirProvider = {
     getFile(property) {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_managed_policy.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_managed_policy.js
@@ -10,16 +10,18 @@ const {EnterprisePolicyTesting} = Chrome
 Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref(PREF_DISABLE_SECURITY);
 });
 
 // Load policy engine
 Services.policies; // eslint-disable-line no-unused-expressions
 
+AddonTestUtils.init(this);
+
 add_task(async function test_storage_managed_policy() {
   await ExtensionTestUtils.startAddonManager();
 
   await EnterprisePolicyTesting.setupPolicyEngineWithJson({
     "policies": {
       "3rdparty": {
         "Extensions": {
           "test-storage-managed-policy@mozilla.com": {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js
@@ -12,16 +12,18 @@ async function checkExtensionStartupAndU
   await ext.startup();
   Assert.ok(ext.id, "Extension ID should be available");
   Assert.ok(ext.uuid, "Extension UUID should be available");
   await ext.unload();
   // Once set nothing clears the UUID.
   Assert.ok(ext.uuid, "Extension UUID exists after unload");
 }
 
+AddonTestUtils.init(this);
+
 add_task(async function setup() {
   await ExtensionTestUtils.startAddonManager();
 });
 
 add_task(async function test_MockExtension() {
   let ext = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
     manifest: {},
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2073,16 +2073,38 @@ var AddonManagerInternal = {
     if (!gStarted)
       throw Components.Exception("AddonManager is not initialized",
                                  Cr.NS_ERROR_NOT_INITIALIZED);
 
     return AddonManagerInternal._getProviderByName("XPIProvider")
                                .installBuiltinAddon(aBase);
   },
 
+  /**
+   * Like `installBuiltinAddon`, but only installs the addon at `aBase`
+   * if an existing built-in addon with the ID `aID` and version doesn't
+   * already exist.
+   *
+   * @param {string} aID
+   *        The ID of the add-on being registered.
+   * @param {string} aVersion
+   *        The version of the add-on being registered.
+   * @param {string} aBase
+   *        A string containing the base URL.  Must be a resource: URL.
+   * @returns a Promise that resolves when the addon is installed.
+   */
+  maybeInstallBuiltinAddon(aID, aVersion, aBase) {
+    if (!gStarted)
+      throw Components.Exception("AddonManager is not initialized",
+                                 Cr.NS_ERROR_NOT_INITIALIZED);
+
+    return AddonManagerInternal._getProviderByName("XPIProvider")
+                               .maybeInstallBuiltinAddon(aID, aVersion, aBase);
+  },
+
    syncGetAddonIDByInstanceID(aInstanceID) {
      if (!gStarted)
        throw Components.Exception("AddonManager is not initialized",
                                   Cr.NS_ERROR_NOT_INITIALIZED);
 
      if (!aInstanceID || typeof aInstanceID != "symbol")
        throw Components.Exception("aInstanceID must be a Symbol()",
                                   Cr.NS_ERROR_INVALID_ARG);
@@ -3359,16 +3381,20 @@ var AddonManager = {
   installTemporaryAddon(aDirectory) {
     return AddonManagerInternal.installTemporaryAddon(aDirectory);
   },
 
   installBuiltinAddon(aBase) {
     return AddonManagerInternal.installBuiltinAddon(aBase);
   },
 
+  maybeInstallBuiltinAddon(aID, aVersion, aBase) {
+    return AddonManagerInternal.maybeInstallBuiltinAddon(aID, aVersion, aBase);
+  },
+
   addManagerListener(aListener) {
     AddonManagerInternal.addManagerListener(aListener);
   },
 
   removeManagerListener(aListener) {
     AddonManagerInternal.removeManagerListener(aListener);
   },
 
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -743,16 +743,18 @@ var AddonTestUtils = {
 
     this.addonIntegrationService = Cc["@mozilla.org/addons/integration;1"]
           .getService(Ci.nsIObserver);
 
     this.addonIntegrationService.observe(null, "addons-startup", null);
 
     this.emit("addon-manager-started");
 
+    await Promise.all(XPIScope.XPIProvider.startupPromises);
+
     // Load the add-ons list as it was after extension registration
     await this.loadAddonsList(true);
 
     // Wait for all add-ons to finish starting up before resolving.
     const {XPIProvider} = ChromeUtils.import("resource://gre/modules/addons/XPIProvider.jsm");
     await Promise.all(Array.from(XPIProvider.activeAddons.values(),
                                  addon => addon.startupPromise));
   },
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1963,16 +1963,18 @@ var XPIProvider = {
 
   // A Map of active addons to their bootstrapScope by ID
   activeAddons: new Map(),
   // Per-addon telemetry information
   _telemetryDetails: {},
   // Have we started shutting down bootstrap add-ons?
   _closing: false,
 
+  startupPromises: [],
+
   // Check if the XPIDatabase has been loaded (without actually
   // triggering unwanted imports or I/O)
   get isDBLoaded() {
     // Make sure we don't touch the XPIDatabase getter before it's
     // actually loaded, and force an early load.
     return (Object.getOwnPropertyDescriptor(gGlobalScope, "XPIDatabase").value &&
             XPIDatabase.initialized) || false;
   },
@@ -2570,16 +2572,23 @@ var XPIProvider = {
       } catch (e) {
         logger.error(`Failed to install distribution add-on ${file.path}`, e);
       }
     }
 
     return changed;
   },
 
+  maybeInstallBuiltinAddon(aID, aVersion, aBase) {
+    let existing = BuiltInLocation.get(aID);
+    if (!existing || existing.version != aVersion) {
+      this.startupPromises.push(this.installBuiltinAddon(aBase));
+    }
+  },
+
   getDependentAddons(aAddon) {
     return Array.from(XPIDatabase.getAddons())
                 .filter(addon => addon.dependencies.includes(aAddon.id));
   },
 
   /**
    * Checks for any changes that have occurred since the last time the
    * application was launched.