Bug 1369482 - Make hostnames in permission prompts much more prominent. r?johannh draft
authorPrathiksha <prathikshaprasadsuman@gmail.com>
Thu, 14 Dec 2017 21:00:28 +0530
changeset 720488 6f16e4192251d91dfa1b440c25b28d8c96e38600
parent 720403 a00e3a3dbad43759ee9474d6c7c908fa16deaee8
child 746081 01abfb18b536cf6712a34d1ca98bd5ffe178144d
push id95562
push userbmo:prathikshaprasadsuman@gmail.com
push dateMon, 15 Jan 2018 17:46:54 +0000
reviewersjohannh
bugs1369482
milestone59.0a1
Bug 1369482 - Make hostnames in permission prompts much more prominent. r?johannh MozReview-Commit-ID: 89m2QVzj8DG
browser/base/content/test/general/browser_bug553455.js
browser/base/content/test/popupNotifications/head.js
browser/modules/PermissionUI.jsm
browser/modules/webrtcUI.jsm
toolkit/content/widgets/notification.xml
toolkit/modules/PopupNotifications.jsm
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -239,17 +239,17 @@ async function test_disabledInstall() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "amosigned.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Enable", "Should have seen the right button");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "Software installation is currently disabled. Click Enable and try again.");
 
   let closePromise = waitForNotificationClose();
   // Click on Enable
   EventUtils.synthesizeMouseAtCenter(notification.button, {});
   await closePromise;
 
   try {
@@ -270,17 +270,17 @@ async function test_blockedInstall() {
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Allow", "Should have seen the right button");
   is(notification.getAttribute("origin"), "example.com",
      "Should have seen the right origin host");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      gApp + " prevented this site from asking you to install software on your computer.",
      "Should have seen the right message");
 
   let dialogPromise = waitForInstallDialog();
   // Click on Allow
   EventUtils.synthesizeMouse(notification.button, 20, 10, {});
   // Notification should have changed to progress notification
   ok(PopupNotifications.isPanelOpen, "Notification should still be open");
@@ -289,17 +289,17 @@ async function test_blockedInstall() {
   let installDialog = await dialogPromise;
 
   notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   panel = await notificationPromise;
 
   notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
   await removeTab();
 },
@@ -326,17 +326,17 @@ async function test_whitelistedInstall()
      "tab selected in response to the addon-install-confirmation notification");
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
@@ -352,17 +352,17 @@ async function test_failedDownload() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "missing.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let panel = await failPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "The add-on could not be downloaded because of a connection failure.",
      "Should have seen the right message");
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTab();
 },
 
 async function test_corruptFile() {
@@ -374,17 +374,17 @@ async function test_corruptFile() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "corrupt.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let panel = await failPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "The add-on downloaded from this site could not be installed " +
      "because it appears to be corrupt.",
      "Should have seen the right message");
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTab();
 },
 
@@ -397,17 +397,17 @@ async function test_incompatible() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "incompatible.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let panel = await failPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "XPI Test could not be installed because it is not compatible with " +
      gApp + " " + gVersion + ".",
      "Should have seen the right message");
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTab();
 },
 
@@ -536,17 +536,17 @@ async function test_allUnverified() {
   let triggers = encodeURIComponent(JSON.stringify({
     "Extension XPI": "restartless-unsigned.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let installDialog = await dialogPromise;
 
   let notification = document.getElementById("addon-install-confirmation-notification");
-  let message = notification.getAttribute("label");
+  let message = notification.getAttribute("startlabel");
   is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
 
   let container = document.getElementById("addon-install-confirmation-content");
   is(container.childNodes.length, 1, "Should be one item listed");
   is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
   is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
 
   let notificationPromise = waitForNotification("addon-installed");
@@ -574,17 +574,17 @@ async function test_url() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
 
   await removeTab();
@@ -611,17 +611,17 @@ async function test_localFile() {
   gBrowser.loadURI(path);
   await failPromise;
 
   // Wait for the browser code to add the failure notification
   await waitForSingleNotification();
 
   let notification = PopupNotifications.panel.childNodes[0];
   is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "This add-on could not be installed because it appears to be corrupt.",
      "Should have seen the right message");
 
   await removeTab();
 },
 
 async function test_tabClose() {
   if (!Services.prefs.getBoolPref("xpinstall.customConfirmationUI", false)) {
@@ -700,17 +700,17 @@ async function test_urlBar() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
 
   await removeTab();
@@ -726,17 +726,17 @@ async function test_wrongHost() {
 
   let progressPromise = waitForProgressNotification();
   let notificationPromise = waitForNotification("addon-install-failed");
   gBrowser.loadURI(TESTROOT + "corrupt.xpi");
   await progressPromise;
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "The add-on downloaded from this site could not be installed " +
      "because it appears to be corrupt.",
      "Should have seen the right message");
 
   await removeTab();
 },
 
 async function test_reload() {
@@ -753,17 +753,17 @@ async function test_reload() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   function testFail() {
     ok(false, "Reloading should not have hidden the notification");
   }
   PopupNotifications.panel.addEventListener("popuphiding", testFail);
   let requestedUrl = TESTROOT2 + "enabled.html";
@@ -794,17 +794,17 @@ async function test_theme() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("label"),
+  is(notification.getAttribute("startlabel"),
      "Theme Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let addon = await new Promise(resolve => {
     AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(result) {
       resolve(result);
     });
   });
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -198,17 +198,25 @@ function checkPopup(popup, notifyObj) {
     return;
   let icon = document.getAnonymousElementByAttribute(notification, "class",
                                                      "popup-notification-icon");
   if (notifyObj.id == "geolocation") {
     isnot(icon.boxObject.width, 0, "icon for geo displayed");
     ok(popup.anchorNode.classList.contains("notification-anchor-icon"),
        "notification anchored to icon");
   }
-  is(notification.getAttribute("label"), notifyObj.message, "message matches");
+
+  if (typeof notifyObj.message == "string") {
+    is(notification.getAttribute("startlabel"), notifyObj.message, "message matches");
+  } else {
+    is(notification.getAttribute("startlabel"), notifyObj.message.start, "message matches");
+    is(notification.getAttribute("hostname"), notifyObj.message.host, "message matches");
+    is(notification.getAttribute("endlabel"), notifyObj.message.end, "message matches");
+  }
+
   is(notification.id, notifyObj.id + "-notification", "id matches");
   if (notifyObj.mainAction) {
     is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
        "main action label matches");
     is(notification.getAttribute("buttonaccesskey"),
        notifyObj.mainAction.accessKey, "main action accesskey matches");
     is(notification.getAttribute("buttonhighlight"),
        (!notifyObj.mainAction.disableHighlight).toString(),
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -456,26 +456,30 @@ GeolocationPermissionPrompt.prototype = 
     return "geolocation";
   },
 
   get anchorID() {
     return "geo-notification-icon";
   },
 
   get message() {
-    let message;
+    let message = {};
     if (this.principal.URI.schemeIs("file")) {
-      message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile3");
+      message.start = gBrowserBundle.GetStringFromName("geolocation.shareWithFile3");
     } else {
-      let hostPort = "<>";
+      let header = gBrowserBundle.formatStringFromName("geolocation.shareWithSite3",
+                                                    ["<>"], 1);
+      header = header.split("<>");
+      message.end = header[1];
+      message.start = header[0];
+
+      message.host = "<>";
       try {
-        hostPort = this.principal.URI.hostPort;
+        message.host = this.principal.URI.hostPort;
       } catch (ex) { }
-      message = gBrowserBundle.formatStringFromName("geolocation.shareWithSite3",
-                                                    [hostPort], 1);
     }
     return message;
   },
 
   get promptActions() {
     // We collect Telemetry data on Geolocation prompts and how users
     // respond to them. The probe keys are a bit verbose, so let's alias them.
     const SHARE_LOCATION =
@@ -554,22 +558,30 @@ DesktopNotificationPermissionPrompt.prot
     return "web-notifications";
   },
 
   get anchorID() {
     return "web-notifications-notification-icon";
   },
 
   get message() {
-    let hostPort = "<>";
+    let message = {};
+
+    message.host = "<>";
     try {
-      hostPort = this.principal.URI.hostPort;
+      message.host = this.principal.URI.hostPort;
     } catch (ex) { }
-    return gBrowserBundle.formatStringFromName("webNotifications.receiveFromSite2",
-                                               [hostPort], 1);
+
+    let header = gBrowserBundle.formatStringFromName("webNotifications.receiveFromSite2",
+                                                    ["<>"], 1);
+    header = header.split("<>");
+    message.end = header[1];
+    message.start = header[0];
+
+    return message;
   },
 
   get promptActions() {
     let actions = [
       {
         label: gBrowserBundle.GetStringFromName("webNotifications.allow"),
         accessKey:
           gBrowserBundle.GetStringFromName("webNotifications.allow.accesskey"),
@@ -625,35 +637,44 @@ PersistentStoragePermissionPrompt.protot
     if (checkbox.show) {
       checkbox.checked = true;
       checkbox.label = gBrowserBundle.GetStringFromName("persistentStorage.remember");
     }
     let learnMoreURL =
       Services.urlFormatter.formatURLPref("app.support.baseURL") + "storage-permissions";
     return {
       checkbox,
-      learnMoreURL
+      learnMoreURL,
+      displayURI: false
     };
   },
 
   get notificationID() {
     return "persistent-storage";
   },
 
   get anchorID() {
     return "persistent-storage-notification-icon";
   },
 
   get message() {
-    let hostPort = "<>";
+    let message = {};
+
+    message.host = "<>";
     try {
-      hostPort = this.principal.URI.hostPort;
+      message.host = this.principal.URI.hostPort;
     } catch (ex) {}
-    return gBrowserBundle.formatStringFromName(
-      "persistentStorage.allowWithSite", [hostPort], 1);
+
+    let header = gBrowserBundle.formatStringFromName("persistentStorage.allowWithSite",
+                                                    ["<>"], 1);
+    header = header.split("<>");
+    message.end = header[1];
+    message.start = header[0];
+
+    return message;
   },
 
   get promptActions() {
     return [
       {
         label: gBrowserBundle.GetStringFromName("persistentStorage.allow"),
         accessKey:
           gBrowserBundle.GetStringFromName("persistentStorage.allow.accesskey"),
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -370,17 +370,18 @@ function prompt(aBrowser, aRequest) {
 
   let uri;
   try {
     // This fails for principals that serialize to "null", e.g. file URIs.
     uri = Services.io.newURI(aRequest.origin);
   } catch (e) {
     uri = Services.io.newURI(aRequest.documentURI);
   }
-  let host = getHost(uri);
+  let message = {};
+  message.host = getHost(uri);
   let chromeDoc = aBrowser.ownerDocument;
   let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
 
   // Mind the order, because for simplicity we're iterating over the list using
   // "includes()". This allows the rotation of string identifiers. We list the
   // full identifiers here so they can be cross-referenced more easily.
   let joinedRequestTypes = requestTypes.join("And");
   let stringId = [
@@ -391,17 +392,20 @@ function prompt(aBrowser, aRequest) {
     "getUserMedia.shareAudioCapture2.message",
     // Combinations of the above request types last.
     "getUserMedia.shareCameraAndMicrophone2.message",
     "getUserMedia.shareCameraAndAudioCapture2.message",
     "getUserMedia.shareScreenAndMicrophone3.message",
     "getUserMedia.shareScreenAndAudioCapture3.message",
   ].find(id => id.includes(joinedRequestTypes));
 
-  let message = stringBundle.getFormattedString(stringId, [host]);
+  let header = stringBundle.getFormattedString(stringId, ["<>"], 1);
+  header = header.split("<>");
+  message.end = header[1];
+  message.start = header[0];
 
   let notification; // Used by action callbacks.
   let mainAction = {
     label: stringBundle.getString("getUserMedia.allow.label"),
     accessKey: stringBundle.getString("getUserMedia.allow.accesskey"),
     // The real callback will be set during the "showing" event. The
     // empty function here is so that PopupNotifications.show doesn't
     // reject the action.
--- a/toolkit/content/widgets/notification.xml
+++ b/toolkit/content/widgets/notification.xml
@@ -7,17 +7,18 @@
 <!DOCTYPE bindings [
 <!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
 %notificationDTD;
 ]>
 
 <bindings id="notificationBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xbl="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:html = "http://www.w3.org/1999/xhtml">
 
   <binding id="notificationbox">
     <content>
       <xul:stack xbl:inherits="hidden=notificationshidden"
                  class="notificationbox-stack">
         <xul:spacer/>
         <children includes="notification"/>
       </xul:stack>
@@ -506,18 +507,21 @@
                    xbl:inherits="popupid,src=icon,class=iconclass"/>
         <xul:vbox flex="1" pack="start"
                   class="popup-notification-body" xbl:inherits="popupid">
           <xul:hbox align="start">
             <xul:vbox flex="1">
               <xul:label class="popup-notification-origin header"
                          xbl:inherits="value=origin,tooltiptext=origin"
                          crop="center"/>
-              <xul:description class="popup-notification-description"
-                               xbl:inherits="xbl:text=label,popupid"/>
+              <xul:description>
+                <html:span xbl:inherits="xbl:text=startlabel,popupid"/>
+                <html:b    xbl:inherits="xbl:text=hostname,popupid"/>
+                <html:span xbl:inherits="xbl:text=endlabel,popupid"/>
+              </xul:description>
             </xul:vbox>
             <xul:toolbarbutton anonid="closebutton"
                                class="messageCloseButton close-icon popup-notification-closebutton tabbable"
                                xbl:inherits="oncommand=closebuttoncommand,hidden=closebuttonhidden"
                                tooltiptext="&closeNotification.tooltip;"/>
           </xul:hbox>
           <children includes="popupnotificationcontent"/>
           <xul:label class="text-link popup-notification-learnmore-link"
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -326,17 +326,23 @@ PopupNotifications.prototype = {
    *        The <xul:browser> element associated with the notification. Must not
    *        be null.
    * @param id
    *        A unique ID that identifies the type of notification (e.g.
    *        "geolocation"). Only one notification with a given ID can be visible
    *        at a time. If a notification already exists with the given ID, it
    *        will be replaced.
    * @param message
-   *        The text to be displayed in the notification.
+   *        A JavaScript object or a string containing the text to be displayed in the
+   *        notification header. It must have the following properties:
+   *          - start(string): First part of the notification header text. Optionally,
+   *            it is also the entire header text when the notification header does not
+   *            contain a host name. eg. file URIs.
+   *          - host(string): Hostname of the site displaying the notifiation.
+   *          - end(string): An optional end label to the notification header text.
    * @param anchorID
    *        The ID of the element that should be used as this notification
    *        popup's anchor. May be null, in which case the notification will be
    *        anchored to the iconBox.
    * @param mainAction
    *        A JavaScript object literal describing the notification button's
    *        action. If present, it must have the following properties:
    *          - label (string): the button's label.
@@ -762,17 +768,33 @@ PopupNotifications.prototype = {
       // If the chrome document provides a popupnotification with this id, use
       // that. Otherwise create it ad-hoc.
       let popupnotification = doc.getElementById(popupnotificationID);
       if (popupnotification)
         gNotificationParents.set(popupnotification, popupnotification.parentNode);
       else
         popupnotification = doc.createElementNS(XUL_NS, "popupnotification");
 
-      popupnotification.setAttribute("label", n.message);
+      // Create the notification description element.
+
+      // Adding an if condition to check if n.message(i.e. the notification-description-text) is a string or an object.
+      if (typeof n.message == "string") {
+        popupnotification.setAttribute("startlabel", n.message);
+      } else {
+        if (n.message.start) {
+          popupnotification.setAttribute("startlabel", n.message.start);
+        }
+        if (n.message.host) {
+          popupnotification.setAttribute("hostname", n.message.host);
+        }
+        if (n.message.end) {
+          popupnotification.setAttribute("endlabel", n.message.end);
+        }
+      }
+
       popupnotification.setAttribute("id", popupnotificationID);
       popupnotification.setAttribute("popupid", n.id);
       popupnotification.setAttribute("oncommand", "PopupNotifications._onCommand(event);");
       if (Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton")) {
         popupnotification.setAttribute("closebuttoncommand", "PopupNotifications._onButtonEvent(event, 'secondarybuttoncommand');");
       } else {
         popupnotification.setAttribute("closebuttoncommand", `PopupNotifications._dismiss(event, ${TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON});`);
       }