summaryrefslogtreecommitdiffstats
path: root/remote/shared/listeners/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'remote/shared/listeners/test/browser')
-rw-r--r--remote/shared/listeners/test/browser/browser.toml21
-rw-r--r--remote/shared/listeners/test/browser/browser_BrowsingContextListener.js117
-rw-r--r--remote/shared/listeners/test/browser/browser_ConsoleAPIListener.js162
-rw-r--r--remote/shared/listeners/test/browser/browser_ConsoleAPIListener_cached_messages.js100
-rw-r--r--remote/shared/listeners/test/browser/browser_ConsoleListener.js148
-rw-r--r--remote/shared/listeners/test/browser/browser_ConsoleListener_cached_messages.js82
-rw-r--r--remote/shared/listeners/test/browser/browser_ContextualIdentityListener.js38
-rw-r--r--remote/shared/listeners/test/browser/browser_NetworkListener.js100
-rw-r--r--remote/shared/listeners/test/browser/browser_PromptListener.js173
-rw-r--r--remote/shared/listeners/test/browser/head.js89
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();
+ });
+}