Bug 1312802 - Implement chrome.privacy API, r=aswan

MozReview-Commit-ID: 5DoGnYb945Z

--HG--
extra : rebase_source : 4dec751bb420beb262e74e812ebd6ff98cba5aca
extra : source : 73764944f1f914ca5c0c45b0a38be26cd5b38143
This commit is contained in:
Bob Silverberg 2017-02-15 17:32:24 -05:00
Родитель e43e63e3a7
Коммит 74b6266547
11 изменённых файлов: 737 добавлений и 1 удалений

Просмотреть файл

@ -91,6 +91,7 @@ webextPerms.description.history=Access browsing history
# %S will be replaced with the name of the application
webextPerms.description.nativeMessaging=Exchange messages with programs other than %S
webextPerms.description.notifications=Display notifications to you
webextPerms.description.privacy=Read and modify privacy settings
webextPerms.description.sessions=Access recently closed tabs
webextPerms.description.tabs=Access browser tabs
webextPerms.description.topSites=Access browsing history

Просмотреть файл

@ -1617,7 +1617,7 @@ class SubModuleProperty extends Entry {
`is not a sub-module`);
}
}
let subpath = [path, this.name];
let subpath = [...path, this.name];
let namespace = subpath.join(".");
let functions = type.functions;

Просмотреть файл

@ -0,0 +1,164 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
ExtensionError,
} = ExtensionUtils;
function checkScope(scope) {
if (scope && scope !== "regular") {
throw new ExtensionError(
`Firefox does not support the ${scope} settings scope.`);
}
}
function getAPI(extension, context, name, callback) {
let anythingSet = false;
return {
async get(details) {
return {
levelOfControl: details.incognito ?
"not_controllable" :
await ExtensionPreferencesManager.getLevelOfControl(
extension, name),
value: await callback(),
};
},
async set(details) {
checkScope(details.scope);
if (!anythingSet) {
anythingSet = true;
context.callOnClose({
close: async () => {
if (["ADDON_DISABLE", "ADDON_UNINSTALL"].includes(extension.shutdownReason)) {
await ExtensionPreferencesManager.unsetAll(extension);
anythingSet = false;
}
},
});
}
return await ExtensionPreferencesManager.setSetting(
extension, name, details.value);
},
async clear(details) {
checkScope(details.scope);
return await ExtensionPreferencesManager.unsetSetting(
extension, name);
},
};
}
// Add settings objects for supported APIs to the preferences manager.
ExtensionPreferencesManager.addSetting("network.networkPredictionEnabled", {
prefNames: [
"network.predictor.enabled",
"network.prefetch-next",
"network.http.speculative-parallel-limit",
"network.dns.disablePrefetch",
],
setCallback(value) {
return {
"network.http.speculative-parallel-limit": value ? undefined : 0,
"network.dns.disablePrefetch": !value,
"network.predictor.enabled": value,
"network.prefetch-next": value,
};
},
});
ExtensionPreferencesManager.addSetting("network.webRTCIPHandlingPolicy", {
prefNames: [
"media.peerconnection.ice.default_address_only",
"media.peerconnection.ice.no_host",
"media.peerconnection.ice.proxy_only",
],
setCallback(value) {
let prefs = {};
// Start with all prefs being reset.
for (let pref of this.prefNames) {
prefs[pref] = undefined;
}
switch (value) {
case "default":
// All prefs are already set to be reset.
break;
case "default_public_and_private_interfaces":
prefs["media.peerconnection.ice.default_address_only"] = true;
break;
case "default_public_interface_only":
prefs["media.peerconnection.ice.default_address_only"] = true;
prefs["media.peerconnection.ice.no_host"] = true;
break;
case "disable_non_proxied_udp":
prefs["media.peerconnection.ice.proxy_only"] = true;
break;
}
return prefs;
},
});
ExtensionPreferencesManager.addSetting("websites.hyperlinkAuditingEnabled", {
prefNames: [
"browser.send_pings",
],
setCallback(value) {
return {[this.prefNames[0]]: value};
},
});
extensions.registerSchemaAPI("privacy.network", "addon_parent", context => {
let {extension} = context;
return {
privacy: {
network: {
networkPredictionEnabled: getAPI(extension, context,
"network.networkPredictionEnabled",
() => {
return Preferences.get("network.predictor.enabled") &&
Preferences.get("network.prefetch-next") &&
Preferences.get("network.http.speculative-parallel-limit") > 0 &&
!Preferences.get("network.dns.disablePrefetch");
}),
webRTCIPHandlingPolicy: getAPI(extension, context,
"network.webRTCIPHandlingPolicy",
() => {
if (Preferences.get("media.peerconnection.ice.proxy_only")) {
return "disable_non_proxied_udp";
}
let default_address_only =
Preferences.get("media.peerconnection.ice.default_address_only");
if (default_address_only) {
if (Preferences.get("media.peerconnection.ice.no_host")) {
return "default_public_interface_only";
}
return "default_public_and_private_interfaces";
}
return "default";
}),
},
websites: {
hyperlinkAuditingEnabled: getAPI(extension, context,
"websites.hyperlinkAuditingEnabled",
() => {
return Preferences.get("browser.send_pings");
}),
},
},
};
});

Просмотреть файл

@ -15,6 +15,7 @@ category webextension-scripts runtime chrome://extensions/content/ext-runtime.js
category webextension-scripts extension chrome://extensions/content/ext-extension.js
category webextension-scripts storage chrome://extensions/content/ext-storage.js
category webextension-scripts topSites chrome://extensions/content/ext-topSites.js
category webextension-scripts privacy chrome://extensions/content/ext-privacy.js
# scripts specific for content process.
category webextension-scripts-content extension chrome://extensions/content/ext-c-extension.js
@ -57,9 +58,11 @@ category webextension-schemas idle chrome://extensions/content/schemas/idle.json
category webextension-schemas management chrome://extensions/content/schemas/management.json
category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json
category webextension-schemas notifications chrome://extensions/content/schemas/notifications.json
category webextension-schemas privacy chrome://extensions/content/schemas/privacy.json
category webextension-schemas runtime chrome://extensions/content/schemas/runtime.json
category webextension-schemas storage chrome://extensions/content/schemas/storage.json
category webextension-schemas test chrome://extensions/content/schemas/test.json
category webextension-schemas top_sites chrome://extensions/content/schemas/top_sites.json
category webextension-schemas types chrome://extensions/content/schemas/types.json
category webextension-schemas web_navigation chrome://extensions/content/schemas/web_navigation.json
category webextension-schemas web_request chrome://extensions/content/schemas/web_request.json

Просмотреть файл

@ -21,6 +21,7 @@ toolkit.jar:
content/extensions/ext-extension.js
content/extensions/ext-storage.js
content/extensions/ext-topSites.js
content/extensions/ext-privacy.js
content/extensions/ext-c-backgroundPage.js
content/extensions/ext-c-extension.js
#ifndef ANDROID

Просмотреть файл

@ -21,9 +21,11 @@ toolkit.jar:
content/extensions/schemas/manifest.json
content/extensions/schemas/native_host_manifest.json
content/extensions/schemas/notifications.json
content/extensions/schemas/privacy.json
content/extensions/schemas/runtime.json
content/extensions/schemas/storage.json
content/extensions/schemas/test.json
content/extensions/schemas/top_sites.json
content/extensions/schemas/types.json
content/extensions/schemas/web_navigation.json
content/extensions/schemas/web_request.json

Просмотреть файл

