Bug 1511483 - pageAction show_matches/hide_matches should allow unrestricted schemes on privileged mozilla addons. r=mixedpuppy
authorLuca Greco <lgreco@mozilla.com>
Fri, 30 Nov 2018 22:30:39 +0000
changeset 508695 a30f9bf957fdda96baacd07245fa4f486b1737bc
parent 508694 5732f962288c49e1520f04abb7f2f90b1942b175
child 508696 8447262c404e08f623466e07ef0f56a9adb07639
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmixedpuppy
bugs1511483
milestone65.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 1511483 - pageAction show_matches/hide_matches should allow unrestricted schemes on privileged mozilla addons. r=mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D13582
browser/components/extensions/parent/ext-pageAction.js
browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js
toolkit/components/extensions/Extension.jsm
--- a/browser/components/extensions/parent/ext-pageAction.js
+++ b/browser/components/extensions/parent/ext-pageAction.js
@@ -50,18 +50,19 @@ this.pageAction = class extends Extensio
     let show, showMatches, hideMatches;
     let show_matches = options.show_matches || [];
     let hide_matches = options.hide_matches || [];
     if (!show_matches.length) {
       // Always hide by default. No need to do any pattern matching.
       show = false;
     } else {
       // Might show or hide depending on the URL. Enable pattern matching.
-      showMatches = new MatchPatternSet(show_matches);
-      hideMatches = new MatchPatternSet(hide_matches);
+      const {restrictSchemes} = extension;
+      showMatches = new MatchPatternSet(show_matches, {restrictSchemes});
+      hideMatches = new MatchPatternSet(hide_matches, {restrictSchemes});
     }
 
     this.defaults = {
       show,
       showMatches,
       hideMatches,
       title: options.default_title || extension.name,
       popup: options.default_popup || "",
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js
@@ -170,8 +170,68 @@ add_task(async function test_pageAction_
   info("Check <all_urls> is not allowed in hide_matches");
   let extension = getExtension({
     "show_matches": ["*://mochi.test/*"],
     "hide_matches": ["<all_urls>"],
   });
   let rejects = await extension.startup().then(() => false, () => true);
   is(rejects, true, "startup failed");
 });
+
+add_task(async function test_pageAction_restrictScheme_false() {
+  info("Check restricted origins are allowed in show_matches for privileged extensions");
+  let extension = ExtensionTestUtils.loadExtension({
+    isPrivileged: true,
+    manifest: {
+      permissions: ["mozillaAddons", "tabs"],
+      page_action: {
+        "show_matches": ["about:reader*"],
+        "hide_matches": ["*://*/*"],
+      },
+    },
+    background: function() {
+      browser.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
+        if (changeInfo.url && changeInfo.url.startsWith("about:reader")) {
+          browser.test.sendMessage("readerModeEntered");
+        }
+      });
+
+      browser.test.onMessage.addListener(async (msg) => {
+        if (msg !== "enterReaderMode") {
+          browser.test.fail(`Received unexpected test message: ${msg}`);
+          return;
+        }
+
+        browser.tabs.toggleReaderMode();
+      });
+    },
+  });
+
+  async function expectPageAction(extension, tab, isShown) {
+    await promiseAnimationFrame();
+    let widgetId = makeWidgetId(extension.id);
+    let pageActionId = BrowserPageActions.urlbarButtonNodeIDForActionID(widgetId);
+    let iconEl = document.getElementById(pageActionId);
+
+    if (isShown) {
+      ok(iconEl && !iconEl.hasAttribute("disabled"), "pageAction is shown");
+    } else {
+      ok(iconEl == null || iconEl.getAttribute("disabled") == "true", "pageAction is hidden");
+    }
+  }
+
+  const baseUrl = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+  const url = `${baseUrl}/readerModeArticle.html`;
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, true, true);
+
+  await extension.startup();
+
+  await expectPageAction(extension, tab, false);
+
+  extension.sendMessage("enterReaderMode");
+  await extension.awaitMessage("readerModeEntered");
+
+  await expectPageAction(extension, tab, true);
+
+  BrowserTestUtils.removeTab(tab);
+
+  await extension.unload();
+});
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -462,31 +462,38 @@ class ExtensionData {
           resolve(JSON.parse(text));
         } catch (e) {
           reject(e);
         }
       });
     });
   }
 
