Bug 1270416 - handler registration permission control integration with Control Center. r?Paolo Amadini draft
authorEden Chuang <echuang@mozilla.com>
Thu, 05 Jan 2017 22:45:30 +0800
changeset 456414 6c6333739c8493b0ca88f88b74f939f2c0f7b784
parent 456413 929b0a29a592316ce98cc13a9c74c69fd98dd9c8
child 541211 c7b4491dd3038ccd6da2f5292a541fbdde170c40
push id40480
push userechuang@mozilla.com
push dateThu, 05 Jan 2017 14:46:31 +0000
reviewersPaolo
bugs1270416
milestone53.0a1
Bug 1270416 - handler registration permission control integration with Control Center. r?Paolo Amadini MozReview-Commit-ID: HIg2vc68sk6
browser/base/content/pageinfo/permissions.js
browser/components/feeds/test/browser/browser.ini
browser/components/feeds/test/browser/browser_handlerRegistrationPermissionControl.js
browser/components/feeds/test/browser/head.js
browser/locales/en-US/chrome/browser/sitePermissions.properties
browser/modules/SitePermissions.jsm
browser/modules/test/xpcshell/test_SitePermissions.js
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -69,16 +69,22 @@ function onUnloadPermission()
 
 function initRow(aPartId)
 {
   if (aPartId == "plugins") {
     initPluginsRow();
     return;
   }
 
+  // don't show the handler-registration permissions which are not in the database
+  if (aPartId.startsWith("handler-registration") &&
+      SitePermissions.get(gPermURI, aPartId) == SitePermissions.UNKNOWN) {
+    return;
+  }
+
   createRow(aPartId);
 
   var checkbox = document.getElementById(aPartId + "Def");
   var command  = document.getElementById("cmd_" + aPartId + "Toggle");
   var perm = SitePermissions.get(gPermURI, aPartId);
 
   if (perm) {
     checkbox.checked = false;
@@ -172,16 +178,23 @@ function onPluginRadioClick(aEvent) {
   onRadioClick(aEvent.originalTarget.getAttribute("id").split('#')[0]);
 }
 
 function onRadioClick(aPartId)
 {
   var radioGroup = document.getElementById(aPartId + "RadioGroup");
   var id = radioGroup.selectedItem.id;
   var permission = id.split('#')[1];
+
+  // only allow one site for handler-registration-XXX permission
+  if (permission == SitePermissions.ALLOW &&
+      aPartId.startsWith("handler-registration")) {
+    SitePermissions.updateOtherSitesPermission(aPartId, SitePermissions.BLOCK);
+  }
+
   SitePermissions.set(gPermURI, aPartId, permission);
 }
 
 function setRadioState(aPartId, aValue)
 {
   var radio = document.getElementById(aPartId + "#" + aValue);
   radio.radioGroup.selectedItem = radio;
 }
--- a/browser/components/feeds/test/browser/browser.ini
+++ b/browser/components/feeds/test/browser/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
   head.js
   protocolHandler.html
   superLongProtocolHandler.html
   multipleRegistration.html
 
 [browser_registerHandlerWithPermissionUI.js]
+[browser_handlerRegistrationPermissionControl.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/browser/browser_handlerRegistrationPermissionControl.js
@@ -0,0 +1,208 @@
+"use strict";
+Cu.import('resource://gre/modules/Services.jsm');
+
+let gPermsSvc = Services.perms;
+let gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].
+                     getService(Ci.nsIHandlerService);
+let gExtProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+                         getService(Ci.nsIExternalProtocolService);
+const kTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
+                                                      "https://example.com");
+
+const kDefaultHandler = {
+  uri: Services.io.newURI("https://test1.example.org/testprotocol=%s", null, null),
+  name: "testprotocol default handler",
+};
+const kTestObj = {
+  url: kTestRoot + "protocolHandler.html",
+  protocolType: "testprotocol",
+  permissionKey: "handler-registration-testprotocol",
+  handlerName: "testprotocol handler",
+  handlerUriTemplate: "https://example.com/foobar?uri=%s",
+};
+const CONTROL_CENTER_PANEL = gIdentityHandler._identityPopup;
+
+registerCleanupFunction(function() {
+  gPermsSvc.remove(kDefaultHandler.uri, kTestObj.permissionKey);
+  let handlerInfo = gExtProtocolSvc.getProtocolHandlerInfo(kTestObj.protocolType);
+  if (gHandlerSvc.exists(handlerInfo)) {
+    gHandlerSvc.remove(handlerInfo);
+  }
+});
+
+function setupDefaultHandler()
+{
+  let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+                   createInstance(Ci.nsIWebHandlerApp);
+  handler.name = kDefaultHandler.name;
+  handler.uriTemplate = kDefaultHandler.uri.spec;
+  let handlerInfo = gExtProtocolSvc.getProtocolHandlerInfo(kTestObj.protocolType);
+  handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
+  handlerInfo.alwaysAskBeforeHandling = false;
+  handlerInfo.preferredApplicationHandler = handler;
+  handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+  gHandlerSvc.store(handlerInfo);
+  gPermsSvc.add(kDefaultHandler.uri,
+                kTestObj.permissionKey,
+                gPermsSvc.ALLOW_ACTION,
+                gPermsSvc.EXIPRE_NEVER);
+}
+
+function* openIdentityPopup() {
+  let shownPromise =
+    BrowserTestUtils.waitForEvent(CONTROL_CENTER_PANEL, "popupshown");
+  gIdentityHandler._identityBox.click();
+  return shownPromise;
+}
+
+function* closeIdentityPopup() {
+  let closePromise =
+    BrowserTestUtils.waitForEvent(CONTROL_CENTER_PANEL, "popuphidden");
+  gIdentityHandler._identityPopup.hidePopup();
+  return closePromise;
+}
+
+let permissionElementsGetter = {
+  permissionsList() {
+    return document.getElementById("identity-popup-permission-list");
+  },
+  permissionLabels() {
+    return this.permissionsList().querySelectorAll(".identity-popup-permission-label");
+  },
+  cancelButtons() {
+    return this.permissionsList().querySelectorAll(".identity-popup-permission-remove-button");
+  },
+}
+
+
+/**
+ *  Test handler-registration permission control with Control Center.
+ */
+add_task(function*() {
+  yield BrowserTestUtils.withNewTab(
+    kTestObj.url,
+    function* (browser) {
+      let principal = browser.contentPrincipal;
+
+      yield clickButton(false);
+
+      // check permissions' label on control center
+      isnot(CONTROL_CENTER_PANEL, null, "control center panel should not be null.");
+      yield openIdentityPopup();
+
+      let labels = permissionElementsGetter.permissionLabels();
+      is(labels.length, 1,
+         "Should be only one permission in the permission list of control center.");
+
+      // click the cancel button to remove the permission
+      let cancelButtons = permissionElementsGetter.cancelButtons();
+      cancelButtons[0].click();
+
+      labels = permissionElementsGetter.permissionLabels();
+      is(labels.length, 0,
+         "Should be no permissions in the permission list of control center.");
+
+      is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+         gPermsSvc.UNKNOWN_ACTION,
+         "Handler registration permission for " + kTestObj.protocolType + " protocol should be removed.");
+
+      yield closeIdentityPopup();
+
+      // cleanup permissions and handler info
+      gPermsSvc.removeFromPrincipal(principal, kTestObj.permissionKey);
+      gPermsSvc.remove(kDefaultHandler.uri, kTestObj.permissionKey);
+      let handlerInfo = gExtProtocolSvc.getProtocolHandlerInfo(kTestObj.protocolType);
+      if (gHandlerSvc.exists(handlerInfo)) {
+        gHandlerSvc.remove(handlerInfo);
+      }
+    }
+  );
+});
+
+/**
+ *  Test handler-registration permission control with page info.
+ */
+add_task(function*() {
+
+  yield BrowserTestUtils.withNewTab(
+    kTestObj.url,
+    function* (browser) {
+      let principal = browser.contentPrincipal;
+      setupDefaultHandler();
+      yield clickButton(false);
+
+      let pageInfo;
+      let shownPromise =  new Promise(resolve => {
+        function observer(subject, topic, data) {
+          let observedWindow = subject.QueryInterface(Ci.nsIDOMWindow);
+          Services.obs.removeObserver(observer, "page-info-dialog-loaded");
+          pageInfo.onFinished.push(() => {
+            // check the radio buttons status of page info's permission tab
+            let permissionRadioGroup = pageInfo.document.
+              getElementById(kTestObj.permissionKey + "RadioGroup");
+            isnot(permissionRadioGroup, null,
+                  "Radio button group should not be null.");
+            let permissionRadioAllow = pageInfo.document.
+              getElementById(kTestObj.permissionKey + "#1");
+            isnot(permissionRadioAllow, null,
+                  "Allow radio button should not be null.");
+            let permissionRadioBlock = pageInfo.document.
+              getElementById(kTestObj.permissionKey + "#2");
+            isnot(permissionRadioBlock, null,
+                  "Block radio button should not be null.");
+            is(permissionRadioGroup.selectedItem, permissionRadioBlock,
+               "Should be selected on block radio buttion.");
+            is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+               gPermsSvc.DENY_ACTION,
+               "Permission should be DENY ACTION");
+            is(gPermsSvc.testExactPermission(kDefaultHandler.uri, kTestObj.permissionKey),
+               gPermsSvc.ALLOW_ACTION,
+               "Permission of default handler should be ALLOW ACTION");
+
+            // click allow radio button and then test the permission status
+            permissionRadioAllow.click();
+            is(permissionRadioGroup.selectedItem, permissionRadioAllow,
+               "Should be selected on allow radio buttion.");
+            is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+               gPermsSvc.ALLOW_ACTION,
+               "Permission should be ALLOW ACTION");
+            is(gPermsSvc.testExactPermission(kDefaultHandler.uri, kTestObj.permissionKey),
+               gPermsSvc.DENY_ACTION,
+               "Permission of default handler should be DENY ACTION");
+
+            // click block radio button and then test the permission status
+            permissionRadioBlock.click();
+            is(gPermsSvc.testExactPermissionFromPrincipal(principal, kTestObj.permissionKey),
+               gPermsSvc.DENY_ACTION,
+               "Permission should be DENY ACTION");
+            is(gPermsSvc.testExactPermission(kDefaultHandler.uri, kTestObj.permissionKey),
+               gPermsSvc.DENY_ACTION,
+               "Permission of default handler should be DENY ACTION");
+
+            // check the useDefault checkbox
+            let useDefaultCheckbox = pageInfo.document.
+              getElementById(kTestObj.permissionKey + "Def");
+            isnot(useDefaultCheckbox, null,
+                  "useDefault checkbox should not be null");
+            ok(!useDefaultCheckbox.checked, "useDefault checkbox should not be checked");
+
+            pageInfo.close();
+          });
+          resolve(observedWindow);
+        }
+        Services.obs.addObserver(observer, "page-info-dialog-loaded", false);
+      });
+      pageInfo = BrowserPageInfo(browser.currentURI.spec, "permTab");
+      let win = yield shownPromise;
+      yield BrowserTestUtils.domWindowClosed(win);
+
+      // cleanup permissions and handlerInfo
+      gPermsSvc.removeFromPrincipal(principal, kTestObj.permissionKey);
+      gPermsSvc.remove(kDefaultHandler.uri, kTestObj.permissionKey);
+      let handlerInfo = gExtProtocolSvc.getProtocolHandlerInfo(kTestObj.protocolType);
+      if (gHandlerSvc.exists(handlerInfo)) {
+        gHandlerSvc.remove(handlerInfo);
+      }
+    }
+  );
+});
--- a/browser/components/feeds/test/browser/head.js
+++ b/browser/components/feeds/test/browser/head.js
@@ -1,15 +1,15 @@
 function getPopupNotificationNode() {
   let popupNotifications = PopupNotifications.panel.childNodes;
   is(popupNotifications.length, 1, "Should be showing a <xul:popupnotification>");
   return popupNotifications[0];
 }
 
-function clickButton(aAlwaysAllow) {
+function* clickButton(aAlwaysAllow) {
   let removePromise =
     BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
   let popupNotification = getPopupNotificationNode();
   if (aAlwaysAllow) {
     popupNotification.button.click();
   } else {
     popupNotification.secondaryButton.click();
   }
--- a/browser/locales/en-US/chrome/browser/sitePermissions.properties
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -13,8 +13,9 @@ permission.desktop-notification2.label =
 permission.image.label = Load Images
 permission.camera.label = Use the Camera
 permission.microphone.label = Use the Microphone
 permission.screen.label = Share the Screen
 permission.install.label = Install Add-ons
 permission.popup.label = Open Pop-up Windows
 permission.geo.label = Access Your Location
 permission.indexedDB.label = Maintain Offline Storage
+permission.handler-registration.label = Set Default Application for %S Links
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -162,16 +162,22 @@ this.SitePermissions = {
     Services.perms.remove(aURI, aPermissionID);
   },
 
   /* Returns the localized label for the permission with the given ID, to be
    * used in a UI for managing permissions.
    */
   getPermissionLabel(aPermissionID) {
     let labelID = gPermissionObject[aPermissionID].labelID || aPermissionID;
+    if (labelID == "handler-registration") {
+      let params = [aPermissionID.slice(labelID.length + 1)];
+      return gStringBundle.formatStringFromName("permission." + labelID + ".label",
+                                                params,
+                                                params.length);
+    }
     return gStringBundle.GetStringFromName("permission." + labelID + ".label");
   },
 
   /* Returns the localized label for the given permission state, to be used in
    * a UI for managing permissions.
    */
   getStateLabel(aPermissionID, aState, aInUse = false) {
     switch (aState) {
@@ -183,16 +189,26 @@ this.SitePermissions = {
         return gStringBundle.GetStringFromName("allow");
       case this.SESSION:
         return gStringBundle.GetStringFromName("allowForSession");
       case this.BLOCK:
         return gStringBundle.GetStringFromName("block");
       default:
         throw new Error("unknown permission state");
     }
+  },
+
+  updateOtherSitesPermission(aPermissionID, aState) {
+    let perms = Services.perms.enumerator;
+    while (perms.hasMoreElements()) {
+      let perm = perms.getNext();
+      if (perm.type == aPermissionID && perm.capability != aState) {
+        Services.perms.addFromPrincipal(perm.principal, aPermissionID, aState);
+      }
+    }
   }
 };
 
 var gPermissionObject = {
   /* Holds permission ID => options pairs.
    *
    * Supported options:
    *
@@ -258,12 +274,183 @@ var gPermissionObject = {
                SitePermissions.BLOCK : SitePermissions.ALLOW;
     }
   },
 
   "geo": {
     exactHostMatch: true
   },
 
-  "indexedDB": {}
+  "indexedDB": {},
+
+  // only show the protocol of whitelist in w3c spec,
+  // except handler-registration-testprotocl for testing
+  // detail in https://www.w3.org/TR/html5/webappapis.html
+  "handler-registration-bitcoin": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-geo": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-im": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-irc": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-ircs": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-magnet": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-mailto": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-mms": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-news": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-nntp": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-openpgp4fpr": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-sip": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-sms": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-smsto": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-ssh": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-tel": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-urn": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-webcal": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-wtai": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-xmpp": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
+
+  "handler-registration-testprotocol": {
+    labelID: "handler-registration",
+    exactHostMatch: true,
+    getDefault() {
+      return SitePermissions.ALLOW;
+    }
+  },
 };
 
 const kPermissionIDs = Object.keys(gPermissionObject);
--- a/browser/modules/test/xpcshell/test_SitePermissions.js
+++ b/browser/modules/test/xpcshell/test_SitePermissions.js
@@ -3,18 +3,28 @@
  */
 "use strict";
 
 Components.utils.import("resource:///modules/SitePermissions.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 add_task(function* testPermissionsListing() {
   Assert.deepEqual(SitePermissions.listPermissions().sort(),
-    ["camera", "cookie", "desktop-notification", "geo", "image",
-     "indexedDB", "install", "microphone", "popup", "screen"],
+    ["camera", "cookie", "desktop-notification", "geo",
+     "handler-registration-bitcoin", "handler-registration-geo",
+     "handler-registration-im", "handler-registration-irc",
+     "handler-registration-ircs", "handler-registration-magnet",
+     "handler-registration-mailto", "handler-registration-mms",
+     "handler-registration-news", "handler-registration-nntp",
+     "handler-registration-openpgp4fpr", "handler-registration-sip",
+     "handler-registration-sms", "handler-registration-smsto",
+     "handler-registration-ssh", "handler-registration-tel",
+     "handler-registration-urn", "handler-registration-webcal",
+     "handler-registration-wtai", "handler-registration-xmpp",
+     "image", "indexedDB", "install", "microphone", "popup", "screen"],
     "Correct list of all permissions");
 });
 
 add_task(function* testGetAllByURI() {
   // check that it returns an empty array on an invalid URI
   // like a file URI, which doesn't support site permissions
   let wrongURI = Services.io.newURI("file:///example.js", null, null)
   Assert.deepEqual(SitePermissions.getAllByURI(wrongURI), []);