Bug 1247435 - Add support for runtime.onStartup r?kmag draft
authorMatthew Wein <mwein@mozilla.com>
Wed, 19 Oct 2016 18:42:27 +0100
changeset 434847 17124ec52046d5909064561f3955b7d6ce59d4d2
parent 434846 6c6ed5d7155ceed03e249f92612aa91b90d4211e
child 536136 b22feb216ce37c8db4d2a0c69ad7ca27a863dbc2
push id34848
push usermwein@mozilla.com
push dateMon, 07 Nov 2016 16:45:29 +0000
reviewerskmag
bugs1247435
milestone52.0a1
Bug 1247435 - Add support for runtime.onStartup r?kmag MozReview-Commit-ID: F4leEN59vQe
toolkit/components/extensions/ext-runtime.js
toolkit/components/extensions/schemas/runtime.json
toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled.js
toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -9,25 +9,38 @@ Cu.import("resource://gre/modules/Extens
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 
 var {
-  ignoreEvent,
   SingletonEventManager,
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("runtime", "addon_parent", context => {
   let {extension} = context;
   return {
     runtime: {
-      onStartup: ignoreEvent(context, "runtime.onStartup"),
+      onStartup: new SingletonEventManager(context, "runtime.onStartup", fire => {
+        if (context.incognito) {
+          // This event should not fire if we are operating in a private profile.
+          return () => {};
+        }
+        let listener = () => {
+          if (extension.startupReason === "APP_STARTUP") {
+            fire();
+          }
+        };
+        extension.on("startup", listener);
+        return () => {
+          extension.off("startup", listener);
+        };
+      }).api(),
 
       onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => {
         let listener = () => {
           switch (extension.startupReason) {
             case "APP_STARTUP":
               if (Extension.browserUpdated) {
                 fire({reason: "browser_update"});
               }
--- a/toolkit/components/extensions/schemas/runtime.json
+++ b/toolkit/components/extensions/schemas/runtime.json
@@ -448,19 +448,18 @@
             ]
           }
         ]
       }
     ],
     "events": [
       {
         "name": "onStartup",
-        "unsupported": true,
         "type": "function",
-        "description": "Fired when a profile that has this extension installed first starts up. This event is not fired when an incognito profile is started, even if this extension is operating in 'split' incognito mode."
+        "description": "Fired when a profile that has this extension installed first starts up. This event is not fired for incognito profiles."
       },
       {
         "name": "onInstalled",
         "type": "function",
         "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when the browser is updated to a new version.",
         "parameters": [
           {
             "type": "object",
--- a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
@@ -69,16 +69,17 @@ let expectedBackgroundApis = [
   "management.ExtensionInstallType",
   "management.ExtensionType",
   "management.getSelf",
   "management.uninstallSelf",
   "runtime.getBackgroundPage",
   "runtime.getBrowserInfo",
   "runtime.getPlatformInfo",
   "runtime.onInstalled",
+  "runtime.onStartup",
   "runtime.onUpdateAvailable",
   "runtime.openOptionsPage",
   "runtime.reload",
   "runtime.setUninstallURL",
 ];
 
 function sendAllApis() {
   function isEvent(key, val) {
rename from toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled.js
rename to toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
@@ -34,16 +34,59 @@ function awaitEvent(eventName) {
         resolve(...args);
       }
     };
 
     Management.on(eventName, listener);
   });
 }
 
+function background() {
+  let onInstalledDetails = null;
+  let onStartupFired = false;
+
+  browser.runtime.onInstalled.addListener(details => {
+    onInstalledDetails = details;
+  });
+
+  browser.runtime.onStartup.addListener(() => {
+    onStartupFired = true;
+  });
+
+  browser.test.onMessage.addListener(message => {
+    if (message === "get-on-installed-details") {
+      onInstalledDetails = onInstalledDetails || {fired: false};
+      browser.test.sendMessage("on-installed-details", onInstalledDetails);
+    } else if (message === "did-on-startup-fire") {
+      browser.test.sendMessage("on-startup-fired", onStartupFired);
+    } else if (message === "reload-extension") {
+      browser.runtime.reload();
+    }
+  });
+
+  browser.runtime.onUpdateAvailable.addListener(details => {
+    browser.test.sendMessage("reloading");
+    browser.runtime.reload();
+  });
+}
+
+function* expectEvents(extension, {onStartupFired, onInstalledFired, onInstalledReason}) {
+  extension.sendMessage("get-on-installed-details");
+  let details = yield extension.awaitMessage("on-installed-details");
+  if (onInstalledFired) {
+    equal(details.reason, onInstalledReason, "runtime.onInstalled fired with the correct reason");
+  } else {
+    equal(details.fired, onInstalledFired, "runtime.onInstalled should not have fired");
+  }
+
+  extension.sendMessage("did-on-startup-fire");
+  let fired = yield extension.awaitMessage("on-startup-fired");
+  equal(fired, onStartupFired, `Expected runtime.onStartup to ${onStartupFired ? "" : "not "} fire`);
+}
+
 add_task(function* test_should_fire_on_addon_update() {
   const EXTENSION_ID = "test_runtime_on_installed_addon_update@tests.mozilla.org";
 
   const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
 
   // The test extension uses an insecure update url.
   Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
 
@@ -56,26 +99,17 @@ add_task(function* test_should_fire_on_a
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
           "update_url": `http://localhost:${port}/test_update.json`,
         },
       },
     },
-    background() {
-      browser.runtime.onUpdateAvailable.addListener(details => {
-        browser.test.sendMessage("reloading");
-        browser.runtime.reload();
-      });
-
-      browser.runtime.onInstalled.addListener(details => {
-        browser.test.sendMessage("installed", details);
-      });
-    },
+    background,
   });
 
   testServer.registerPathHandler("/test_update.json", (request, response) => {
     response.write(`{
       "addons": {
         "${EXTENSION_ID}": {
           "updates": [
             {
@@ -92,30 +126,30 @@ add_task(function* test_should_fire_on_a
     manifest: {
       version: "2.0",
       applications: {
         gecko: {
           id: EXTENSION_ID,
         },
       },
     },
-    background() {
-      browser.runtime.onInstalled.addListener(details => {
-        browser.test.sendMessage("installed", details);
-      });
-    },
+    background,
   });
 
   testServer.registerFile("/addons/test_runtime_on_installed-2.0.xpi", webExtensionFile);
 
   yield promiseStartupManager();
 
   yield extension.startup();
-  let details = yield extension.awaitMessage("installed");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let addon = yield promiseAddonByID(EXTENSION_ID);
   equal(addon.version, "1.0", "The installed addon has the correct version");
 
   let update = yield promiseFindAddonUpdates(addon);
   let install = update.updateAvailable;
 
   let promiseInstalled = promiseAddonEvent("onInstalled");
@@ -126,18 +160,21 @@ add_task(function* test_should_fire_on_a
   let startupPromise = awaitEvent("ready");
 
   let [updated_addon] = yield promiseInstalled;
   equal(updated_addon.version, "2.0", "The updated addon has the correct version");
 
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  details = yield extension.awaitMessage("installed");
-  equal(details.reason, "update", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "update",
+  });
 
   yield extension.unload();
 
   yield updated_addon.uninstall();
   yield promiseShutdownManager();
 });
 
 add_task(function* test_should_fire_on_browser_update() {
@@ -150,75 +187,71 @@ add_task(function* test_should_fire_on_b
     manifest: {
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
         },
       },
     },
-    background() {
-      let onInstalledDetails = null;
-
-      browser.runtime.onInstalled.addListener(details => {
-        onInstalledDetails = details;
-      });
-
-      browser.test.onMessage.addListener(message => {
-        if (message == "get-on-installed-details") {
-          browser.test.sendMessage("on-installed-details", onInstalledDetails);
-        }
-      });
-    },
+    background,
   });
 
   yield extension.startup();
 
-  extension.sendMessage("get-on-installed-details");
-  let details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let startupPromise = awaitEvent("ready");
   yield promiseRestartManager("1");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: false,
+  });
 
   // Update the browser.
   startupPromise = awaitEvent("ready");
   yield promiseRestartManager("2");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "browser_update", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: true,
+    onInstalledReason: "browser_update",
+  });
 
   // Restart the browser.
   startupPromise = awaitEvent("ready");
   yield promiseRestartManager("2");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: false,
+  });
 
   // Update the browser again.
   startupPromise = awaitEvent("ready");
   yield promiseRestartManager("3");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "browser_update", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: true,
+    onInstalledReason: "browser_update",
+  });
 
   yield extension.unload();
 
   yield promiseShutdownManager();
 });
 
 add_task(function* test_should_not_fire_on_reload() {
   const EXTENSION_ID = "test_runtime_on_installed_reload@tests.mozilla.org";
@@ -230,47 +263,36 @@ add_task(function* test_should_not_fire_
     manifest: {
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
         },
       },
     },
-    background() {
-      let onInstalledDetails = null;
-
-      browser.runtime.onInstalled.addListener(details => {
-        onInstalledDetails = details;
-      });
-
-      browser.test.onMessage.addListener(message => {
-        if (message == "reload-extension") {
-          browser.runtime.reload();
-        } else if (message == "get-on-installed-details") {
-          browser.test.sendMessage("on-installed-details", onInstalledDetails);
-        }
-      });
-    },
+    background,
   });
 
   yield extension.startup();
 
-  extension.sendMessage("get-on-installed-details");
-  let details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let startupPromise = awaitEvent("ready");
   extension.sendMessage("reload-extension");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: false,
+  });
 
   yield extension.unload();
   yield promiseShutdownManager();
 });
 
 add_task(function* test_should_not_fire_on_restart() {
   const EXTENSION_ID = "test_runtime_on_installed_restart@tests.mozilla.org";
 
@@ -281,44 +303,35 @@ add_task(function* test_should_not_fire_
     manifest: {
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
         },
       },
     },
-    background() {
-      let onInstalledDetails = null;
-
-      browser.runtime.onInstalled.addListener(details => {
-        onInstalledDetails = details;
-      });
-
-      browser.test.onMessage.addListener(message => {
-        if (message == "get-on-installed-details") {
-          browser.test.sendMessage("on-installed-details", onInstalledDetails);
-        }
-      });
-    },
+    background,
   });
 
   yield extension.startup();
 
-  extension.sendMessage("get-on-installed-details");
-  let details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let addon = yield promiseAddonByID(EXTENSION_ID);
   addon.userDisabled = true;
 
   let startupPromise = awaitEvent("ready");
   addon.userDisabled = false;
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: false,
+  });
 
   yield extension.markUnloaded();
   yield promiseShutdownManager();
 });
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -41,17 +41,17 @@ skip-if = release_or_beta
 [test_ext_management_uninstall_self.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.js]
 [test_ext_onmessage_removelistener.js]
 [test_ext_runtime_connect_no_receiver.js]
 [test_ext_runtime_getBrowserInfo.js]
 [test_ext_runtime_getPlatformInfo.js]
-[test_ext_runtime_onInstalled.js]
+[test_ext_runtime_onInstalled_and_onStartup.js]
 [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_schemas.js]
 [test_ext_schemas_api_injection.js]
 [test_ext_schemas_async.js]
 [test_ext_schemas_allowed_contexts.js]