Bug 1322235: Part 3 - Use MatchPatternSet for whiteListedHosts and webRequest. r=aswan,zombie
authorKris Maglione <maglione.k@gmail.com>
Sat, 03 Jun 2017 22:11:20 -0700
changeset 410368 34316bd2fecfb2802b135ea4f8592468c5fc7618
parent 410367 50e2a20caa7a189c0ed5a8679f483a5b10100aaf
child 410369 19d92b19910f3ce9bfa2b2874d17635fe323080b
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, zombie
bugs1322235
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1322235: Part 3 - Use MatchPatternSet for whiteListedHosts and webRequest. r=aswan,zombie This is the second step to migrating the policy service to pure native code, with similar impacts and reasoning to the previous patch. MozReview-Commit-ID: L5XdPzWNZXM
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/ext-tabs.js
browser/components/extensions/test/browser/browser_ext_contextMenus_urlPatterns.js
toolkit/components/extensions/.eslintrc.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionChild.jsm
toolkit/components/extensions/ExtensionCommon.jsm
toolkit/components/extensions/ExtensionPermissions.jsm
toolkit/components/extensions/ExtensionTabs.jsm
toolkit/components/extensions/ext-cookies.js
toolkit/components/extensions/ext-management.js
toolkit/components/extensions/ext-permissions.js
toolkit/components/extensions/ext-webRequest.js
toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
toolkit/modules/addons/MatchPattern.jsm
toolkit/modules/addons/WebRequest.jsm
toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -1,14 +1,12 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-Cu.import("resource://gre/modules/ExtensionManagement.jsm");
-Cu.import("resource://gre/modules/MatchPattern.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 var {
   ExtensionError,
   IconDetails,
@@ -376,21 +374,21 @@ MenuItem.prototype = {
       if (createProperties[propName] === null) {
         // Omitted optional argument.
         continue;
       }
       this[propName] = createProperties[propName];
     }
 
     if (createProperties.documentUrlPatterns != null) {
-      this.documentUrlMatchPattern = new MatchPattern(this.documentUrlPatterns);
+      this.documentUrlMatchPattern = new MatchPatternSet(this.documentUrlPatterns);
     }
 
     if (createProperties.targetUrlPatterns != null) {
-      this.targetUrlMatchPattern = new MatchPattern(this.targetUrlPatterns);
+      this.targetUrlMatchPattern = new MatchPatternSet(this.targetUrlPatterns);
     }
 
     // If a child MenuItem does not specify any contexts, then it should
     // inherit the contexts specified from its parent.
     if (createProperties.parentId && !createProperties.contexts) {
       this.contexts = this.parent.contexts;
     }
   },
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -1,18 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
-XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
-                                  "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 let tabListener = {
@@ -473,17 +471,17 @@ this.tabs = class extends ExtensionAPI {
 
         async query(queryInfo) {
           if (queryInfo.url !== null) {
             if (!extension.hasPermission("tabs")) {
               return Promise.reject({message: 'The "tabs" permission is required to use the query API with the "url" parameter'});
             }
 
             queryInfo = Object.assign({}, queryInfo);
-            queryInfo.url = new MatchPattern(queryInfo.url);
+            queryInfo.url = new MatchPatternSet([].concat(queryInfo.url));
           }
 
           return Array.from(tabManager.query(queryInfo, context),
                             tab => tab.convert());
         },
 
         async captureVisibleTab(windowId, options) {
           let window = windowId == null ?
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_urlPatterns.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_urlPatterns.js
@@ -97,60 +97,60 @@ add_task(async function() {
         title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextAll",
         documentUrlPatterns: ["*://*/*context.html"],
         targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
         contexts: ["all"],
       });
 
       browser.contextMenus.create({
         title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextAll",
-        documentUrlPatterns: ["*://does-not-match"],
+        documentUrlPatterns: ["*://*/does-not-match"],
         targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
         contexts: ["all"],
       });
 
       browser.contextMenus.create({
         title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextAll",
         documentUrlPatterns: ["*://*/*context.html"],
-        targetUrlPatterns: ["*://does-not-match"],
+        targetUrlPatterns: ["*://*/does-not-match"],
         contexts: ["all"],
       });
 
       browser.contextMenus.create({
         title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextAll",
-        documentUrlPatterns: ["*://does-not-match"],
-        targetUrlPatterns: ["*://does-not-match"],
+        documentUrlPatterns: ["*://*/does-not-match"],
+        targetUrlPatterns: ["*://*/does-not-match"],
         contexts: ["all"],
       });
 
       browser.contextMenus.create({
         title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextImage",
         documentUrlPatterns: ["*://*/*context.html"],
         targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
         contexts: ["image"],
       });
 
       browser.contextMenus.create({
         title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextImage",
-        documentUrlPatterns: ["*://does-not-match"],
+        documentUrlPatterns: ["*://*/does-not-match"],
         targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
         contexts: ["image"],
       });
 
       browser.contextMenus.create({
         title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextImage",
         documentUrlPatterns: ["*://*/*context.html"],
-        targetUrlPatterns: ["*://does-not-match"],
+        targetUrlPatterns: ["*://*/does-not-match"],
         contexts: ["image"],
       });
 
       browser.contextMenus.create({
         title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextImage",
-        documentUrlPatterns: ["*://does-not-match"],
-        targetUrlPatterns: ["*://does-not-match"],
+        documentUrlPatterns: ["*://*/does-not-match/"],
+        targetUrlPatterns: ["*://*/does-not-match"],
         contexts: ["image"],
       });
 
       browser.test.notifyPass("contextmenus-urlPatterns");
     },
   });
 
   function confirmContextMenuItems(menu, expected) {
--- a/toolkit/components/extensions/.eslintrc.js
+++ b/toolkit/components/extensions/.eslintrc.js
@@ -6,16 +6,18 @@ module.exports = {
     "Cc": true,
     "Ci": true,
     "Cr": true,
     "Cu": true,
     "TextDecoder": false,
     "TextEncoder": false,
 
     "MatchGlob": false,
+    "MatchPattern": true,
+    "MatchPatternSet": false,
     // Specific to WebExtensions:
     "AppConstants": true,
     "Extension": true,
     "ExtensionAPI": true,
     "ExtensionManagement": true,
     "ExtensionUtils": true,
     "extensions": true,
     "getContainerForCookieStoreId": true,
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -57,18 +57,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                   "resource://gre/modules/ExtensionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestCommon",
                                   "resource://testing-common/ExtensionTestCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                   "resource://gre/modules/Log.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
-                                  "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
@@ -420,17 +418,17 @@ this.ExtensionData = class {
 
   // This method should return a structured representation of any
   // capabilities this extension has access to, as derived from the
   // manifest.  The current implementation just returns the contents
   // of the permissions attribute, if we add things like url_overrides,
   // they should also be added here.
   get userPermissions() {
     let result = {
-      origins: this.whiteListedHosts.pat,
+      origins: this.whiteListedHosts.patterns.map(matcher => matcher.pattern),
       apis: [...this.apiNames],
     };
 
     if (Array.isArray(this.manifest.content_scripts)) {
       for (let entry of this.manifest.content_scripts) {
         result.origins.push(...entry.matches);
       }
     }
@@ -528,25 +526,29 @@ this.ExtensionData = class {
       if (perm === "geckoProfiler") {
         const acceptedExtensions = Preferences.get("extensions.geckoProfiler.acceptedExtensionIds");
         if (!acceptedExtensions.split(",").includes(this.id)) {
           this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
           continue;
         }
       }
 
-      this.permissions.add(perm);
       let type = classifyPermission(perm);
       if (type.origin) {
-        whitelist.push(perm);
+        let matcher = new MatchPattern(perm, {ignorePath: true});
+
+        whitelist.push(matcher);
+        perm = matcher.pattern;
       } else if (type.api) {
         this.apiNames.add(type.api);
       }
+
+      this.permissions.add(perm);
     }
-    this.whiteListedHosts = new MatchPattern(whitelist);
+    this.whiteListedHosts = new MatchPatternSet(whitelist);
 
     for (let api of this.apiNames) {
       this.dependencies.add(`${api}@experiments.addons.mozilla.org`);
     }
 
     return this.manifest;
   }
 
@@ -735,28 +737,34 @@ this.Extension = class extends Extension
 
     /* eslint-disable mozilla/balanced-listeners */
     this.on("add-permissions", (ignoreEvent, permissions) => {
       for (let perm of permissions.permissions) {
         this.permissions.add(perm);
       }
 
       if (permissions.origins.length > 0) {
-        this.whiteListedHosts = new MatchPattern(this.whiteListedHosts.pat.concat(...permissions.origins));
+        let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
+
+        this.whiteListedHosts = new MatchPatternSet([...patterns, ...permissions.origins],
+                                                    {ignorePath: true});
       }
     });
 
     this.on("remove-permissions", (ignoreEvent, permissions) => {
       for (let perm of permissions.permissions) {
         this.permissions.delete(perm);
       }
 
-      for (let origin of permissions.origins) {
-        this.whiteListedHosts.removeOne(origin);
-      }
+      let origins = permissions.origins.map(
+        origin => new MatchPattern(origin, {ignorePath: true}).pattern);
+
+      this.whiteListedHosts = new MatchPatternSet(
+        this.whiteListedHosts.patterns
+            .filter(host => !origins.includes(host.pattern)));
     });
     /* eslint-enable mozilla/balanced-listeners */
   }
 
   static generateXPI(data) {
     return ExtensionTestCommon.generateXPI(data);
   }
 
@@ -854,17 +862,17 @@ this.Extension = class extends Extension
       id: this.id,
       uuid: this.uuid,
       instanceId: this.instanceId,
       manifest: this.manifest,
       resourceURL: this.addonData.resourceURI.spec,
       baseURL: this.baseURI.spec,
       content_scripts: this.manifest.content_scripts || [],  // eslint-disable-line camelcase
       webAccessibleResources: this.webAccessibleResources.map(res => res.glob),
-      whiteListedHosts: this.whiteListedHosts.serialize(),
+      whiteListedHosts: this.whiteListedHosts.patterns.map(pat => pat.pattern),
       localeData: this.localeData.serialize(),
       permissions: this.permissions,
       principal: this.principal,
       optionalPermissions: this.manifest.optional_permissions,
     };
   }
 
   broadcast(msg, data) {
@@ -991,17 +999,20 @@ this.Extension = class extends Extension
 
       GlobalManager.init(this);
 
       // Apply optional permissions
       for (let perm of perms.permissions) {
         this.permissions.add(perm);
       }
       if (perms.origins.length > 0) {
-        this.whiteListedHosts = new MatchPattern(this.whiteListedHosts.pat.concat(...perms.origins));
+        let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
+
+        this.whiteListedHosts = new MatchPatternSet([...patterns, ...perms.origins],
+                                                    {ignorePath: true});
       }
 
       // The "startup" Management event sent on the extension instance itself
       // is emitted just before the Management "startup" event,
       // and it is used to run code that needs to be executed before
       // any of the "startup" listeners.
       this.emit("startup", this);
       Management.emit("startup", this);
@@ -1129,13 +1140,13 @@ this.Extension = class extends Extension
 
   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);
+      this._optionalOrigins = new MatchPatternSet(origins, {ignorePath: true});
     }
     return this._optionalOrigins;
   }
 };
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -21,18 +21,16 @@ const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionContent",
                                   "resource://gre/modules/ExtensionContent.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
-                                  "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
                                   "resource://gre/modules/NativeMessaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
@@ -477,17 +475,17 @@ class BrowserExtensionContent extends Ev
     this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
     Services.cpmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);
 
     defineLazyGetter(this, "scripts", () => {
       return data.content_scripts.map(scriptData => new ExtensionContent.Script(this, scriptData));
     });
 
     this.webAccessibleResources = data.webAccessibleResources.map(res => new MatchGlob(res));
-    this.whiteListedHosts = new MatchPattern(data.whiteListedHosts);
+    this.whiteListedHosts = new MatchPatternSet(data.whiteListedHosts, {ignorePath: true});
     this.permissions = data.permissions;
     this.optionalPermissions = data.optionalPermissions;
     this.principal = data.principal;
 
     this.localeData = new LocaleData(data.localeData);
 
     this.manifest = data.manifest;
     this.baseURI = Services.io.newURI(data.baseURL);
@@ -502,31 +500,37 @@ class BrowserExtensionContent extends Ev
     this.on("add-permissions", (ignoreEvent, permissions) => {
       if (permissions.permissions.length > 0) {
         for (let perm of permissions.permissions) {
           this.permissions.add(perm);
         }
       }
 
       if (permissions.origins.length > 0) {
-        this.whiteListedHosts = new MatchPattern(this.whiteListedHosts.pat.concat(...permissions.origins));
+        let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
+
+        this.whiteListedHosts = new MatchPatternSet([...patterns, ...permissions.origins],
+                                                    {ignorePath: true});
       }
     });
 
     this.on("remove-permissions", (ignoreEvent, permissions) => {
       if (permissions.permissions.length > 0) {
         for (let perm of permissions.permissions) {
           this.permissions.delete(perm);
         }
       }
 
       if (permissions.origins.length > 0) {
-        for (let origin of permissions.origins) {
-          this.whiteListedHosts.removeOne(origin);
-        }
+        let origins = permissions.origins.map(
+          origin => new MatchPattern(origin, {ignorePath: true}).pattern);
+
+        this.whiteListedHosts = new MatchPatternSet(
+          this.whiteListedHosts.patterns
+              .filter(host => !origins.includes(host.pattern)));
       }
     });
     /* eslint-enable mozilla/balanced-listeners */
 
     ExtensionManager.extensions.set(this.id, this);
   }
 
   shutdown() {
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -1062,17 +1062,17 @@ class SchemaAPIManager extends EventEmit
    * @returns {object} A sandbox that is used as the global by `loadScript`.
    */
   _createExtGlobal() {
     let global = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
       wantXrays: false,
       sandboxName: `Namespace of ext-*.js scripts for ${this.processType}`,
     });
 
