Bug 1316996 Text for webextensions permissions draft
authorAndrew Swan <aswan@mozilla.com>
Fri, 20 Jan 2017 08:49:59 -0800
changeset 464172 a782301b79ef05134633a0e155d1eeb0b5989f4d
parent 463950 3668e665a020e8c8288249b827812ab22bb82643
child 542881 8a8a2f3a484abd77285d9165f12feede9ec57711
push id42302
push useraswan@mozilla.com
push dateFri, 20 Jan 2017 16:52:04 +0000
bugs1316996
milestone53.0a1
Bug 1316996 Text for webextensions permissions MozReview-Commit-ID: 3W7zEemDOTa
browser/base/content/browser-addons.js
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/ExtensionsUI.jsm
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/windows/browser.css
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -498,43 +498,51 @@ const gExtensionsNotifications = {
     }
 
     let container = document.getElementById("PanelUI-footer-addons");
 
     while (container.firstChild) {
       container.firstChild.remove();
     }
 
-    // Strings below to be properly localized in bug 1316996
     const DEFAULT_EXTENSION_ICON =
       "chrome://mozapps/skin/extensions/extensionGeneric.svg";
     let items = 0;
     for (let update of updates) {
       if (++items > 4) {
         break;
       }
+
       let button = document.createElement("toolbarbutton");
-      button.setAttribute("label", `"${update.addon.name}" requires new permissions`);
+      let text = gNavigatorBundle.getFormattedString("webextPerms.updateMenuItem", [update.addon.name]);
+      button.setAttribute("label", text);
 
       let icon = update.addon.iconURL || DEFAULT_EXTENSION_ICON;
       button.setAttribute("image", icon);
 
       button.addEventListener("click", evt => {
         ExtensionsUI.showUpdate(gBrowser, update);
       });
 
       container.appendChild(button);
     }
 
