Bug 932854 - when a site uses a hidden plugin, show a notification bar to aid discoverability. This patch, suitable for trunk and branch, uses existing strings which are not ideal. r=jaws
authorBenjamin Smedberg <benjamin@smedbergs.us>
Mon, 11 Nov 2013 16:11:12 -0500
changeset 155151 be954182277a40a93c4535a0037ef211bed8caab
parent 155015 99084b22e38d9751a1e9dc9b6bcef4a1ae572aee
child 155152 aafde14ba2775facdc11cde7f606165155eb2d16
push id25664
push useremorley@mozilla.com
push dateMon, 18 Nov 2013 10:56:38 +0000
treeherdermozilla-central@8c20a3bb8b81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws
bugs932854
milestone28.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 932854 - when a site uses a hidden plugin, show a notification bar to aid discoverability. This patch, suitable for trunk and branch, uses existing strings which are not ideal. r=jaws
browser/base/content/browser-plugins.js
browser/base/content/test/general/browser_pluginnotification.js
browser/themes/shared/plugin-doorhanger.inc.css
toolkit/themes/shared/plugins/pluginProblem.css
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -875,34 +875,36 @@ var gPluginHandler = {
     if (!aBrowser.docShell || !aBrowser.contentWindow) {
       return;
     }
 
     let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
     if (!notification)
       return;
 
-    let iconClasses = document.getElementById("plugins-notification-icon").classList;
-
-    // Make a copy so we can remove visible plugins
-    let actions = new Map(notification.options.centerActions);
+    // Make a copy of the actions, removing active plugins and checking for
+    // outdated plugins.
+    let haveInsecure = false;
+    let actions = new Map();
+    for (let action of notification.options.centerActions.values()) {
+      switch (action.fallbackType) {
+        // haveInsecure will trigger the red flashing icon and the infobar
+        // styling below
+        case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+        case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+          haveInsecure = true;
+          // fall through
 
-    for (let pluginInfo of actions.values()) {
-      let fallbackType = pluginInfo.fallbackType;
-      if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE ||
-          fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE ||
-          fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED) {
-        iconClasses.add("plugin-blocked");
-        iconClasses.remove("plugin-hidden");
-        return;
+        case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
+          actions.set(action.permissionString, action);
+          continue;
       }
     }
 
-    iconClasses.remove("plugin-blocked");
-
+    // check for hidden plugins
     let contentWindow = aBrowser.contentWindow;
     let contentDoc = aBrowser.contentDocument;
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
     for (let plugin of cwu.plugins) {
       let info = this._getPluginInfo(plugin);
       if (!actions.has(info.permissionString)) {
         continue;
@@ -910,32 +912,143 @@ var gPluginHandler = {
       let fallbackType = info.fallbackType;
       if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
         actions.delete(info.permissionString);
         if (actions.size == 0) {
           break;
         }
         continue;
       }
-      if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) {
+      if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
+          fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
+          fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
         continue;
       }
       let overlay = this.getPluginUI(plugin, "main");
       if (!overlay) {
         continue;
       }
       if (!this.isTooSmall(plugin, overlay)) {
         actions.delete(info.permissionString);
         if (actions.size == 0) {
           break;
         }
       }
     }
 
-    iconClasses.toggle("plugin-hidden", actions.size != 0);
+    // Set up the icon
+    document.getElementById("plugins-notification-icon").classList.
+      toggle("plugin-blocked", haveInsecure);
+
+    // Now configure the notification bar
+
+    let notificationBox = gBrowser.getNotificationBox(aBrowser);
+
+    function hideNotification() {
+      let n = notificationBox.getNotificationWithValue("plugin-hidden");
+      if (n) {
+        notificationBox.removeNotification(n, true);
+      }
+    }
+
+    // There are three different cases when showing an infobar:
+    // 1.  A single type of plugin is hidden on the page. Show the UI for that
+    //     plugin.
+    // 2a. Multiple types of plugins are hidden on the page. Show the multi-UI
+    //     with the vulnerable styling.
+    // 2b. Multiple types of plugins are hidden on the page, but none are
+    //     vulnerable. Show the nonvulnerable multi-UI.
+    function showNotification() {
+      let n = notificationBox.getNotificationWithValue("plugin-hidden");
+      if (n) {
+        // If something is already shown, just keep it
+        return;
+      }
+
+      let message;
+      // Icons set directly cannot be manipulated using moz-image-region, so
+      // we use CSS classes instead.
+      let host = gPluginHandler._getHostFromPrincipal(aBrowser.contentDocument.nodePrincipal);
+      let brand = document.getElementById("bundle_brand").getString("brandShortName");
+
+      if (actions.size == 1) {
+        let pluginInfo = [...actions.values()][0];
+        let pluginName = pluginInfo.pluginName;
+
+        switch (pluginInfo.fallbackType) {
+          case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
+            message = gNavigatorBundle.getFormattedString(
+              "pluginActivateNew.message",
+              [pluginName, host]);
+            break;
+          case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+            message = gNavigatorBundle.getFormattedString(
+              "pluginActivateOutdated.message",
+              [pluginName, host, brand]);
+            break;
+          case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+            message = gNavigatorBundle.getFormattedString(
+              "pluginActivateVulnerable.message",
+              [pluginName, host, brand]);
+        }
+      } else {
+        // Multi-plugin
+        message = gNavigatorBundle.getFormattedString(
+          "pluginActivateMultiple.message", [host]);
+
+        for (let action of actions.values()) {
+          if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) {
+            break;
+          }
+        }
+      }
+
+      // These strings are temporary no-string-change for branch uplift
+      let buttons = [
+        {
+          label: gNavigatorBundle.getString("pluginBlockNow.label"),
+          accessKey: gNavigatorBundle.getString("pluginBlockNow.accesskey"),
+          callback: function() {
+            Services.perms.addFromPrincipal(aBrowser.contentDocument.nodePrincipal,
+                                            "plugin-hidden-notification",
+                                            Services.perms.DENY_ACTION);
+          }
+        },
+        {
+          label: gNavigatorBundle.getString("offlineApps.allow"),
+          accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+          callback: function() {
+            let curNotification =
+              PopupNotifications.getNotification("click-to-play-plugins",
+                                                 aBrowser);
+            if (curNotification) {
+              curNotification.reshow();
+            }
+          }
+        }
+      ];
+      n = notificationBox.
+        appendNotification(message, "plugin-hidden", null,
+                           notificationBox.PRIORITY_INFO_HIGH, buttons);
+      if (haveInsecure) {
+        n.classList.add('pluginVulnerable');
+      }
+    }
+
+    if (actions.size == 0) {
+      hideNotification();
+    } else {
+      let notificationPermission = Services.perms.testPermissionFromPrincipal(
+        aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification");
+      if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) {
+        hideNotification();
+      } else {
+        showNotification();
+      }
+    }
   },
 
   // Crashed-plugin observer. Notified once per plugin crash, before events
   // are dispatched to individual plugin instances.
   pluginCrashed : function(subject, topic, data) {
     let propertyBag = subject;
     if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
         !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
--- a/browser/base/content/test/general/browser_pluginnotification.js
+++ b/browser/base/content/test/general/browser_pluginnotification.js
@@ -846,30 +846,30 @@ function test25() {
 
   prepareTest(() => executeSoon(test26), gTestRoot + "plugin_small.html");
 }
 
 function test26() {
   let notification = PopupNotifications.getNotification("click-to-play-plugins");
   ok(notification, "Test 26: There should be a plugin notification");
 
-  waitForCondition(() => gBrowser.ownerDocument.
-      getElementById("plugins-notification-icon").classList.
-      contains("plugin-hidden"),
+  let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+
+  waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
     () => {
       // Don't use setTestPluginEnabledState here because we already saved the
       // prior value
       getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
       prepareTest(test27, gTestRoot + "plugin_small.html");
     },
     "Test 26, expected the plugin notification icon to be highlighted");
 }
 
 function test27() {
   let notification = PopupNotifications.getNotification("click-to-play-plugins");
   ok(notification, "Test 27: There should be a plugin notification");
 
-  waitForCondition(() => !gBrowser.ownerDocument.
-      getElementById("plugins-notification-icon").classList.
-      contains("plugin-hidden"),
+  let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+
+  waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null,
     finishTest,
     "Test 27, expected the plugin notification icon to not be highlighted");
 }
--- a/browser/themes/shared/plugin-doorhanger.inc.css
+++ b/browser/themes/shared/plugin-doorhanger.inc.css
@@ -42,8 +42,24 @@
 .click-to-play-plugins-outer-description {
   margin-top: 8px;
 }
 
 .click-to-play-plugins-notification-link,
 .center-item-link {
   margin: 0;
 }
+
+.messageImage[value="plugin-hidden"] {
+  list-style-image: url("chrome://browser/skin/notification-pluginNormal.png");
+  -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+/* Keep any changes to this style in sync with pluginProblem.css */
+notification.pluginVulnerable {
+  background-color: rgb(72,72,72);
+  background-image: url(chrome://mozapps/skin/plugins/contentPluginStripe.png);
+  color: white;
+}
+
+notification.pluginVulnerable .messageImage {
+  list-style-image: url("chrome://browser/skin/notification-pluginBlocked.png");
+}
--- a/toolkit/themes/shared/plugins/pluginProblem.css
+++ b/toolkit/themes/shared/plugins/pluginProblem.css
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace html url(http://www.w3.org/1999/xhtml);
 
 /* These styles affect only the bound element, not other page content. */
-
+/* Keep any changes to these styles in sync with plugin-doorhanger.inc.css */
 .mainBox {
   font: message-box;
   font-size: 12px;
   text-align: center;
   display: table;
   width: 100%;
   height: 100%;
   background-color: rgb(72,72,72);