mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-14 02:31:59 +00:00
Bug 1542403 Add privileged activity logging api r=rpl,zombie
Differential Revision: https://phabricator.services.mozilla.com/D34440 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
3a4666603f
commit
4c24a7f0ac
@ -147,6 +147,7 @@ const CHILD_SHUTDOWN_TIMEOUT_MS = 8000;
|
||||
|
||||
// Permissions that are only available to privileged extensions.
|
||||
const PRIVILEGED_PERMS = new Set([
|
||||
"activityLog",
|
||||
"mozillaAddons",
|
||||
"geckoViewAddons",
|
||||
"telemetry",
|
||||
|
@ -82,6 +82,14 @@
|
||||
["extension"]
|
||||
]
|
||||
},
|
||||
"activityLog": {
|
||||
"url": "chrome://extensions/content/parent/ext-activityLog.js",
|
||||
"schema": "chrome://extensions/content/schemas/activity_log.json",
|
||||
"scopes": ["addon_parent"],
|
||||
"paths": [
|
||||
["activityLog"]
|
||||
]
|
||||
},
|
||||
"i18n": {
|
||||
"url": "chrome://extensions/content/parent/ext-i18n.js",
|
||||
"schema": "chrome://extensions/content/schemas/i18n.json",
|
||||
|
@ -7,6 +7,7 @@ toolkit.jar:
|
||||
content/extensions/dummy.xul
|
||||
content/extensions/ext-browser-content.js
|
||||
content/extensions/ext-toolkit.json
|
||||
content/extensions/parent/ext-activityLog.js (parent/ext-activityLog.js)
|
||||
content/extensions/parent/ext-alarms.js (parent/ext-alarms.js)
|
||||
content/extensions/parent/ext-backgroundPage.js (parent/ext-backgroundPage.js)
|
||||
content/extensions/parent/ext-browserSettings.js (parent/ext-browserSettings.js)
|
||||
|
38
toolkit/components/extensions/parent/ext-activityLog.js
Normal file
38
toolkit/components/extensions/parent/ext-activityLog.js
Normal file
@ -0,0 +1,38 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ExtensionCommon",
|
||||
"resource://gre/modules/ExtensionCommon.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ExtensionActivityLog",
|
||||
"resource://gre/modules/ExtensionActivityLog.jsm"
|
||||
);
|
||||
|
||||
this.activityLog = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
activityLog: {
|
||||
onExtensionActivity: new ExtensionCommon.EventManager({
|
||||
context,
|
||||
name: "activityLog.onExtensionActivity",
|
||||
register: (fire, id) => {
|
||||
function handler(details) {
|
||||
fire.async(details);
|
||||
}
|
||||
|
||||
ExtensionActivityLog.addListener(id, handler);
|
||||
return () => {
|
||||
ExtensionActivityLog.removeListener(id, handler);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
87
toolkit/components/extensions/schemas/activity_log.json
Normal file
87
toolkit/components/extensions/schemas/activity_log.json
Normal file
@ -0,0 +1,87 @@
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"activityLog"
|
||||
]
|
||||
}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"namespace": "activityLog",
|
||||
"description": "Monitor extension activity",
|
||||
"permissions": ["activityLog"],
|
||||
"events": [
|
||||
{
|
||||
"name": "onExtensionActivity",
|
||||
"description": "Receives an activityItem for each logging event.",
|
||||
"type": "function",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "details",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timeStamp": {
|
||||
"$ref": "extensionTypes.Date",
|
||||
"description": "The date string when this call is triggered."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["api_call", "api_event", "content_script", "user_script"],
|
||||
"description": "The type of log entry. api_call is a function call made by the extension and api_event is an event callback to the extension. content_script is logged when a content script is injected."
|
||||
},
|
||||
"viewType": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"enum": ["background", "popup", "sidebar", "tab", "devtools_page", "devtools_panel"],
|
||||
"description": "The type of view where the activity occurred. Content scripts will not have a viewType."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the api call or event, or the script url if this is a content or user script event."
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"args": {
|
||||
"type": "array",
|
||||
"optional": true,
|
||||
"items": {
|
||||
"type": "any"
|
||||
},
|
||||
"description": "A list of arguments passed to the call."
|
||||
},
|
||||
"result": {
|
||||
"type": "object",
|
||||
"optional": true,
|
||||
"description": "The result of the call."
|
||||
},
|
||||
"tabId": {
|
||||
"type": "integer",
|
||||
"optional": true,
|
||||
"description": "The tab associated with this event if it is a tab or content script."
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "If the type is content_script, this is the url of the script that was injected."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"extraParameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -4,6 +4,7 @@
|
||||
|
||||
toolkit.jar:
|
||||
% content extensions %content/extensions/
|
||||
content/extensions/schemas/activity_log.json
|
||||
content/extensions/schemas/alarms.json
|
||||
content/extensions/schemas/browser_settings.json
|
||||
#ifndef ANDROID
|
||||
|
@ -61,6 +61,7 @@ prefs =
|
||||
browser.chrome.guess_favicon=true
|
||||
skip-if = toolkit == 'android' && !is_fennec
|
||||
|
||||
[test_ext_activityLog.html]
|
||||
[test_ext_async_clipboard.html]
|
||||
skip-if = fission || toolkit == 'android' # near-permafail after landing bug 1270059: Bug 1523131
|
||||
[test_ext_background_canvas.html]
|
||||
|
@ -0,0 +1,290 @@
|
||||
<!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 = "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest/file_sample.html";
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
applications: { gecko: { id: "watched@tests.mozilla.org" } },
|
||||
permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ["http://mochi.test/*/file_sample.html"],
|
||||
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() {
|
||||
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.test.onMessage.addListener(async msg => {
|
||||
});
|
||||
|
||||
// Test a parent event manager.
|
||||
browser.webRequest.onBeforeRequest.addListener(
|
||||
details => {
|
||||
return { cancel: false };
|
||||
},
|
||||
{ urls: ["http://mochi.test/*/file_sample.html"] },
|
||||
["blocking"]
|
||||
);
|
||||
|
||||
// A manifest based content script is already
|
||||
// registered, we do a dynamic registration here.
|
||||
await browser.contentScripts.register({
|
||||
js: [{ file: "registered_script.js" }],
|
||||
matches: ["http://mochi.test/*/file_sample.html"],
|
||||
runAt: "document_start",
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
browser.test.onMessage.addListener(msg => {
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
browser.test.sendMessage("url", browser.extension.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: [null, null, "activityLog requires permission"],
|
||||
},
|
||||
},
|
||||
// Test child addListener calls.
|
||||
{
|
||||
type: "api_call",
|
||||
name: "test.onMessage.addListener",
|
||||
data: {
|
||||
args: [],
|
||||
},
|
||||
},
|
||||
// Test parent addListener calls.
|
||||
{
|
||||
type: "api_call",
|
||||
name: "webRequest.onBeforeRequest.addListener",
|
||||
data: {
|
||||
args: [
|
||||
{
|
||||
incognito: null,
|
||||
tabId: null,
|
||||
types: null,
|
||||
urls: ["http://mochi.test/*/file_sample.html"],
|
||||
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,
|
||||
matches: ["http://mochi.test/*/file_sample.html"],
|
||||
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_event",
|
||||
name: "webRequest.onBeforeRequest",
|
||||
data: {
|
||||
args: [
|
||||
{
|
||||
url: expectedUrl,
|
||||
method: "GET",
|
||||
type: "main_frame",
|
||||
frameId: 0,
|
||||
parentFrameId: -1,
|
||||
ip: null,
|
||||
frameAncestors: [],
|
||||
incognito: false,
|
||||
},
|
||||
],
|
||||
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 },
|
||||
},
|
||||
];
|
||||
browser.test.assertTrue(browser.activityLog, "activityLog is privileged");
|
||||
|
||||
let tab;
|
||||
browser.activityLog.onExtensionActivity.addListener(async details => {
|
||||
browser.test.log(`onExtensionActivity ${JSON.stringify(details)}`);
|
||||
let test = expecting.shift();
|
||||
if (!test) {
|
||||
browser.test.notifyFail(`no test for ${details.name}`);
|
||||
}
|
||||
delete details.timeStamp;
|
||||
// On multiple runs, tabId will be different. Get the current tabId and
|
||||
// use that.
|
||||
if (test.data.tabId !== undefined) {
|
||||
test.data.tabId = tab.id;
|
||||
}
|
||||
|
||||
// hack for webRequest test
|
||||
if (details.name === "webRequest.onBeforeRequest") {
|
||||
// Remove items that may be variable, the important
|
||||
// aspect is that we generally get the activity
|
||||
// logging we expect.
|
||||
delete details.data.args[0].requestId;
|
||||
delete details.data.args[0].tabId;
|
||||
delete details.data.args[0].originUrl;
|
||||
delete details.data.args[0].timeStamp;
|
||||
delete details.data.args[0].proxyInfo;
|
||||
}
|
||||
|
||||
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.assertEq(
|
||||
JSON.stringify(test.data),
|
||||
JSON.stringify(details.data),
|
||||
"message matches"
|
||||
);
|
||||
if (expecting.length == 0) {
|
||||
await browser.tabs.remove(tab.id);
|
||||
browser.test.notifyPass("activity");
|
||||
}
|
||||
}, "watched@tests.mozilla.org");
|
||||
|
||||
browser.test.onMessage.addListener(async msg => {
|
||||
if (msg === "opentab") {
|
||||
tab = await browser.tabs.create({url: expectedUrl});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await extension.startup();
|
||||
let extensionUrl = await extension.awaitMessage("url");
|
||||
|
||||
let logger = ExtensionTestUtils.loadExtension({
|
||||
isPrivileged: true,
|
||||
manifest: {
|
||||
applications: { 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");
|
||||
|
||||
await Promise.all([
|
||||
extension.awaitMessage("content_script"),
|
||||
extension.awaitMessage("registered_script"),
|
||||
logger.awaitFinish("activity"),
|
||||
]);
|
||||
|
||||
await extension.unload();
|
||||
await logger.unload();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_api_restricted() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: { id: "activityLog-permission@tests.mozilla.org" },
|
||||
},
|
||||
permissions: ["activityLog"],
|
||||
},
|
||||
async background() {
|
||||
browser.test.assertEq(
|
||||
undefined,
|
||||
browser.activityLog,
|
||||
"activityLog is privileged"
|
||||
);
|
||||
},
|
||||
});
|
||||
await extension.startup();
|
||||
await extension.unload();
|
||||
});
|
@ -566,6 +566,7 @@ const GRANTED_WITHOUT_USER_PROMPT = [
|
||||
"contextMenus",
|
||||
"contextualIdentities",
|
||||
"cookies",
|
||||
"activityLog",
|
||||
"geckoProfiler",
|
||||
"identity",
|
||||
"idle",
|
||||
|
@ -1,5 +1,6 @@
|
||||
[test_ext_MessageManagerProxy.js]
|
||||
skip-if = os == 'android' # Bug 1545439
|
||||
[test_ext_activityLog.js]
|
||||
[test_ext_alarms.js]
|
||||
[test_ext_alarms_does_not_fire.js]
|
||||
[test_ext_alarms_periodic.js]
|
||||
|
Loading…
Reference in New Issue
Block a user