Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE HTML>
<html>
<head>
<title>WebExtension activityLog test</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script 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 test_api() {
let URL =
// Test that an unspecified extension is not logged by the watcher extension.
let unlogged = ExtensionTestUtils.loadExtension({
isPrivileged: true,
manifest: {
browser_specific_settings: { gecko: { id: "unlogged@tests.mozilla.org" } },
permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
},
background() {
// This privileged test extension should not affect the webRequest
// data received by non-privileged extensions (See Bug 1576272).
browser.webRequest.onBeforeRequest.addListener(
() => {
return { cancel: false };
},
["blocking"]
);
},
});
await unlogged.startup();
let extension = ExtensionTestUtils.loadExtension({
manifest: {
browser_specific_settings: { gecko: { id: "watched@tests.mozilla.org" } },
permissions: [
"tabs",
"tabHide",
"storage",
"webRequest",
"webRequestBlocking",
"<all_urls>",
],
content_scripts: [
{
js: ["content_script.js"],
run_at: "document_idle",
},
],
},
files: {
"content_script.js": () => {
browser.test.sendMessage("content_script");
},
"registered_script.js": () => {
browser.test.sendMessage("registered_script");
},
},
async background() {
let listen = () => {};
async function runTest() {
// Test activity for a child function call.
browser.test.assertEq(
undefined,
browser.activityLog,
"activityLog requires permission"
);
// Test a child event manager.
browser.storage.onChanged.addListener(listen);
browser.storage.onChanged.removeListener(listen);
// Test a parent event manager.
let webRequestListener = () => {
browser.webRequest.onBeforeRequest.removeListener(webRequestListener);
return { cancel: false };
};
browser.webRequest.onBeforeRequest.addListener(
webRequestListener,
["blocking"]
);
// A manifest based content script is already
// registered, we do a dynamic registration here.
await browser.contentScripts.register({
js: [{ file: "registered_script.js" }],
runAt: "document_start",
});
browser.test.sendMessage("ready");
}
browser.test.onMessage.addListener((msg, data) => {
// Logging has started here so this listener is logged, but the
// call adding it was not. We do an additional onMessage.addListener
// call in the test function to validate child based event managers.
if (msg == "runtest") {
browser.test.assertTrue(true, msg);
runTest();
}
if (msg == "hideTab") {
browser.tabs.hide(data);
}
});
browser.test.sendMessage("url", browser.runtime.getURL(""));
},
});
async function backgroundScript(expectedUrl, extensionUrl) {
let expecting = [
// Test child-only api_call.
{
type: "api_call",
name: "test.assertTrue",
data: { args: [true, "runtest"] },
},
// Test child-only api_call.
{
type: "api_call",
name: "test.assertEq",
data: {
args: [undefined, undefined, "activityLog requires permission"],
},
},
// Test child addListener calls.
{
type: "api_call",
name: "storage.onChanged.addListener",
data: {
args: [],
},
},
{
type: "api_call",
name: "storage.onChanged.removeListener",
data: {
args: [],
},
},
// Test parent addListener calls.
{
type: "api_call",
name: "webRequest.onBeforeRequest.addListener",
data: {
args: [
{
incognito: null,
tabId: null,
types: null,
windowId: null,
},
["blocking"],
],
},
},
// Test an api that makes use of callParentAsyncFunction.
{
type: "api_call",
name: "contentScripts.register",
data: {
args: [
{
allFrames: null,
css: null,
excludeGlobs: null,
excludeMatches: null,
includeGlobs: null,
js: [
{
file: `${extensionUrl}registered_script.js`,
},
],
matchAboutBlank: null,
runAt: "document_start",
},
],
},
},
// Test child api_event calls.
{
type: "api_event",
name: "test.onMessage",
data: { args: ["runtest"] },
},
{
type: "api_call",
name: "test.sendMessage",
data: { args: ["ready"] },
},
// Test parent api_event calls.
{
type: "api_call",
name: "webRequest.onBeforeRequest.removeListener",
data: {
args: [],
},
},
{
type: "api_event",
name: "webRequest.onBeforeRequest",
data: {
args: [
{
url: expectedUrl,
method: "GET",
type: "main_frame",
frameId: 0,
parentFrameId: -1,
incognito: false,
thirdParty: false,
ip: null,
frameAncestors: [],
urlClassification: { firstParty: [], thirdParty: [] },
requestSize: 0,
responseSize: 0,
},
],
result: {
cancel: false,
},
},
},
// Test manifest based content script.
{
type: "content_script",
name: "content_script.js",
data: { url: expectedUrl, tabId: 1 },
},
// registered script test
{
type: "content_script",
name: `${extensionUrl}registered_script.js`,
data: { url: expectedUrl, tabId: 1 },
},
{
type: "api_call",
name: "test.sendMessage",
data: { args: ["registered_script"], tabId: 1 },
},
{
type: "api_call",
name: "test.sendMessage",
data: { args: ["content_script"], tabId: 1 },
},
// Child api call
{
type: "api_call",
name: "tabs.hide",
data: { args: ["__TAB_ID"] },
},
{
type: "api_event",
name: "test.onMessage",
data: { args: ["hideTab", "__TAB_ID"] },
},
];
browser.test.assertTrue(browser.activityLog, "activityLog is privileged");
// Slightly less than a normal deep equal, we want to know that the values
// in our expected data are the same in the actual data, but we don't care
// if actual data has additional data or if data is in the same order in objects.
// This allows us to ignore keys that may be variable, or that are set in
// the api with an undefined value.
function deepEquivalent(a, b) {
if (a === b) {
return true;
}
if (
typeof a != "object" ||
typeof b != "object" ||
a === null ||
b === null
) {
return false;
}
for (let k in a) {
if (!deepEquivalent(a[k], b[k])) {
return false;
}
}
return true;
}
let tab;
let handler = async details => {
browser.test.log(`onExtensionActivity ${JSON.stringify(details)}`);
let test = expecting.shift();
if (!test) {
browser.test.notifyFail(`no test for ${details.name}`);
}
// On multiple runs, tabId will be different. Set the current
// tabId where we need it.
if (test.data.tabId !== undefined) {
test.data.tabId = tab.id;
}
if (test.data.args !== undefined) {
test.data.args = test.data.args.map(value =>
value === "__TAB_ID" ? tab.id : value
);
}
browser.test.assertEq(test.type, details.type, "type matches");
if (test.type == "content_script") {
browser.test.assertTrue(
details.name.includes(test.name),
"content script name matches"
);
} else {
browser.test.assertEq(test.name, details.name, "name matches");
}
browser.test.assertTrue(
deepEquivalent(test.data, details.data),
`expected ${JSON.stringify(
test.data
)} included in actual ${JSON.stringify(details.data)}`
);
if (!expecting.length) {
await browser.tabs.remove(tab.id);
browser.test.notifyPass("activity");
}
};
browser.activityLog.onExtensionActivity.addListener(
handler,
"watched@tests.mozilla.org"
);
browser.test.onMessage.addListener(async msg => {
if (msg === "opentab") {
tab = await browser.tabs.create({ url: expectedUrl });
browser.test.sendMessage("tabid", tab.id);
}
if (msg === "done") {
browser.activityLog.onExtensionActivity.removeListener(
handler,
"watched@tests.mozilla.org"
);
}
});
}
await extension.startup();
let extensionUrl = await extension.awaitMessage("url");
let logger = ExtensionTestUtils.loadExtension({
isPrivileged: true,
manifest: {
browser_specific_settings: { gecko: { id: "watcher@tests.mozilla.org" } },
permissions: ["activityLog"],
},
background: `(${backgroundScript})("${URL}", "${extensionUrl}")`,
});
await logger.startup();
extension.sendMessage("runtest");
await extension.awaitMessage("ready");
logger.sendMessage("opentab");
let id = await logger.awaitMessage("tabid");
await Promise.all([
extension.awaitMessage("content_script"),
extension.awaitMessage("registered_script"),
]);
extension.sendMessage("hideTab", id);
await logger.awaitFinish("activity");
// Stop watching because we get extra calls on extension shutdown
// such as listener removal.
logger.sendMessage("done");
await extension.unload();
await unlogged.unload();
await logger.unload();
});
</script>
</body>
</html>