+  get restrictSchemes() {
+    // ExtensionData can't check the signature (as it is not yet passed to its constructor
+    // as it is for the Extension class, where this getter is overridden to check both the
+    // signature and the permissions).
+    return !this.hasPermission("mozillaAddons");
+  }
+
   /**
    * Returns an object representing any capabilities that the extension
    * has access to based on fixed properties in the manifest.  The result
    * includes the contents of the "permissions" property as well as other
    * capabilities that are derived from manifest fields that users should
    * be informed of (e.g., origins where content scripts are injected).
    */
   get manifestPermissions() {
     if (this.type !== "extension") {
       return null;
     }
 
     let permissions = new Set();
     let origins = new Set();
-    let restrictSchemes = !this.hasPermission("mozillaAddons");
+    let {restrictSchemes} = this;
     for (let perm of this.manifest.permissions || []) {
       let type = classifyPermission(perm, restrictSchemes);
       if (type.origin) {
         origins.add(perm);
       } else if (type.permission) {
         permissions.add(perm);
       }
     }
@@ -853,17 +860,19 @@ class ExtensionData {
     this.type = manifestData.type;
 
     this.modules = manifestData.modules;
 
     this.apiManager = this.getAPIManager();
     await this.apiManager.lazyInit();
 
     this.webAccessibleResources = manifestData.webAccessibleResources.map(res => new MatchGlob(res));
-    this.whiteListedHosts = new MatchPatternSet(manifestData.originPermissions, {restrictSchemes: !this.hasPermission("mozillaAddons")});
+    this.whiteListedHosts = new MatchPatternSet(manifestData.originPermissions, {
+      restrictSchemes: this.restrictSchemes,
+    });
 
     return this.manifest;
   }
 
   hasPermission(perm, includeOptional = false) {
     // If the permission is a "manifest property" permission, we check if the extension
     // does have the required property in its manifest.
     let manifest_ = "manifest:";
@@ -1371,18 +1380,20 @@ class Extension extends ExtensionData {
     this.on("add-permissions", (ignoreEvent, permissions) => {
       for (let perm of permissions.permissions) {
         this.permissions.add(perm);
       }
 
       if (permissions.origins.length > 0) {
         let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
 
-        this.whiteListedHosts = new MatchPatternSet(new Set([...patterns, ...permissions.origins]),
-                                                    {restrictSchemes: !this.hasPermission("mozillaAddons"), ignorePath: true});
+        this.whiteListedHosts = new MatchPatternSet(new Set([...patterns, ...permissions.origins]), {
+          restrictSchemes: this.restrictSchemes,
+          ignorePath: true,
+        });
       }
 
       this.policy.permissions = Array.from(this.permissions);
       this.policy.allowedOrigins = this.whiteListedHosts;
 
       this.cachePermissions();
     });
 
@@ -1401,16 +1412,20 @@ class Extension extends ExtensionData {
       this.policy.permissions = Array.from(this.permissions);
       this.policy.allowedOrigins = this.whiteListedHosts;
 
       this.cachePermissions();
     });
     /* eslint-enable mozilla/balanced-listeners */
   }
 
+  get restrictSchemes() {
+    return !(this.isPrivileged && this.hasPermission("mozillaAddons"));
+  }
+
   // Some helpful properties added elsewhere:
   /**
    * An object used to map between extension-visible tab ids and
    * native Tab object
    * @property {TabManager} tabManager
    */
 
   static getBootstrapScope(id, file) {
@@ -2010,17 +2025,17 @@ class Extension extends ExtensionData {
   }
 
   get name() {
     return this.manifest.name;
   }
 
   get optionalOrigins() {
     if (this._optionalOrigins == null) {
-      let restrictSchemes = !this.hasPermission("mozillaAddons");
+      let {restrictSchemes} = this;
       let origins = this.manifest.optional_permissions.filter(perm => classifyPermission(perm, restrictSchemes).origin);
       this._optionalOrigins = new MatchPatternSet(origins, {restrictSchemes, ignorePath: true});
     }
     return this._optionalOrigins;
   }
 }
 
 class Dictionary extends ExtensionData {