Bug 1450388 Part 2: Expose startupData to webextensions draft
authorAndrew Swan <aswan@mozilla.com>
Wed, 21 Mar 2018 10:22:13 -0700
changeset 779299 dc26174a27d44bd6badaa653abfebe69ffa2a160
parent 779298 697085ab65708af7aa24a0163171b3efc97e473a
child 779300 5f0d554a44dbd247877ef0cc3b854bcd20781066
push id105738
push useraswan@mozilla.com
push dateMon, 09 Apr 2018 17:23:03 +0000
bugs1450388
milestone61.0a1
Bug 1450388 Part 2: Expose startupData to webextensions MozReview-Commit-ID: AbqR3hECLZe
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/test/xpcshell/test_ext_startupData.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1158,16 +1158,17 @@ class Extension extends ExtensionData {
 
     if (addonData.cleanupFile) {
       Services.obs.addObserver(this, "xpcom-shutdown");
       this.cleanupFile = addonData.cleanupFile || null;
       delete addonData.cleanupFile;
     }
 
     this.addonData = addonData;
+    this.startupData = addonData.startupData || {};
     this.startupReason = startupReason;
 
     if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)) {
       StartupCache.clearAddonData(addonData.id);
     }
 
     this.remote = !WebExtensionPolicy.isExtensionProcess;
 
@@ -1352,16 +1353,20 @@ class Extension extends ExtensionData {
              this.addonData.temporarilyInstalled));
   }
 
   get experimentsAllowed() {
     return (AddonSettings.ALLOW_LEGACY_EXTENSIONS ||
             this.isPrivileged);
   }
 
+  saveStartupData() {
+    AddonManagerPrivate.setStartupData(this.id, this.startupData);
+  }
+
   async _parseManifest() {
     let manifest = await super.parseManifest();
     if (manifest && manifest.permissions.has("mozillaAddons") &&
         !this.isPrivileged) {
       Cu.reportError(`Stripping mozillaAddons permission from ${this.id}`);
       manifest.permissions.delete("mozillaAddons");
       let i = manifest.manifest.permissions.indexOf("mozillaAddons");
       if (i >= 0) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_startupData.js
@@ -0,0 +1,40 @@
+"use strict";
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+// Tests that startupData is persisted and is available at startup
+add_task(async function test_startupData() {
+  await AddonTestUtils.promiseStartupManager();
+
+  let wrapper = ExtensionTestUtils.loadExtension({useAddonManager: "permanent"});
+  await wrapper.startup();
+
+  let {extension} = wrapper;
+
+  deepEqual(extension.startupData, {}, "startupData for a new extension defaults to empty object");
+
+  const DATA = {test: "i am some startup data"};
+  extension.startupData = DATA;
+  extension.saveStartupData();
+
+  await AddonTestUtils.promiseRestartManager();
+  await wrapper.startupPromise;
+
+  ({extension} = wrapper);
+  deepEqual(extension.startupData, DATA, "startupData is present on restart");
+
+  const DATA2 = {other: "this is different data"};
+  extension.startupData = DATA2;
+  extension.saveStartupData();
+
+  await AddonTestUtils.promiseRestartManager();
+  await wrapper.startupPromise;
+
+  ({extension} = wrapper);
+  deepEqual(extension.startupData, DATA2, "updated startupData is present on restart");
+
+  await wrapper.unload();
+  await AddonTestUtils.promiseShutdownManager();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -78,16 +78,17 @@ skip-if = true # bug 1315829
 [test_ext_runtime_sendMessage.js]
 [test_ext_runtime_sendMessage_errors.js]
 [test_ext_runtime_sendMessage_no_receiver.js]
 [test_ext_runtime_sendMessage_self.js]
 [test_ext_shutdown_cleanup.js]
 [test_ext_sandbox_var.js]
 [test_ext_schema.js]
 [test_ext_simple.js]
+[test_ext_startupData.js]
 [test_ext_startup_cache.js]
 skip-if = os == "android"
 [test_ext_startup_perf.js]
 [test_ext_storage.js]
 [test_ext_storage_content.js]
 [test_ext_storage_managed.js]
 skip-if = os == "android"
 [test_ext_storage_sync.js]
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -3116,16 +3116,36 @@ var AddonManagerPrivate = {
     return AddonManagerInternal._getProviderByName("XPIProvider")
                                .isTemporaryInstallID(extensionId);
   },
 
   isDBLoaded() {
     let provider = AddonManagerInternal._getProviderByName("XPIProvider");
     return provider ? provider.isDBLoaded : false;
   },
+
+  /**
+   * Sets startupData for the given addon.  The provided data will be stored
+   * in addonsStartup.json so it is available early during browser startup.
+   * Note that this file is read synchronously at startup, so startupData
+   * should be used with care.
+   *
+   * @param {string} aID
+   *         The id of the addon to save startup data for.
+   * @param {any} aData
+   *        The data to store.  Must be JSON serializable.
+   */
+  setStartupData(aID, aData) {
+    if (!gStarted)
+      throw Components.Exception("AddonManager is not initialized",
+                                 Cr.NS_ERROR_NOT_INITIALIZED);
+
+    AddonManagerInternal._getProviderByName("XPIProvider")
+                        .setStartupData(aID, aData);
+  },
 };
 
 /**
  * This is the public API that UI and developers should be calling. All methods
  * just forward to AddonManagerInternal.
  * @class
  */
 var AddonManager = {
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1290,17 +1290,19 @@ class XPIState {
     if (aDBAddon.type == "theme") {
       this.enabled = aDBAddon.internalName == DEFAULT_SKIN;
     } else {
       this.enabled = aDBAddon.visible && !aDBAddon.disabled;
     }
 
     this.version = aDBAddon.version;
     this.type = aDBAddon.type;
-    this.startupData = aDBAddon.startupData;
+    if (aDBAddon.startupData) {
+      this.startupData = aDBAddon.startupData;
+    }
 
     this.bootstrapped = !!aDBAddon.bootstrap;
     if (this.bootstrapped) {
       this.hasEmbeddedWebExtension = aDBAddon.hasEmbeddedWebExtension;
       this.dependencies = aDBAddon.dependencies;
       this.runInSafeMode = canRunInSafeMode(aDBAddon);
     }
 
@@ -3444,16 +3446,33 @@ var XPIProvider = {
     // Notify providers that a new theme has been enabled.
     if (isTheme(addon.type))
       AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false);
 
     return addon.wrapper;
   },
 
   /**
+   * Sets startupData for the given addon.  The provided data will be stored
+   * in addonsStartup.json so it is available early during browser startup.
+   * Note that this file is read synchronously at startup, so startupData
+   * should be used with care.
+   *
+   * @param {string} aID
+   *         The id of the addon to save startup data for.
+   * @param {any} aData
+   *        The data to store.  Must be JSON serializable.
+   */
+  setStartupData(aID, aData) {
+    let state = XPIStates.findAddon(aID);
+    state.startupData = aData;
+    XPIStates.save();
+  },
+
+  /**
    * Returns an Addon corresponding to an instance ID.
    * @param aInstanceID
    *        An Addon Instance ID
    * @return {Promise}
    * @resolves The found Addon or null if no such add-on exists.
    * @rejects  Never
    * @throws if the aInstanceID argument is not specified
    */