summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js')
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js453
1 files changed, 453 insertions, 0 deletions
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
new file mode 100644
index 0000000000..c02aef3da9
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -0,0 +1,453 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function testExecuteScript() {
+ let { MessageChannel } = ChromeUtils.importESModule(
+ "resource://testing-common/MessageChannel.sys.mjs"
+ );
+
+ function countMM(messageManagerMap) {
+ let count = 0;
+ // List of permanent message managers in the main process. We should not
+ // count them in the test because MessageChannel unsubscribes when the
+ // message manager closes, which never happens to these, of course.
+ let globalMMs = [Services.mm, Services.ppmm, Services.ppmm.getChildAt(0)];
+ for (let mm of messageManagerMap.keys()) {
+ if (!globalMMs.includes(mm)) {
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ let messageManagersSize = countMM(MessageChannel.messageManagers);
+
+ const BASE =
+ "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+ const URL = BASE + "file_iframe_document.html";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL, true);
+
+ async function background() {
+ try {
+ // This promise is meant to be resolved when browser.tabs.executeScript({file: "script.js"})
+ // is called and the content script does message back, registering the runtime.onMessage
+ // listener here is meant to prevent intermittent failures due to a race on executing the
+ // array of promises passed to the `await Promise.all(...)` below.
+ const promiseRuntimeOnMessage = new Promise(resolve => {
+ browser.runtime.onMessage.addListener(message => {
+ browser.test.assertEq(
+ "script ran",
+ message,
+ "Expected runtime message"
+ );
+ resolve();
+ });
+ });
+
+ let [tab] = await browser.tabs.query({
+ active: true,
+ currentWindow: true,
+ });
+ let frames = await browser.webNavigation.getAllFrames({ tabId: tab.id });
+ browser.test.assertEq(3, frames.length, "Expect exactly three frames");
+ browser.test.assertEq(0, frames[0].frameId, "Main frame has frameId:0");
+ browser.test.assertTrue(frames[1].frameId > 0, "Subframe has a valid id");
+
+ browser.test.log(
+ `FRAMES: ${frames[1].frameId} ${JSON.stringify(frames)}\n`
+ );
+ await Promise.all([
+ browser.tabs
+ .executeScript({
+ code: "42",
+ })
+ .then(result => {
+ browser.test.assertEq(
+ 1,
+ result.length,
+ "Expected one callback result"
+ );
+ browser.test.assertEq(42, result[0], "Expected callback result");
+ }),
+
+ browser.tabs
+ .executeScript({
+ file: "script.js",
+ code: "42",
+ })
+ .then(
+ result => {
+ browser.test.fail(
+ "Expected not to be able to execute a script with both file and code"
+ );
+ },
+ error => {
+ browser.test.assertTrue(
+ /a 'code' or a 'file' property, but not both/.test(
+ error.message
+ ),
+ "Got expected error"
+ );
+ }
+ ),
+
+ browser.tabs
+ .executeScript({
+ file: "script.js",
+ })
+ .then(result => {
+ browser.test.assertEq(
+ 1,
+ result.length,
+ "Expected one callback result"
+ );
+ browser.test.assertEq(
+ undefined,
+ result[0],
+ "Expected callback result"
+ );
+ }),
+
+ browser.tabs
+ .executeScript({
+ file: "script2.js",
+ })
+ .then(result => {
+ browser.test.assertEq(
+ 1,
+ result.length,
+ "Expected one callback result"
+ );
+ browser.test.assertEq(27, result[0], "Expected callback result");
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "location.href;",
+ allFrames: true,
+ })
+ .then(result => {
+ browser.test.assertTrue(
+ Array.isArray(result),
+ "Result is an array"
+ );
+
+ browser.test.assertEq(
+ 2,
+ result.length,
+ "Result has correct length"
+ );
+
+ browser.test.assertTrue(
+ /\/file_iframe_document\.html$/.test(result[0]),
+ "First result is correct"
+ );
+ browser.test.assertEq(
+ "http://mochi.test:8888/",
+ result[1],
+ "Second result is correct"
+ );
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "location.href;",
+ allFrames: true,
+ matchAboutBlank: true,
+ })
+ .then(result => {
+ browser.test.assertTrue(
+ Array.isArray(result),
+ "Result is an array"
+ );
+
+ browser.test.assertEq(
+ 3,
+ result.length,
+ "Result has correct length"
+ );
+
+ browser.test.assertTrue(
+ /\/file_iframe_document\.html$/.test(result[0]),
+ "First result is correct"
+ );
+ browser.test.assertEq(
+ "http://mochi.test:8888/",
+ result[1],
+ "Second result is correct"
+ );
+ browser.test.assertEq(
+ "about:blank",
+ result[2],
+ "Thirds result is correct"
+ );
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "location.href;",
+ runAt: "document_end",
+ })
+ .then(result => {
+ browser.test.assertEq(1, result.length, "Expected callback result");
+ browser.test.assertEq(
+ "string",
+ typeof result[0],
+ "Result is a string"
+ );
+
+ browser.test.assertTrue(
+ /\/file_iframe_document\.html$/.test(result[0]),
+ "Result is correct"
+ );
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "window",
+ })
+ .then(
+ result => {
+ browser.test.fail(
+ "Expected error when returning non-structured-clonable object"
+ );
+ },
+ error => {
+ browser.test.assertEq(
+ "<anonymous code>",
+ error.fileName,
+ "Got expected fileName"
+ );
+ browser.test.assertEq(
+ "Script '<anonymous code>' result is non-structured-clonable data",
+ error.message,
+ "Got expected error"
+ );
+ }
+ ),
+
+ browser.tabs
+ .executeScript({
+ code: "Promise.resolve(window)",
+ })
+ .then(
+ result => {
+ browser.test.fail(
+ "Expected error when returning non-structured-clonable object"
+ );
+ },
+ error => {
+ browser.test.assertEq(
+ "<anonymous code>",
+ error.fileName,
+ "Got expected fileName"
+ );
+ browser.test.assertEq(
+ "Script '<anonymous code>' result is non-structured-clonable data",
+ error.message,
+ "Got expected error"
+ );
+ }
+ ),
+
+ browser.tabs
+ .executeScript({
+ file: "script3.js",
+ })
+ .then(
+ result => {
+ browser.test.fail(
+ "Expected error when returning non-structured-clonable object"
+ );
+ },
+ error => {
+ const expected =
+ /Script '.*script3.js' result is non-structured-clonable data/;
+ browser.test.assertTrue(
+ expected.test(error.message),
+ "Got expected error"
+ );
+ browser.test.assertTrue(
+ error.fileName.endsWith("script3.js"),
+ "Got expected fileName"
+ );
+ }
+ ),
+
+ browser.tabs
+ .executeScript({
+ frameId: Number.MAX_SAFE_INTEGER,
+ code: "42",
+ })
+ .then(
+ result => {
+ browser.test.fail(
+ "Expected error when specifying invalid frame ID"
+ );
+ },
+ error => {
+ browser.test.assertEq(
+ `Invalid frame IDs: [${Number.MAX_SAFE_INTEGER}].`,
+ error.message,
+ "Got expected error"
+ );
+ }
+ ),
+
+ browser.tabs
+ .create({ url: "http://example.net/", active: false })
+ .then(async tab => {
+ await browser.tabs
+ .executeScript(tab.id, {
+ code: "42",
+ })
+ .then(
+ result => {
+ browser.test.fail(
+ "Expected error when trying to execute on invalid domain"
+ );
+ },
+ error => {
+ browser.test.assertEq(
+ "Missing host permission for the tab",
+ error.message,
+ "Got expected error"
+ );
+ }
+ );
+
+ await browser.tabs.remove(tab.id);
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "Promise.resolve(42)",
+ })
+ .then(result => {
+ browser.test.assertEq(
+ 42,
+ result[0],
+ "Got expected promise resolution value as result"
+ );
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "location.href;",
+ runAt: "document_end",
+ allFrames: true,
+ })
+ .then(result => {
+ browser.test.assertTrue(
+ Array.isArray(result),
+ "Result is an array"
+ );
+
+ browser.test.assertEq(
+ 2,
+ result.length,
+ "Result has correct length"
+ );
+
+ browser.test.assertTrue(
+ /\/file_iframe_document\.html$/.test(result[0]),
+ "First result is correct"
+ );
+ browser.test.assertEq(
+ "http://mochi.test:8888/",
+ result[1],
+ "Second result is correct"
+ );
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "location.href;",
+ frameId: frames[0].frameId,
+ })
+ .then(result => {
+ browser.test.assertEq(1, result.length, "Expected one result");
+ browser.test.assertTrue(
+ /\/file_iframe_document\.html$/.test(result[0]),
+ `Result for main frame (frameId:0) is correct: ${result[0]}`
+ );
+ }),
+
+ browser.tabs
+ .executeScript({
+ code: "location.href;",
+ frameId: frames[1].frameId,
+ })
+ .then(result => {
+ browser.test.assertEq(1, result.length, "Expected one result");
+ browser.test.assertEq(
+ "http://mochi.test:8888/",
+ result[0],
+ "Result for frameId[1] is correct"
+ );
+ }),
+
+ browser.tabs.create({ url: "http://example.com/" }).then(async tab => {
+ let result = await browser.tabs.executeScript(tab.id, {
+ code: "location.href",
+ });
+
+ browser.test.assertEq(
+ "http://example.com/",
+ result[0],
+ "Script executed correctly in new tab"
+ );
+
+ await browser.tabs.remove(tab.id);
+ }),
+
+ promiseRuntimeOnMessage,
+ ]);
+
+ browser.test.notifyPass("executeScript");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("executeScript");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "http://mochi.test/",
+ "http://example.com/",
+ "webNavigation",
+ ],
+ },
+
+ background,
+
+ files: {
+ "script.js": function () {
+ browser.runtime.sendMessage("script ran");
+ },
+
+ "script2.js": "27",
+
+ "script3.js": "window",
+ },
+ });
+
+ await extension.startup();
+
+ await extension.awaitFinish("executeScript");
+
+ await extension.unload();
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Make sure that we're not holding on to references to closed message
+ // managers.
+ is(
+ countMM(MessageChannel.messageManagers),
+ messageManagersSize,
+ "Message manager count"
+ );
+ is(MessageChannel.pendingResponses.size, 0, "Pending response count");
+});