--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -79,16 +79,27 @@ webextPerms.updateMenuItem=%S requires n
# LOCALIZATION NOTE (webextPerms.updateText)
# %S is replaced with the localized name of the updated extension.
# Note, this string will be used as raw markup. Avoid characters like <, >, &
webextPerms.updateText=%S has been updated. You must approve new permissions before the updated version will install. Choosing “Cancel” will maintain your current add-on version.
webextPerms.updateAccept.label=Update
webextPerms.updateAccept.accessKey=U
+# LOCALIZATION NOTE (webextPerms.optionalPermsHheader)
+# %S is replace with the localized name of the extension requested new
+# permissions.
+# Note, this string will be used as raw markup. Avoid characters like <, >, &
+webextPerms.optionalPermsHeader=%S requests additional permissions.
+webextPerms.optionalPermsListIntro=It wants to:
+webextPerms.optionalPermsAllow.label=Allow
+webextPerms.optionalPermsAllow.accessKey=A
+webextPerms.optionalPermsDeny.label=Deny
+webextPerms.optionalPermsDeny.accessKey=D
+
webextPerms.description.bookmarks=Read and modify bookmarks
webextPerms.description.clipboardRead=Get data from the clipboard
webextPerms.description.clipboardWrite=Input data to the clipboard
webextPerms.description.downloads=Download files and read and modify the browser’s download history
webextPerms.description.geolocation=Access your location
webextPerms.description.history=Access browsing history
# LOCALIZATION NOTE (webextPerms.description.nativeMessaging)
# %S will be replaced with the name of the application
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -36,16 +36,17 @@ this.ExtensionsUI = {
histogram: null,
init() {
this.histogram = Services.telemetry.getHistogramById("EXTENSION_INSTALL_PROMPT_RESULT");
Services.obs.addObserver(this, "webextension-permission-prompt", false);
Services.obs.addObserver(this, "webextension-update-permissions", false);
Services.obs.addObserver(this, "webextension-install-notify", false);
+ Services.obs.addObserver(this, "webextension-optional-permission-prompt", false);
this._checkForSideloaded();
},
_checkForSideloaded() {
AddonManager.getAllAddons(addons => {
// Check for any side-loaded addons that the user is allowed
// to enable.
@@ -198,16 +199,24 @@ this.ExtensionsUI = {
this.emit("change");
} else if (topic == "webextension-install-notify") {
let {target, addon, callback} = subject.wrappedJSObject;
this.showInstallNotification(target, addon).then(() => {
if (callback) {
callback();
}
});
+ } else if (topic == "webextension-optional-permission-prompt") {
+ let {browser, name, icon, permissions, resolve} = subject.wrappedJSObject;
+ let strings = this._buildStrings({
+ type: "optional",
+ addon: {name},
+ permissions,
+ });
+ resolve(this.showPermissionsPrompt(browser, strings, icon));
}
},
// Escape &, <, and > characters in a string so that it may be
// injected as part of raw markup.
_sanitizeName(name) {
return name.replace(/&/g, "&")
.replace(/</g, "<")
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -76,16 +76,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
var {
GlobalManager,
ParentAPIManager,
apiManager: Management,
} = ExtensionParent;
const {
+ classifyPermission,
EventEmitter,
LocaleData,
StartupCache,
getUniqueId,
validateThemeManifest,
} = ExtensionUtils;
XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
@@ -477,22 +478,21 @@ this.ExtensionData = class {
let whitelist = [];
for (let perm of this.manifest.permissions) {
if (perm == "contextualIdentities" && !Preferences.get("privacy.userContext.enabled")) {
continue;
}
this.permissions.add(perm);
-
- let match = /^(\w+)(?:\.(\w+)(?:\.\w+)*)?$/.exec(perm);
- if (!match) {
+ let type = classifyPermission(perm);
+ if (type.origin) {
whitelist.push(perm);
- } else if (match[1] == "experiments" && match[2]) {
- this.apiNames.add(match[2]);
+ } else if (type.api) {
+ this.apiNames.add(type.api);
}
}
this.whiteListedHosts = new MatchPattern(whitelist);
for (let api of this.apiNames) {
this.dependencies.add(`${api}@experiments.addons.mozilla.org`);
}
@@ -675,16 +675,17 @@ this.Extension = class extends Extension
this.hasShutdown = false;
this.onShutdown = new Set();
this.uninstallURL = null;
this.apis = [];
this.whiteListedHosts = null;
+ this._optionalOrigins = null;
this.webAccessibleResources = null;
this.emitter = new EventEmitter();
}
static set browserUpdated(updated) {
_browserUpdated = updated;
}
@@ -1013,9 +1014,17 @@ this.Extension = class extends Extension
}
return this.permissions.has(perm);
}
get name() {
return this.manifest.name;
}
+
+ get optionalOrigins() {
+ if (this._optionalOrigins == null) {
+ let origins = this.manifest.optional_permissions.filter(perm => classifyPermission(perm).origin);
+ this._optionalOrigins = new MatchPattern(origins);
+ }
+ return this._optionalOrigins;
+ }
};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ExtensionPermissions.jsm
@@ -0,0 +1,111 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
+ "resource://gre/modules/JSONFile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+this.EXPORTED_SYMBOLS = ["ExtensionPermissions"];
+
+const FILE_NAME = "extension-preferences.json";
+
+let prefs;
+let _initPromise;
+function lazyInit() {
+ if (!_initPromise) {
+ prefs = new JSONFile({path: OS.Path.join(OS.Constants.Path.profileDir, FILE_NAME)});
+
+ _initPromise = prefs.load();
+ }
+ return _initPromise;
+}
+
+function emptyPermissions() {
+ return {permissions: [], origins: []};
+}
+
+this.ExtensionPermissions = {
+ async get(extension) {
+ await lazyInit();
+
+ let perms = emptyPermissions();
+ if (prefs.data[extension.id]) {
+ Object.assign(perms, prefs.data[extension.id]);
+ }
+ return perms;
+ },
+
+ // Add new permissions for the given extension. `permissions` is
+ // in the format that is passed to browser.permissions.request().
+ async add(extension, perms) {
+ await lazyInit();
+
+ if (!prefs.data[extension.id]) {
+ prefs.data[extension.id] = emptyPermissions();
+ }
+ let {permissions, origins} = prefs.data[extension.id];
+
+ let added = emptyPermissions();
+
+ for (let perm of perms.permissions) {
+ if (!permissions.includes(perm)) {
+ added.permissions.push(perm);
+ permissions.push(perm);
+ }
+ }
+ for (let origin of perms.origins) {
+ if (!origins.includes(origin)) {
+ added.origins.push(origin);
+ origins.push(origin);
+ }
+ }
+
+ if (added.permissions.length > 0 || added.origins.length > 0) {
+ prefs.saveSoon();
+ // TODO apply the changes
+ }
+ },
+
+ // Revoke permissions from the given extension. `permissions` is
+ // in the format that is passed to browser.permissions.remove().
+ async remove(extension, perms) {
+ await lazyInit();
+
+ if (!prefs.data[extension.id]) {
+ return;
+ }
+ let {permissions, origins} = prefs.data[extension.id];
+
+ let removed = emptyPermissions();
+
+ for (let perm of perms.permissions) {
+ let i = permissions.indexOf(perm);
+ if (i >= 0) {
+ removed.permissions.push(perm);
+ permissions.splice(i, 1);
+ }
+ }
+ for (let origin of perms.origins) {
+ let i = origins.indexOf(origin);
+ if (i >= 0) {
+ removed.origins.push(origin);
+ origins.splice(i, 1);
+ }
+ }
+
+ if (removed.permissions.length > 0 || removed.origins.length > 0) {
+ prefs.saveSoon();
+ // TODO apply the changes
+ }
+ },
+
+ async removeAll(extension) {
+ await lazyInit();
+ delete prefs.data[extension.id];
+ prefs.saveSoon();
+ },
+};
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -1323,17 +1323,41 @@ class MessageManagerProxy {
handleEvent(event) {
if (event.type == "SwapDocShells") {
this.removeListeners(this.eventTarget);
this.addListeners(event.detail);
}
}
}
+/**
+ * Classify an individual permission from a webextension manifest
+ * as a host/origin permission, an api permission, or a regular permission.
+ *
+ * @param {string} perm The permission string to classify
+ *
+ * @returns {object}
+ * An object with exactly one of the following properties:
+ * "origin" to indicate this is a host/origin permission.
+ * "api" to indicate this is an api permission
+ * (as used for webextensions experiments).
+ * "permission" to indicate this is a regular permission.
+ */
+function classifyPermission(perm) {
+ let match = /^(\w+)(?:\.(\w+)(?:\.\w+)*)?$/.exec(perm);
+ if (!match) {
+ return {origin: perm};
+ } else if (match[1] == "experiments" && match[2]) {
+ return {api: match[2]};
+ }
+ return {permission: perm};
+}
+
this.ExtensionUtils = {
+ classifyPermission,
defineLazyGetter,
detectLanguage,
extend,
findPathInObject,
flushJarCache,
getConsole,
getInnerWindowID,
getMessageManager,
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ext-c-permissions.js
@@ -0,0 +1,19 @@
+"use strict";
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+const {ExtensionError} = ExtensionUtils;
+
+extensions.registerSchemaAPI("permissions", "addon_child", context => {
+ return {
+ permissions: {
+ async request(perms) {
+ let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils);
+ if (!winUtils.isHandlingUserInput) {
+ throw new ExtensionError("May only request permissions from a user input handler");
+ }
+
+ return context.childManager.callParentAsyncFunction("permissions.request_parent", [perms]);
+ },
+ },
+ };
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ext-permissions.js
@@ -0,0 +1,97 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPermissions",
+ "resource://gre/modules/ExtensionPermissions.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+const {
+ ExtensionError,
+} = ExtensionUtils;
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "promptsEnabled",
+ "extensions.webextOptionalPermissionPrompts");
+
+extensions.registerSchemaAPI("permission", "addon_parent", context => {
+ return {
+ permissions: {
+ async request_parent(perms) {
+ let {permissions, origins} = perms;
+
+ let manifestPermissions = context.extension.manifest.optional_permissions;
+ for (let perm of permissions) {
+ if (!manifestPermissions.includes(perm)) {
+ throw new ExtensionError(`Cannot request permission ${perm} since it was not declared in optional_permissions`);
+ }
+ }
+
+ let optionalOrigins = context.extension.optionalOrigins;
+ for (let origin of origins) {
+ if (!optionalOrigins.subsumes(origin)) {
+ throw new ExtensionError(`Cannot request origin permission for ${origin} since it was not declared in optional_permissions`);
+ }
+ }
+
+ if (promptsEnabled) {
+ let allow = await new Promise(resolve => {
+ let subject = {
+ wrappedJSObject: {
+ browser: context.xulBrowser,
+ name: context.extension.name,
+ icon: context.extension.iconURL,
+ permissions: {permissions, origins},
+ resolve,
+ },
+ };
+ Services.obs.notifyObservers(subject, "webextension-optional-permission-prompt", null);
+ });
+ if (!allow) {
+ return false;
+ }
+ }
+
+ await ExtensionPermissions.add(context.extension, perms);
+ return true;
+ },
+
+ async getAll() {
+ let perms = context.extension.userPermissions;
+ delete perms.apis;
+ return perms;
+ },
+
+ async contains(permissions) {
+ for (let perm of permissions.permissions) {
+ if (!context.extension.hasPermission(perm)) {
+ return false;
+ }
+ }
+
+ for (let origin of permissions.origins) {
+ if (!context.extension.whiteListedHosts.subsumes(origin)) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ async remove(permissions) {
+ await ExtensionPermissions.remove(context.extension, permissions);
+ return true;
+ },
+ },
+ };
+});
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("uninstall", extension => {
+ ExtensionPermissions.removeAll(extension);
+});
--- a/toolkit/components/extensions/extensions-toolkit.manifest
+++ b/toolkit/components/extensions/extensions-toolkit.manifest
@@ -6,28 +6,30 @@ category webextension-scripts cookies ch
category webextension-scripts downloads chrome://extensions/content/ext-downloads.js
category webextension-scripts extension chrome://extensions/content/ext-extension.js
category webextension-scripts geolocation chrome://extensions/content/ext-geolocation.js
category webextension-scripts handlers chrome://extensions/content/ext-protocolHandlers.js
category webextension-scripts i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts idle chrome://extensions/content/ext-idle.js
category webextension-scripts management chrome://extensions/content/ext-management.js
category webextension-scripts notifications chrome://extensions/content/ext-notifications.js
+category webextension-scripts permissions chrome://extensions/content/ext-permissions.js
category webextension-scripts privacy chrome://extensions/content/ext-privacy.js
category webextension-scripts proxy chrome://extensions/content/ext-proxy.js
category webextension-scripts runtime chrome://extensions/content/ext-runtime.js
category webextension-scripts storage chrome://extensions/content/ext-storage.js
category webextension-scripts theme chrome://extensions/content/ext-theme.js
category webextension-scripts topSites chrome://extensions/content/ext-topSites.js
category webextension-scripts webNavigation chrome://extensions/content/ext-webNavigation.js
category webextension-scripts webRequest chrome://extensions/content/ext-webRequest.js
# scripts specific for content process.
category webextension-scripts-content extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-content i18n chrome://extensions/content/ext-i18n.js
+category webextension-scripts-content permissions chrome://extensions/content/ext-c-permissions.js
category webextension-scripts-content runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-content storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-content test chrome://extensions/content/ext-c-test.js
# scripts specific for devtools extension contexts.
category webextension-scripts-devtools extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-devtools i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts-devtools runtime chrome://extensions/content/ext-c-runtime.js
@@ -36,16 +38,17 @@ category webextension-scripts-devtools t
# scripts that must run in the same process as addon code.
category webextension-scripts-addon backgroundPage chrome://extensions/content/ext-c-backgroundPage.js
category webextension-scripts-addon extension chrome://extensions/content/ext-c-extension.js
category webextension-scripts-addon i18n chrome://extensions/content/ext-i18n.js
#ifndef ANDROID
category webextension-scripts-addon identity chrome://extensions/content/ext-c-identity.js
#endif
+category webextension-scripts-addon permissions chrome://extensions/content/ext-c-permissions.js
category webextension-scripts-addon runtime chrome://extensions/content/ext-c-runtime.js
category webextension-scripts-addon storage chrome://extensions/content/ext-c-storage.js
category webextension-scripts-addon test chrome://extensions/content/ext-c-test.js
# schemas
category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json
category webextension-schemas contextualIdentities chrome://extensions/content/schemas/contextual_identities.json
category webextension-schemas cookies chrome://extensions/content/schemas/cookies.json
@@ -57,16 +60,17 @@ category webextension-schemas handlers c
category webextension-schemas i18n chrome://extensions/content/schemas/i18n.json
#ifndef ANDROID
category webextension-schemas identity chrome://extensions/content/schemas/identity.json
#endif
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 permissions chrome://extensions/content/schemas/permissions.json
category webextension-schemas privacy chrome://extensions/content/schemas/privacy.json
category webextension-schemas proxy chrome://extensions/content/schemas/proxy.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 theme chrome://extensions/content/schemas/theme.json
category webextension-schemas top_sites chrome://extensions/content/schemas/top_sites.json
category webextension-schemas types chrome://extensions/content/schemas/types.json
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -11,27 +11,29 @@ toolkit.jar:
content/extensions/ext-cookies.js
content/extensions/ext-downloads.js
content/extensions/ext-extension.js
content/extensions/ext-geolocation.js
content/extensions/ext-i18n.js
content/extensions/ext-idle.js
content/extensions/ext-management.js
content/extensions/ext-notifications.js
+ content/extensions/ext-permissions.js
content/extensions/ext-privacy.js
content/extensions/ext-protocolHandlers.js
content/extensions/ext-proxy.js
content/extensions/ext-runtime.js
content/extensions/ext-storage.js
content/extensions/ext-theme.js
content/extensions/ext-topSites.js
content/extensions/ext-webRequest.js
content/extensions/ext-webNavigation.js
# Below is a separate group using the naming convention ext-c-*.js that run
# in the child process.
content/extensions/ext-c-backgroundPage.js
content/extensions/ext-c-extension.js
#ifndef ANDROID
content/extensions/ext-c-identity.js
#endif
+ content/extensions/ext-c-permissions.js
content/extensions/ext-c-runtime.js
content/extensions/ext-c-storage.js
content/extensions/ext-c-test.js
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -7,16 +7,17 @@
EXTRA_JS_MODULES += [
'Extension.jsm',
'ExtensionAPI.jsm',
'ExtensionChild.jsm',
'ExtensionCommon.jsm',
'ExtensionContent.jsm',
'ExtensionManagement.jsm',
'ExtensionParent.jsm',
+ 'ExtensionPermissions.jsm',
'ExtensionPreferencesManager.jsm',
'ExtensionSettingsStore.jsm',
'ExtensionStorage.jsm',
'ExtensionStorageSync.jsm',
'ExtensionTabs.jsm',
'ExtensionUtils.jsm',
'LegacyExtensionsUtils.jsm',
'MessageChannel.jsm',
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -17,16 +17,17 @@ toolkit.jar:
#ifndef ANDROID
content/extensions/schemas/identity.json
#endif
content/extensions/schemas/idle.json
content/extensions/schemas/management.json
content/extensions/schemas/manifest.json
content/extensions/schemas/native_host_manifest.json
content/extensions/schemas/notifications.json
+ content/extensions/schemas/permissions.json
content/extensions/schemas/proxy.json
content/extensions/schemas/privacy.json
content/extensions/schemas/runtime.json
content/extensions/schemas/storage.json
content/extensions/schemas/test.json
content/extensions/schemas/theme.json
content/extensions/schemas/top_sites.json
content/extensions/schemas/types.json
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/schemas/permissions.json
@@ -0,0 +1,153 @@
+[
+ {
+ "namespace": "permissions",
+ "permissions": ["manifest:optional_permissions"],
+ "types": [
+ {
+ "id": "Permissions",
+ "type": "object",
+ "properties": {
+ "permissions": {
+ "type": "array",
+ "items": { "$ref": "manifest.OptionalPermission" },
+ "optional": true,
+ "default": []
+ },
+ "origins": {
+ "type": "array",
+ "items": { "$ref": "manifest.MatchPattern" },
+ "optional": true,
+ "default": []
+ }
+ }
+ },
+ {
+ "id": "AnyPermissions",
+ "type": "object",
+ "properties": {
+ "permissions": {
+ "type": "array",
+ "items": { "$ref": "manifest.Permission" },
+ "optional": true,
+ "default": []
+ },
+ "origins": {
+ "type": "array",
+ "items": { "$ref": "manifest.MatchPattern" },
+ "optional": true,
+ "default": []
+ }
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "getAll",
+ "type": "function",
+ "async": "callback",
+ "description": "Get a list of all the extension's permissions.",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "permissions",
+ "$ref": "AnyPermissions"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "contains",
+ "type": "function",
+ "async": "callback",
+ "description": "Check if the extension has the given permissions.",
+ "parameters": [
+ {
+ "name": "permissions",
+ "$ref": "AnyPermissions"
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "result",
+ "type": "boolean"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "request",
+ "type": "function",
+ "allowedContexts": ["content"],
+ "async": "callback",
+ "description": "Request the given permissions.",
+ "parameters": [
+ {
+ "name": "permissions",
+ "$ref": "Permissions"
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "granted",
+ "type": "boolean"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "remove",
+ "type": "function",
+ "async": "callback",
+ "description": "Relinquish the given permissions.",
+ "parameters": [
+ {
+ "name": "permissions",
+ "$ref": "Permissions"
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onAdded",
+ "type": "function",
+ "unsupported": true,
+ "description": "Fired when the extension acquires new permissions.",
+ "parameters": [
+ {
+ "name": "permissions",
+ "$ref": "Permissions"
+ }
+ ]
+ },
+ {
+ "name": "onRemoved",
+ "type": "function",
+ "unsupported": true,
+ "description": "Fired when permissions are removed from the extension.",
+ "parameters": [
+ {
+ "name": "permissions",
+ "$ref": "Permissions"
+ }
+ ]
+ }
+ ]
+ }
+]
--- a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
@@ -68,16 +68,20 @@ let expectedBackgroundApis = [
"extensionTypes.CSSOrigin",
"extensionTypes.ImageFormat",
"extensionTypes.RunAt",
"management.ExtensionDisabledReason",
"management.ExtensionInstallType",
"management.ExtensionType",
"management.getSelf",
"management.uninstallSelf",
+ "permissions.getAll",
+ "permissions.contains",
+ "permissions.request",
+ "permissions.remove",
"runtime.getBackgroundPage",
"runtime.getBrowserInfo",
"runtime.getPlatformInfo",
"runtime.onConnectExternal",
"runtime.onInstalled",
"runtime.onMessageExternal",
"runtime.onStartup",
"runtime.onUpdateAvailable",
--- a/toolkit/modules/addons/MatchPattern.jsm
+++ b/toolkit/modules/addons/MatchPattern.jsm
@@ -16,16 +16,22 @@ XPCOMUtils.defineLazyModuleGetter(this,
this.EXPORTED_SYMBOLS = ["MatchPattern", "MatchGlobs", "MatchURLFilters"];
/* globals MatchPattern, MatchGlobs */
const PERMITTED_SCHEMES = ["http", "https", "file", "ftp", "data"];
const PERMITTED_SCHEMES_REGEXP = PERMITTED_SCHEMES.join("|");
+// The basic RE for matching patterns
+const PATTERN_REGEXP = new RegExp(`^(${PERMITTED_SCHEMES_REGEXP}|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$`);
+
+// The schemes/protocols implied by a pattern that starts with *://
+const WILDCARD_SCHEMES = ["http", "https"];
+
// This function converts a glob pattern (containing * and possibly ?
// as wildcards) to a regular expression.
function globToRegexp(pat, allowQuestion) {
// Escape everything except ? and *.
pat = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");
if (allowQuestion) {
pat = pat.replace(/\?/g, ".");
@@ -42,26 +48,25 @@ function SingleMatchPattern(pat) {
this.pat = pat;
if (pat == "<all_urls>") {
this.schemes = PERMITTED_SCHEMES;
this.hostMatch = () => true;
this.pathMatch = () => true;
} else if (!pat) {
this.schemes = [];
} else {
- let re = new RegExp(`^(${PERMITTED_SCHEMES_REGEXP}|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$`);
- let match = re.exec(pat);
+ let match = PATTERN_REGEXP.exec(pat);
if (!match) {
Cu.reportError(`Invalid match pattern: '${pat}'`);
this.schemes = [];
return;
}
if (match[1] == "*") {
- this.schemes = ["http", "https"];
+ this.schemes = WILDCARD_SCHEMES;
} else {
this.schemes = [match[1]];
}
// We allow the host to be empty for file URLs.
if (match[2] == "" && this.schemes[0] != "file") {
Cu.reportError(`Invalid match pattern: '${pat}'`);
this.schemes = [];
@@ -169,16 +174,33 @@ MatchPattern.prototype = {
return true;
}
}
}
return false;
},
+ // Test if this MatchPattern subsumes the given pattern (i.e., whether
+ // this pattern matches everything the given pattern does).
+ // Note, this method considers only to protocols and hosts/domains,
+ // paths are ignored.
+ subsumes(pattern) {
+ let match = PATTERN_REGEXP.exec(pattern);
+ if (!match) {
+ throw new Error("Invalid match pattern");
+ }
+
+ if (match[1] == "*") {
+ return WILDCARD_SCHEMES.every(scheme => this.matchesIgnoringPath({scheme, host: match[2]}));
+ }
+
+ return this.matchesIgnoringPath({scheme: match[1], host: match[2]});
+ },
+
serialize() {
return this.pat;
},
};
// Globs can match everything. Be careful, this DOES NOT filter by allowed schemes!
this.MatchGlobs = function(globs) {
this.original = globs;