"use strict"; const { Management } = ChromeUtils.importESModule( "resource://gre/modules/Extension.sys.mjs" ); // This test task quits in the middle of the test - do NOT add more tests below // this, unless you explicitly want to verify the behavior after quitting. // // Regression test for bug 2051934, specifically the scenario described at // https://bugzilla.mozilla.org/show_bug.cgi?id=2051934#c13 // // This is similar to test_ext_background_early_quit2.js, except here we load // another extension first (which ensures that HiddenXULWindow is initialized), // and then initiate quit while the second extension starts. This verifies that // `createBrowserElement` in `HiddenXULWindow` settles without continuing to // create a background page (or worse, gets stuck forever, e.g. waiting for // "XULFrameLoaderCreated"). add_task(async function test_quit_while_background_starts_after_another_ext() { let unrelatedExtension = ExtensionTestUtils.loadExtension({ background() { // Nothing here, when startup() resolves we have loaded. }, }); await unrelatedExtension.startup(); let extension = ExtensionTestUtils.loadExtension({ // Extension startup is blocked on background startup (bug 1543354). // If we somehow fail to make progress, then we should notice that. delayedStartup: false, background() { browser.test.fail( "Unexpected background page execution. eForceQuit should have aborted createBrowserElement in HiddenXULWindow" ); }, }); info("Waiting for extension to start up"); let startupCount = 0; let runManifestCount = 0; // Verifies that background startup is interrupted; "startup" is emitted // before runManifest invokes the background initialization logic that // initializes HiddenXULWindow. // - https://searchfox.org/firefox-main/rev/d83a08d81de20f3b72db70f531fad19090e1db4b/toolkit/components/extensions/Extension.sys.mjs#4281,4284 // - https://searchfox.org/firefox-main/rev/d83a08d81de20f3b72db70f531fad19090e1db4b/toolkit/components/extensions/parent/ext-backgroundPage.js#125 Management.once("startup", (eventName, ext) => { ++startupCount; const origRunManifest = ext.runManifest; ext.runManifest = function () { ++runManifestCount; return Reflect.apply(origRunManifest, this, arguments); }; // The Quit() call below calls ExitLastWindowClosingSurvivalArea() at // https://searchfox.org/mozilla-central/rev/38e462fe13ea42ae6cc391fb36e8b9e82e842b00/toolkit/components/startup/nsAppStartup.cpp#428,431 // which expects an EnterLastWindowClosingSurvivalArea() to have called // before, or else the following assertion will be triggered at: // https://searchfox.org/mozilla-central/rev/38e462fe13ea42ae6cc391fb36e8b9e82e842b00/toolkit/components/startup/nsAppStartup.cpp#597-598 // ASSERTION: consider quit stopper out of bounds: 'mConsiderQuitStopper > 0 // // During normal (non-xpcshell) execution, nsAppStartup::Run() runs, which // calls EnterLastWindowClosingSurvivalArea(). In xpcshell tests, this is // not called, and we need to call it here: Services.startup.enterLastWindowClosingSurvivalArea(); Services.startup.quit(Ci.nsIAppStartup.eForceQuit); }); const { messages } = await promiseConsoleOutput(async () => { await extension.startup(); }); equal(startupCount, 1, "Observed one extension startup"); equal(runManifestCount, 1, "Observed runManifest call after emit startup"); equal(extension.extension.backgroundState, "stopped", "backgroundState"); equal( messages.filter(m => m.message.includes("Cannot create hidden browser past shutdown") ).length, 1, "Found message when HiddenXULWindow.createBrowserElement was aborted" ); // To be most realistic, set the APP_SHUTDOWN flag when shutdown() is called // via extension.unload(). Otherwise we would run logic that ordinarily does // not run when extensions unload on browser shutdown. // E.g. ServiceWorkerCleanUp.removeFromPrincipal at // https://searchfox.org/firefox-main/rev/c0dff85fa67e5ab148ce20d4298c13f13ceec342/toolkit/components/extensions/Extension.sys.mjs#4542 // throws NS_ERROR_XPC_GS_RETURNED_FAILURE upon calling // ServiceManager::GetService via unregisterServiceWorkersMatching. let shutdownCount = 0; const Extension_prototype = Object.getPrototypeOf(extension.extension); const origShutdown = Extension_prototype.shutdown; Extension_prototype.shutdown = function () { ++shutdownCount; return origShutdown.call(this, "APP_SHUTDOWN"); }; await extension.unload(); await unrelatedExtension.unload(); equal(shutdownCount, 2, "extension.shutdown() was called twice"); });