-    Object.assign(global, {global, Cc, Ci, Cu, Cr, XPCOMUtils, ChromeWorker, extensions: this});
+    Object.assign(global, {global, Cc, Ci, Cu, Cr, XPCOMUtils, ChromeWorker, MatchPattern, MatchPatternSet, extensions: this});
 
     Cu.import("resource://gre/modules/AppConstants.jsm", global);
     Cu.import("resource://gre/modules/ExtensionAPI.jsm", global);
 
     XPCOMUtils.defineLazyGetter(global, "console", getConsole);
 
     XPCOMUtils.defineLazyModuleGetter(global, "ExtensionUtils",
                                       "resource://gre/modules/ExtensionUtils.jsm");
--- a/toolkit/components/extensions/ExtensionPermissions.jsm
+++ b/toolkit/components/extensions/ExtensionPermissions.jsm
@@ -52,17 +52,19 @@ this.ExtensionPermissions = {
     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) {
+      origin = new MatchPattern(origin, {ignorePath: true}).pattern;
       if (!origins.includes(origin)) {
         added.origins.push(origin);
         origins.push(origin);
       }
     }
 
     if (added.permissions.length > 0 || added.origins.length > 0) {
       prefs.saveSoon();
@@ -84,17 +86,20 @@ this.ExtensionPermissions = {
 
     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) {
+      origin = new MatchPattern(origin, {ignorePath: true}).pattern;
+
       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) {
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -545,17 +545,17 @@ class TabBase {
       return Promise.reject({message: `'frameId' and 'allFrames' are mutually exclusive`});
     }
 
     if (this.hasActiveTabPermission) {
       // If we have the "activeTab" permission for this tab, ignore
       // the host whitelist.
       options.matchesHost = ["<all_urls>"];
     } else {
-      options.matchesHost = this.extension.whiteListedHosts.serialize();
+      options.matchesHost = this.extension.whiteListedHosts.patterns.map(host => host.pattern);
     }
 
     if (details.code !== null) {
       options[`${kind}Code`] = details.code;
     }
     if (details.file !== null) {
       let url = context.uri.resolve(details.file);
       if (!this.extension.isExtensionURL(url)) {
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -64,17 +64,17 @@ function checkSetCookiePermissions(exten
   //
   // See source/netwerk/cookie/nsCookieService.cpp, in particular
   // CheckDomain() and SetCookieInternal().
 
   if (uri.scheme != "http" && uri.scheme != "https") {
     return false;
   }
 
-  if (!extension.whiteListedHosts.matchesIgnoringPath(uri)) {
+  if (!extension.whiteListedHosts.matches(uri)) {
     return false;
   }
 
   if (!cookie.host) {
     // If no explicit host is specified, this becomes a host-only cookie.
     cookie.host = uri.host;
     return true;
   }
--- a/toolkit/components/extensions/ext-management.js
+++ b/toolkit/components/extensions/ext-management.js
@@ -51,20 +51,24 @@ function getExtensionInfoForAddon(extens
     enabled: addon.isActive,
     optionsUrl: addon.optionsURL || "",
     installType: installType(addon),
     type: addon.type,
   };
 
   if (extension) {
     let m = extension.manifest;
+
+    let hostPerms = extension.whiteListedHosts.patterns.map(matcher => matcher.pattern);
+
     extInfo.permissions = Array.from(extension.permissions).filter(perm => {
-      return !extension.whiteListedHosts.pat.includes(perm);
+      return !hostPerms.includes(perm);
     });
-    extInfo.hostPermissions = extension.whiteListedHosts.pat;
+    extInfo.hostPermissions = hostPerms;
+
     extInfo.shortName = m.short_name || "";
     if (m.icons) {
       extInfo.icons = Object.keys(m.icons).map(key => {
         return {size: Number(key), url: m.icons[key]};
       });
     }
   }
 
--- a/toolkit/components/extensions/ext-permissions.js
+++ b/toolkit/components/extensions/ext-permissions.js
@@ -25,17 +25,17 @@ this.permissions = class extends Extensi
           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)) {
+            if (!optionalOrigins.subsumes(new MatchPattern(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: {
@@ -66,17 +66,17 @@ this.permissions = class extends Extensi
         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)) {
+            if (!context.extension.whiteListedHosts.subsumes(new MatchPattern(origin))) {
               return false;
             }
           }
 
           return true;
         },
 
         async remove(permissions) {
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -1,14 +1,12 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
-                                  "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
                                   "resource://gre/modules/WebRequest.jsm");
 
 // EventManager-like class specifically for WebRequest. Inherits from
 // SingletonEventManager. Takes care of converting |details| parameter
 // when invoking listeners.
 function WebRequestEventManager(context, eventName) {
   let name = `webRequest.${eventName}`;
@@ -17,23 +15,23 @@ function WebRequestEventManager(context,
       // Prevent listening in on requests originating from system principal to
       // prevent tinkering with OCSP, app and addon updates, etc.
       if (data.isSystemPrincipal) {
         return;
       }
 
       // Check hosts permissions for both the resource being requested,
       const hosts = context.extension.whiteListedHosts;
-      if (!hosts.matchesIgnoringPath(Services.io.newURI(data.url))) {
+      if (!hosts.matches(Services.io.newURI(data.url))) {
         return;
       }
       // and the origin that is loading the resource.
       const origin = data.documentUrl;
       const own = origin && origin.startsWith(context.extension.getURL());
-      if (origin && !own && !hosts.matchesIgnoringPath(Services.io.newURI(origin))) {
+      if (origin && !own && !hosts.matches(Services.io.newURI(origin))) {
         return;
       }
 
       let browserData = {tabId: -1, windowId: -1};
       if (data.browser) {
         browserData = tabTracker.getBrowserData(data.browser);
       }
       if (filter.tabId != null && browserData.tabId != filter.tabId) {
@@ -73,18 +71,22 @@ function WebRequestEventManager(context,
         }
       }
 
       return fire.sync(data2);
     };
 
     let filter2 = {};
     if (filter.urls) {
-      filter2.urls = new MatchPattern(filter.urls);
-      if (!filter2.urls.overlapsPermissions(context.extension.whiteListedHosts, context.extension.optionalOrigins)) {
+      let perms = new MatchPatternSet([...context.extension.whiteListedHosts.patterns,
+                                       ...context.extension.optionalOrigins.patterns]);
+
+      filter2.urls = new MatchPatternSet(filter.urls);
+
+      if (!perms.overlapsAll(filter2.urls)) {
         Cu.reportError("The webRequest.addListener filter doesn't overlap with host permissions.");
       }
     }
     if (filter.types) {
       filter2.types = filter.types;
     }
     if (filter.tabId) {
       filter2.tabId = filter.tabId;
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -24,18 +24,21 @@ function findWinUtils(extension) {
   notEqual(bgwin, null, "Found background window for the test extension");
   return bgwin.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindowUtils);
 }
 
 add_task(async function test_permissions() {
   const REQUIRED_PERMISSIONS = ["downloads"];
   const REQUIRED_ORIGINS = ["*://site.com/", "*://*.domain.com/"];
+  const REQUIRED_ORIGINS_NORMALIZED = ["*://site.com/*", "*://*.domain.com/*"];
+
   const OPTIONAL_PERMISSIONS = ["idle", "clipboardWrite"];
   const OPTIONAL_ORIGINS = ["http://optionalsite.com/", "https://*.optionaldomain.com/"];
+  const OPTIONAL_ORIGINS_NORMALIZED = ["http://optionalsite.com/*", "https://*.optionaldomain.com/*"];
 
   let acceptPrompt = false;
   const observer = {
     observe(subject, topic, data) {
       if (topic == "webextension-optional-permission-prompt") {
         let {resolve} = subject.wrappedJSObject;
         resolve(acceptPrompt);
       }
@@ -87,17 +90,17 @@ add_task(async function test_permissions
 
   function call(method, arg) {
     extension.sendMessage(method, arg);
     return extension.awaitMessage(`${method}.result`);
   }
 
   let result = await call("getAll");
   deepEqual(result.permissions, REQUIRED_PERMISSIONS);
-  deepEqual(result.origins, REQUIRED_ORIGINS);
+  deepEqual(result.origins, REQUIRED_ORIGINS_NORMALIZED);
 
   for (let perm of REQUIRED_PERMISSIONS) {
     result = await call("contains", {permissions: [perm]});
     equal(result, true, `contains() returns true for fixed permission ${perm}`);
   }
   for (let origin of REQUIRED_ORIGINS) {
     result = await call("contains", {origins: [origin]});
     equal(result, true, `contains() returns true for fixed origin ${origin}`);
@@ -150,17 +153,17 @@ add_task(async function test_permissions
   };
   result = await call("request", allOptional);
   equal(result.status, "success", "request() returned cleanly");
   equal(result.result, true, "request() returned true for accepted permissions");
   userInputHandle.destruct();
 
   let allPermissions = {
     permissions: [...REQUIRED_PERMISSIONS, ...OPTIONAL_PERMISSIONS],
-    origins: [...REQUIRED_ORIGINS, ...OPTIONAL_ORIGINS],
+    origins: [...REQUIRED_ORIGINS_NORMALIZED, ...OPTIONAL_ORIGINS_NORMALIZED],
   };
 
   result = await call("getAll");
   deepEqual(result, allPermissions, "getAll() returns required and runtime requested permissions");
 
   result = await call("contains", allPermissions);
   equal(result, true, "contains() returns true for runtime requested permissions");
 
@@ -172,25 +175,25 @@ add_task(async function test_permissions
   deepEqual(result, allPermissions, "Runtime requested permissions are still present after restart");
 
   // Check remove()
   result = await call("remove", {permissions: OPTIONAL_PERMISSIONS});
   equal(result, true, "remove() succeeded");
 
   let perms = {
     permissions: REQUIRED_PERMISSIONS,
-    origins: [...REQUIRED_ORIGINS, ...OPTIONAL_ORIGINS],
+    origins: [...REQUIRED_ORIGINS_NORMALIZED, ...OPTIONAL_ORIGINS_NORMALIZED],
   };
   result = await call("getAll");
   deepEqual(result, perms, "Expected permissions remain after removing some");
 
   result = await call("remove", {origins: OPTIONAL_ORIGINS});
   equal(result, true, "remove() succeeded");
 
-  perms.origins = REQUIRED_ORIGINS;
+  perms.origins = REQUIRED_ORIGINS_NORMALIZED;
   result = await call("getAll");
   deepEqual(result, perms, "Back to default permissions after removing more");
 
   await extension.unload();
 });
 
 add_task(async function test_startup() {
   async function background() {
@@ -202,17 +205,17 @@ add_task(async function test_startup() {
     let all = await browser.permissions.getAll();
     browser.test.sendMessage("perms", all);
   }
 
   const PERMS1 = {
     permissions: ["clipboardRead", "tabs"],
   };
   const PERMS2 = {
-    origins: ["https://site2.com/"],
+    origins: ["https://site2.com/*"],
   };
 
   let extension1 = ExtensionTestUtils.loadExtension({
     background,
     manifest: {optional_permissions: PERMS1.permissions},
     useAddonManager: "permanent",
   });
   let extension2 = ExtensionTestUtils.loadExtension({
--- a/toolkit/modules/addons/MatchPattern.jsm
+++ b/toolkit/modules/addons/MatchPattern.jsm
@@ -106,16 +106,18 @@ SingleMatchPattern.prototype = {
     );
   },
 
   // Tests if this can possibly overlap with the |other| SingleMatchPattern.
   overlapsIgnoringPath(other) {
     return this.schemes.some(scheme => other.schemes.includes(scheme)) &&
            (this.hostMatch(other) || other.hostMatch(this));
   },
+
+  get pattern() { return this.pat; },
 };
 
 this.MatchPattern = function(pat) {
   this.pat = pat;
   if (!pat) {
     this.matchers = [];
   } else if (pat instanceof String || typeof(pat) == "string") {
     this.matchers = [new SingleMatchPattern(pat)];
@@ -131,16 +133,18 @@ this.MatchPattern = function(pat) {
 };
 
 MatchPattern.prototype = {
   // |uri| should be an nsIURI.
   matches(uri) {
     return this.matchers.some(matcher => matcher.matches(uri));
   },
 
+  get patterns() { return this.matchers; },
+
   matchesIgnoringPath(uri, explicit = false) {
     if (explicit) {
       return this.explicitMatchers.some(matcher => matcher.matches(uri, true));
     }
     return this.matchers.some(matcher => matcher.matches(uri, true));
   },
 
   // Checks that this match pattern grants access to read the given
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -304,17 +304,17 @@ var ContentPolicyManager = {
 
   addListener(callback, opts) {
     // Clone opts, since we're going to modify them for IPC.
     opts = Object.assign({}, opts);
     let id = this.nextId++;
     opts.id = id;
     if (opts.filter.urls) {
       opts.filter = Object.assign({}, opts.filter);
-      opts.filter.urls = opts.filter.urls.serialize();
+      opts.filter.urls = opts.filter.urls.patterns.map(url => url.pattern);
     }
     Services.ppmm.broadcastAsyncMessage("WebRequest:AddContentPolicy", opts);
 
     this.policyData.set(id, opts);
 
     this.policies.set(id, callback);
     this.idMap.set(callback, id);
   },
--- a/toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js
@@ -14,17 +14,17 @@ function backgroundGetSelf() {
     browser.test.notifyFail(`getSelf rejected with error: ${error}`);
   });
 }
 /* eslint-enable no-undef */
 
 add_task(async function test_management_get_self_complete() {
   const id = "get_self_test_complete@tests.mozilla.com";
   const permissions = ["management", "cookies"];
-  const hostPermissions = ["*://example.org/", "https://foo.example.org/"];
+  const hostPermissions = ["*://example.org/*", "https://foo.example.org/*"];
 
   let manifest = {
     applications: {
       gecko: {
         id,
         update_url: "https://updates.mozilla.com/",
       },
     },