+    let appName;
     for (let addon of sideloaded) {
       if (++items > 4) {
         break;
       }
+      if (!appName) {
+        let brandBundle = document.getElementById("bundle_brand");
+        appName = brandBundle.getString("brandShortName");
+      }
+
       let button = document.createElement("toolbarbutton");
-      button.setAttribute("label", `"${addon.name}" added to Firefox`);
+      let text = gNavigatorBundle.getFormattedString("webextPerms.sideloadMenuItem", [addon.name, appName]);
+      button.setAttribute("label", text);
 
       let icon = addon.iconURL || DEFAULT_EXTENSION_ICON;
       button.setAttribute("image", icon);
 
       button.addEventListener("click", evt => {
         ExtensionsUI.showSideloaded(gBrowser, addon);
       });
 
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -28,16 +28,103 @@ xpinstallPromptAllowButton=Allow
 # Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
 # See http://www.mozilla.org/access/keyboard/accesskey for details
 xpinstallPromptAllowButton.accesskey=A
 xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
 xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
 xpinstallDisabledButton=Enable
 xpinstallDisabledButton.accesskey=n
 
+# LOCALIZATION NOTE (webextPerms.header)
+# This string is used as a header in the webextension permissions dialog,
+# %S is replaced with the localized name of the extension being installed.
+# See https://bug1308309.bmoattachments.org/attachment.cgi?id=8814612
+# for an example of the full dialog.
+# Note, this string will be used as raw markup. Avoid characters like <, >, &
+webextPerms.header=Add %S?
+
+# LOCALIZATION NOTE (webextPerms.listIntro)
+# This string will be followed by a list of permissions requested
+# by the webextension.
+webextPerms.listIntro=It requires your permission to:
+webextPerms.add.label=Add
+webextPerms.add.accessKey=A
+webextPerms.cancel.label=Cancel
+webextPerms.cancel.accessKey=C
+
+# LOCALIZATION NOTE (webextPerms.sideloadMenuItem)
+# %1$S will be replaced with the localized name of the sideloaded add-on.
+# %2$S will be replace with the name of the application (e.g., Firefox, Nightly)
+webextPerms.sideloadMenuItem=%1$S added to %2$S
+
+# LOCALIZATION NOTE (webextPerms.sideloadHeader)
+# This string is used as a header in the webextension permissions dialog
+# when the extension is side-loaded.
+# %S is replaced with the localized name of the extension being installed.
+# Note, this string will be used as raw markup. Avoid characters like <, >, &
+webextPerms.sideloadHeader=%S added
+webextPerms.sideloadText=Another program on your computer installed an add-on that may affect your browser. Please review this add-on’s permissions requests and choose to Enable or Disable.
+
+webextPerms.sideloadEnable.label=Enable
+webextPerms.sideloadEnable.accessKey=E
+webextPerms.sideloadDisable.label=Disable
+webextPerms.sideloadDisable.accessKey=D
+
+# LOCALIZATION NOTE (webextPerms.updateMenuItem)
+# %S will be replaced with the localized name of the extension which
+# has been updated.
+webextPerms.updateMenuItem=%S requires new permissions
+
+# 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
+
+webextPerms.description.bookmarks=Read and modify bookmarks
+webextPerms.description.downloads=Download files and read and modify the browser’s download history
+webextPerms.description.history=Access browsing history
+# LOCALIZATION NOTE (webextPerms.description.nativeMessaging)
+# %S will be replaced with the name of the application
+webextPerms.description.nativeMessaging=Exchange messages with programs other than %S
+webextPerms.description.notifications=Display notifications to you
+webextPerms.description.sessions=Access browser recently closed tabs
+webextPerms.description.tabs=Access browser tabs
+webextPerms.description.topSites=Access browsing history
+webextPerms.description.webNavigation=Access browser activity during navigation
+
+webextPerms.hostDescription.allUrls=Access your data for all websites
+
+# LOCALIZATION NOTE (webextPerms.hostDescription.wildcard)
+# %S will be replaced by the DNS domain for which a webextension
+# is requesting access (e.g., mozilla.org)
+webextPerms.hostDescription.wildcard=Access your data for sites in the %S domain
+
+# LOCALIZATION NOTE (webextPerms.hostDescription.tooManyWildcards):
+# Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 will be replaced by an integer indicating the number of additional
+# domains for which this webextension is requesting permission.
+webextPerms.hostDescription.tooManyWildcards=Access your data in #1 other domain;Access your data in #1 other domains
+
+# LOCALIZATION NOTE (webextPerms.hostDescription.oneSite)
+# %S will be replaced by the DNS host name for which a webextension
+# is requesting access (e.g., www.mozilla.org)
+webextPerms.hostDescription.oneSite=Access your data for %S
+
+# LOCALIZATION NOTE (webextPerms.hostDescription.tooManySites)
+# Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 will be replaced by an integer indicating the number of additional
+# hosts for which this webextension is requesting permission.
+webextPerms.hostDescription.tooManySites=Access your data on #1 other site;Access your data on #1 other sites
+
+
 # LOCALIZATION NOTE (addonDownloadingAndVerifying):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
 addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying #1 add-ons…
 addonDownloadVerifying=Verifying
 
 addonInstall.unsigned=(Unverified)
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -7,16 +7,18 @@ const {classes: Cc, interfaces: Ci, resu
 
 this.EXPORTED_SYMBOLS = ["ExtensionsUI"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://devtools/shared/event-emitter.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+                                  "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
                                       "extensions.webextPermissionPrompts", false);
 
@@ -149,114 +151,138 @@ this.ExtensionsUI = {
     }
 
     let win = target.ownerGlobal;
 
     let name = info.addon.name;
     if (name.length > 50) {
       name = name.slice(0, 49) + "…";
     }
-
-    // The strings below are placeholders, they will switch over to the
-    // bundle.get*String() calls as part of bug 1316996.
+    name = name.replace(/&/g, "&amp;")
+               .replace(/</g, "&lt;")
+               .replace(/>/g, "&gt;");
 
-    // let bundle = win.gNavigatorBundle;
-    // let header = bundle.getFormattedString("webextPerms.header", [name])
-    // let listHeader = bundle.getString("webextPerms.listHeader");
-    let header = "Add ADDON?".replace("ADDON", name);
+    let addonLabel = `<label class="addon-webext-name">${name}</label>`;
+    let bundle = win.gNavigatorBundle;
+
+    let header = bundle.getFormattedString("webextPerms.header", [addonLabel]);
     let text = "";
-    let listHeader = "It can:";
+    let listIntro = bundle.getString("webextPerms.listIntro");
 
-    // let acceptText = bundle.getString("webextPerms.accept.label");
-    // let acceptKey = bundle.getString("webextPerms.accept.accessKey");
-    // let cancelText = bundle.getString("webextPerms.cancel.label");
-    // let cancelKey = bundle.getString("webextPerms.cancel.accessKey");
-    let acceptText = "Add extension";
-    let acceptKey = "A";
-    let cancelText = "Cancel";
-    let cancelKey = "C";
+    let acceptText = bundle.getString("webextPerms.add.label");
+    let acceptKey = bundle.getString("webextPerms.add.accessKey");
+    let cancelText = bundle.getString("webextPerms.cancel.label");
+    let cancelKey = bundle.getString("webextPerms.cancel.accessKey");
 
     if (info.type == "sideload") {
-      header = `${name} added`;
-      text = "Another program on your computer installed an add-on that may affect your browser.  Please review this add-on's permission requests and choose to Enable or Disable";
-      acceptText = "Enable";
-      acceptKey = "E";
-      cancelText = "Disable";
-      cancelKey = "D";
+      header = bundle.getFormattedString("webextPerms.sideloadHeader", [addonLabel]);
+      text = bundle.getString("webextPerms.sideloadText");
+      acceptText = bundle.getString("webextPerms.sideloadEnable.label");
+      acceptKey = bundle.getString("webextPerms.sideloadEnable.accessKey");
+      cancelText = bundle.getString("webextPerms.sideloadDisable.label");
+      cancelKey = bundle.getString("webextPerms.sideloadDisable.accessKey");
     } else if (info.type == "update") {
       header = "";
-      text = `${name} has been updated.  You must approve new permissions before the updated version will install.`;
-      acceptText = "Update";
-      acceptKey = "U";
+      text = bundle.getFormattedString("webextPerms.updateText", [addonLabel]);
+      acceptText = bundle.getString("webextPerms.updateAccept.label");
+      acceptKey = bundle.getString("webextPerms.updateAccept.accessKey");
     }
 
-    let formatPermission = perm => {
-      try {
-        // return bundle.getString(`webextPerms.description.${perm}`);
-        return `localized description of permission ${perm}`;
-      } catch (err) {
-        // return bundle.getFormattedString("webextPerms.description.unknown",
-        //                                  [perm]);
-        return `localized description of unknown permission ${perm}`;
+    let msgs = [];
+    for (let permission of perms.permissions) {
+      let key = `webextPerms.description.${permission}`;
+      if (permission == "nativeMessaging") {
+        let brandBundle = win.document.getElementById("bundle_brand");
+        let appName = brandBundle.getString("brandShortName");
+        msgs.push(bundle.getFormattedString(key, [appName]));
+      } else {
+        try {
+          msgs.push(bundle.getString(key));
+        } catch (err) {
+          // We deliberately do not include all permissions in the prompt.
+          // So if we don't find one then just skip it.
+        }
       }
-    };
+    }
 
-    let formatHostPermission = perm => {
-      if (perm == "<all_urls>") {
-        // return bundle.getString("webextPerms.hostDescription.allUrls");
-        return "localized description of <all_urls> host permission";
+    let allUrls = false, wildcards = [], sites = [];
+    for (let permission of perms.hosts) {
+      if (permission == "<all_urls>") {
+        allUrls = true;
+        break;
       }
-      let match = /^[htps*]+:\/\/([^/]+)\//.exec(perm);
+      let match = /^[htps*]+:\/\/([^/]+)\//.exec(permission);
       if (!match) {
         throw new Error("Unparseable host permission");
       }
-      if (match[1].startsWith("*.")) {
-        let domain = match[1].slice(2);
-        // return bundle.getFormattedString("webextPerms.hostDescription.wildcard", [domain]);
-        return `localized description of wildcard host permission for ${domain}`;
+      if (match[1] == "*") {
+        allUrls = true;
+      } else if (match[1].startsWith("*.")) {
+        wildcards.push(match[1].slice(2));
+      } else {
+        sites.push(match[1]);
+      }
+    }
+
+    if (allUrls) {
+      msgs.push(bundle.getString("webextPerms.hostDescription.allUrls"));
+    } else {
+      // Formats a list of host permissions.  If we have 4 or fewer, display
+      // them all, otherwise display the first 3 followed by an item that
+      // says "...plus N others"
+      function format(list, itemKey, moreKey) {
+        function formatItems(items) {
+          msgs.push(...items.map(item => bundle.getFormattedString(itemKey, [item])));
+        }
+        if (list.length < 5) {
+          formatItems(list);
+        } else {
+          formatItems(list.slice(0, 3));
+
+          let remaining = list.length - 3;
+          msgs.push(PluralForm.get(remaining, bundle.getString(moreKey))
+                              .replace("#1", remaining));
+        }
       }
 
-      //  return bundle.getFormattedString("webextPerms.hostDescription.oneSite", [match[1]]);
-      return `localized description of single host permission for ${match[1]}`;
-    };
-
-    let msgs = [
-      ...perms.permissions.map(formatPermission),
-      ...perms.hosts.map(formatHostPermission),
-    ];
+      format(wildcards, "webextPerms.hostDescription.wildcard",
+             "webextPerms.hostDescription.tooManyWildcards");
+      format(sites, "webextPerms.hostDescription.oneSite",
+             "webextPerms.hostDescription.tooManySites");
+    }
 
     let rendered = false;
     let popupOptions = {
       hideClose: true,
       popupIconURL: info.icon,
       persistent: true,
 
       eventCallback(topic) {
         if (topic == "showing") {
           // This check can be removed when bug 1325223 is resolved.
           if (rendered) {
             return false;
           }
 
           let doc = this.browser.ownerDocument;
-          doc.getElementById("addon-webext-perm-header").textContent = header;
+          doc.getElementById("addon-webext-perm-header").innerHTML = header;
+
+          if (text) {
+            doc.getElementById("addon-webext-perm-text").innerHTML = text;
+          }
+
+          let listIntroEl = doc.getElementById("addon-webext-perm-intro");
+          listIntroEl.value = listIntro;
+          listIntroEl.hidden = (msgs.length == 0);
 
           let list = doc.getElementById("addon-webext-perm-list");
           while (list.firstChild) {
             list.firstChild.remove();
           }
 
-          if (text) {
-            doc.getElementById("addon-webext-perm-text").textContent = text;
-          }
-
-          let listHeaderEl = doc.getElementById("addon-webext-perm-intro");
-          listHeaderEl.value = listHeader;
-          listHeaderEl.hidden = (msgs.length == 0);
-
           for (let msg of msgs) {
             let item = doc.createElementNS(HTML_NS, "li");
             item.textContent = msg;
             list.appendChild(item);
           }
           rendered = true;
         } else if (topic == "dismissed") {
           rendered = false;
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -829,18 +829,22 @@ menuitem.bookmark-item {
   max-width: 28em;
 }
 
 .addon-install-confirmation-name {
   font-weight: bold;
 }
 
 .addon-webext-perm-header {
+  font-size: 1.3em;
+}
+
+.addon-webext-name {
   font-weight: bold;
-  font-size: 1.3em;
+  margin: 0;
 }
 
 /* Notification icon box */
 
 .notification-anchor-icon:-moz-focusring {
   outline: 1px dotted -moz-DialogText;
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3087,18 +3087,22 @@ menulist.translate-infobar-element > .me
   max-width: 28em;
 }
 
 .addon-install-confirmation-name {
   font-weight: bold;
 }
 
 .addon-webext-perm-header {
+  font-size: 1.3em;
+}
+
+.addon-webext-name {
   font-weight: bold;
-  font-size: 1.3em;
+  margin: 0;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
   background: linear-gradient(#fff, #ddd);
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2132,18 +2132,22 @@ toolbarbutton.bookmark-item[dragover="tr
   max-width: 28em;
 }
 
 .addon-install-confirmation-name {
   font-weight: bold;
 }
 
 .addon-webext-perm-header {
+  font-size: 1.3em;
+}
+
+.addon-webext-name {
   font-weight: bold;
-  font-size: 1.3em;
+  margin: 0;
 }
 
 /* Notification icon box */
 
 .notification-anchor-icon:-moz-focusring {
   outline: 1px dotted -moz-DialogText;
 }