"use strict"; const { Management } = ChromeUtils.importESModule( "resource://gre/modules/Extension.sys.mjs" ); const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"], }); // One test below relies on a slow-loading stylesheet. This function and promise // enables the script to control exactly when the stylesheet load should finish. let allowStylesheetToLoad; let stylesheetBlockerPromise = new Promise(resolve => { allowStylesheetToLoad = resolve; }); server.registerPathHandler("/slow.css", (request, response) => { response.setHeader("Cache-Control", "no-cache", false); response.setHeader("Content-Type", "text/css", false); response.processAsync(); stylesheetBlockerPromise.then(() => { response.write("body { color: rgb(1, 2, 3); }"); response.finish(); }); }); // Test helper to keep track of the number of background context loads, from // any extension. class BackgroundWatcher { constructor() { // Number of background page loads observed. this.bgBrowserCount = 0; // Number of top-level background context loads observed. this.bgViewCount = 0; this.observing = false; this.onBrowserInserted = this.onBrowserInserted.bind(this); this.onBackgroundViewLoaded = this.onBackgroundViewLoaded.bind(this); this.startObserving(); } startObserving() { this.observing = true; Management.on("extension-browser-inserted", this.onBrowserInserted); } stopObserving() { this.observing = false; Management.off("extension-browser-inserted", this.onBrowserInserted); // Note: onBrowserInserted adds message listeners to the message manager // of background contexts, but we do not explicitly unregister these here // because the caller should only stop observing after knowing for sure // that there are no new or pending loads of background pages. } onBrowserInserted(eventName, browser) { Assert.equal(eventName, "extension-browser-inserted", "Seen bg browser"); if (!browser.getAttribute("webextension-view-type") === "background") { return; } this.bgBrowserCount++; browser.messageManager.addMessageListener( "Extension:BackgroundViewLoaded", this.onBackgroundViewLoaded ); } onBackgroundViewLoaded({ data }) { if (!this.observing) { // We shouldn't receive this event - see comment in stopObserving. Assert.ok(false, "Got onBackgroundViewLoaded while !observing"); } this.bgViewCount++; Assert.ok(data.childId, "childId passed to Extension:BackgroundViewLoaded"); } } add_task(async function test_first_extension_api_call_in_iframe() { // In this test we test what happens when an extension API call happens in // an iframe before the top-level document observes DOMContentLoaded. // // 1. Because DOMContentLoaded is blocked on the execution on `, "background-subframe.html": ` body_of_iframe`, "background.js": backgroundScript, "background-deferred.js": backgroundScriptDeferred, }, }); const bgWatcher = new BackgroundWatcher(); // No "await extension.startup();" because extension.startup() in tests is // currently blocked on background startup (due to TEST_NO_DELAYED_STARTUP // defaulting to true). Because the background startup completion is blocked // on the DOMContentLoaded of the background, extension.startup() does not // resolve until we've unblocked the DOMContentLoaded notification. const startupPromise = extension.startup(); await extension.awaitMessage("allowStylesheetToLoad"); Assert.equal(bgWatcher.bgBrowserCount, 1, "Got background page"); Assert.equal(bgWatcher.bgViewCount, 0, "Background view still loading"); info("frame loaded; allowing slow.css to load to unblock DOMContentLoaded"); allowStylesheetToLoad(); info("Waiting for extension.startup() to resolve (background completion)"); await startupPromise; info("extension.startup() resolved. Waiting for top_and_frame_done..."); await extension.awaitMessage("top_and_frame_done"); Assert.equal( extension.extension.backgroundContext?.uri?.spec, `moz-extension://${extension.uuid}/background.html`, `extension.backgroundContext should exist and point to the main background` ); Assert.equal(bgWatcher.bgViewCount, 1, "Background has loaded once"); Assert.equal( extension.extension.views.size, 2, "Got ProxyContextParent instances for background and iframe" ); await extension.unload(); bgWatcher.stopObserving(); }); add_task(async function test_only_script_execution_in_iframe() { function backgroundSubframeScript() { // The exact API call does not matter, as any extension API call will // ensure that ProxyContextParent is initialized if it was not before: // https://searchfox.org/mozilla-central/rev/892475f3ba2b959aeaef19d1d8602494e3f2ae32/toolkit/components/extensions/ExtensionPageChild.sys.mjs#221,223,227-228 browser.runtime.getPlatformInfo().then(info => { browser.test.assertTrue("os" in info, "extension API called in iframe"); browser.test.assertTrue( browser.extension.getBackgroundPage() === top, "extension.getBackgroundPage() returns the top context" ); browser.test.sendMessage("iframe_done"); }); } const extension = ExtensionTestUtils.loadExtension({ manifest: { background: { page: "background.html", }, }, files: { "background.html": ` `, "background-subframe.html": ` `, "background-subframe.js": backgroundSubframeScript, }, }); const bgWatcher = new BackgroundWatcher(); await extension.startup(); await extension.awaitMessage("iframe_done"); Assert.equal(bgWatcher.bgBrowserCount, 1, "Got background page"); Assert.equal(bgWatcher.bgViewCount, 1, "Got background view"); Assert.equal( extension.extension.views.size, 2, "Got ProxyContextParent instances for background and iframe" ); Assert.equal( extension.extension.backgroundContext?.uri?.spec, `moz-extension://${extension.uuid}/background.html`, `extension.backgroundContext should exist and point to the main background` ); await extension.unload(); bgWatcher.stopObserving(); });