summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/mochitest/test_ext_scripting_executeScript.html
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/mochitest/test_ext_scripting_executeScript.html')
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_scripting_executeScript.html1479
1 files changed, 1479 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_scripting_executeScript.html b/toolkit/components/extensions/test/mochitest/test_ext_scripting_executeScript.html
new file mode 100644
index 0000000000..a2d741606f
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_scripting_executeScript.html
@@ -0,0 +1,1479 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests scripting.executeScript()</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<iframe src="https://example.com/tests/toolkit/components/extensions/test/mochitest/file_sample.html"></iframe>
+
+<script type="text/javascript">
+
+"use strict";
+
+const MOCHITEST_HOST_PERMISSIONS = [
+ "*://mochi.test/",
+ "*://mochi.xorigin-test/",
+ "*://test1.example.com/",
+];
+
+const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
+ return ExtensionTestUtils.loadExtension({
+ manifest: {
+ manifest_version: 3,
+ permissions: ["scripting"],
+ host_permissions: [
+ ...MOCHITEST_HOST_PERMISSIONS,
+ "https://example.com/",
+ // Used in `file_contains_iframe.html`
+ "https://example.org/",
+ ],
+ granted_host_permissions: true,
+ ...manifestProps,
+ },
+ useAddonManager: "temporary",
+ ...otherProps,
+ });
+};
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.manifestV3.enabled", true]],
+ });
+});
+
+add_task(async function test_executeScript_params_validation() {
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ const tabId = tabs[0].id;
+
+ const TEST_CASES = [
+ {
+ title: "no files and no func",
+ executeScriptParams: {},
+ expectedError: /Exactly one of files and func must be specified/,
+ },
+ {
+ title: "both files and func are passed",
+ executeScriptParams: { files: ["script.js"], func() {} },
+ expectedError: /Exactly one of files and func must be specified/,
+ },
+ {
+ title: "non-empty args is passed with files",
+ executeScriptParams: { files: ["script.js"], args: [123] },
+ expectedError: /'args' may not be used with file injections/,
+ },
+ {
+ title: "empty args is passed with files",
+ executeScriptParams: { files: ["script.js"], args: [] },
+ expectedError: /'args' may not be used with file injections/,
+ },
+ {
+ title: "unserializable argument",
+ executeScriptParams: { func() {}, args: [window] },
+ expectedError: /Unserializable arguments/,
+ },
+ {
+ title: "both allFrames and frameIds are passed",
+ executeScriptParams: {
+ target: {
+ tabId,
+ allFrames: true,
+ frameIds: [1, 2, 3],
+ },
+ files: ["script.js"],
+ },
+ expectedError: /Cannot specify both 'allFrames' and 'frameIds'/,
+ },
+ {
+ title: "invalid IDs in frameIds",
+ executeScriptParams: {
+ target: { tabId, frameIds: [0, 1, 2] },
+ func: () => {},
+ },
+ expectedError: "Invalid frame IDs: [1, 2].",
+ },
+ {
+ title: "throw non-structurally cloneable data in all frames",
+ executeScriptParams: {
+ target: {
+ tabId,
+ allFrames: true,
+ },
+ func: () => {
+ throw window;
+ },
+ },
+ expectedError: /Script '<anonymous code>' result is non-structured-clonable data/,
+ },
+ ];
+
+ for (const { title, executeScriptParams, expectedError } of TEST_CASES) {
+ await browser.test.assertRejects(
+ browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ ...executeScriptParams,
+ }),
+ expectedError,
+ `expected error when: ${title}`
+ );
+ }
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_main_world() {
+ let extension = makeExtension({
+ async background() {
+ browser.test.assertThrows(
+ () => {
+ browser.scripting.executeScript({
+ target: { tabId: 123 },
+ func: () => {},
+ world: "MAIN",
+ });
+ },
+ /world: Invalid enumeration value "MAIN"/,
+ "expected 'MAIN' world to not be supported yet"
+ );
+
+ browser.test.notifyPass("background-done");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_isolated_world() {
+ let extension = makeExtension({
+ manifest: {
+ browser_specific_settings: {
+ gecko: { id: "@isolated-addon-id" },
+ },
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ let results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: () => {
+ globalThis.defaultWorldVar = browser.runtime.id;
+ return "default world";
+ },
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ "default world",
+ results[0].result,
+ "got expected return value"
+ );
+
+ results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: () => {
+ return `isolated: ${browser.runtime.id}; existing default var: ${typeof defaultWorldVar}`;
+ },
+ world: "ISOLATED",
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ "isolated: @isolated-addon-id; existing default var: string",
+ results[0].result,
+ "got expected return value"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_sample.html",
+ true
+ );
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_execution_world_constants() {
+ let extension = makeExtension({
+ async background() {
+ browser.test.assertTrue(
+ !!browser.scripting.ExecutionWorld,
+ "expected scripting.ExecutionWorld to be defined"
+ );
+ browser.test.assertEq(
+ 1,
+ Object.keys(browser.scripting.ExecutionWorld).length,
+ "expected 1 ExecutionWorld constant"
+ );
+ browser.test.assertEq(
+ "ISOLATED",
+ browser.scripting.ExecutionWorld.ISOLATED,
+ "expected ISOLATED constant to be defined"
+ );
+ // TODO: Bug 1736575 - Add support for other execution worlds like MAIN.
+ browser.test.assertEq(
+ undefined,
+ browser.scripting.ExecutionWorld.MAIN,
+ "expected MAIN constant to be undefined"
+ );
+
+ browser.test.notifyPass("background-done");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_with_wrong_host_permissions() {
+ let extension = makeExtension({
+ manifest: {
+ host_permissions: [],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ await browser.test.assertRejects(
+ browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: () => {
+ browser.test.fail("Unexpected execution");
+ },
+ }),
+ "Missing host permission for the tab",
+ "expected host permission error"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_with_invalid_tabId() {
+ let extension = makeExtension({
+ async background() {
+ // This tab ID should not exist.
+ const tabId = 123456789;
+
+ await browser.test.assertRejects(
+ browser.scripting.executeScript({
+ target: { tabId },
+ func: () => {
+ browser.test.fail("Unexpected execution");
+ },
+ }),
+ `Invalid tab ID: ${tabId}`
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_with_func() {
+ let extension = makeExtension({
+ async background() {
+ const getTitle = () => {
+ return document.title;
+ };
+
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: getTitle,
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ "file sample",
+ results[0].result,
+ "got the expected title"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_sample.html",
+ true
+ );
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_executeScript_with_func_and_args() {
+ let extension = makeExtension({
+ async background() {
+ const formatArgs = (a, b, c) => {
+ return `received ${a}, ${b} and ${c}`;
+ };
+
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: formatArgs,
+ args: [true, undefined, "str"],
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ // undefined is converted to null when json-stringified in an array.
+ "received true, null and str",
+ results[0].result,
+ "got the expected return value"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_returns_nothing() {
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: () => {},
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ undefined,
+ results[0].result,
+ "got expected undefined result"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_returns_null() {
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: () => {
+ return null;
+ },
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ null,
+ results[0].result,
+ "got expected null result"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_with_error_in_func() {
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ func: () => {
+ throw new Error(`Thrown at ${location.pathname.split("/").pop()}`);
+ },
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+ browser.test.assertEq(
+ "Thrown at file_sample.html",
+ results[0].error.message,
+ "got the expected error message"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_sample.html",
+ true
+ );
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_executeScript_with_a_file() {
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ files: ["script.js"],
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ "value from script.js",
+ results[0].result,
+ "got the expected result"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+
+ browser.test.notifyPass("execute-script");
+ },
+ files: {
+ "script.js": function() {
+ return "value from script.js";
+ },
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_in_one_frame() {
+ let extension = makeExtension({
+ manifest: {
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame with the MochiTest runner
+ // 2. Frame for this file
+ // 3. Frame that loads `file_sample.html` at the top of this file
+ browser.test.assertEq(3, frames.length, "expected 3 frames");
+
+ const fileSampleFrameId = frames[2].frameId;
+ browser.test.assertTrue(
+ frames[2].url.includes("file_sample.html"),
+ "expected frame URL"
+ );
+
+ const TEST_CASES = [
+ {
+ title: "with a file and a frame ID",
+ params: {
+ target: { tabId, frameIds: [fileSampleFrameId] },
+ files: ["script.js"],
+ },
+ expectedResults: [
+ {
+ frameId: fileSampleFrameId,
+ result: "Sample text",
+ },
+ ],
+ },
+ {
+ title: "with no frame ID",
+ params: {
+ target: { tabId },
+ func: () => {
+ return 123;
+ },
+ },
+ expectedResults: [{ frameId: 0, result: 123 }],
+ },
+ ];
+
+ for (const { title, params, expectedResults } of TEST_CASES) {
+ const results = await browser.scripting.executeScript(params);
+
+ browser.test.assertEq(
+ expectedResults.length,
+ results.length,
+ `${title} - got expected number of results`
+ );
+ expectedResults.forEach(({ frameId, result }, index) => {
+ browser.test.assertEq(
+ result,
+ results[index].result,
+ `${title} - got the expected results[${index}].result`
+ );
+ browser.test.assertEq(
+ frameId,
+ results[index].frameId,
+ `${title} - got the expected results[${index}].frameId`
+ );
+ });
+ }
+
+ browser.test.notifyPass("execute-script");
+ },
+ files: {
+ "script.js": function() {
+ return document.getElementById("test").textContent;
+ },
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_in_multiple_frameIds() {
+ let extension = makeExtension({
+ manifest: {
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame that loads `file_contains_iframe.html`
+ // 2. Frame that loads `file_contains_img.html`
+ browser.test.assertEq(2, frames.length, "expected 2 frames");
+
+ const frameIds = frames.map(frame => frame.frameId);
+
+ const getTitle = () => {
+ return document.title;
+ };
+
+ const TEST_CASES = [
+ {
+ title: "multiple frame IDs",
+ params: {
+ target: { tabId, frameIds },
+ func: getTitle,
+ },
+ expectedResults: [
+ {
+ frameId: frameIds[0],
+ result: "file contains iframe",
+ },
+ {
+ frameId: frameIds[1],
+ result: "file contains img",
+ },
+ ],
+ },
+ {
+ title: "empty list of frame IDs",
+ params: {
+ target: { tabId, frameIds: [] },
+ func: getTitle,
+ },
+ expectedResults: [],
+ },
+ ];
+
+ for (const { title, params, expectedResults } of TEST_CASES) {
+ const results = await browser.scripting.executeScript(params);
+
+ browser.test.assertEq(
+ expectedResults.length,
+ results.length,
+ `${title} - got expected number of results`
+ );
+ // Sort injection results by frameId to always assert the results in
+ // the same order.
+ results.sort((a, b) => a.frameId - b.frameId);
+
+ browser.test.assertEq(
+ JSON.stringify(expectedResults),
+ JSON.stringify(results),
+ `${title} - got expected results`
+ );
+ }
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_executeScript_with_errors_in_multiple_frameIds() {
+ let extension = makeExtension({
+ manifest: {
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame that loads `file_contains_iframe.html`
+ // 2. Frame that loads `file_contains_img.html`
+ browser.test.assertEq(2, frames.length, "expected 2 frames");
+
+ const frameIds = frames.map(frame => frame.frameId);
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId, frameIds },
+ func: () => {
+ throw new Error(`Thrown at ${location.pathname.split("/").pop()}`);
+ },
+ });
+
+ browser.test.assertEq(
+ 2,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ "Thrown at file_contains_iframe.html",
+ results[0].error.message,
+ "got expected error message in results[0]"
+ );
+ browser.test.assertEq(
+ "Thrown at file_contains_img.html",
+ results[1].error.message,
+ "got expected error message in results[1]"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_executeScript_with_frameId_and_wrong_host_permission() {
+ let extension = makeExtension({
+ manifest: {
+ host_permissions: MOCHITEST_HOST_PERMISSIONS,
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame with the MochiTest runner
+ // 2. Frame for this file
+ // 3. Frame that loads `file_sample.html` at the top of this file
+ browser.test.assertEq(3, frames.length, "expected 3 frames");
+
+ const frameIds = frames.map(frame => frame.frameId);
+
+ await browser.test.assertRejects(
+ browser.scripting.executeScript({
+ target: { tabId, frameIds: [frameIds[2]] },
+ func: () => {
+ browser.test.fail("Unexpected execution");
+ },
+ }),
+ "Missing host permission for the tab or frames",
+ "got the expected error message"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_with_multiple_frameIds_and_wrong_host_permissions() {
+ let extension = makeExtension({
+ manifest: {
+ host_permissions: MOCHITEST_HOST_PERMISSIONS,
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame with the MochiTest runner
+ // 2. Frame for this file
+ // 3. Frame that loads `file_sample.html` at the top of this file
+ browser.test.assertEq(3, frames.length, "expected 3 frames");
+
+ const frameIds = frames.map(frame => frame.frameId);
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId, frameIds },
+ func: () => {},
+ });
+
+ // We get 2 results because we cannot inject into the 3rd frame.
+ browser.test.assertEq(
+ 2,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertTrue(
+ typeof results[0].error === "undefined",
+ "expected no error in results[0]"
+ );
+ browser.test.assertTrue(
+ typeof results[1].error === "undefined",
+ "expected no error in results[1]"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_with_iframe_srcdoc_and_aboutblank() {
+ let iframe = document.createElement("iframe");
+ iframe.srcdoc = `<!DOCTYPE html>
+ <html>
+ <head><title>iframe with srcdoc</title></head>
+ </html>`;
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ });
+
+ let iframeAboutBlank = document.createElement("iframe");
+ iframeAboutBlank.src = "about:blank";
+ await new Promise(resolve => {
+ iframeAboutBlank.onload = resolve;
+ document.body.appendChild(iframeAboutBlank);
+ });
+
+ let extension = makeExtension({
+ manifest: {
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame with the MochiTest runner
+ // 2. Frame for this file
+ // 3. Frame that loads `file_sample.html` at the top of this file
+ // 4. Frame that loads the `srcdoc`
+ // 5. Frame for `about:blank`
+ browser.test.assertEq(5, frames.length, "expected 5 frames");
+
+ const frameIds = frames.map(frame => frame.frameId);
+
+ const TEST_CASES = [
+ {
+ title: "with frameIds for all frames",
+ params: {
+ target: { tabId, frameIds },
+ },
+ expectedResults: {
+ count: 5,
+ entriesAtIndex: {
+ 3: {
+ frameId: frameIds[3],
+ result: "iframe with srcdoc",
+ },
+ 4: {
+ frameId: frameIds[4],
+ result: "about:blank",
+ },
+ },
+ },
+ },
+ {
+ title: "with allFrames: true",
+ params: {
+ target: { tabId, allFrames: true },
+ },
+ expectedResults: {
+ count: 5,
+ entriesAtIndex: {
+ 3: {
+ frameId: frameIds[3],
+ result: "iframe with srcdoc",
+ },
+ 4: {
+ frameId: frameIds[4],
+ result: "about:blank",
+ },
+ },
+ },
+ },
+ {
+ title: "with a single frame specified",
+ params: {
+ target: { tabId, frameIds: [frameIds[3]] },
+ },
+ expectedResults: {
+ count: 1,
+ entriesAtIndex: {
+ 0: {
+ frameId: frameIds[3],
+ result: "iframe with srcdoc",
+ },
+ },
+ },
+ },
+ ];
+
+ for (const { title, params, expectedResults } of TEST_CASES) {
+ const results = await browser.scripting.executeScript({
+ ...params,
+ func: () => {
+ return document.title || document.URL;
+ },
+ });
+ // Sort injection results by frameId to always assert the results in
+ // the same order.
+ results.sort((a, b) => a.frameId - b.frameId);
+
+ browser.test.assertEq(
+ expectedResults.count,
+ results.length,
+ `${title} - got the expected number of results`
+ );
+ Object.keys(expectedResults.entriesAtIndex).forEach(index => {
+ browser.test.assertEq(
+ JSON.stringify(expectedResults.entriesAtIndex[index]),
+ JSON.stringify(results[index]),
+ `${title} - got expected results[${index}]`
+ );
+ });
+ }
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ iframe.remove();
+ iframeAboutBlank.remove();
+});
+
+add_task(async function test_executeScript_with_multiple_files() {
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ files: ["1.js", "2.js"],
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(
+ "value from 2.js",
+ results[0].result,
+ "got the expected result"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+
+ browser.test.notifyPass("execute-script");
+ },
+ files: {
+ "1.js": function() {
+ return "value from 1.js";
+ },
+ "2.js": function() {
+ return "value from 2.js";
+ },
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+});
+
+add_task(async function test_executeScript_with_multiple_files_and_an_error() {
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ files: ["1.js", "2.js"],
+ });
+
+ browser.test.assertEq(
+ 1,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
+ browser.test.assertEq(
+ "Thrown at file_contains_iframe.html",
+ results[0].error.message,
+ "got the expected error message"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ files: {
+ "1.js": function() {
+ throw new Error(`Thrown at ${location.pathname.split("/").pop()}`);
+ },
+ "2.js": function() {
+ return "value from 2.js";
+ },
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_executeScript_with_file_not_in_extension() {
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ let extension = makeExtension({
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ await browser.test.assertRejects(
+ browser.scripting.executeScript({
+ target: { tabId: tabs[0].id },
+ files: ["https://example.com/script.js"],
+ }),
+ /Files to be injected must be within the extension/,
+ "got the expected error message"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_executeScript_allFrames() {
+ let extension = makeExtension({
+ manifest: {
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame that loads `file_contains_iframe.html`
+ // 2. Frame that loads `file_contains_img.html`
+ browser.test.assertEq(2, frames.length, "expected 2 frames");
+ const frameIds = frames.map(frame => frame.frameId);
+
+ const getTitle = () => {
+ return document.title;
+ };
+
+ const TEST_CASES = [
+ {
+ title: "allFrames set to true",
+ scriptingParams: {
+ target: { tabId, allFrames: true },
+ func: getTitle,
+ },
+ expectedResults: [
+ {
+ frameId: frameIds[0],
+ result: "file contains iframe",
+ },
+ {
+ frameId: frameIds[1],
+ result: "file contains img",
+ },
+ ],
+ },
+ {
+ title: "allFrames set to false",
+ scriptingParams: {
+ target: { tabId, allFrames: false },
+ func: getTitle,
+ },
+ expectedResults: [
+ {
+ frameId: frameIds[0],
+ result: "file contains iframe",
+ },
+ ],
+ },
+ ];
+
+ for (const { title, scriptingParams, expectedResults } of TEST_CASES) {
+ const results = await browser.scripting.executeScript(scriptingParams);
+ // Sort injection results by frameId to always assert the results in
+ // the same order.
+ results.sort((a, b) => a.frameId - b.frameId);
+
+ browser.test.assertDeepEq(
+ expectedResults,
+ results,
+ `${title} - got expected results`
+ );
+
+ // Make sure the `error` prop is never set.
+ for (const result of results) {
+ browser.test.assertFalse(
+ "error" in result,
+ `${title} - expected error property to be unset`
+ );
+ }
+ }
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_executeScript_runtime_errors() {
+ let extension = makeExtension({
+ manifest: {
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame that loads `file_contains_iframe.html`
+ // 2. Frame that loads `file_contains_img.html`
+ browser.test.assertEq(2, frames.length, "expected 2 frames");
+
+ const TEST_CASES = [
+ {
+ title: "reference error",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ // We do not define `e` on purpose.
+ // eslint-disable-next-line no-undef
+ return String(e);
+ },
+ },
+ expectedErrors: [
+ { type: "Error", stringRepr: "ReferenceError: e is not defined" },
+ ],
+ },
+ {
+ title: "eval error",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ // We use `eval()` on purpose.
+ // eslint-disable-next-line no-eval
+ eval("");
+ },
+ },
+ expectedErrors: [
+ { type: "Error", stringRepr: "EvalError: call to eval() blocked by CSP" },
+ ],
+ },
+ {
+ title: "errors thrown in allFrames",
+ scriptingParams: {
+ target: { tabId, allFrames: true },
+ func: () => {
+ throw new Error(`Thrown at ${location.pathname.split("/").pop()}`);
+ },
+ },
+ expectedErrors: [
+ { type: "Error", stringRepr: "Error: Thrown at file_contains_iframe.html" },
+ { type: "Error", stringRepr: "Error: Thrown at file_contains_img.html" },
+ ],
+ },
+ {
+ title: "custom error",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ class CustomError extends Error {
+ constructor(message) {
+ super(message);
+
+ this.name = 'CustomError';
+ }
+ }
+
+ throw new CustomError("a custom error message");
+ },
+ },
+ // See Bug 1556604 for why a custom (derived) error looks like a
+ // normal error object after cloning.
+ expectedErrors: [
+ { type: "Error", stringRepr: "Error: a custom error message" },
+ ],
+ },
+ {
+ title: "promise rejection with a string value",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ // eslint-disable-next-line no-throw-literal
+ throw 'an error message';
+ },
+ },
+ expectedErrors: [
+ { type: "String", stringRepr: "an error message" },
+ ],
+ },
+ {
+ title: "promise rejection with an error",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ throw new Error('ooops');
+ },
+ },
+ expectedErrors: [
+ { type: "Error", stringRepr: "Error: ooops" },
+ ],
+ },
+ {
+ title: "promise rejection with null",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ throw null; // eslint-disable-line no-throw-literal
+ },
+ },
+ expectedErrors: [
+ // This means we would receive `error: null`.
+ { type: "Null", stringRepr: "null" },
+ ],
+ },
+ {
+ title: "promise rejection with undefined",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ return new Promise((resolve, reject) => {
+ reject(undefined);
+ });
+ },
+ },
+ expectedErrors: [
+ // This means we would receive `error: undefined`.
+ { type: "Undefined", stringRepr: "undefined" },
+ ],
+ },
+ {
+ title: "promise rejection with empty string",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ throw ""; // eslint-disable-line no-throw-literal
+ },
+ },
+ expectedErrors: [
+ { type: "String", stringRepr: "" },
+ ],
+ },
+ {
+ title: "promise rejection with zero",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ throw 0; // eslint-disable-line no-throw-literal
+ },
+ },
+ expectedErrors: [
+ { type: "Number", stringRepr: "0" },
+ ],
+ },
+ {
+ title: "promise rejection with false",
+ scriptingParams: {
+ target: { tabId },
+ func: () => {
+ throw false; // eslint-disable-line no-throw-literal
+ },
+ },
+ expectedErrors: [
+ { type: "Boolean", stringRepr: "false" },
+ ],
+ },
+ ];
+
+ for (const { title, scriptingParams, expectedErrors } of TEST_CASES) {
+ const results = await browser.scripting.executeScript(scriptingParams);
+ // Sort injection results by frameId to always assert the results in
+ // the same order.
+ results.sort((a, b) => a.frameId - b.frameId);
+
+ browser.test.assertEq(
+ expectedErrors.length,
+ results.length,
+ `expected ${expectedErrors.length} results`
+ );
+
+ for (const [i, { type, stringRepr }] of expectedErrors.entries()) {
+ browser.test.assertTrue(
+ "error" in results[i],
+ `${title} - expected error property to be set`
+ );
+ browser.test.assertFalse(
+ "result" in results[i],
+ `${title} - expected result property to be unset`
+ );
+
+ const { frameId, error } = results[i];
+
+ browser.test.assertEq(
+ `[object ${type}]`,
+ Object.prototype.toString.call(error),
+ `${title} - expected instance of ${type} - ${frameId}`
+ );
+ browser.test.assertEq(
+ stringRepr,
+ String(error),
+ `${title} - got expected errors - ${frameId}`
+ );
+ }
+ }
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(
+ async function test_executeScript_with_allFrames_and_wrong_host_permissions() {
+ let extension = makeExtension({
+ manifest: {
+ host_permissions: MOCHITEST_HOST_PERMISSIONS,
+ permissions: ["scripting", "webNavigation"],
+ },
+ async background() {
+ const tabs = await browser.tabs.query({ active: true });
+ browser.test.assertEq(1, tabs.length, "expected 1 tab");
+
+ const tabId = tabs[0].id;
+ const frames = await browser.webNavigation.getAllFrames({ tabId });
+ // 1. Top-level frame with the MochiTest runner
+ // 2. Frame for this file
+ // 3. Frame that loads `file_sample.html` at the top of this file
+ browser.test.assertEq(3, frames.length, "expected 3 frames");
+
+ const results = await browser.scripting.executeScript({
+ target: { tabId, allFrames: true },
+ func: () => {},
+ });
+
+ browser.test.assertEq(
+ 2,
+ results.length,
+ "got expected number of results"
+ );
+ browser.test.assertTrue(
+ typeof results[0].error === "undefined",
+ "expected no error in results[0]"
+ );
+ browser.test.assertTrue(
+ typeof results[1].error === "undefined",
+ "expected no error in results[1]"
+ );
+
+ browser.test.notifyPass("execute-script");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("execute-script");
+ await extension.unload();
+ }
+);
+
+</script>
+
+</body>
+</html>