From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../test/xpcshell/test_ext_eventpage_messaging.js | 225 +++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js') diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js new file mode 100644 index 0000000000..c343f19a5c --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js @@ -0,0 +1,225 @@ +"use strict"; + +// This test checks that the suspension of the event page is delayed when the +// runtime.onConnect / runtime.onMessage events are involved. +// +// Another test (test_ext_eventpage_messaging_wakeup.js) verifies that the event +// page wakes up when these events are to be triggered. + +const { ExtensionTestCommon } = ChromeUtils.importESModule( + "resource://testing-common/ExtensionTestCommon.sys.mjs" +); + +add_setup(() => { + // In this test, we want to verify that an idle timeout is reset when + // extension messages are set. To avoid waiting for too long, reduce the + // default timeout to a short value. To avoid premature test termination, + // this timeout should be sufficiently large to run the relevant logic that + // is supposed to postpone event page termination: + // - The idle timer starts when the background has loaded. + // - The idle timer should reset when expected by tests. + Services.prefs.setIntPref("extensions.background.idle.timeout", 1000); +}); + +async function loadEventPageAndExtensionPage({ + backgroundScript, + extensionPageScript, +}) { + let extension = ExtensionTestUtils.loadExtension({ + // Delay startup, to ensure that the event page does not suspend until we + // have started the extension page that runs extensionPageScript. + startupReason: "APP_STARTUP", + // APP_STARTUP is not enough, delayedStartup is needed (bug 1756225). + delayedStartup: true, + manifest: { + background: { persistent: false }, + }, + background: backgroundScript, + files: { + "page.html": ``, + "page.js": extensionPageScript, + }, + }); + + // Delay event page startup until notifyEarlyStartup+notifyLateStartup below. + await ExtensionTestCommon.resetStartupPromises(); + await extension.startup(); + + // Start extension page first, so that it can register runtime.onSuspend + // before there is any chance of encountering a suspended event page. + let contentPage = await ExtensionTestUtils.loadContentPage( + `moz-extension://${extension.uuid}/page.html` + ); + + info("Extension page loaded, permitting event page to start up"); + await ExtensionTestCommon.notifyEarlyStartup(); + await ExtensionTestCommon.notifyLateStartup(); + + await extension.awaitMessage("FINAL_SUSPEND"); + info("Received FINAL_SUSPEND, awaiting full event page shutdown."); + await promiseExtensionEvent(extension, "shutdown-background-script"); + await contentPage.close(); + await extension.unload(); +} + +add_task(async function test_runtime_onMessage_cancels_suspend() { + await loadEventPageAndExtensionPage({ + backgroundScript() { + // This background script registers listeners without calling any other + // extension API. This ensures that if the event page suspend is canceled, + // that it was intentionally done by the listener, and not as a side + // effect of an unrelated extension API call. + browser.runtime.onMessage.addListener(msg => { + return Promise.resolve(`bg_pong:${msg}`); + }); + }, + extensionPageScript() { + let cancelCount = 0; + browser.runtime.onSuspendCanceled.addListener(() => { + browser.test.assertEq(1, ++cancelCount, "onSuspendCanceled 1x"); + }); + let suspendCount = 0; + let firstSuspendTestCompleted = false; + browser.runtime.onSuspend.addListener(async () => { + // We expect 2x suspend: first one we cancel, second one is final. + if (++suspendCount === 1) { + // First suspend attempt. + browser.test.assertEq(0, cancelCount, "Not suspended yet"); + let res = await browser.runtime.sendMessage("ping"); + browser.test.assertEq(1, cancelCount, "onMessage cancels suspend"); + browser.test.assertEq("bg_pong:ping", res, "onMessage result"); + firstSuspendTestCompleted = true; + } else { + browser.test.assertTrue(firstSuspendTestCompleted, "First test done"); + browser.test.assertEq(2, suspendCount, "Second onSuspend"); + browser.test.sendMessage("FINAL_SUSPEND"); + } + }); + browser.test.log("Waiting for background to be suspended"); + }, + }); +}); + +add_task(async function test_runtime_onConnect_cancels_suspend() { + await loadEventPageAndExtensionPage({ + backgroundScript() { + // This background script registers listeners without calling any other + // extension API. This ensures that if the event page suspend is canceled, + // that it was intentionally done by the listener, and not as a side + // effect of an unrelated extension API call. + browser.runtime.onConnect.addListener(port => { + // Set by extensionPageScript before runtime.connect(): + globalThis.notify_extensionPage_got_onConnect(); + }); + }, + extensionPageScript() { + let cancelCount = 0; + browser.runtime.onSuspendCanceled.addListener(() => { + browser.test.assertEq(1, ++cancelCount, "onSuspendCanceled 1x"); + }); + let suspendCount = 0; + let firstSuspendTestCompleted = false; + let port; // Prevent port from being gc'd during test. + browser.runtime.onSuspend.addListener(async () => { + // We expect 2x suspend: first one we cancel, second one is final. + if (++suspendCount === 1) { + // First suspend attempt. + browser.test.assertEq(0, cancelCount, "Not suspended yet"); + // Call runtime.connect() twice: + // 1. First connect() should be triggering the reset. + // 2. We are immediately notified when runtime.onConnect is called. + // 2. We call connect() again to have another page->parent->background + // roundtrip. This ensures that enough time to have been passed to + // allow the first runtime.onConnect handling to have finished, + // and to have triggeres onSuspendCanceled as desired. + for (let i = 0; i < 2; ++i) { + await new Promise(resolve => { + let bgGlobal = browser.extension.getBackgroundPage(); + browser.test.assertTrue(!!bgGlobal, "Event page still running"); + bgGlobal.notify_extensionPage_got_onConnect = resolve; + port = browser.runtime.connect({}); + }); + } + browser.test.assertEq(1, cancelCount, "onConnect cancels suspend"); + firstSuspendTestCompleted = true; + } else { + browser.test.assertTrue(firstSuspendTestCompleted, "First test done"); + browser.test.assertEq(2, suspendCount, "Second onSuspend"); + browser.test.assertEq(null, port.error, "port has no error"); + browser.test.sendMessage("FINAL_SUSPEND"); + } + }); + browser.test.log("Waiting for background to be suspended"); + }, + }); +}); + +add_task(async function test_runtime_Port_onMessage_cancels_suspend() { + await loadEventPageAndExtensionPage({ + backgroundScript() { + // This background script registers listeners without calling any other + // extension API. This ensures that if the event page suspend is canceled, + // that it was intentionally done by the listener, and not as a side + // effect of an unrelated extension API call. + browser.runtime.onConnect.addListener(port => { + port.onMessage.addListener(msg => { + // Set by extensionPageScript before runtime.connect(): + globalThis.notify_extensionPage_got_port_onMessage(); + }); + }); + }, + extensionPageScript() { + let cancelCount = 0; + browser.runtime.onSuspendCanceled.addListener(() => { + browser.test.assertEq(1, ++cancelCount, "onSuspendCanceled 1x"); + }); + let suspendCount = 0; + let firstSuspendTestCompleted = false; + let port; + browser.runtime.onSuspend.addListener(async () => { + // We expect 2x suspend: first one we cancel, second one is final. + if (++suspendCount === 1) { + // First suspend attempt. + browser.test.assertEq(0, cancelCount, "Not suspended yet"); + browser.test.assertTrue(!!port, "Should run after we opened a port"); + // Call port.postMessage() twice: + // 1. First port.postMessage() should be triggering the reset. + // 2. We are immediately notified when runtime.onMessage is called. + // 2. We postMessage() again to have another page->parent->background + // roundtrip. This ensures that enough time to have been passed to + // allow the first port.onMessage handling to have finished, + // and to have triggeres onSuspendCanceled as desired. + for (let i = 0; i < 2; ++i) { + await new Promise(resolve => { + let bgGlobal = browser.extension.getBackgroundPage(); + browser.test.assertTrue(!!bgGlobal, "Event page still running"); + bgGlobal.notify_extensionPage_got_port_onMessage = resolve; + port.postMessage(""); + }); + } + browser.test.assertEq( + 1, + cancelCount, + "port.onMessage cancels suspend" + ); + firstSuspendTestCompleted = true; + } else { + browser.test.assertTrue(firstSuspendTestCompleted, "First test done"); + browser.test.assertEq(2, suspendCount, "Second onSuspend"); + browser.test.sendMessage("FINAL_SUSPEND"); + } + }); + browser.runtime.getBackgroundPage(bgGlobal => { + browser.test.assertTrue(!!bgGlobal, "Event page has started"); + // Since the event page has started, this should trigger onConnect in + // the event page. If somehow the event page has suspended in the + // meantime, then we will detect that in runtime.onSuspend (and fail). + port = browser.runtime.connect({}); + // Assuming that runtime.onConnect in the event page has received the + // port and started listening, we should now wait for an attempt to + // suspend the event page (and try to cancel that via port.onMessage). + browser.test.log("Waiting for background to be suspended"); + }); + }, + }); +}); -- cgit v1.2.3