diff options
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.html | 1479 |
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> |