@ -0,0 +1,73 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[
{
"namespace": "manifest",
"types": [
{
"$extend": "Permission",
"choices": [{
"type": "string",
"enum": [
"privacy"
]
}]
}
]
},
{
"namespace": "privacy",
"permissions": ["privacy"]
},
{
"namespace": "privacy.network",
"description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
"permissions": ["privacy"],
"types": [
{
"id": "IPHandlingPolicy",
"type": "string",
"enum": ["default", "default_public_and_private_interfaces", "default_public_interface_only", "disable_non_proxied_udp"],
"description": "The IP handling policy of WebRTC."
}
],
"properties": {
"networkPredictionEnabled": {
"$ref": "types.Setting",
"description": "If enabled, the browser attempts to speed up your web browsing experience by pre-resolving DNS entries, prerendering sites (<code>&lt;link rel='prefetch' ...&gt;</code>), and preemptively opening TCP and SSL connections to servers. This preference's value is a boolean, defaulting to <code>true</code>."
},
"webRTCIPHandlingPolicy": {
"$ref": "types.Setting",
"description": "Allow users to specify the media performance/privacy tradeoffs which impacts how WebRTC traffic will be routed and how much local address information is exposed. This preference's value is of type IPHandlingPolicy, defaulting to <code>default</code>."
}
}
},
{
"namespace": "privacy.websites",
"description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
"permissions": ["privacy"],
"properties": {
"thirdPartyCookiesAllowed": {
"$ref": "types.Setting",
"description": "If disabled, the browser blocks third-party sites from setting cookies. The value of this preference is of type boolean, and the default value is <code>true</code>.",
"unsupported": true
},
"hyperlinkAuditingEnabled": {
"$ref": "types.Setting",
"description": "If enabled, the browser sends auditing pings when requested by a website (<code>&lt;a ping&gt;</code>). The value of this preference is of type boolean, and the default value is <code>true</code>."
},
"referrersEnabled": {
"$ref": "types.Setting",
"description": "If enabled, the browser sends <code>referer</code> headers with your requests. Yes, the name of this preference doesn't match the misspelled header. No, we're not going to change it. The value of this preference is of type boolean, and the default value is <code>true</code>.",
"unsupported": true
},
"protectedContentEnabled": {
"$ref": "types.Setting",
"description": "<strong>Available on Windows and ChromeOS only</strong>: If enabled, the browser provides a unique ID to plugins in order to run protected content. The value of this preference is of type boolean, and the default value is <code>true</code>.",
"unsupported": true
}
}
}
]

Просмотреть файл

@ -0,0 +1,163 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[
{
"namespace": "types",
"description": "Contains types used by other schemas.",
"types": [
{
"id": "SettingScope",
"type": "string",
"enum": ["regular", "regular_only", "incognito_persistent", "incognito_session_only"],
"description": "The scope of the Setting. One of<ul><li><var>regular</var>: setting for the regular profile (which is inherited by the incognito profile if not overridden elsewhere),</li><li><var>regular_only</var>: setting for the regular profile only (not inherited by the incognito profile),</li><li><var>incognito_persistent</var>: setting for the incognito profile that survives browser restarts (overrides regular preferences),</li><li><var>incognito_session_only</var>: setting for the incognito profile that can only be set during an incognito session and is deleted when the incognito session ends (overrides regular and incognito_persistent preferences).</li></ul> Only <var>regular</var> is supported by Firefox at this time."
},
{
"id": "LevelOfControl",
"type": "string",
"enum": ["not_controllable", "controlled_by_other_extensions", "controllable_by_this_extension", "controlled_by_this_extension"],
"description": "One of<ul><li><var>not_controllable</var>: cannot be controlled by any extension</li><li><var>controlled_by_other_extensions</var>: controlled by extensions with higher precedence</li><li><var>controllable_by_this_extension</var>: can be controlled by this extension</li><li><var>controlled_by_this_extension</var>: controlled by this extension</li></ul>"
},
{
"id": "Setting",
"type": "object",
"functions": [
{
"name": "get",
"type": "function",
"description": "Gets the value of a setting.",
"async": "callback",
"parameters": [
{
"name": "details",
"type": "object",
"description": "Which setting to consider.",
"properties": {
"incognito": {
"type": "boolean",
"optional": true,
"description": "Whether to return the value that applies to the incognito session (default false)."
}
}
},
{
"name": "callback",
"type": "function",
"parameters": [
{
"name": "details",
"type": "object",
"description": "Details of the currently effective value.",
"properties": {
"value": {
"description": "The value of the setting.",
"type": "any"
},
"levelOfControl": {
"$ref": "LevelOfControl",
"description": "The level of control of the setting."
},
"incognitoSpecific": {
"description": "Whether the effective value is specific to the incognito session.<br/>This property will <em>only</em> be present if the <var>incognito</var> property in the <var>details</var> parameter of <code>get()</code> was true.",
"type": "boolean",
"optional": true
}
}
}
]
}
]
},
{
"name": "set",
"type": "function",
"description": "Sets the value of a setting.",
"async": "callback",
"parameters": [
{
"name": "details",
"type": "object",
"description": "Which setting to change.",
"properties": {
"value": {
"description": "The value of the setting. <br/>Note that every setting has a specific value type, which is described together with the setting. An extension should <em>not</em> set a value of a different type.",
"type": "any"
},
"scope": {
"$ref": "SettingScope",
"optional": true,
"description": "Where to set the setting (default: regular)."
}
}
},
{
"name": "callback",
"type": "function",
"description": "Called at the completion of the set operation.",
"optional": true,
"parameters": []
}
]
},
{
"name": "clear",
"type": "function",
"description": "Clears the setting, restoring any default value.",
"async": "callback",
"parameters": [
{
"name": "details",
"type": "object",
"description": "Which setting to clear.",
"properties": {
"scope": {
"$ref": "SettingScope",
"optional": true,
"description": "Where to clear the setting (default: regular)."
}
}
},
{
"name": "callback",
"type": "function",
"description": "Called at the completion of the clear operation.",
"optional": true,
"parameters": []
}
]
}
],
"events": [
{
"name": "onChange",
"type": "function",
"description": "Fired after the setting changes.",
"unsupported": true,
"parameters": [
{
"type": "object",
"name": "details",
"properties": {
"value": {
"description": "The value of the setting after the change.",
"type": "any"
},
"levelOfControl": {
"$ref": "LevelOfControl",
"description": "The level of control of the setting."
},
"incognitoSpecific": {
"description": "Whether the value that has changed is specific to the incognito session.<br/>This property will <em>only</em> be present if the user has enabled the extension in incognito mode.",
"type": "boolean",
"optional": true
}
}
}
]
}
]
}
]
}
]

