Last active
March 29, 2022 13:53
-
-
Save rpl/8e7a339ad5d874f3943c8f32a03a30b9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
changeset: 614313:f9729039d78d | |
parent: 614310:cd9488c30f8e | |
user: Luca Greco <[email protected]> | |
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({ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
changeset: 614316:a7be2e073291 | |
parent: 614309:f6b633c2190c | |
user: Luca Greco <[email protected]> | |
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("[email protected] | |
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 = "[email protected]"; | |
+ | |
+ 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(); | |
+ } | |
+); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
changeset: 614309:f6b633c2190c | |
bookmark: projects/mv3-eventpages-reviews-20220329 | |
tag: tip | |
parent: 614305:f5e1d3d4bb8e | |
user: Luca Greco <[email protected]> | |
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