Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE HTML>
<html>
<head>
<title>Test for content script</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" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
add_task(async function setup() {
SimpleTest.requestCompleteLog();
await SpecialPowers.pushPrefEnv({
set: [
["extensions.manifestV3.enabled", true],
["extensions.originControls.grantByDefault", false],
],
});
});
// Create a test extension with the provided function as the background
// script. The background script will have a few helpful functions
// available.
/* global awaitLoad, gatherFrameSources */
function makeExtension({
background,
useScriptingAPI = false,
manifest_version = 2,
host_permissions,
}) {
// Wait for a webNavigation.onCompleted event where the details for the
// loaded page match the attributes of `filter`.
function awaitLoad(filter) {
return new Promise(resolve => {
const listener = details => {
if (Object.keys(filter).every(key => details[key] === filter[key])) {
browser.webNavigation.onCompleted.removeListener(listener);
resolve();
}
};
browser.webNavigation.onCompleted.addListener(listener);
});
}
// Return a string with a (sorted) list of the source of all frames
// in the given tab into which this extension can inject scripts
// (ie all frames for which it has the activeTab permission).
// Source is the hostname for frames in http sources, or the full
// location href in other documents (eg about: pages)
const gatherFrameSources = useScriptingAPI ?
async function gatherFrameSources(tabid) {
let results = await browser.scripting.executeScript({
target: { tabId: tabid, allFrames: true },
func: () => window.location.hostname || window.location.href,
});
// Adjust `result` so that it looks like the one returned by
// `tabs.executeScript()`.
let result = results.map(res => res.result);
return String(result.sort());
} : async function gatherFrameSources(tabid) {
let result = await browser.tabs.executeScript(tabid, {
allFrames: true,
matchAboutBlank: true,
code: "window.location.hostname || window.location.href;",
});
return String(result.sort());
};
const permissions = ["webNavigation"];
if (useScriptingAPI) {
permissions.push("scripting");
}
// When host_permissions is passed, test "automatic activeTab" for ungranted
// host_permissions in mv3, else test with the normal activeTab permission.
if (!host_permissions) {
permissions.push("activeTab");
}
return ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
permissions,
host_permissions,
},
background: [
`const useScriptingAPI = ${useScriptingAPI};`,
`const manifest_version = ${manifest_version};`,
`${awaitLoad}`,
`${gatherFrameSources}`,
`${ExtensionTestCommon.serializeScript(background)}`,
].join("\n")
});
}
// Helper function to verify that executeScript() fails without the activeTab
// permission (or any specific origin permissions).
const verifyNoActiveTab = async ({ useScriptingAPI, manifest_version, host_permissions }) => {
let extension = makeExtension({
async background() {
let [tab] = await Promise.all([
browser.tabs.create({url: URL}),
awaitLoad({frameId: 0}),
]);
await browser.test.assertRejects(
gatherFrameSources(tab.id),
/^Missing host permission/,
"executeScript should fail without activeTab permission"
);
await browser.tabs.remove(tab.id);
browser.test.notifyPass("no-active-tab");
},
useScriptingAPI,
manifest_version,
host_permissions,
});
await extension.startup();
await extension.awaitFinish("no-active-tab");
await extension.unload();
};
add_task(async function test_no_activeTab_tabs() {
await verifyNoActiveTab({ useScriptingAPI: false });
});
add_task(async function test_no_activeTab_scripting() {
await verifyNoActiveTab({ useScriptingAPI: true });
});
add_task(async function test_no_activeTab_scripting_mv3() {
await verifyNoActiveTab({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: null,
});
});
add_task(async function test_no_activeTab_scripting_mv3_autoActiveTab() {
await verifyNoActiveTab({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: ["http://mochi.test/"],
});
});
// Test helper to verify that dynamically created iframes do not get the
// activeTab permission, unless same-origin with the top page.
const verifyDynamicFrames = async ({ useScriptingAPI, manifest_version, host_permissions }) => {
let extension = makeExtension({
async background() {
const BASE_HOST = "www.example.com";
let [tab] = await Promise.all([
browser.tabs.create({url: `https://${BASE_HOST}/`}),
awaitLoad({frameId: 0}),
]);
function inject() {
let nframes = 4;
function frameLoaded() {
nframes--;
if (nframes == 0) {
browser.runtime.sendMessage("frames-loaded");
}
}
// about:blank
let frame = document.createElement("iframe");
frame.addEventListener("load", frameLoaded, {once: true});
document.body.appendChild(frame);
let div = document.createElement("div");
div.innerHTML = "<iframe src='https://test1.example.com/'></iframe>";
let framelist = div.getElementsByTagName("iframe");
browser.test.assertEq(1, framelist.length, "Found 1 frame inside div");
framelist[0].addEventListener("load", frameLoaded, {once: true});
document.body.appendChild(div);
// about:srcdoc containing cross-origin frame.
let div2 = document.createElement("div");
div2.innerHTML = "<iframe srcdoc=\"<iframe src='https://test2.example.com/'&gt;</iframe&gt;\"></iframe>";
framelist = div2.getElementsByTagName("iframe");
browser.test.assertEq(1, framelist.length, "Found 1 frame inside div");
framelist[0].addEventListener("load", frameLoaded, {once: true});
document.body.appendChild(div2);
// Note: URL's host is BASE_HOST (same as top).
let xhr = new XMLHttpRequest();
xhr.open("GET", URL);
xhr.responseType = "document";
xhr.overrideMimeType("text/html");
xhr.addEventListener("load", () => {
if (xhr.readyState != 4) {
return;
}
if (xhr.status != 200) {
browser.runtime.sendMessage("error");
}
let frame = xhr.response.getElementById("frame");
browser.test.assertEq(
frame?.src,
"Found frame in response document with cross-origin URL"
);
frame.addEventListener("load", frameLoaded, {once: true});
document.body.appendChild(frame);
}, {once: true});
xhr.addEventListener("error", () => {
browser.runtime.sendMessage("error");
}, {once: true});
xhr.send();
}
browser.test.onMessage.addListener(async msg => {
if (msg !== "go") {
browser.test.fail(`unexpected message received: ${msg}`);
return;
}
let loadedPromise = new Promise((resolve, reject) => {
let listener = msg => {
let unlisten = () => browser.runtime.onMessage.removeListener(listener);
if (msg == "frames-loaded") {
unlisten();
resolve();
} else if (msg == "error") {
unlisten();
reject();
}
};
browser.runtime.onMessage.addListener(listener);
});
if (useScriptingAPI) {
await browser.scripting.executeScript({
target: { tabId: tab.id },
func: inject,
});
} else {
await browser.tabs.executeScript(tab.id, {
code: `(${inject})();`,
});
}
await loadedPromise;
let result = await gatherFrameSources(tab.id);
browser.test.assertEq(
String(["about:blank", "about:srcdoc", BASE_HOST]),
result,
`Script injected only into (same origin) about:blank-ish dynamically created frames`
);
await browser.tabs.remove(tab.id);
browser.test.notifyPass("dynamic-frames");
});
browser.test.sendMessage("ready", tab.id);
},
useScriptingAPI,
manifest_version,
host_permissions,
});
await extension.startup();
let tabId = await extension.awaitMessage("ready");
extension.grantActiveTab(tabId);
extension.sendMessage("go");
await extension.awaitFinish("dynamic-frames");
await extension.unload();
};
add_task(async function test_dynamic_frames_tabs() {
await verifyDynamicFrames({ useScriptingAPI: false });
});
add_task(async function test_dynamic_frames_scripting() {
await verifyDynamicFrames({ useScriptingAPI: true });
});
add_task(async function test_dynamic_frames_scripting_mv3() {
await verifyDynamicFrames({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: null,
});
});
add_task(async function test_dynamic_frames_scripting_mv3_autoActiveTab() {
await verifyDynamicFrames({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: ["https://www.example.com/"],
});
});
// Test helper to verify that an iframe created from an <iframe srcdoc> gets
// the activeTab permission.
const verifySrcdoc = async ({ useScriptingAPI, manifest_version, host_permissions }) => {
let extension = makeExtension({
async background() {
const OUTER_SOURCE = "about:srcdoc";
const PAGE_SOURCE = "mochi.test";
const FRAME_SOURCE = "test1.example.com";
let [tab] = await Promise.all([
browser.tabs.create({url: URL}),
awaitLoad({frameId: 0}),
]);
browser.test.onMessage.addListener(async msg => {
if (msg !== "go") {
browser.test.fail(`unexpected message received: ${msg}`);
return;
}
let result = await gatherFrameSources(tab.id);
if (manifest_version < 3) {
browser.test.assertEq(
String([OUTER_SOURCE, PAGE_SOURCE, FRAME_SOURCE]),
result,
"Script is injected into frame created from <iframe srcdoc>"
);
} else {
browser.test.assertEq(
String([OUTER_SOURCE, PAGE_SOURCE]),
result,
"Script is not injected into cross-origin frame created from <iframe srcdoc>"
);
}
await browser.tabs.remove(tab.id);
browser.test.notifyPass("srcdoc");
});
browser.test.sendMessage("ready", tab.id);
},
useScriptingAPI,
manifest_version,
host_permissions,
});
await extension.startup();
let tabId = await extension.awaitMessage("ready");
extension.grantActiveTab(tabId);
extension.sendMessage("go");
await extension.awaitFinish("srcdoc");
await extension.unload();
};
add_task(async function test_srcdoc_tabs() {
await verifySrcdoc({ useScriptingAPI: false });
});
add_task(async function test_srcdoc_scripting() {
await verifySrcdoc({ useScriptingAPI: true });
});
add_task(async function test_srcdoc_scripting_mv3() {
await verifySrcdoc({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: null,
});
});
add_task(async function test_srcdoc_scripting_mv3_autoActiveTab() {
await verifySrcdoc({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: ["http://mochi.test/"],
});
});
// Test helper to verify that navigating frames by setting the src attribute
// from the parent page does not grant the activeTab permission to the frame,
// unless the frame is same-origin to the top page.
const verifyNavigateBySrc = async ({ useScriptingAPI, manifest_version, host_permissions }) => {
let extension = makeExtension({
async background() {
const PAGE_SOURCE = "mochi.test";
const EMPTY_SOURCE = "about:blank";
const FRAME_SOURCE = "test1.example.com";
let [tab] = await Promise.all([
browser.tabs.create({url: URL}),
awaitLoad({frameId: 0}),
]);
browser.test.onMessage.addListener(async msg => {
if (msg !== "go") {
browser.test.fail(`unexpected message received: ${msg}`);
return;
}
let result = await gatherFrameSources(tab.id);
if (manifest_version < 3) {
browser.test.assertEq(
String([EMPTY_SOURCE, PAGE_SOURCE, FRAME_SOURCE]),
result,
"In original page, script is injected into base page and original frames"
);
} else {
browser.test.assertEq(
String([EMPTY_SOURCE, PAGE_SOURCE]),
result,
"In original page, script is injected into same-origin frames"
);
}
let loadedPromise = awaitLoad({tabId: tab.id});
let func = () => {
document.getElementById('emptyframe').src = 'http://test2.example.com/';
};
if (useScriptingAPI) {
await browser.scripting.executeScript({
target: { tabId: tab.id },
func,
});
} else {
await browser.tabs.executeScript(tab.id, { code: `(${func})();` });
}
await loadedPromise;
result = await gatherFrameSources(tab.id);
if (manifest_version < 3) {
browser.test.assertEq(
String([PAGE_SOURCE, FRAME_SOURCE]),
result,
"Script is not injected into initially empty frame after cross-origin navigation"
);
} else {
browser.test.assertEq(
String([PAGE_SOURCE]),
result,
"Script is not injected into initially empty frame after cross-origin navigation"
);
}
loadedPromise = awaitLoad({tabId: tab.id});
func = () => {
document.getElementById('regularframe').src = 'http://mochi.test:8888/';
};
if (useScriptingAPI) {
await browser.scripting.executeScript({
target: { tabId: tab.id },
func,
});
} else {
await browser.tabs.executeScript(tab.id, { code: `(${func})();` });
}
await loadedPromise;
result = await gatherFrameSources(tab.id);
browser.test.assertEq(
String([PAGE_SOURCE, PAGE_SOURCE]),
result,
"Script injected into frame after navigating to same-origin"
);
await browser.tabs.remove(tab.id);
browser.test.notifyPass("test-scripts");
});
browser.test.sendMessage("ready", tab.id);
},
useScriptingAPI,
manifest_version,
host_permissions,
});
await extension.startup();
let tabId = await extension.awaitMessage("ready");
extension.grantActiveTab(tabId);
extension.sendMessage("go");
await extension.awaitFinish("test-scripts");
await extension.unload();
};
add_task(async function test_navigate_by_src_tabs() {
await verifyNavigateBySrc({ useScriptingAPI: false });
});
add_task(async function test_navigate_by_src_scripting() {
await verifyNavigateBySrc({ useScriptingAPI: true });
});
add_task(async function test_navigate_by_src_scripting_mv3() {
await verifyNavigateBySrc({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: null,
});
});
add_task(async function test_navigate_by_src_scripting_mv3_autoActiveTab() {
await verifyNavigateBySrc({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: ["http://mochi.test/"],
});
});
// Test helper to verify that navigating frames by setting window.location from
// inside the frame revokes the activeTab permission.
const verifyNavigateByWindowLocation = async ({ useScriptingAPI, manifest_version, host_permissions }) => {
let extension = makeExtension({
async background() {
const PAGE_SOURCE = "mochi.test";
const EMPTY_SOURCE = "about:blank";
const FRAME_SOURCE = "test1.example.com";
let [tab] = await Promise.all([
browser.tabs.create({url: URL}),
awaitLoad({frameId: 0}),
]);
browser.test.onMessage.addListener(async msg => {
if (msg !== "go") {
browser.test.fail(`unexpected message received: ${msg}`);
return;
}
let result = await gatherFrameSources(tab.id);
if (manifest_version < 3) {
browser.test.assertEq(
String([EMPTY_SOURCE, PAGE_SOURCE, FRAME_SOURCE]),
result,
"Script initially injected into all frames"
);
} else {
browser.test.assertEq(
String([EMPTY_SOURCE, PAGE_SOURCE]),
result,
"Script initially injected into all same-origin frames"
);
}
let nframes = 0;
let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
for (let frame of frames) {
if (frame.parentFrameId == -1) {
continue;
}
if (manifest_version >= 3 && frame.url.includes(FRAME_SOURCE)) {
// In MV3, can't access cross-origin iframes from the start.
let invalidPromise = browser.scripting.executeScript({
target: { tabId: tab.id, frameIds: [frame.frameId] },
func: () => window.location.hostname,
});
await browser.test.assertRejects(
invalidPromise,
/^Missing host permission for the tab or frames/,
"executeScript should fail on cross-origin frame"
);
continue;
}
let loadPromise = awaitLoad({
tabId: tab.id,
frameId: frame.frameId,
});
let func = () => {
window.location.href = 'https://test2.example.com/';
};
if (useScriptingAPI) {
await browser.scripting.executeScript({
target: { tabId: tab.id, frameIds: [frame.frameId] },
func,
});
} else {
await browser.tabs.executeScript(tab.id, {
frameId: frame.frameId,
matchAboutBlank: true,
code: `(${func})();`,
});
}
await loadPromise;
let executePromise;
func = () => window.location.hostname;
if (useScriptingAPI) {
executePromise = browser.scripting.executeScript({
target: { tabId: tab.id, frameIds: [frame.frameId] },
func,
});
} else {
executePromise = browser.tabs.executeScript(tab.id, {
frameId: frame.frameId,
matchAboutBlank: true,
code: `(${func})();`,
});
}
await browser.test.assertRejects(
executePromise,
/^Missing host permission for the tab or frames/,
"executeScript should have failed on navigated frame"
);
nframes++;
}
if (manifest_version < 3) {
browser.test.assertEq(2, nframes, "Found 2 frames");
} else {
browser.test.assertEq(1, nframes, "Found 1 frame");
}
await browser.tabs.remove(tab.id);
browser.test.notifyPass("scripted-navigation");
});
browser.test.sendMessage("ready", tab.id);
},
useScriptingAPI,
manifest_version,
host_permissions,
});
await extension.startup();
let tabId = await extension.awaitMessage("ready");
extension.grantActiveTab(tabId);
extension.sendMessage("go");
await extension.awaitFinish("scripted-navigation");
await extension.unload();
};
add_task(async function test_navigate_by_window_location_tabs() {
await verifyNavigateByWindowLocation({ useScriptingAPI: false });
});
add_task(async function test_navigate_by_window_location_scripting() {
await verifyNavigateByWindowLocation({ useScriptingAPI: true });
});
add_task(async function test_navigate_by_window_location_scripting_mv3() {
await verifyNavigateByWindowLocation({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: null,
});
});
add_task(async function test_navigate_by_window_location_scripting_mv3_autoActiveTab() {
await verifyNavigateByWindowLocation({
useScriptingAPI: true,
manifest_version: 3,
host_permissions: ["http://mochi.test/"],
});
});
</script>
</body>
</html>