Просмотреть файл

@ -82,6 +82,8 @@ let expectedBackgroundApis = [
"runtime.openOptionsPage",
"runtime.reload",
"runtime.setUninstallURL",
"types.LevelOfControl",
"types.SettingScope",
];
function sendAllApis() {

Просмотреть файл

@ -0,0 +1,326 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
"resource://gre/modules/ExtensionPreferencesManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
const {
createAppInfo,
promiseShutdownManager,
promiseStartupManager,
} = AddonTestUtils;
AddonTestUtils.init(this);
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
add_task(async function test_privacy() {
// Create a object to hold the values to which we will initialize the prefs.
const SETTINGS = {
"network.networkPredictionEnabled": {
"network.predictor.enabled": true,
"network.prefetch-next": true,
"network.http.speculative-parallel-limit": 10,
"network.dns.disablePrefetch": false,
},
"websites.hyperlinkAuditingEnabled": {
"browser.send_pings": true,
},
};
async function background() {
browser.test.onMessage.addListener(async (msg, ...args) => {
let data = args[0];
// The second argument is the end of the api name,
// e.g., "network.networkPredictionEnabled".
let apiObj = args[1].split(".").reduce((o, i) => o[i], browser.privacy);
let settingData;
switch (msg) {
case "get":
settingData = await apiObj.get(data);
browser.test.sendMessage("gotData", settingData);
break;
case "set":
await apiObj.set(data);
settingData = await apiObj.get({});
browser.test.sendMessage("afterSet", settingData);
break;
case "clear":
await apiObj.clear(data);
settingData = await apiObj.get({});
browser.test.sendMessage("afterClear", settingData);
break;
}
});
}
// Set prefs to our initial values.
for (let setting in SETTINGS) {
for (let pref in SETTINGS[setting]) {
Preferences.set(pref, SETTINGS[setting][pref]);
}
}
do_register_cleanup(() => {
// Reset the prefs.
for (let setting in SETTINGS) {
for (let pref in SETTINGS[setting]) {
Preferences.reset(pref);
}
}
});
// Create an array of extensions to install.
let testExtensions = [
ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["privacy"],
},
useAddonManager: "temporary",
}),
ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["privacy"],
},
useAddonManager: "temporary",
}),
];
await promiseStartupManager();
for (let extension of testExtensions) {
await extension.startup();
}
for (let setting in SETTINGS) {
testExtensions[0].sendMessage("get", {}, setting);
let data = await testExtensions[0].awaitMessage("gotData");
ok(data.value, "get returns expected value.");
equal(data.levelOfControl, "controllable_by_this_extension",
"get returns expected levelOfControl.");
testExtensions[0].sendMessage("get", {incognito: true}, setting);
data = await testExtensions[0].awaitMessage("gotData");
ok(data.value, "get returns expected value with incognito.");
equal(data.levelOfControl, "not_controllable",
"get returns expected levelOfControl with incognito.");
// Change the value to false.
testExtensions[0].sendMessage("set", {value: false}, setting);
data = await testExtensions[0].awaitMessage("afterSet");
ok(!data.value, "get returns expected value after setting.");
equal(data.levelOfControl, "controlled_by_this_extension",
"get returns expected levelOfControl after setting.");
// Verify the prefs have been set to match the "false" setting.
for (let pref in SETTINGS[setting]) {
let msg = `${pref} set correctly for ${setting}`;
if (pref === "network.http.speculative-parallel-limit") {
equal(Preferences.get(pref), 0, msg);
} else {
equal(Preferences.get(pref), !SETTINGS[setting][pref], msg);
}
}
// Change the value with a newer extension.
testExtensions[1].sendMessage("set", {value: true}, setting);
data = await testExtensions[1].awaitMessage("afterSet");
ok(data.value, "get returns expected value after setting via newer extension.");
equal(data.levelOfControl, "controlled_by_this_extension",
"get returns expected levelOfControl after setting.");
// Verify the prefs have been set to match the "true" setting.
for (let pref in SETTINGS[setting]) {
let msg = `${pref} set correctly for ${setting}`;
if (pref === "network.http.speculative-parallel-limit") {
equal(Preferences.get(pref), ExtensionPreferencesManager.getDefaultValue(pref), msg);
} else {
equal(Preferences.get(pref), SETTINGS[setting][pref], msg);
}
}
// Change the value with an older extension.
testExtensions[0].sendMessage("set", {value: false}, setting);
data = await testExtensions[0].awaitMessage("afterSet");
ok(data.value, "Newer extension remains in control.");
equal(data.levelOfControl, "controlled_by_other_extensions",
"get returns expected levelOfControl when controlled by other.");
// Clear the value of the newer extension.
testExtensions[1].sendMessage("clear", {}, setting);
data = await testExtensions[1].awaitMessage("afterClear");
ok(!data.value, "Older extension gains control.");
equal(data.levelOfControl, "controllable_by_this_extension",
"Expected levelOfControl returned after clearing.");
testExtensions[0].sendMessage("get", {}, setting);
data = await testExtensions[0].awaitMessage("gotData");
ok(!data.value, "Current, older extension has control.");
equal(data.levelOfControl, "controlled_by_this_extension",
"Expected levelOfControl returned after clearing.");
// Set the value again with the newer extension.
testExtensions[1].sendMessage("set", {value: true}, setting);
data = await testExtensions[1].awaitMessage("afterSet");
ok(data.value, "get returns expected value after setting via newer extension.");
equal(data.levelOfControl, "controlled_by_this_extension",
"get returns expected levelOfControl after setting.");
// Unload the newer extension. Expect the older extension to regain control.
await testExtensions[1].unload();
testExtensions[0].sendMessage("get", {}, setting);
data = await testExtensions[0].awaitMessage("gotData");
ok(!data.value, "Older extension regained control.");
equal(data.levelOfControl, "controlled_by_this_extension",
"Expected levelOfControl returned after unloading.");
// Reload the extension for the next iteration of the loop.
testExtensions[1] = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["privacy"],
},
useAddonManager: "temporary",
});
await testExtensions[1].startup();
// Clear the value of the older extension.
testExtensions[0].sendMessage("clear", {}, setting);
data = await testExtensions[0].awaitMessage("afterClear");
ok(data.value, "Setting returns to original value when all are cleared.");
equal(data.levelOfControl, "controllable_by_this_extension",
"Expected levelOfControl returned after clearing.");
// Verify that our initial values were restored.
for (let pref in SETTINGS[setting]) {
equal(Preferences.get(pref), SETTINGS[setting][pref], `${pref} was reset to its initial value.`);
}
}
for (let extension of testExtensions) {
await extension.unload();
}
await promiseShutdownManager();
});
add_task(async function test_privacy_webRTCIPHandlingPolicy() {
// Create a object to hold the default values of all the prefs.
const PREF_DEFAULTS = {
"media.peerconnection.ice.default_address_only": null,
"media.peerconnection.ice.no_host": null,
"media.peerconnection.ice.proxy_only": null,
};
// Store the default values of each pref.
for (let pref in PREF_DEFAULTS) {
PREF_DEFAULTS[pref] = ExtensionPreferencesManager.getDefaultValue(pref);
}
do_register_cleanup(() => {
// Reset the prefs.
for (let pref in PREF_DEFAULTS) {
Preferences.reset(pref);
}
});
async function background() {
browser.test.onMessage.addListener(async (msg, value) => {
let rtcData;
switch (msg) {
case "set":
await browser.privacy.network.webRTCIPHandlingPolicy.set({value});
rtcData = await browser.privacy.network.webRTCIPHandlingPolicy.get({});
browser.test.sendMessage("rtcData", rtcData);
break;
case "clear":
await browser.privacy.network.webRTCIPHandlingPolicy.clear({});
rtcData = await browser.privacy.network.webRTCIPHandlingPolicy.get({});
browser.test.sendMessage("rtcData", rtcData);
break;
}
});
}
let extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["privacy"],
},
useAddonManager: "temporary",
});
await promiseStartupManager();
await extension.startup();
async function testSetting(value, truePrefs) {
extension.sendMessage("set", value);
let data = await extension.awaitMessage("rtcData");
equal(data.value, value);
for (let pref in PREF_DEFAULTS) {
let prefValue = Preferences.get(pref);
if (truePrefs.includes(pref)) {
ok(prefValue, `${pref} set correctly for ${value}`);
} else {
equal(prefValue, PREF_DEFAULTS[pref], `${pref} contains default value for ${value}`);
}
}
}
await testSetting(
"default_public_and_private_interfaces",
["media.peerconnection.ice.default_address_only"]);
await testSetting(
"default_public_interface_only",
["media.peerconnection.ice.default_address_only", "media.peerconnection.ice.no_host"]);
await testSetting(
"disable_non_proxied_udp",
["media.peerconnection.ice.proxy_only"]);
await testSetting("default", []);
await extension.unload();
await promiseShutdownManager();
});
add_task(async function test_exceptions() {
async function background() {
await browser.test.assertRejects(
browser.privacy.network.networkPredictionEnabled.set({value: true, scope: "regular_only"}),
"Firefox does not support the regular_only settings scope.",
"Expected rejection calling set with invalid scope.");
await browser.test.assertRejects(
browser.privacy.network.networkPredictionEnabled.clear({scope: "incognito_persistent"}),
"Firefox does not support the incognito_persistent settings scope.",
"Expected rejection calling clear with invalid scope.");
browser.test.notifyPass("exceptionTests");
}
let extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["privacy"],
},
});
await extension.startup();
await extension.awaitFinish("exceptionTests");
await extension.unload();
});

Просмотреть файл

@ -48,6 +48,7 @@ skip-if = release_or_beta
[test_ext_manifest_minimum_chrome_version.js]
[test_ext_onmessage_removelistener.js]
skip-if = true # This test no longer tests what it is meant to test.
[test_ext_privacy.js]
[test_ext_runtime_connect_no_receiver.js]
[test_ext_runtime_getBrowserInfo.js]
[test_ext_runtime_getPlatformInfo.js]