Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Tests scripting.executeScript() with world option</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 test_executeScript_main_world_files() {
let extension = makeExtension({
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 },
files: ["main.js"],
world: "MAIN",
});
browser.test.assertEq(undefined, results[0].error?.message, "No error");
browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
browser.test.assertDeepEq(
{
varInPageReadByExt: "varInPage",
thisIsGlobalThis: true,
thisIsWindow: true,
browserIsUndefined: true,
evalBlockedMessage: "call to eval() blocked by CSP",
},
results[0].result,
"executeScript with world:MAIN should run in the page's context"
);
browser.test.notifyPass("background-done");
},
files: {
"main.js": () => {
let evalBlockedMessage = "unexpectedly not blocked???";
try {
// eslint-disable-next-line no-eval
eval("// CSP of file_simple_inline_script.html should block eval.");
} catch (e) {
evalBlockedMessage = e.message;
}
return {
// varInPage is defined in file_simple_inline_script.html
varInPageReadByExt: window.varInPage,
thisIsGlobalThis: this === globalThis,
thisIsWindow: this === window,
browserIsUndefined: typeof browser == "undefined",
evalBlockedMessage,
};
},
},
});
let tab = await AppTestDelegate.openNewForegroundTab(
window,
true
);
await extension.startup();
await extension.awaitFinish("background-done");
await extension.unload();
await AppTestDelegate.removeTab(window, tab);
});
add_task(async function test_executeScript_main_world_func() {
let extension = makeExtension({
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: () => {
let evalBlockedMessage = "unexpectedly not blocked???";
try {
// eslint-disable-next-line no-eval
eval("// CSP of file_simple_inline_script.html should block eval.");
} catch (e) {
evalBlockedMessage = e.message;
}
return {
// varInPage is defined in file_simple_inline_script.html
varInPageReadByExt: window.varInPage,
thisIsGlobalThis: this === globalThis,
thisIsWindow: this === window,
browserIsUndefined: typeof browser == "undefined",
evalBlockedMessage,
};
},
world: "MAIN",
});
browser.test.assertEq(undefined, results[0].error?.message, "No error");
browser.test.assertEq(0, results[0].frameId, "got the expected frameId");
browser.test.assertDeepEq(
{
varInPageReadByExt: "varInPage",
thisIsGlobalThis: true,
thisIsWindow: true,
browserIsUndefined: true,
evalBlockedMessage: "call to eval() blocked by CSP",
},
results[0].result,
"executeScript with world:MAIN should run in the page's context"
);
browser.test.notifyPass("background-done");
},
});
let tab = await AppTestDelegate.openNewForegroundTab(
window,
true
);
await extension.startup();
await extension.awaitFinish("background-done");
await extension.unload();
await AppTestDelegate.removeTab(window, tab);
});
add_task(async function test_executeScript_anonymous_filename() {
let extension = makeExtension({
async background() {
const BACKGROUND_SCRIPT_URL =
browser.runtime.getManifest().background.scripts[0];
const tabs = await browser.tabs.query({ active: true });
browser.test.assertEq(1, tabs.length, "expected 1 tab");
function funcExtractFilename() {
let error = new Error();
return {
fileName: error.fileName,
// We only care about file names, so strip line and column numbers.
stack: error.stack.replaceAll(/:\d+:\d+/g, ""),
};
}
// Verify that the script source cannot be exfiltrated by the MAIN world
// script. If it were to be possible, then a script on the web page can
// do the same, which is undesirable because of fingerprinting and script
// source secrecy.
let results = await browser.scripting.executeScript({
target: { tabId: tabs[0].id },
func: funcExtractFilename,
world: "MAIN",
});
browser.test.assertEq(undefined, results[0].error?.message, "No error");
browser.test.assertDeepEq(
{
fileName: "<anonymous code>",
stack: "funcExtractFilename@<anonymous code>\n@<anonymous code>\n",
},
results[0].result,
"executeScript with world:MAIN & code should not leak the extension URL"
);
results = await browser.scripting.executeScript({
target: { tabId: tabs[0].id },
files: ["file_leak_me.js"],
world: "MAIN",
});
browser.test.assertEq(undefined, results[0].error?.message, "No error");
browser.test.assertDeepEq(
{
fileName: "<anonymous code>",
stack: "fileExtractFilename@<anonymous code>\n@<anonymous code>\n",
},
results[0].result,
"executeScript with world:MAIN & file should not leak the extension URL"
);
// Same tests, but with world: "ISOLATED". Since the world is isolated,
// it does not really matter what the values are, but just to verify that
// the value is sane, check its value.
results = await browser.scripting.executeScript({
target: { tabId: tabs[0].id },
func: funcExtractFilename,
world: "ISOLATED",
});
browser.test.assertEq(undefined, results[0].error?.message, "No error");
browser.test.assertDeepEq(
{
fileName: "sandbox eval code",
stack: `funcExtractFilename@sandbox eval code
@sandbox eval code
Async*background@${BACKGROUND_SCRIPT_URL}
async*@${BACKGROUND_SCRIPT_URL}
`
},
results[0].result,
"executeScript with world:ISOLATED & code may expose extension URLs"
);
results = await browser.scripting.executeScript({
target: { tabId: tabs[0].id },
files: ["file_leak_me.js"],
world: "ISOLATED",
});
browser.test.assertEq(undefined, results[0].error?.message, "No error");
browser.test.assertDeepEq(
{
fileName: location.origin + "/file_leak_me.js",
stack: `fileExtractFilename@${location.origin}/file_leak_me.js
@${location.origin}/file_leak_me.js
Async*background@${BACKGROUND_SCRIPT_URL}
async*@${BACKGROUND_SCRIPT_URL}
`,
},
results[0].result,
"executeScript with world:ISOLATED & file may expose extension URLs"
);
browser.test.notifyPass("background-done");
},
files: {
"file_leak_me.js": function fileExtractFilename() {
let error = new Error();
return {
fileName: error.fileName,
// We only care about file names, so strip line and column numbers.
stack: error.stack.replaceAll(/:\d+:\d+/g, ""),
};
},
},
});
let tab = await AppTestDelegate.openNewForegroundTab(
window,
true
);
await extension.startup();
await extension.awaitFinish("background-done");
await extension.unload();
await AppTestDelegate.removeTab(window, tab);
});
add_task(async function test_executeScript_invalid_world() {
let extension = makeExtension({
async background() {
browser.test.assertThrows(
() => {
browser.scripting.executeScript({
target: { tabId: 123 },
func: () => {},
world: "USER_SCRIPT",
});
},
/world: Invalid enumeration value "USER_SCRIPT"/,
"executeScript should throw when an invalid world is passed"
);
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.assertDeepEq(
{
ISOLATED: "ISOLATED",
MAIN: "MAIN",
},
browser.scripting.ExecutionWorld,
"expected scripting.ExecutionWorld to be defined"
);
browser.test.notifyPass("background-done");
},
});
await extension.startup();
await extension.awaitFinish("background-done");
await extension.unload();
});
</script>
</body>
</html>