diff options
Diffstat (limited to '')
10 files changed, 1030 insertions, 0 deletions
diff --git a/remote/shared/listeners/test/browser/browser.toml b/remote/shared/listeners/test/browser/browser.toml new file mode 100644 index 0000000000..d462bf1e82 --- /dev/null +++ b/remote/shared/listeners/test/browser/browser.toml @@ -0,0 +1,21 @@ +[DEFAULT] +tags = "remote" +subsuite = "remote" +support-files = ["head.js"] +prefs = ["remote.messagehandler.modulecache.useBrowserTestRoot=true"] + +["browser_BrowsingContextListener.js"] + +["browser_ConsoleAPIListener.js"] + +["browser_ConsoleAPIListener_cached_messages.js"] + +["browser_ConsoleListener.js"] + +["browser_ConsoleListener_cached_messages.js"] + +["browser_ContextualIdentityListener.js"] + +["browser_NetworkListener.js"] + +["browser_PromptListener.js"] diff --git a/remote/shared/listeners/test/browser/browser_BrowsingContextListener.js b/remote/shared/listeners/test/browser/browser_BrowsingContextListener.js new file mode 100644 index 0000000000..9a08df7857 --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_BrowsingContextListener.js @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { BrowsingContextListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs" +); + +add_task(async function test_attachedOnNewTab() { + const listener = new BrowsingContextListener(); + const attached = listener.once("attached"); + + listener.startListening(); + + const tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + const { browsingContext, why } = await attached; + + is( + browsingContext.id, + tab.linkedBrowser.browsingContext.id, + "Received expected browsing context" + ); + is(why, "attach", "Browsing context has been attached"); + + listener.stopListening(); + gBrowser.removeTab(tab); +}); + +add_task(async function test_attachedValidEmbedderElement() { + const listener = new BrowsingContextListener(); + + let hasEmbedderElement = false; + listener.on( + "attached", + (evtName, { browsingContext }) => { + hasEmbedderElement = !!browsingContext.embedderElement; + }, + { once: true } + ); + + listener.startListening(); + + const tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + ok( + hasEmbedderElement, + "Attached browsing context has a valid embedder element" + ); + + listener.stopListening(); + gBrowser.removeTab(tab); +}); + +add_task(async function test_discardedOnCloseTab() { + const listener = new BrowsingContextListener(); + const discarded = listener.once("discarded"); + + const tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + const browsingContext = tab.linkedBrowser.browsingContext; + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + listener.startListening(); + gBrowser.removeTab(tab); + const { browsingContext: discardedBrowsingContext, why } = await discarded; + + is( + discardedBrowsingContext.id, + browsingContext.id, + "Received expected browsing context" + ); + is(why, "discard", "Browsing context has been discarded"); + + listener.stopListening(); +}); + +add_task(async function test_replaceTopLevelOnNavigation() { + const listener = new BrowsingContextListener(); + const attached = listener.once("attached"); + const discarded = listener.once("discarded"); + + const tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + const browsingContext = tab.linkedBrowser.browsingContext; + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + listener.startListening(); + + await loadURL(tab.linkedBrowser, "about:mozilla"); + + const discardEvent = await discarded; + const attachEvent = await attached; + + is( + discardEvent.browsingContext.id, + browsingContext.id, + "Received expected browsing context for discarded" + ); + is(discardEvent.why, "replace", "Browsing context has been replaced"); + + is( + attachEvent.browsingContext.id, + tab.linkedBrowser.browsingContext.id, + "Received expected browsing context for attached" + ); + is(discardEvent.why, "replace", "Browsing context has been replaced"); + + isnot( + discardEvent.browsingContext, + attachEvent.browsingContext, + "Got different browsing contexts" + ); + + listener.stopListening(); + gBrowser.removeTab(gBrowser.selectedTab); +}); diff --git a/remote/shared/listeners/test/browser/browser_ConsoleAPIListener.js b/remote/shared/listeners/test/browser/browser_ConsoleAPIListener.js new file mode 100644 index 0000000000..ccff78c7a0 --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_ConsoleAPIListener.js @@ -0,0 +1,162 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const TESTS = [ + { method: "log", args: ["log1"] }, + { method: "log", args: ["log2", "log3"] }, + { method: "log", args: [[1, 2, 3], { someProperty: "someValue" }] }, + { method: "warn", args: ["warn1"] }, + { method: "error", args: ["error1"] }, + { method: "info", args: ["info1"] }, + { method: "debug", args: ["debug1"] }, + { method: "trace", args: ["trace1"] }, + { method: "assert", args: [false, "assert1"] }, +]; + +const TEST_PAGE = "https://example.com/document-builder.sjs?html=tab"; + +add_task(async function test_method_and_arguments() { + for (const { method, args } of TESTS) { + // Use a dedicated tab for each test to avoid cached messages. + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + info(`Test ConsoleApiListener for ${JSON.stringify({ method, args })}`); + + const listenerId = await listenToConsoleAPIMessage(); + await useConsoleInContent(method, args); + const { + arguments: msgArguments, + level, + timeStamp, + stacktrace, + } = await getConsoleAPIMessage(listenerId); + + if (method == "assert") { + // console.assert() consumes first argument. + args.shift(); + } + + is( + msgArguments.length, + args.length, + "Message event has the expected number of arguments" + ); + for (let i = 0; i < args.length; i++) { + Assert.deepEqual( + msgArguments[i], + args[i], + `Message event has the expected argument at index ${i}` + ); + } + is(level, method, "Message event has the expected level"); + ok(Number.isInteger(timeStamp), "Message event has a valid timestamp"); + + if (["assert", "error", "warn", "trace"].includes(method)) { + // Check stacktrace if method is allowed to contain one. + if (method === "warn") { + todo( + Array.isArray(stacktrace), + "stacktrace is of expected type Array (Bug 1744705)" + ); + } else { + ok(Array.isArray(stacktrace), "stacktrace is of expected type Array"); + Assert.greaterOrEqual( + stacktrace.length, + 1, + "stack trace contains at least one frame" + ); + } + } else { + is(typeof stacktrace, "undefined", "stack trace is is not present"); + } + + gBrowser.removeTab(gBrowser.selectedTab); + } +}); + +add_task(async function test_stacktrace() { + const script = ` + function foo() { console.error("cheese"); } + function bar() { foo(); } + bar(); + `; + + const listenerId = await listenToConsoleAPIMessage(); + await createScriptNode(script); + const { stacktrace } = await getConsoleAPIMessage(listenerId); + + ok(Array.isArray(stacktrace), "stacktrace is of expected type Array"); + + // First 3 frames are from the test script. + Assert.greaterOrEqual( + stacktrace.length, + 3, + "stack trace contains at least 3 frames" + ); + checkStackFrame(stacktrace[0], "about:blank", "foo", 2, 30); + checkStackFrame(stacktrace[1], "about:blank", "bar", 3, 22); + checkStackFrame(stacktrace[2], "about:blank", "", 4, 5); +}); + +function useConsoleInContent(method, args) { + info(`Call console API: console.${method}("${args.join('", "')}");`); + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [method, args], + (_method, _args) => { + content.console[_method].apply(content.console, _args); + } + ); +} + +function listenToConsoleAPIMessage() { + info("Listen to a console api message in content"); + return SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const innerWindowId = content.windowGlobalChild.innerWindowId; + const { ConsoleAPIListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs" + ); + const consoleAPIListener = new ConsoleAPIListener(innerWindowId); + const onMessage = consoleAPIListener.once("message"); + consoleAPIListener.startListening(); + + const listenerId = Math.random(); + content[listenerId] = { consoleAPIListener, onMessage }; + return listenerId; + }); +} + +function getConsoleAPIMessage(listenerId) { + info("Retrieve the message event captured for listener: " + listenerId); + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [listenerId], + async _listenerId => { + const { consoleAPIListener, onMessage } = content[_listenerId]; + const message = await onMessage; + + consoleAPIListener.destroy(); + + return message; + } + ); +} + +function checkStackFrame( + frame, + filename, + functionName, + lineNumber, + columnNumber +) { + is(frame.filename, filename, "Received expected filename for frame"); + is( + frame.functionName, + functionName, + "Received expected function name for frame" + ); + is(frame.lineNumber, lineNumber, "Received expected line for frame"); + is(frame.columnNumber, columnNumber, "Received expected column for frame"); +} diff --git a/remote/shared/listeners/test/browser/browser_ConsoleAPIListener_cached_messages.js b/remote/shared/listeners/test/browser/browser_ConsoleAPIListener_cached_messages.js new file mode 100644 index 0000000000..dae35a0b9a --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_ConsoleAPIListener_cached_messages.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const TEST_PAGE = "https://example.com/document-builder.sjs?html=tab"; + +add_task(async function test_cached_messages() { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const innerWindowId = content.windowGlobalChild.innerWindowId; + const { ConsoleAPIListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs" + ); + + info("Log two messages before starting the ConsoleAPIListener"); + content.console.log("message_1"); + content.console.log("message_2"); + + const listener = new ConsoleAPIListener(innerWindowId); + const messages = []; + + // We will keep the onMessage callback attached to the ConsoleAPIListener + // during the whole test to catch all the emitted events. + const onMessage = (evtName, message) => messages.push(message.arguments[0]); + + listener.on("message", onMessage); + listener.startListening(); + + info("Wait until the 2 cached messages have been emitted"); + await ContentTaskUtils.waitForCondition(() => messages.length == 2); + is(messages[0], "message_1"); + is(messages[1], "message_2"); + + info("Stop listening and log another message"); + listener.stopListening(); + content.backup = { listener, messages, onMessage }; + }); + + // Force a GC to check that old cached messages which have been garbage + // collected are not re-displayed. + await doGC(); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const { listener, messages, onMessage } = content.backup; + content.console.log("message_3"); + + info("Start listening again and check the previous message is emitted"); + listener.startListening(); + await ContentTaskUtils.waitForCondition(() => messages.length == 3); + is(messages[2], "message_3"); + + info("Log another message and wait until it is emitted"); + content.console.log("message_4"); + await ContentTaskUtils.waitForCondition(() => messages.length == 4); + is(messages[3], "message_4"); + + listener.off("message", onMessage); + listener.destroy(); + + is(messages.length, 4, "Received 4 messages in total"); + }); + + info("Reload the current tab and check only new messages are emitted"); + await BrowserTestUtils.reloadTab(gBrowser.selectedTab); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const innerWindowId = content.windowGlobalChild.innerWindowId; + const { ConsoleAPIListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs" + ); + + info("Log a message before creating the ConsoleAPIListener"); + content.console.log("new_message_1"); + + const listener = new ConsoleAPIListener(innerWindowId); + const newMessages = []; + const onMessage = (evtName, message) => + newMessages.push(message.arguments[0]); + listener.on("message", onMessage); + + info("Start listening and wait for the cached message"); + listener.startListening(); + await ContentTaskUtils.waitForCondition(() => newMessages.length == 1); + is(newMessages[0], "new_message_1"); + + info("Log another message and wait until it is emitted"); + content.console.log("new_message_2"); + await ContentTaskUtils.waitForCondition(() => newMessages.length == 2); + is(newMessages[1], "new_message_2"); + + listener.off("message", onMessage); + listener.destroy(); + + is(newMessages.length, 2, "Received 2 messages in total"); + }); + + gBrowser.removeTab(gBrowser.selectedTab); +}); diff --git a/remote/shared/listeners/test/browser/browser_ConsoleListener.js b/remote/shared/listeners/test/browser/browser_ConsoleListener.js new file mode 100644 index 0000000000..41936a6c0d --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_ConsoleListener.js @@ -0,0 +1,148 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function test_message_properties() { + const listenerId = await listenToConsoleMessage("error"); + await logConsoleMessage({ message: "foo" }); + const { level, message, timeStamp, stack } = await getConsoleMessage( + listenerId + ); + + is(level, "error", "Received expected log level"); + is(message, "foo", "Received expected log message"); + // Services.console.logMessage() doesn't include a stack. + is(stack, undefined, "No stack present"); + is(typeof timeStamp, "number", "timestamp is of expected type number"); + + // Clear the console to avoid side effects with other tests in this file. + await clearConsole(); +}); + +add_task(async function test_level() { + for (const level of ["error", "info", "warn"]) { + const listenerId = await listenToConsoleMessage(level); + await logConsoleMessage({ message: "foo", level }); + const message = await getConsoleMessage(listenerId); + + is(message.level, level, "Received expected log level"); + } + + // Clear the console to avoid side effects with other tests in this file. + await clearConsole(); +}); + +add_task(async function test_stacktrace() { + const script = ` + function foo() { throw new Error("cheese"); } + function bar() { foo(); } + bar(); + `; + + const listenerId = await listenToConsoleMessage("error"); + await createScriptNode(script); + const { message, level, stacktrace } = await getConsoleMessage(listenerId); + is(level, "error", "Received expected log level"); + is(message, "Error: cheese", "Received expected log message"); + ok(Array.isArray(stacktrace), "frames is of expected type Array"); + Assert.greaterOrEqual(stacktrace.length, 4, "Got at least 4 stack frames"); + + // First 3 stack frames are from the injected script and one more frame comes + // from head.js (chrome scope) where we inject the script. + checkStackFrame(stacktrace[0], "about:blank", "foo", 2, 28); + checkStackFrame(stacktrace[1], "about:blank", "bar", 3, 22); + checkStackFrame(stacktrace[2], "about:blank", "", 4, 5); + checkStackFrame( + stacktrace[3], + "chrome://mochitests/content/browser/remote/shared/listeners/test/browser/head.js", + "", + 34, + 29 + ); + + // Clear the console to avoid side effects with other tests in this file. + await clearConsole(); +}); + +function logConsoleMessage(options = {}) { + info(`Log console message ${options.message}`); + return SpecialPowers.spawn(gBrowser.selectedBrowser, [options], _options => { + const { level = "error" } = _options; + + const levelToFlags = { + error: Ci.nsIScriptError.errorFlag, + info: Ci.nsIScriptError.infoFlag, + warn: Ci.nsIScriptError.warningFlag, + }; + + const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance( + Ci.nsIScriptError + ); + scriptError.initWithWindowID( + _options.message, + _options.sourceName || "sourceName", + null, + _options.lineNumber || 0, + _options.columnNumber || 0, + levelToFlags[level], + _options.category || "javascript", + content.windowGlobalChild.innerWindowId + ); + + Services.console.logMessage(scriptError); + }); +} + +function listenToConsoleMessage(level) { + info("Listen to a console message in content"); + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [level], + async _level => { + const innerWindowId = content.windowGlobalChild.innerWindowId; + const { ConsoleListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs" + ); + const consoleListener = new ConsoleListener(innerWindowId); + const onMessage = consoleListener.once(_level); + consoleListener.startListening(); + + const listenerId = Math.random(); + content[listenerId] = { consoleListener, onMessage }; + return listenerId; + } + ); +} + +function getConsoleMessage(listenerId) { + info("Retrieve the message event captured for listener: " + listenerId); + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [listenerId], + async _listenerId => { + const { consoleListener, onMessage } = content[_listenerId]; + const message = await onMessage; + + consoleListener.destroy(); + + return message; + } + ); +} + +function checkStackFrame( + frame, + filename, + functionName, + lineNumber, + columnNumber +) { + is(frame.filename, filename, "Received expected filename for frame"); + is( + frame.functionName, + functionName, + "Received expected function name for frame" + ); + is(frame.lineNumber, lineNumber, "Received expected line for frame"); + is(frame.columnNumber, columnNumber, "Received expected column for frame"); +} diff --git a/remote/shared/listeners/test/browser/browser_ConsoleListener_cached_messages.js b/remote/shared/listeners/test/browser/browser_ConsoleListener_cached_messages.js new file mode 100644 index 0000000000..1020aee661 --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_ConsoleListener_cached_messages.js @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const TEST_PAGE = + "https://example.com/document-builder.sjs?html=<meta charset=utf8></meta>"; + +add_task(async function test_cached_javascript_errors() { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + await createScriptNode(`(() => {throw "error1"})()`); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const innerWindowId = content.windowGlobalChild.innerWindowId; + const { ConsoleListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs" + ); + + const listener = new ConsoleListener(innerWindowId); + + const errors = []; + // Do not push the whole error object in the array. It would create a strong + // reference preventing from reproducing GC-related bugs. + const onError = (evtName, error) => errors.push(error.message); + listener.on("error", onError); + + const waitForMessage = listener.once("error"); + listener.startListening(); + const error = await waitForMessage; + is(error.message, "uncaught exception: error1"); + is(errors.length, 1); + + listener.stopListening(); + content.backup = { listener, errors, onError }; + }); + + // Force a GC to check that old cached messages which have been garbage + // collected are not re-displayed. + await doGC(); + await createScriptNode(`(() => {throw "error2"})()`); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const { listener, errors, onError } = content.backup; + + const waitForMessage = listener.once("error"); + listener.startListening(); + const { message } = await waitForMessage; + is(message, "uncaught exception: error2"); + is(errors.length, 2); + + listener.off("error", onError); + listener.destroy(); + }); + + info("Reload the current tab and check only new messages are emitted"); + await BrowserTestUtils.reloadTab(gBrowser.selectedTab); + + await createScriptNode(`(() => {throw "error3"})()`); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const innerWindowId = content.windowGlobalChild.innerWindowId; + const { ConsoleListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs" + ); + + const listener = new ConsoleListener(innerWindowId); + + const errors = []; + const onError = (evtName, error) => errors.push(error.message); + listener.on("error", onError); + + const waitForMessage = listener.once("error"); + listener.startListening(); + const error = await waitForMessage; + is(error.message, "uncaught exception: error3"); + is(errors.length, 1); + + listener.off("error", onError); + listener.destroy(); + }); + + gBrowser.removeTab(gBrowser.selectedTab); +}); diff --git a/remote/shared/listeners/test/browser/browser_ContextualIdentityListener.js b/remote/shared/listeners/test/browser/browser_ContextualIdentityListener.js new file mode 100644 index 0000000000..df783a5688 --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_ContextualIdentityListener.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { ContextualIdentityListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/ContextualIdentityListener.sys.mjs" +); + +add_task(async function test_createdOnNewContextualIdentity() { + const listener = new ContextualIdentityListener(); + const created = listener.once("created"); + + listener.startListening(); + + ContextualIdentityService.create("test_name"); + + const { identity } = await created; + is(identity.name, "test_name", "Received expected identity"); + + listener.stopListening(); + + ContextualIdentityService.remove(identity.userContextId); +}); + +add_task(async function test_deletedOnRemovedContextualIdentity() { + const listener = new ContextualIdentityListener(); + const deleted = listener.once("deleted"); + + listener.startListening(); + + const testIdentity = ContextualIdentityService.create("test_name"); + ContextualIdentityService.remove(testIdentity.userContextId); + + const { identity } = await deleted; + is(identity.name, "test_name", "Received expected identity"); + + listener.stopListening(); +}); diff --git a/remote/shared/listeners/test/browser/browser_NetworkListener.js b/remote/shared/listeners/test/browser/browser_NetworkListener.js new file mode 100644 index 0000000000..78865f6b80 --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_NetworkListener.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { NetworkListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/NetworkListener.sys.mjs" +); +const { TabManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/TabManager.sys.mjs" +); + +add_task(async function test_beforeRequestSent() { + const listener = new NetworkListener(); + const events = []; + const onEvent = (name, data) => events.push(data); + listener.on("before-request-sent", onEvent); + + const tab1 = BrowserTestUtils.addTab( + gBrowser, + "https://example.com/document-builder.sjs?html=tab" + ); + await BrowserTestUtils.browserLoaded(tab1.linkedBrowser); + const contextId1 = TabManager.getIdForBrowser(tab1.linkedBrowser); + + const tab2 = BrowserTestUtils.addTab( + gBrowser, + "https://example.com/document-builder.sjs?html=tab2" + ); + await BrowserTestUtils.browserLoaded(tab2.linkedBrowser); + const contextId2 = TabManager.getIdForBrowser(tab2.linkedBrowser); + + listener.startListening(); + + await fetch(tab1.linkedBrowser, "https://example.com/?1"); + ok(events.length == 1, "One event was received"); + assertNetworkEvent(events[0], contextId1, "https://example.com/?1"); + + info("Check that events are no longer emitted after calling stopListening"); + listener.stopListening(); + await fetch(tab1.linkedBrowser, "https://example.com/?2"); + ok(events.length == 1, "No new event was received"); + + listener.startListening(); + await fetch(tab1.linkedBrowser, "https://example.com/?3"); + ok(events.length == 2, "A new event was received"); + assertNetworkEvent(events[1], contextId1, "https://example.com/?3"); + + info("Check network event from the new tab"); + await fetch(tab2.linkedBrowser, "https://example.com/?4"); + ok(events.length == 3, "A new event was received"); + assertNetworkEvent(events[2], contextId2, "https://example.com/?4"); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); + listener.off("before-request-sent", onEvent); + listener.destroy(); +}); + +add_task(async function test_beforeRequestSent_newTab() { + const listener = new NetworkListener(); + const onBeforeRequestSent = listener.once("before-request-sent"); + listener.startListening(); + + info("Check network event related to loading a new tab"); + const tab = BrowserTestUtils.addTab( + gBrowser, + "https://example.com/document-builder.sjs?html=tab" + ); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + const contextId = TabManager.getIdForBrowser(tab.linkedBrowser); + const event = await onBeforeRequestSent; + + assertNetworkEvent( + event, + contextId, + "https://example.com/document-builder.sjs?html=tab" + ); + gBrowser.removeTab(tab); +}); + +add_task(async function test_fetchError() { + const listener = new NetworkListener(); + const onFetchError = listener.once("fetch-error"); + listener.startListening(); + + info("Check fetchError event when loading a new tab"); + const tab = BrowserTestUtils.addTab(gBrowser, "https://not_a_valid_url/"); + BrowserTestUtils.browserLoaded(tab.linkedBrowser); + const contextId = TabManager.getIdForBrowser(tab.linkedBrowser); + const event = await onFetchError; + + assertNetworkEvent(event, contextId, "https://not_a_valid_url/"); + is(event.errorText, "NS_ERROR_UNKNOWN_HOST"); + gBrowser.removeTab(tab); +}); + +function assertNetworkEvent(event, expectedContextId, expectedUrl) { + is(event.contextId, expectedContextId, "Event has the expected context id"); + is(event.requestData.url, expectedUrl, "Event has the expected url"); +} diff --git a/remote/shared/listeners/test/browser/browser_PromptListener.js b/remote/shared/listeners/test/browser/browser_PromptListener.js new file mode 100644 index 0000000000..0d3f23db3f --- /dev/null +++ b/remote/shared/listeners/test/browser/browser_PromptListener.js @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { PromptListener } = ChromeUtils.importESModule( + "chrome://remote/content/shared/listeners/PromptListener.sys.mjs" +); + +add_task(async function test_without_curBrowser() { + const listener = new PromptListener(); + const opened = listener.once("opened"); + const closed = listener.once("closed"); + + listener.startListening(); + + const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(); + await createScriptNode(`setTimeout(() => window.confirm('test'))`); + const dialogWin = await dialogPromise; + + const openedEvent = await opened; + + is(openedEvent.prompt.window, dialogWin, "Received expected prompt"); + + dialogWin.document.querySelector("dialog").acceptDialog(); + + const closedEvent = await closed; + + is(closedEvent.detail.accepted, true, "Received correct event details"); + + listener.destroy(); +}); + +add_task(async function test_with_curBrowser() { + const listener = new PromptListener(() => ({ + contentBrowser: gBrowser.selectedBrowser, + window, + })); + const opened = listener.once("opened"); + const closed = listener.once("closed"); + + listener.startListening(); + + const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(); + await createScriptNode(`setTimeout(() => window.confirm('test'))`); + const dialogWin = await dialogPromise; + + const openedEvent = await opened; + + is(openedEvent.prompt.window, dialogWin, "Received expected prompt"); + + dialogWin.document.querySelector("dialog").acceptDialog(); + + const closedEvent = await closed; + + is(closedEvent.detail.accepted, true, "Received correct event details"); + + listener.destroy(); +}); + +add_task(async function test_close_event_details() { + const listener = new PromptListener(); + let closed = listener.once("closed"); + + listener.startListening(); + + let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(); + await createScriptNode(`setTimeout(() => window.prompt('Enter your name:'))`); + let dialogWin = await dialogPromise; + + dialogWin.document.getElementById("loginTextbox").value = "Test"; + dialogWin.document.querySelector("dialog").acceptDialog(); + + let closedEvent = await closed; + + is( + closedEvent.detail.accepted, + true, + "Received correct `accepted` value in event details" + ); + is( + closedEvent.detail.userText, + "Test", + "Received correct `userText` value in event details" + ); + + closed = listener.once("closed"); + + dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(); + await createScriptNode(`setTimeout(() => window.prompt('Enter your name:'))`); + dialogWin = await dialogPromise; + + dialogWin.document.getElementById("loginTextbox").value = "Test"; + dialogWin.document.querySelector("dialog").cancelDialog(); + + closedEvent = await closed; + + is( + closedEvent.detail.accepted, + false, + "Received correct `accepted` value in event details" + ); + is( + closedEvent.detail.userText, + undefined, + "Received correct `userText` value in event details" + ); + + listener.destroy(); +}); + +add_task(async function test_dialogClosed() { + const listener = new PromptListener(); + + listener.startListening(); + + let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(); + await createScriptNode(`setTimeout(() => window.alert('test'))`); + let dialogWin = await dialogPromise; + let closed = listener.dialogClosed(); + + dialogWin.document.querySelector("dialog").acceptDialog(); + + await closed; + + is(true, true, "Close promise got resolved"); + + dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(); + await createScriptNode(`setTimeout(() => window.alert('test'))`); + dialogWin = await dialogPromise; + closed = listener.dialogClosed(); + + dialogWin.document.querySelector("dialog").cancelDialog(); + + await closed; + + is(true, true, "Close promise got resolved"); + + listener.destroy(); +}); + +add_task(async function test_events_in_another_browser() { + const win = await BrowserTestUtils.openNewBrowserWindow(); + const selectedBrowser = win.gBrowser.selectedBrowser; + const listener = new PromptListener(() => ({ + contentBrowser: selectedBrowser, + window: selectedBrowser.ownerGlobal, + })); + const events = []; + const onEvent = (name, data) => events.push(data); + listener.on("opened", onEvent); + listener.on("closed", onEvent); + + listener.startListening(); + + const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(); + await createScriptNode(`setTimeout(() => window.confirm('test'))`); + const dialogWin = await dialogPromise; + + ok(events.length === 0, "No event was received"); + + dialogWin.document.querySelector("dialog").acceptDialog(); + + // Wait a bit to make sure that the event didn't come. + await new Promise(resolve => { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(resolve, 500); + }); + + ok(events.length === 0, "No event was received"); + + listener.destroy(); + await BrowserTestUtils.closeWindow(win); +}); diff --git a/remote/shared/listeners/test/browser/head.js b/remote/shared/listeners/test/browser/head.js new file mode 100644 index 0000000000..1691a6f59b --- /dev/null +++ b/remote/shared/listeners/test/browser/head.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +async function clearConsole() { + for (const tab of gBrowser.tabs) { + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + Services.console.reset(); + }); + } + Services.console.reset(); +} + +/** + * Execute the provided script content by generating a dynamic script tag and + * inserting it in the page for the current selected browser. + * + * @param {string} script + * The script to execute. + * @returns {Promise} + * A promise that resolves when the script node was added and removed from + * the content page. + */ +function createScriptNode(script) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [script], + function (_script) { + var script = content.document.createElement("script"); + script.append(content.document.createTextNode(_script)); + content.document.body.append(script); + } + ); +} + +registerCleanupFunction(async () => { + await clearConsole(); +}); + +async function doGC() { + // Run GC and CC a few times to make sure that as much as possible is freed. + const numCycles = 3; + for (let i = 0; i < numCycles; i++) { + Cu.forceGC(); + Cu.forceCC(); + await new Promise(resolve => Cu.schedulePreciseShrinkingGC(resolve)); + } + + const MemoryReporter = Cc[ + "@mozilla.org/memory-reporter-manager;1" + ].getService(Ci.nsIMemoryReporterManager); + await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve)); +} + +/** + * Load the provided url in an existing browser. + * Returns a promise which will resolve when the page is loaded. + * + * @param {Browser} browser + * The browser element where the URL should be loaded. + * @param {string} url + * The URL to load. + */ +async function loadURL(browser, url) { + const loaded = BrowserTestUtils.browserLoaded(browser); + BrowserTestUtils.startLoadingURIString(browser, url); + return loaded; +} + +/** + * Create a fetch request to `url` from the content page loaded in the provided + * `browser`. + * + * + * @param {Browser} browser + * The browser element where the fetch should be performed. + * @param {string} url + * The URL to fetch. + */ +function fetch(browser, url) { + return SpecialPowers.spawn(browser, [url], async _url => { + const response = await content.fetch(_url); + // Wait for response.text() to resolve as well to make sure the response + // has completed before returning. + await response.text(); + }); +} |