/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { AddonTestUtils } = ChromeUtils.importESModule( "resource://testing-common/AddonTestUtils.sys.mjs" ); // The test tasks in this test file tends to trigger an intermittent // exception raised from JSActor::AfterDestroy, because of a race between // when the WebExtensions API event is being emitted from the parent process // and the navigation triggered on the test extension pages. const { PromiseTestUtils } = ChromeUtils.importESModule( "resource://testing-common/PromiseTestUtils.sys.mjs" ); PromiseTestUtils.allowMatchingRejectionsGlobally( /Actor 'Conduits' destroyed before query 'RunListener' was resolved/ ); AddonTestUtils.initMochitest(this); const server = AddonTestUtils.createHttpServer({ hosts: ["example.com", "anotherwebpage.org"], }); server.registerPathHandler("/", (request, response) => { response.write(` test webpage `); }); function createTestExtPage({ script }) { return ` `; } function createTestExtPageScript(name) { return `(${function (pageName) { browser.webRequest.onBeforeRequest.addListener( details => { browser.test.log( `Extension page "${pageName}" got a webRequest event: ${details.url}` ); browser.test.sendMessage(`event-received:${pageName}`); }, { types: ["main_frame"], urls: ["http://example.com/*"] } ); /* eslint-disable mozilla/balanced-listeners */ window.addEventListener("pageshow", () => { browser.test.log(`Extension page "${pageName}" got a pageshow event`); browser.test.sendMessage(`pageshow:${pageName}`); }); window.addEventListener("pagehide", () => { browser.test.log(`Extension page "${pageName}" got a pagehide event`); browser.test.sendMessage(`pagehide:${pageName}`); }); /* eslint-enable mozilla/balanced-listeners */ }})("${name}");`; } // Triggers a WebRequest listener registered by the test extensions by // opening a tab on the given web page URL and then closing it after // it did load. async function triggerWebRequestListener(webPageURL, pause) { let webPageTab = await BrowserTestUtils.openNewForegroundTab( { gBrowser, url: webPageURL, }, true /* waitForLoad */, true /* waitForStop */ ); BrowserTestUtils.removeTab(webPageTab); } // The following tests tasks are testing the expected behaviors related to same-process and cross-process // navigations for an extension page, similarly to test_ext_extension_page_navigated.js, but unlike its // xpcshell counterpart this tests are only testing that after navigating back to an extension page // previously stored in the BFCache the WebExtensions events subscribed are being received as expected. add_task(async function test_extension_page_sameprocess_navigation() { const extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["webRequest", "http://example.com/*"], }, files: { "extpage1.html": createTestExtPage({ script: "extpage1.js" }), "extpage1.js": createTestExtPageScript("extpage1"), "extpage2.html": createTestExtPage({ script: "extpage2.js" }), "extpage2.js": createTestExtPageScript("extpage2"), }, }); await extension.startup(); const policy = WebExtensionPolicy.getByID(extension.id); const extPageURL1 = policy.extension.baseURI.resolve("extpage1.html"); const extPageURL2 = policy.extension.baseURI.resolve("extpage2.html"); info("Opening extension page in a new tab"); const extPageTab = await BrowserTestUtils.addTab(gBrowser, extPageURL1); let browser = gBrowser.getBrowserForTab(extPageTab); info("Wait for the extension page to be loaded"); await extension.awaitMessage("pageshow:extpage1"); await triggerWebRequestListener("http://example.com"); await extension.awaitMessage("event-received:extpage1"); ok(true, "extpage1 got a webRequest event as expected"); info("Load a second extension page in the same tab"); BrowserTestUtils.startLoadingURIString(browser, extPageURL2); info("Wait extpage1 to receive a pagehide event"); await extension.awaitMessage("pagehide:extpage1"); info("Wait extpage2 to receive a pageshow event"); await extension.awaitMessage("pageshow:extpage2"); info( "Trigger a web request event and expect extpage2 to be the only one receiving it" ); await triggerWebRequestListener("http://example.com"); await extension.awaitMessage("event-received:extpage2"); ok(true, "extpage2 got a webRequest event as expected"); info( "Navigating back to extpage1 and expect extpage2 to be the only one receiving the webRequest event" ); browser.goBack(); info("Wait for extpage1 to receive a pageshow event"); await extension.awaitMessage("pageshow:extpage1"); info("Wait for extpage2 to receive a pagehide event"); await extension.awaitMessage("pagehide:extpage2"); // We only expect extpage1 to be able to receive API events. await triggerWebRequestListener("http://example.com"); await extension.awaitMessage("event-received:extpage1"); ok(true, "extpage1 got a webRequest event as expected"); BrowserTestUtils.removeTab(extPageTab); await extension.awaitMessage("pagehide:extpage1"); await extension.unload(); }); add_task(async function test_extension_page_context_navigated_to_web_page() { const extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["webRequest", "http://example.com/*"], }, files: { "extpage.html": createTestExtPage({ script: "extpage.js" }), "extpage.js": createTestExtPageScript("extpage"), }, }); await extension.startup(); const policy = WebExtensionPolicy.getByID(extension.id); const extPageURL = policy.extension.baseURI.resolve("extpage.html"); // NOTE: this test will navigate the extension page to a webpage url that // isn't matching the match pattern the test extension is going to use // in its webRequest event listener, otherwise the extension page being // navigated will be intermittently able to receive an event before it // is navigated to the webpage url (and moved into the BFCache or destroyed) // and trigger an intermittent failure of this test. const webPageURL = "http://anotherwebpage.org/"; const triggerWebRequestURL = "http://example.com/"; info("Opening extension page in a new tab"); const extPageTab1 = await BrowserTestUtils.addTab(gBrowser, extPageURL); let browserForTab1 = gBrowser.getBrowserForTab(extPageTab1); info("Wait for the extension page to be loaded"); await extension.awaitMessage("pageshow:extpage"); info("Navigate the tab from the extension page to a web page"); let promiseLoaded = BrowserTestUtils.browserLoaded( browserForTab1, false, webPageURL ); BrowserTestUtils.startLoadingURIString(browserForTab1, webPageURL); info("Wait the tab to have loaded the new webpage url"); await promiseLoaded; info("Wait the extension page to receive a pagehide event"); await extension.awaitMessage("pagehide:extpage"); // Trigger a webRequest listener, the extension page is expected to // not be active, if that isn't the case a test message will be queued // and will trigger an explicit test failure. await triggerWebRequestListener(triggerWebRequestURL); info("Navigate back to the extension page"); browserForTab1.goBack(); info("Wait for extension page to receive a pageshow event"); await extension.awaitMessage("pageshow:extpage"); await triggerWebRequestListener(triggerWebRequestURL); await extension.awaitMessage("event-received:extpage"); ok( true, "extpage got a webRequest event as expected after being restored from BFCache" ); info("Cleanup and exit test"); BrowserTestUtils.removeTab(extPageTab1); info("Wait the extension page to receive a pagehide event"); await extension.awaitMessage("pagehide:extpage"); await extension.unload(); });