Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rpl/8e7a339ad5d874f3943c8f32a03a30b9 to your computer and use it in GitHub Desktop.
Save rpl/8e7a339ad5d874f3943c8f32a03a30b9 to your computer and use it in GitHub Desktop.
changeset: 614313:f9729039d78d
parent: 614310:cd9488c30f8e
user: Luca Greco <lgreco@mozilla.com>
date: Tue Mar 29 14:28:17 2022 +0200
summary: TMP changes due to rebase and conflicts resolution
diff --git a/toolkit/components/extensions/parent/ext-backgroundPage.js b/toolkit/components/extensions/parent/ext-backgroundPage.js
--- a/toolkit/components/extensions/parent/ext-backgroundPage.js
+++ b/toolkit/components/extensions/parent/ext-backgroundPage.js
@@ -486,6 +486,12 @@ this.backgroundPage = class extends Exte
let { extension } = this;
extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ // runtime.onStartup event support. We listen for the first
+ // background startup then emit a first-run event.
+ extension.once("background-script-started", () => {
+ extension.emit("background-first-run");
+ });
+
await this.primeBackground();
ExtensionParent.browserStartupPromise.then(() => {
diff --git a/toolkit/components/extensions/parent/ext-runtime.js b/toolkit/components/extensions/parent/ext-runtime.js
--- a/toolkit/components/extensions/parent/ext-runtime.js
+++ b/toolkit/components/extensions/parent/ext-runtime.js
@@ -20,75 +20,97 @@ XPCOMUtils.defineLazyPreferenceGetter(
5000
);
-this.runtime = class extends ExtensionAPI {
+this.runtime = class extends ExtensionAPIPersistent {
+ PERSISTENT_EVENTS = {
+ onInstalled({ fire }) {
+ let { extension } = this;
+ let temporary = !!extension.addonData.temporarilyInstalled;
+
+ let listener = () => {
+ switch (extension.startupReason) {
+ case "APP_STARTUP":
+ if (AddonManagerPrivate.browserUpdated) {
+ fire.sync({ reason: "browser_update", temporary });
+ }
+ break;
+ case "ADDON_INSTALL":
+ fire.sync({ reason: "install", temporary });
+ break;
+ case "ADDON_UPGRADE":
+ fire.sync({
+ reason: "update",
+ previousVersion: extension.addonData.oldVersion,
+ temporary,
+ });
+ break;
+ }
+ };
+ extension.on("background-first-run", listener);
+ return {
+ unregister() {
+ extension.off("background-first-run", listener);
+ },
+ convert(_fire) {
+ fire = _fire;
+ },
+ };
+ },
+ onUpdateAvailable({ fire }) {
+ let { extension } = this;
+ let instanceID = extension.addonData.instanceID;
+ AddonManager.addUpgradeListener(instanceID, upgrade => {
+ extension.upgrade = upgrade;
+ let details = {
+ version: upgrade.version,
+ };
+ fire.sync(details);
+ });
+ return {
+ unregister() {
+ AddonManager.removeUpgradeListener(instanceID);
+ },
+ convert(_fire) {
+ fire = _fire;
+ },
+ };
+ },
+ };
+
getAPI(context) {
let { extension } = context;
return {
runtime: {
+ // onStartup is special-cased in ext-backgroundPages to cause
+ // an immediate startup. We do not prime onStartup.
onStartup: new EventManager({
context,
- name: "runtime.onStartup",
+ module: "runtime",
+ event: "onStartup",
register: fire => {
if (context.incognito || extension.startupReason != "APP_STARTUP") {
// This event should not fire if we are operating in a private profile.
return () => {};
}
let listener = () => fire.sync();
- extension.on("background-script-started", listener);
+ extension.on("background-first-run", listener);
return () => {
- extension.off("background-script-started", listener);
+ extension.off("background-first-run", listener);
};
},
}).api(),
onInstalled: new EventManager({
context,
- name: "runtime.onInstalled",
- register: fire => {
- let temporary = !!extension.addonData.temporarilyInstalled;
-
- let listener = () => {
- switch (extension.startupReason) {
- case "APP_STARTUP":
- if (AddonManagerPrivate.browserUpdated) {
- fire.sync({ reason: "browser_update", temporary });
- }
- break;
- case "ADDON_INSTALL":
- fire.sync({ reason: "install", temporary });
- break;
- case "ADDON_UPGRADE":
- fire.sync({
- reason: "update",
- previousVersion: extension.addonData.oldVersion,
- temporary,
- });
- break;
- }
- };
- extension.on("background-script-started", listener);
- return () => {
- extension.off("background-script-started", listener);
- };
- },
+ module: "runtime",
+ event: "onInstalled",
+ extensionApi: this,
}).api(),
onUpdateAvailable: new EventManager({
context,
- name: "runtime.onUpdateAvailable",
- register: fire => {
- let instanceID = extension.addonData.instanceID;
- AddonManager.addUpgradeListener(instanceID, upgrade => {
- extension.upgrade = upgrade;
- let details = {
- version: upgrade.version,
- };
- fire.sync(details);
- });
- return () => {
- AddonManager.removeUpgradeListener(instanceID);
- };
- },
+ module: "runtime",
+ event: "onUpdateAvailable",
+ extensionApi: this,
}).api(),
onSuspend: new EventManager({
diff --git a/toolkit/components/extensions/parent/ext-backgroundPage.js b/toolkit/components/extensions/parent/ext-backgroundPage.js
--- a/toolkit/components/extensions/parent/ext-backgroundPage.js
+++ b/toolkit/components/extensions/parent/ext-backgroundPage.js
@@ -114,46 +114,13 @@ class BackgroundPage extends HiddenExten
});
context = await contextPromise;
+ ExtensionTelemetry.backgroundPageLoad.stopwatchFinish(extension, this);
} catch (e) {
- // Extension was down before the background page has loaded.
- Cu.reportError(e);
ExtensionTelemetry.backgroundPageLoad.stopwatchCancel(extension, this);
- if (extension.persistentListeners) {
- EventManager.clearPrimedListeners(this.extension, false);
- }
- extension.emit("background-script-aborted");
- return;
+ throw e;
}
- ExtensionTelemetry.backgroundPageLoad.stopwatchFinish(extension, this);
-
- if (context) {
- // Wait until all event listeners registered by the script so far
- // to be handled. We then set listenerPromises to null, which indicates
- // to addListener that the background script has finished loading.
- await Promise.all(context.listenerPromises);
- context.listenerPromises = null;
-
- notifyBackgroundScriptStatus(extension.id, true);
- context.callOnClose({
- close() {
- notifyBackgroundScriptStatus(extension.id, false);
- },
- });
- }
-
- if (extension.persistentListeners) {
- // |this.extension| may be null if the extension was shut down.
- // In that case, we still want to clear the primed listeners,
- // but not update the persistent listeners in the startupData.
- EventManager.clearPrimedListeners(extension, !!this.extension);
- }
-
- // TODO(Bug ToBeFiled): in some corner case we may end up emitting
- // background-script-started even if context was not defined,
- // at a first glance it seems this should emit "background-script-aborted"
- // instead when that is the case.
- extension.emit("background-script-started");
+ return context;
}
shutdown() {
@@ -195,66 +162,28 @@ class BackgroundWorker {
const { extension } = this;
let context;
- try {
- const contextPromise = new Promise(resolve => {
- let unwatch = watchExtensionWorkerContextLoaded(
- { extension, viewType: "background_worker" },
- context => {
- unwatch();
- this.validateWorkerInfoForContext(context);
- resolve(context);
- }
- );
- });
-
- // TODO(Bug 17228327): follow up to spawn the active worker for a previously installed
- // background service worker.
- await serviceWorkerManager.registerForAddonPrincipal(
- this.extension.principal
- );
-
- context = await contextPromise;
-
- await this.waitForActiveWorker();
- } catch (e) {
- // Extension may be shutting down before the background worker has registered or
- // loaded.
- Cu.reportError(e);
-
- if (extension.persistentListeners) {
- EventManager.clearPrimedListeners(this.extension, false);
- }
- extension.emit("background-script-aborted");
- return;
- }
-
- if (context) {
- // Wait until all event listeners registered by the script so far
- // to be handled.
- await Promise.all(context.listenerPromises);
- context.listenerPromises = null;
+ const contextPromise = new Promise(resolve => {
+ let unwatch = watchExtensionWorkerContextLoaded(
+ { extension, viewType: "background_worker" },
+ context => {
+ unwatch();
+ this.validateWorkerInfoForContext(context);
+ resolve(context);
+ }
+ );
+ });
- notifyBackgroundScriptStatus(extension.id, true);
- context.callOnClose({
- close() {
- notifyBackgroundScriptStatus(extension.id, false);
- },
- });
- }
+ // TODO(Bug 17228327): follow up to spawn the active worker for a previously installed
+ // background service worker.
+ await serviceWorkerManager.registerForAddonPrincipal(
+ this.extension.principal
+ );
- if (extension.persistentListeners) {
- // |this.extension| may be null if the extension was shut down.
- // In that case, we still want to clear the primed listeners,
- // but not update the persistent listeners in the startupData.
- EventManager.clearPrimedListeners(extension, !!this.extension);
- }
+ context = await contextPromise;
- // TODO(Bug ToBeFiled): in some corner case we may end up emitting
- // background-script-started even if context was not defined,
- // at a first glance it seems this should emit "background-script-aborted"
- // instead when that is the case.
- extension.emit("background-script-started");
+ await this.waitForActiveWorker();
+ return context;
}
shutdown(isAppShutdown) {
@@ -325,7 +254,64 @@ this.backgroundPage = class extends Exte
: BackgroundPage;
this.bgInstance = new BackgroundClass(extension, manifest.background);
- return this.bgInstance.build();
+
+ let context;
+ try {
+ context = await this.bgInstance.build();
+ // Top level execution already happened, RUNNING is
+ // a touch after the fact.
+ extension.backgroundState = BACKGROUND_STATE.RUNNING;
+ } catch (e) {
+ Cu.reportError(e);
+ if (extension.persistentListeners) {
+ // Clear the primed listeners, but leave them persisted.
+ EventManager.clearPrimedListeners(extension, false);
+ }
+ extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ extension.emit("background-script-aborted");
+ return;
+ }
+
+ if (context) {
+ // Wait until all event listeners registered by the script so far
+ // to be handled. We then set listenerPromises to null, which indicates
+ // to addListener that the background script has finished loading.
+ await Promise.all(context.listenerPromises);
+ context.listenerPromises = null;
+
+ // Notify devtools when the background scripts is started or stopped
+ // (used to show the current status in about:debugging).
+ notifyBackgroundScriptStatus(extension.id, /* isRunning */ true);
+ context.callOnClose({
+ close() {
+ notifyBackgroundScriptStatus(extension.id, /* isRunning */ false);
+ },
+ });
+ }
+
+ if (extension.persistentListeners) {
+ // |this.extension| may be null if the extension was shut down.
+ // In that case, we still want to clear the primed listeners,
+ // but not update the persistent listeners in the startupData.
+ EventManager.clearPrimedListeners(extension, !!this.extension);
+ }
+
+ if (context) {
+ // If the extension was shutdown and we have a context, we
+ // shut that down and "abort".
+ if (!this.extension) {
+ this.bgInstance?.shutdown(false);
+ this.bgInstance = null;
+ extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ extension.emit("background-script-aborted");
+ return;
+ }
+ extension.emit("background-script-started");
+ } else {
+ this.bgInstance = null;
+ extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ extension.emit("background-script-aborted");
+ }
}
observe(subject, topic, data) {
changeset: 614316:a7be2e073291
parent: 614309:f6b633c2190c
user: Luca Greco <lgreco@mozilla.com>
date: Tue Mar 29 14:21:53 2022 +0200
summary: IMPORTED Bug 1748563 support persistent events in runtime api
diff --git a/toolkit/components/extensions/ExtensionCommon.jsm b/toolkit/components/extensions/ExtensionCommon.jsm
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -2397,7 +2397,11 @@ class EventManager {
let fireEvent = (...args) =>
new Promise((resolve, reject) => {
if (!listener.primed) {
- reject(new Error("primed listener not re-registered"));
+ reject(
+ new Error(
+ `primed listener ${module}.${event} not re-registered`
+ )
+ );
return;
}
primed.pendingEvents.push({ args, resolve, reject });
diff --git a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -276,22 +276,6 @@ class ExtensionWrapper {
return this.extension.terminateBackground();
}
- /*
- * This method marks the extension unloading without actually calling
- * shutdown, since shutting down a MockExtension causes it to be uninstalled.
- *
- * Normally you shouldn't need to use this unless you need to test something
- * that requires a restart, such as updates.
- */
- markUnloaded() {
- if (this.state != "running") {
- throw new Error("Extension not running");
- }
- this.state = "unloaded";
-
- return Promise.resolve();
- }
-
sendMessage(...args) {
this.extension.testMessage(...args);
}
diff --git a/toolkit/components/extensions/parent/ext-backgroundPage.js b/toolkit/components/extensions/parent/ext-backgroundPage.js
--- a/toolkit/components/extensions/parent/ext-backgroundPage.js
+++ b/toolkit/components/extensions/parent/ext-backgroundPage.js
@@ -486,6 +486,12 @@ this.backgroundPage = class extends Exte
let { extension } = this;
extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ // runtime.onStartup event support. We listen for the first
+ // background startup then emit a first-run event.
+ extension.once("background-script-started", () => {
+ extension.emit("background-first-run");
+ });
+
await this.primeBackground();
ExtensionParent.browserStartupPromise.then(() => {
@@ -499,7 +505,11 @@ this.backgroundPage = class extends Exte
// start the event page so they can be registered.
if (
extension.persistentBackground ||
- !extension.persistentListeners?.size
+ !extension.persistentListeners?.size ||
+ // If runtime.onStartup has a listener and this is app_startup,
+ // start the extension so it will fire the event.
+ (extension.startupReason == "APP_STARTUP" &&
+ extension.persistentListeners?.get("runtime").has("onStartup"))
) {
extension.emit("start-background-script");
} else {
diff --git a/toolkit/components/extensions/parent/ext-runtime.js b/toolkit/components/extensions/parent/ext-runtime.js
--- a/toolkit/components/extensions/parent/ext-runtime.js
+++ b/toolkit/components/extensions/parent/ext-runtime.js
@@ -20,75 +20,97 @@ XPCOMUtils.defineLazyPreferenceGetter(
5000
);
-this.runtime = class extends ExtensionAPI {
+this.runtime = class extends ExtensionAPIPersistent {
+ PERSISTENT_EVENTS = {
+ onInstalled({ fire }) {
+ let { extension } = this;
+ let temporary = !!extension.addonData.temporarilyInstalled;
+
+ let listener = () => {
+ switch (extension.startupReason) {
+ case "APP_STARTUP":
+ if (AddonManagerPrivate.browserUpdated) {
+ fire.sync({ reason: "browser_update", temporary });
+ }
+ break;
+ case "ADDON_INSTALL":
+ fire.sync({ reason: "install", temporary });
+ break;
+ case "ADDON_UPGRADE":
+ fire.sync({
+ reason: "update",
+ previousVersion: extension.addonData.oldVersion,
+ temporary,
+ });
+ break;
+ }
+ };
+ extension.on("background-first-run", listener);
+ return {
+ unregister() {
+ extension.off("background-first-run", listener);
+ },
+ convert(_fire) {
+ fire = _fire;
+ },
+ };
+ },
+ onUpdateAvailable({ fire }) {
+ let { extension } = this;
+ let instanceID = extension.addonData.instanceID;
+ AddonManager.addUpgradeListener(instanceID, upgrade => {
+ extension.upgrade = upgrade;
+ let details = {
+ version: upgrade.version,
+ };
+ fire.sync(details);
+ });
+ return {
+ unregister() {
+ AddonManager.removeUpgradeListener(instanceID);
+ },
+ convert(_fire) {
+ fire = _fire;
+ },
+ };
+ },
+ };
+
getAPI(context) {
let { extension } = context;
return {
runtime: {
+ // onStartup is special-cased in ext-backgroundPages to cause
+ // an immediate startup. We do not prime onStartup.
onStartup: new EventManager({
context,
- name: "runtime.onStartup",
+ module: "runtime",
+ event: "onStartup",
register: fire => {
if (context.incognito || extension.startupReason != "APP_STARTUP") {
// This event should not fire if we are operating in a private profile.
return () => {};
}
let listener = () => fire.sync();
- extension.on("background-script-started", listener);
+ extension.on("background-first-run", listener);
return () => {
- extension.off("background-script-started", listener);
+ extension.off("background-first-run", listener);
};
},
}).api(),
onInstalled: new EventManager({
context,
- name: "runtime.onInstalled",
- register: fire => {
- let temporary = !!extension.addonData.temporarilyInstalled;
-
- let listener = () => {
- switch (extension.startupReason) {
- case "APP_STARTUP":
- if (AddonManagerPrivate.browserUpdated) {
- fire.sync({ reason: "browser_update", temporary });
- }
- break;
- case "ADDON_INSTALL":
- fire.sync({ reason: "install", temporary });
- break;
- case "ADDON_UPGRADE":
- fire.sync({
- reason: "update",
- previousVersion: extension.addonData.oldVersion,
- temporary,
- });
- break;
- }
- };
- extension.on("background-script-started", listener);
- return () => {
- extension.off("background-script-started", listener);
- };
- },
+ module: "runtime",
+ event: "onInstalled",
+ extensionApi: this,
}).api(),
onUpdateAvailable: new EventManager({
context,
- name: "runtime.onUpdateAvailable",
- register: fire => {
- let instanceID = extension.addonData.instanceID;
- AddonManager.addUpgradeListener(instanceID, upgrade => {
- extension.upgrade = upgrade;
- let details = {
- version: upgrade.version,
- };
- fire.sync(details);
- });
- return () => {
- AddonManager.removeUpgradeListener(instanceID);
- };
- },
+ module: "runtime",
+ event: "onUpdateAvailable",
+ extensionApi: this,
}).api(),
onSuspend: new EventManager({
diff --git a/toolkit/components/extensions/test/xpcshell/head.js b/toolkit/components/extensions/test/xpcshell/head.js
--- a/toolkit/components/extensions/test/xpcshell/head.js
+++ b/toolkit/components/extensions/test/xpcshell/head.js
@@ -2,7 +2,7 @@
/* exported createHttpServer, cleanupDir, clearCache, optionalPermissionsPromptHandler, promiseConsoleOutput,
promiseQuotaManagerServiceReset, promiseQuotaManagerServiceClear,
runWithPrefs, testEnv, withHandlingUserInput, resetHandlingUserInput,
- assertPersistentListeners */
+ assertPersistentListeners, promiseExtensionEvent */
var { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
@@ -314,3 +314,9 @@ const optionalPermissionsPromptHandler =
}
},
};
+
+function promiseExtensionEvent(wrapper, event) {
+ return new Promise(resolve => {
+ wrapper.extension.once(event, resolve);
+ });
+}
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_idle.js
@@ -20,10 +20,6 @@ Services.prefs.setBoolPref("extensions.e
// Set minimum idle timeout for testing
Services.prefs.setIntPref("extensions.background.idle.timeout", 0);
-function promiseExtensionEvent(extension, event) {
- return new Promise(resolve => extension.extension.once(event, resolve));
-}
-
add_setup(async () => {
await AddonTestUtils.promiseStartupManager();
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js b/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js
@@ -494,7 +494,7 @@ add_task(async function test_persistent_
);
equal(
(await p)[0].errorMessage,
- "Error: primed listener not re-registered",
+ "Error: primed listener eventtest.onEvent1 not re-registered",
"Primed listener that was not re-registered received an error when event was triggered during startup"
);
@@ -579,7 +579,7 @@ add_task(async function test_shutdown_be
await Assert.rejects(
fire.async(),
- /Error: primed listener not re-registered/,
+ /Error: primed listener eventtest.onEvent1 not re-registered/,
"fire.async after background load failure should be rejected"
);
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js
@@ -33,12 +33,6 @@ server.registerPathHandler("/", (request
response.write("ok");
});
-function promiseExtensionEvent(wrapper, event) {
- return new Promise(resolve => {
- wrapper.extension.once(event, resolve);
- });
-}
-
function trackEvents(wrapper) {
let events = new Map();
for (let event of ["background-script-event", "start-background-script"]) {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
@@ -30,6 +30,7 @@ createAppInfo("xpcshell@tests.mozilla.or
function background() {
let onInstalledDetails = null;
let onStartupFired = false;
+ let eventPage = browser.runtime.getManifest().background.persistent === false;
browser.runtime.onInstalled.addListener(details => {
onInstalledDetails = details;
@@ -54,6 +55,16 @@ function background() {
browser.test.sendMessage("reloading");
browser.runtime.reload();
});
+
+ if (eventPage) {
+ browser.runtime.onSuspend.addListener(() => {
+ browser.test.sendMessage("suspended");
+ });
+ // an event we use to restart the background
+ browser.browserSettings.homepageOverride.onChange.addListener(() => {
+ browser.test.sendMessage("homepageOverride");
+ });
+ }
}
async function expectEvents(
@@ -357,7 +368,7 @@ add_task(async function test_should_not_
onInstalledFired: false,
});
- await extension.markUnloaded();
+ await extension.unload();
await promiseShutdownManager();
});
@@ -392,3 +403,118 @@ add_task(async function test_temporary_i
await extension.unload();
await promiseShutdownManager();
});
+
+add_task(
+ {
+ pref_set: [["extensions.eventPages.enabled", true]],
+ },
+ async function test_runtime_eventpage() {
+ const EXTENSION_ID = "test_runtime_eventpage@tests.mozilla.org";
+
+ await promiseStartupManager("1");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ applications: {
+ gecko: {
+ id: EXTENSION_ID,
+ },
+ },
+ permissions: ["browserSettings"],
+ background: {
+ persistent: false,
+ },
+ },
+ background,
+ });
+
+ await extension.startup();
+
+ await expectEvents(extension, {
+ onStartupFired: false,
+ onInstalledFired: true,
+ onInstalledReason: "install",
+ onInstalledTemporary: false,
+ });
+
+ info(`test onInstall does not fire after suspend`);
+ // we do enough here that idle timeout causes intermittent failure.
+ // using terminateBackground results in the same code path tested.
+ extension.terminateBackground();
+ await extension.awaitMessage("suspended");
+ await promiseExtensionEvent(extension, "shutdown-background-script");
+
+ Services.prefs.setStringPref(
+ "browser.startup.homepage",
+ "http://test.example.com"
+ );
+ await extension.awaitMessage("homepageOverride");
+ // onStartup remains persisted, but not primed
+ assertPersistentListeners(extension, "runtime", "onStartup", {
+ primed: false,
+ persisted: true,
+ });
+
+ await expectEvents(extension, {
+ onStartupFired: false,
+ onInstalledFired: false,
+ });
+
+ info("test onStartup is not primed but background starts automatically");
+ await promiseRestartManager();
+ // onStartup is a bit special. During APP_STARTUP we do not
+ // prime this, we just start since it needs to.
+ assertPersistentListeners(extension, "runtime", "onStartup", {
+ primed: false,
+ persisted: true,
+ });
+ + await extension.awaitBackgroundStarted();
+
+ info("test expectEvents");
+ await expectEvents(extension, {
+ onStartupFired: true,
+ onInstalledFired: false,
+ });
+
+ info("test onInstalled fired during browser update");
+ await promiseRestartManager("2");
+ assertPersistentListeners(extension, "runtime", "onStartup", {
+ primed: false,
+ persisted: true,
+ });
+ await extension.awaitBackgroundStarted();
+
+ await expectEvents(extension, {
+ onStartupFired: true,
+ onInstalledFired: true,
+ onInstalledReason: "browser_update",
+ onInstalledTemporary: false,
+ });
+
+ info(`test onStarted does not fire after suspend`);
+ extension.terminateBackground();
+ await extension.awaitMessage("suspended");
+ await promiseExtensionEvent(extension, "shutdown-background-script");
+
+ Services.prefs.setStringPref(
+ "browser.startup.homepage",
+ "http://homepage.example.com"
+ );
+ await extension.awaitMessage("homepageOverride");
+ // onStartup remains persisted, but not primed
+ assertPersistentListeners(extension, "runtime", "onStartup", {
+ primed: false,
+ persisted: true,
+ });
+
+ await expectEvents(extension, {
+ onStartupFired: false,
+ onInstalledFired: false,
+ });
+
+ await extension.unload();
+ await promiseShutdownManager();
+ }
+);
changeset: 614309:f6b633c2190c
bookmark: projects/mv3-eventpages-reviews-20220329
tag: tip
parent: 614305:f5e1d3d4bb8e
user: Luca Greco <lgreco@mozilla.com>
date: Tue Mar 29 13:50:12 2022 +0200
summary: IMPORT refactor background page build
diff --git a/toolkit/components/extensions/parent/ext-backgroundPage.js b/toolkit/components/extensions/parent/ext-backgroundPage.js
--- a/toolkit/components/extensions/parent/ext-backgroundPage.js
+++ b/toolkit/components/extensions/parent/ext-backgroundPage.js
@@ -114,46 +114,13 @@ class BackgroundPage extends HiddenExten
});
context = await contextPromise;
+ ExtensionTelemetry.backgroundPageLoad.stopwatchFinish(extension, this);
} catch (e) {
- // Extension was down before the background page has loaded.
- Cu.reportError(e);
ExtensionTelemetry.backgroundPageLoad.stopwatchCancel(extension, this);
- if (extension.persistentListeners) {
- EventManager.clearPrimedListeners(this.extension, false);
- }
- extension.emit("background-script-aborted");
- return;
+ throw e;
}
- ExtensionTelemetry.backgroundPageLoad.stopwatchFinish(extension, this);
-
- if (context) {
- // Wait until all event listeners registered by the script so far
- // to be handled. We then set listenerPromises to null, which indicates
- // to addListener that the background script has finished loading.
- await Promise.all(context.listenerPromises);
- context.listenerPromises = null;
-
- notifyBackgroundScriptStatus(extension.id, true);
- context.callOnClose({
- close() {
- notifyBackgroundScriptStatus(extension.id, false);
- },
- });
- }
-
- if (extension.persistentListeners) {
- // |this.extension| may be null if the extension was shut down.
- // In that case, we still want to clear the primed listeners,
- // but not update the persistent listeners in the startupData.
- EventManager.clearPrimedListeners(extension, !!this.extension);
- }
-
- // TODO(Bug ToBeFiled): in some corner case we may end up emitting
- // background-script-started even if context was not defined,
- // at a first glance it seems this should emit "background-script-aborted"
- // instead when that is the case.
- extension.emit("background-script-started");
+ return context;
}
shutdown() {
@@ -195,66 +162,28 @@ class BackgroundWorker {
const { extension } = this;
let context;
- try {
- const contextPromise = new Promise(resolve => {
- let unwatch = watchExtensionWorkerContextLoaded(
- { extension, viewType: "background_worker" },
- context => {
- unwatch();
- this.validateWorkerInfoForContext(context);
- resolve(context);
- }
- );
- });
-
- // TODO(Bug 17228327): follow up to spawn the active worker for a previously installed
- // background service worker.
- await serviceWorkerManager.registerForAddonPrincipal(
- this.extension.principal
- );
-
- context = await contextPromise;
-
- await this.waitForActiveWorker();
- } catch (e) {
- // Extension may be shutting down before the background worker has registered or
- // loaded.
- Cu.reportError(e);
-
- if (extension.persistentListeners) {
- EventManager.clearPrimedListeners(this.extension, false);
- }
- extension.emit("background-script-aborted");
- return;
- }
-
- if (context) {
- // Wait until all event listeners registered by the script so far
- // to be handled.
- await Promise.all(context.listenerPromises);
- context.listenerPromises = null;
+ const contextPromise = new Promise(resolve => {
+ let unwatch = watchExtensionWorkerContextLoaded(
+ { extension, viewType: "background_worker" },
+ context => {
+ unwatch();
+ this.validateWorkerInfoForContext(context);
+ resolve(context);
+ }
+ );
+ });
- notifyBackgroundScriptStatus(extension.id, true);
- context.callOnClose({
- close() {
- notifyBackgroundScriptStatus(extension.id, false);
- },
- });
- }
+ // TODO(Bug 17228327): follow up to spawn the active worker for a previously installed
+ // background service worker.
+ await serviceWorkerManager.registerForAddonPrincipal(
+ this.extension.principal
+ );
- if (extension.persistentListeners) {
- // |this.extension| may be null if the extension was shut down.
- // In that case, we still want to clear the primed listeners,
- // but not update the persistent listeners in the startupData.
- EventManager.clearPrimedListeners(extension, !!this.extension);
- }
+ context = await contextPromise;
- // TODO(Bug ToBeFiled): in some corner case we may end up emitting
- // background-script-started even if context was not defined,
- // at a first glance it seems this should emit "background-script-aborted"
- // instead when that is the case.
- extension.emit("background-script-started");
+ await this.waitForActiveWorker();
+ return context;
}
shutdown(isAppShutdown) {
@@ -325,7 +254,64 @@ this.backgroundPage = class extends Exte
: BackgroundPage;
this.bgInstance = new BackgroundClass(extension, manifest.background);
- return this.bgInstance.build();
+
+ let context;
+ try {
+ context = await this.bgInstance.build();
+ // Top level execution already happened, RUNNING is
+ // a touch after the fact.
+ extension.backgroundState = BACKGROUND_STATE.RUNNING;
+ } catch (e) {
+ Cu.reportError(e);
+ if (extension.persistentListeners) {
+ // Clear the primed listeners, but leave them persisted.
+ EventManager.clearPrimedListeners(extension, false);
+ }
+ extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ extension.emit("background-script-aborted");
+ return;
+ }
+
+ if (context) {
+ // Wait until all event listeners registered by the script so far
+ // to be handled. We then set listenerPromises to null, which indicates
+ // to addListener that the background script has finished loading.
+ await Promise.all(context.listenerPromises);
+ context.listenerPromises = null;
+
+ // Notify devtools when the background scripts is started or stopped
+ // (used to show the current status in about:debugging).
+ notifyBackgroundScriptStatus(extension.id, /* isRunning */ true);
+ context.callOnClose({
+ close() {
+ notifyBackgroundScriptStatus(extension.id, /* isRunning */ false);
+ },
+ });
+ }
+
+ if (extension.persistentListeners) {
+ // |this.extension| may be null if the extension was shut down.
+ // In that case, we still want to clear the primed listeners,
+ // but not update the persistent listeners in the startupData.
+ EventManager.clearPrimedListeners(extension, !!this.extension);
+ }
+
+ if (context) {
+ // If the extension was shutdown and we have a context, we
+ // shut that down and "abort".
+ if (!this.extension) {
+ this.bgInstance?.shutdown(false);
+ this.bgInstance = null;
+ extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ extension.emit("background-script-aborted");
+ return;
+ }
+ extension.emit("background-script-started");
+ } else {
+ this.bgInstance = null;
+ extension.backgroundState = BACKGROUND_STATE.STOPPED;
+ extension.emit("background-script-aborted");
+ }
}
observe(subject, topic, data) {
@@ -368,15 +354,10 @@ this.backgroundPage = class extends Exte
// Used by runtime messaging to wait for background page listeners.
let bgStartupPromise = new Promise(resolve => {
- let done = event => {
+ let done = () => {
extension.off("background-script-started", done);
extension.off("background-script-aborted", done);
extension.off("shutdown", done);
- if (event == "background-script-started" && this.bgInstance) {
- extension.backgroundState = BACKGROUND_STATE.RUNNING;
- } else {
- extension.backgroundState = BACKGROUND_STATE.STOPPED;
- }
resolve();
};
extension.on("background-script-started", done);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment