Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

<!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>
<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,
// Used in `file_contains_iframe.html`
],
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,
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,
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,
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,
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,
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,
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,
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 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,
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,
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>