Merge f-t to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 27 Oct 2013 19:21:46 -0700
changeset 166218 33e004329f53465b9d9ef8ac097059e1fb4794de
parent 166214 64d531ae16c7af209f49b31c7697ffa7aaca8522 (current diff)
parent 166217 da02805334ece606e3a614cbf4cc1ad4ce493f7e (diff)
child 166225 5e49bfec9ecb1adc1e513837b2c9c884a4eae4dc
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.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
Merge f-t to m-c
browser/base/content/test/general/browser_CTP_iframe.js
browser/base/content/test/general/plugin_iframe.html
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -208,30 +208,22 @@ var gPluginHandler = {
     let pluginName = this.nameForSupportedPlugin(aMimeType);
     if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) {
       return true;
     }
     return false;
   },
 
   handleEvent : function(event) {
-    let plugin;
-    let doc;
+    let eventType = event.type;
+    let plugin = event.target;
+    let doc = plugin.ownerDocument;
 
-    let eventType = event.type;
-    if (eventType === "PluginRemoved") {
-      doc = event.target;
-    }
-    else {
-      plugin = event.target;
-      doc = plugin.ownerDocument;
-
-      if (!(plugin instanceof Ci.nsIObjectLoadingContent))
-        return;
-    }
+    if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+      return;
 
     if (eventType == "PluginBindingAttached") {
       // The plugin binding fires this event when it is created.
       // As an untrusted event, ensure that this object actually has a binding
       // and make sure we don't handle it twice
       let overlay = this.getPluginUI(plugin, "main");
       if (!overlay || overlay._bindingHandled) {
         return;
@@ -303,23 +295,22 @@ var gPluginHandler = {
 
       case "PluginDisabled":
         let manageLink = this.getPluginUI(plugin, "managePluginsLink");
         this.addLinkClickCallback(manageLink, "managePlugins");
         shouldShowNotification = true;
         break;
 
       case "PluginInstantiated":
-      case "PluginRemoved":
         shouldShowNotification = true;
         break;
     }
 
     // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
-    if (eventType != "PluginCrashed" && eventType != "PluginRemoved") {
+    if (eventType != "PluginCrashed") {
       let overlay = this.getPluginUI(plugin, "main");
       if (overlay != null) {
         if (!this.isTooSmall(plugin, overlay))
           overlay.style.visibility = "visible";
 
         plugin.addEventListener("overflow", function(event) {
           overlay.style.visibility = "hidden";
         });
@@ -331,17 +322,17 @@ var gPluginHandler = {
           }
         });
       }
     }
 
     // Only show the notification after we've done the isTooSmall check, so
     // that the notification can decide whether to show the "alert" icon
     if (shouldShowNotification) {
-      this._showClickToPlayNotification(browser);
+      this._showClickToPlayNotification(browser, plugin, false);
     }
   },
 
   isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
     return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
             Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
   },
 
@@ -557,17 +548,17 @@ var gPluginHandler = {
         aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true);
         return;
       }
       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
       // Have to check that the target is not the link to update the plugin
       if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
           (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') &&
           aEvent.button == 0 && aEvent.isTrusted) {
-        gPluginHandler._showClickToPlayNotification(browser, plugin);
+        gPluginHandler._showClickToPlayNotification(browser, plugin, true);
         aEvent.stopPropagation();
         aEvent.preventDefault();
       }
     }
   },
 
   _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
     let doc = aPlugin.ownerDocument;
@@ -602,17 +593,17 @@ var gPluginHandler = {
 
       // cleaning up: removes overlay iframe from the DOM
       let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
       if (iframe)
         previewContent.removeChild(iframe);
     }, true);
 
     if (!playPreviewInfo.ignoreCTP) {
-      gPluginHandler._showClickToPlayNotification(browser);
+      gPluginHandler._showClickToPlayNotification(browser, aPlugin, false);
     }
   },
 
   reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
     let browser = gBrowser.selectedBrowser;
     let contentWindow = browser.contentWindow;
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
@@ -620,24 +611,30 @@ var gPluginHandler = {
     for (let plugin of plugins) {
       let overlay = this.getPluginUI(plugin, "main");
       if (overlay)
         overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
       if (gPluginHandler.canActivatePlugin(objLoadingContent))
         gPluginHandler._handleClickToPlayEvent(plugin);
     }
-    gPluginHandler._showClickToPlayNotification(browser);
+    gPluginHandler._showClickToPlayNotification(browser, null, false);
   },
 
   _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
     if (event == "showing") {
-      gPluginHandler._makeCenterActions(this);
       Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
         .add(!this.options.primaryPlugin);
+      // Histograms always start at 0, even though our data starts at 1
+      let histogramCount = this.options.centerActions.size - 1;
+      if (histogramCount > 4) {
+        histogramCount = 4;
+      }
+      Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
+        .add(histogramCount);
     }
     else if (event == "dismissed") {
       // Once the popup is dismissed, clicking the icon should show the full
       // list again
       this.options.primaryPlugin = null;
     }
   },
 
@@ -650,96 +647,16 @@ var gPluginHandler = {
     try {
       if (principal.URI.host)
         return principal.URI.host;
     } catch (e) {}
 
     return principal.origin;
   },
 
-  _makeCenterActions: function PH_makeCenterActions(notification) {
-    let contentWindow = notification.browser.contentWindow;
-    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
-
-    let principal = contentWindow.document.nodePrincipal;
-    // This matches the behavior of nsPermssionManager, used for display purposes only
-    let principalHost = this._getHostFromPrincipal(principal);
-
-    let centerActions = [];
-    let pluginsFound = new Set();
-    for (let plugin of cwu.plugins) {
-      plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-      if (plugin.getContentTypeForMIMEType(plugin.actualType) != Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
-        continue;
-      }
-
-      let pluginInfo = this._getPluginInfo(plugin);
-      if (pluginInfo.permissionString === null) {
-        Components.utils.reportError("No permission string for active plugin.");
-        continue;
-      }
-      if (pluginsFound.has(pluginInfo.permissionString)) {
-        continue;
-      }
-      pluginsFound.add(pluginInfo.permissionString);
-
-      // Add the per-site permissions and details URLs to pluginInfo here
-      // because they are more expensive to compute and so we avoid it in
-      // the tighter loop above.
-      let permissionObj = Services.perms.
-        getPermissionObject(principal, pluginInfo.permissionString, false);
-      if (permissionObj) {
-        pluginInfo.pluginPermissionHost = permissionObj.host;
-        pluginInfo.pluginPermissionType = permissionObj.expireType;
-      }
-      else {
-        pluginInfo.pluginPermissionHost = principalHost;
-        pluginInfo.pluginPermissionType = undefined;
-      }
-
-      let url;
-      // TODO: allow the blocklist to specify a better link, bug 873093
-      if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
-        url = Services.urlFormatter.formatURLPref("plugins.update.url");
-      }
-      else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
-        url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
-      }
-      else {
-        url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
-      }
-      pluginInfo.detailsLink = url;
-
-      centerActions.push(pluginInfo);
-    }
-
-    if (centerActions.length == 0) {
-      // TODO: this is a temporary band-aid to avoid broken doorhangers
-      // until bug 926605 is landed.
-      notification.options.centerActions = [];
-      setTimeout(() => PopupNotifications.remove(notification), 0);
-      return;
-    }
-
-    centerActions.sort(function(a, b) {
-      return a.pluginName.localeCompare(b.pluginName);
-    });
-
-    notification.options.centerActions = centerActions;
-
-    // Histograms always start at 0, even though our data starts at 1
-    let histogramCount = centerActions.length - 1;
-    if (histogramCount > 4) {
-      histogramCount = 4;
-    }
-    Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
-      .add(histogramCount);
-  },
-
   /**
    * Called from the plugin doorhanger to set the new permissions for a plugin
    * and activate plugins if necessary.
    * aNewState should be either "allownow" "allowalways" or "block"
    */
   _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) {
     let permission;
     let expireType;
@@ -793,81 +710,163 @@ var gPluginHandler = {
 
     // Manually activate the plugins that would have been automatically
     // activated.
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
     let plugins = cwu.plugins;
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 
+    let pluginFound = false;
     for (let plugin of plugins) {
       plugin.QueryInterface(Ci.nsIObjectLoadingContent);
       // canActivatePlugin will return false if this isn't a known plugin type,
       // so the pluginHost.getPermissionStringForType call is protected
       if (gPluginHandler.canActivatePlugin(plugin) &&
           aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
         let overlay = this.getPluginUI(plugin, "main");
         if (overlay) {
           overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
         }
         plugin.playPlugin();
+        pluginFound = true;
       }
     }
+
+    // If there are no instances of the plugin on the page any more, what the
+    // user probably needs is for us to allow and then refresh.
+    if (!pluginFound) {
+      browser.reload();
+    }
   },
 
-  _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPrimaryPlugin) {
+  _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) {
     let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
+    let plugins = [];
+
+    // if aPlugin is null, that means the user has navigated back to a page with plugins, and we need
+    // to collect all the plugins
+    if (aPlugin === null) {
+      let contentWindow = aBrowser.contentWindow;
+      let contentDoc = aBrowser.contentDocument;
+      let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindowUtils);
+      // cwu.plugins may contain non-plugin <object>s, filter them out
+      plugins = cwu.plugins.filter((plugin) =>
+        plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+
+      if (plugins.length == 0) {
+        if (notification) {
+          PopupNotifications.remove(notification);
+        }
+        return;
+      }
+    } else {
+      plugins = [aPlugin];
+    }
+
+    // If this is a new notification, create a centerActions map, otherwise append
+    let centerActions;
+    if (notification) {
+      centerActions = notification.options.centerActions;
+    } else {
+      centerActions = new Map();
+    }
+
+    let principal = aBrowser.contentDocument.nodePrincipal;
+    let principalHost = this._getHostFromPrincipal(principal);
+
+    for (var plugin of plugins) {
+      let pluginInfo = this._getPluginInfo(plugin);
+      if (pluginInfo.permissionString === null) {
+        Cu.reportError("No permission string for active plugin.");
+        continue;
+      }
+      if (centerActions.has(pluginInfo.permissionString)) {
+        continue;
+      }
+
+      // Assume that plugins are hidden and then set override later
+      pluginInfo.hidden = true;
+
+      let overlay = this.getPluginUI(plugin, "main");
+      if (overlay && overlay.style.visibility != "hidden" && overlay.style.visibility != "") {
+        pluginInfo.hidden = false;
+      }
 
-    let contentWindow = aBrowser.contentWindow;
-    let contentDoc = aBrowser.contentDocument;
-    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
-    // cwu.plugins may contain non-plugin <object>s, filter them out
-    let plugins = cwu.plugins.filter((plugin) =>
-      plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
-    if (plugins.length == 0) {
-      if (notification) {
-        PopupNotifications.remove(notification);
+      let permissionObj = Services.perms.
+        getPermissionObject(principal, pluginInfo.permissionString, false);
+      if (permissionObj) {
+        pluginInfo.pluginPermissionHost = permissionObj.host;
+        pluginInfo.pluginPermissionType = permissionObj.expireType;
+      }
+      else {
+        pluginInfo.pluginPermissionHost = principalHost;
+        pluginInfo.pluginPermissionType = undefined;
+      }
+
+      let url;
+      // TODO: allow the blocklist to specify a better link, bug 873093
+      if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+        url = Services.urlFormatter.formatURLPref("plugins.update.url");
+      }
+      else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+        url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
+      }
+      else {
+        url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
+      }
+      pluginInfo.detailsLink = url;
+      
+      centerActions.set(pluginInfo.permissionString, pluginInfo);
+    }
+
+    let pluginBlocked = false;
+    let pluginHidden = false;
+    for (let pluginInfo of centerActions.values()) {
+      let fallbackType = pluginInfo.fallbackType;
+      if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE ||
+          fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE ||
+          fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED) {
+        pluginBlocked = true;
+        pluginHidden = false;
+        break;
+      }
+      if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && pluginInfo.hidden) {
+        pluginHidden = true;
+      }
+    }
+
+    let iconClasses = document.getElementById("plugins-notification-icon").classList;
+    iconClasses.toggle("plugin-blocked", pluginBlocked);
+    iconClasses.toggle("plugin-hidden", pluginHidden);
+
+    let primaryPluginPermission = null;
+    if (aShowNow) {
+      primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString;
+    }
+
+    if (notification) {
+      // Don't modify the notification UI while it's on the screen, that would be
+      // jumpy and might allow clickjacking.
+      if (aShowNow) {
+        notification.options.primaryPlugin = primaryPluginPermission;
+        notification.reshow();
       }
       return;
     }
 
-    let icon = 'plugins-notification-icon';
-    for (let plugin of plugins) {
-      let fallbackType = plugin.pluginFallbackType;
-      if (fallbackType == plugin.PLUGIN_VULNERABLE_UPDATABLE ||
-          fallbackType == plugin.PLUGIN_VULNERABLE_NO_UPDATE ||
-          fallbackType == plugin.PLUGIN_BLOCKLISTED) {
-        icon = 'blocked-plugins-notification-icon';
-        break;
-      }
-      if (fallbackType == plugin.PLUGIN_CLICK_TO_PLAY) {
-        let overlay = this.getPluginUI(plugin, "main");
-        if (!overlay || overlay.style.visibility == 'hidden') {
-          icon = 'alert-plugins-notification-icon';
-        }
-      }
-    }
-
-    let dismissed = notification ? notification.dismissed : true;
-    if (aPrimaryPlugin)
-      dismissed = false;
-
-    let primaryPluginPermission = null;
-    if (aPrimaryPlugin) {
-      primaryPluginPermission = this._getPluginInfo(aPrimaryPlugin).permissionString;
-    }
-
     let options = {
-      dismissed: dismissed,
+      dismissed: !aShowNow,
       eventCallback: this._clickToPlayNotificationEventCallback,
-      primaryPlugin: primaryPluginPermission
+      primaryPlugin: primaryPluginPermission,
+      centerActions: centerActions
     };
     PopupNotifications.show(aBrowser, "click-to-play-plugins",
-                            "", icon,
+                            "", "plugins-notification-icon",
                             null, null, options);
   },
 
   // 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) ||
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -751,17 +751,16 @@ var gBrowserInit = {
 
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
     // Note that the XBL binding is untrusted
     gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true);
     gBrowser.addEventListener("PluginCrashed",         gPluginHandler, true);
     gBrowser.addEventListener("PluginOutdated",        gPluginHandler, true);
     gBrowser.addEventListener("PluginInstantiated",    gPluginHandler, true);
-    gBrowser.addEventListener("PluginRemoved",         gPluginHandler, true);
 
     gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
 
     Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
 
     messageManager.loadFrameScript("chrome://browser/content/content.js", true);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -529,18 +529,16 @@
             <image id="identity-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/>
-            <image id="alert-plugins-notification-icon" class="notification-anchor-icon" role="button"/>
-            <image id="blocked-plugins-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="plugin-install-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/>
             <image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/>
           </box>
           <!-- Use onclick instead of normal popup= syntax since the popup
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1272,17 +1272,17 @@ nsContextMenu.prototype = {
         this.sendMedia();
   },
 
   sendMedia: function() {
     MailIntegration.sendMessage(this.mediaURL, "");
   },
 
   playPlugin: function() {
-    gPluginHandler._showClickToPlayNotification(this.browser, this.target);
+    gPluginHandler._showClickToPlayNotification(this.browser, this.target, true);
   },
 
   hidePlugin: function() {
     gPluginHandler.hideClickToPlayOverlay(this.target);
   },
 
   // Generate email address and put it on clipboard.
   copyEmail: function() {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -55,17 +55,16 @@ support-files =
   plugin_bug752516.html
   plugin_bug787619.html
   plugin_bug797677.html
   plugin_bug820497.html
   plugin_clickToPlayAllow.html
   plugin_clickToPlayDeny.html
   plugin_data_url.html
   plugin_hidden_to_visible.html
-  plugin_iframe.html
   plugin_small.html
   plugin_test.html
   plugin_test2.html
   plugin_test3.html
   plugin_two_types.html
   plugin_unknown.html
   print_postdata.sjs
   redirect_bug623155.sjs
@@ -82,17 +81,16 @@ support-files =
   test_no_mcb_on_http_site_img.css
   test_no_mcb_on_http_site_font.html
   test_no_mcb_on_http_site_font.css
   test_no_mcb_on_http_site_font2.html
   test_no_mcb_on_http_site_font2.css
 
 [browser_CTP_data_urls.js]
 [browser_CTP_drag_drop.js]
-[browser_CTP_iframe.js]
 [browser_CTP_nonplugins.js]
 [browser_CTP_resize.js]
 [browser_URLBarSetURI.js]
 [browser_aboutHealthReport.js]
 [browser_aboutHome.js]
 [browser_aboutSyncProgress.js]
 [browser_addKeywordSearch.js]
 [browser_addon_bar_aomlistener.js]
--- a/browser/base/content/test/general/browser_CTP_context_menu.js
+++ b/browser/base/content/test/general/browser_CTP_context_menu.js
@@ -66,37 +66,48 @@ function runAfterPluginBindingAttached(f
 function test1() {
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(popupNotification, "Test 1, Should have a click-to-play notification");
 
   let plugin = gTestBrowser.contentDocument.getElementById("test");
   let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(!objLoadingContent.activated, "Test 1, Plugin should not be activated");
 
-  window.document.addEventListener("popupshown", test2, false);
+  // When the popupshown DOM event is fired, the actual showing of the popup
+  // may still be pending. Clear the event loop before continuing so that
+  // subsequently-opened popups aren't cancelled by accident.
+  let goToNext = function() {
+    window.document.removeEventListener("popupshown", goToNext, false);
+    executeSoon(test2);
+  };
+  window.document.addEventListener("popupshown", goToNext, false);
   EventUtils.synthesizeMouseAtCenter(plugin,
                                      { type: "contextmenu", button: 2 },
                                      gTestBrowser.contentWindow);
 }
 
 function test2() {
-  window.document.removeEventListener("popupshown", test2, false);
   let activate = window.document.getElementById("context-ctp-play");
   ok(activate, "Test 2, Should have a context menu entry for activating the plugin");
 
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 2, Should have a click-to-play notification");
+  ok(notification.dismissed, "Test 2, notification should be dismissed");
+
   // Trigger the click-to-play popup
   activate.doCommand();
 
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 2, Should have a click-to-play notification");
-  ok(!notification.dismissed, "Test 2, The click-to-play notification should not be dismissed");
+  waitForCondition(() => !notification.dismissed,
+		   test3, "Test 2, waited too long for context activation");
+}
 
+function test3() {
   // Activate the plugin
   PopupNotifications.panel.firstChild._primaryButton.click();
 
   let plugin = gTestBrowser.contentDocument.getElementById("test");
   let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  waitForCondition(() => objLoadingContent.activated, test3, "Waited too long for plugin to activate");
+  waitForCondition(() => objLoadingContent.activated, test4, "Waited too long for plugin to activate");
 }
 
-function test3() {
+function test4() {
   finishTest();
 }
--- a/browser/base/content/test/general/browser_CTP_data_urls.js
+++ b/browser/base/content/test/general/browser_CTP_data_urls.js
@@ -156,20 +156,20 @@ function test2a() {
 }
 
 function test2b() {
   let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(notification, "Test 2b, Should have a click-to-play notification");
 
   // Simulate choosing "Allow now" for the test plugin
   notification.reshow();
-  is(notification.options.centerActions.length, 2, "Test 2b, Should have two types of plugin in the notification");
+  is(notification.options.centerActions.size, 2, "Test 2b, Should have two types of plugin in the notification");
 
   var centerAction = null;
-  for (var action of notification.options.centerActions) {
+  for (var action of notification.options.centerActions.values()) {
     if (action.pluginName == "Test") {
       centerAction = action;
       break;
     }
   }
   ok(centerAction, "Test 2b, found center action for the Test plugin");
 
   var centerItem = null;
--- a/browser/base/content/test/general/browser_CTP_drag_drop.js
+++ b/browser/base/content/test/general/browser_CTP_drag_drop.js
@@ -59,23 +59,21 @@ function part4() {
   gNextTest = part5;
   gBrowser.selectedBrowser.contentDocument.location = gHttpTestRoot + "plugin_test.html";
 }
 
 function part5() {
   gBrowser.selectedBrowser.removeEventListener("PluginBindingAttached", handleEvent);
   ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab");
 
-  gNextTest = part6;
   gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
-  gNewWindow.addEventListener("load", handleEvent, true);
+  waitForFocus(part6, gNewWindow);
 }
 
 function part6() {
-  gNewWindow.removeEventListener("load", handleEvent);
   let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
   waitForCondition(condition, part7, "Waited too long for click-to-play notification");
 }
 
 function part7() {
   ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
   ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
 
deleted file mode 100644
--- a/browser/base/content/test/general/browser_CTP_iframe.js
+++ /dev/null
@@ -1,135 +0,0 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir;
-const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-
-var gTestBrowser = null;
-var gNextTest = null;
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
-var gPageLoads = 0;
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
-
-function test() {
-  waitForExplicitFinish();
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-  });
-  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
-
-  var newTab = gBrowser.addTab();
-  gBrowser.selectedTab = newTab;
-  gTestBrowser = gBrowser.selectedBrowser;
-  gTestBrowser.addEventListener("load", pageLoad, true);
-
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
-
-  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_iframe.html");
-}
-
-function finishTest() {
-  clearAllPluginPermissions();
-  gTestBrowser.removeEventListener("load", pageLoad, true);
-  gBrowser.removeCurrentTab();
-  window.focus();
-  finish();
-}
-
-function pageLoad() {
-  // Wait for the iframe to be loaded as well.
-  if (gPageLoads++ < 1)
-    return;
-
-  // The plugin events are async dispatched and can come after the load event
-  // This just allows the events to fire before we then go on to test the states.
-  executeSoon(gNextTest);
-}
-
-function prepareTest(nextTest, url) {
-  gNextTest = nextTest;
-  gTestBrowser.contentWindow.location = url;
-}
-
-// Due to layout being async, "PluginBindAttached" may trigger later.
-// This wraps a function to force a layout flush, thus triggering it,
-// and schedules the function execution so they're definitely executed
-// afterwards.
-function runAfterPluginBindingAttached(func) {
-  return function() {
-    let doc = gTestBrowser.contentDocument.getElementById('frame').contentDocument;
-    let elems = doc.getElementsByTagName('embed');
-    if (elems.length < 1) {
-      elems = doc.getElementsByTagName('object');
-    }
-    elems[0].clientTop;
-    executeSoon(func);
-  };
-}
-
-// Test that we don't show a doorhanger after removing the last plugin
-// when the plugin was in an iframe.
-
-function test1() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 1, Should have a click-to-play notification");
-
-  let frame = gTestBrowser.contentDocument.getElementById("frame");
-  frame.parentElement.removeChild(frame);
-
-  let condition = () => {
-    let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-    if (notification) {
-      notification.reshow();
-    }
-    return !notification;
-  }
-
-  waitForCondition(condition, finishTest, "Test1, Waited too long for notification too be removed");
-}
--- a/browser/base/content/test/general/browser_CTP_nonplugins.js
+++ b/browser/base/content/test/general/browser_CTP_nonplugins.js
@@ -144,17 +144,17 @@ function test1() {
 
 function test2() {
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(popupNotification, "Test 2, Should have a click-to-play notification");
 
   let plugin = gTestBrowser.contentDocument.getElementById("test");
   plugin.parentNode.removeChild(plugin);
 
-  runAfterPluginRemoved(() => executeSoon(test3));
+  executeSoon(test3);
 }
 
 function test3() {
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(!popupNotification, "Test 3, Should not have a click-to-play notification");
+  ok(popupNotification, "Test 3, Should still have a click-to-play notification");
 
   finishTest();
 }
--- a/browser/base/content/test/general/browser_bug820497.js
+++ b/browser/base/content/test/general/browser_bug820497.js
@@ -35,25 +35,25 @@ function pluginBindingAttached() {
     var testplugin = doc.getElementById("test");
     ok(testplugin, "should have test plugin");
     var secondtestplugin = doc.getElementById("secondtest");
     ok(!secondtestplugin, "should not yet have second test plugin");
     var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
     ok(notification, "should have popup notification");
     // We don't set up the action list until the notification is shown
     notification.reshow();
-    is(notification.options.centerActions.length, 1, "should be 1 type of plugin in the popup notification");
+    is(notification.options.centerActions.size, 1, "should be 1 type of plugin in the popup notification");
     XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addSecondPlugin();
   } else if (gNumPluginBindingsAttached == 2) {
     var doc = gTestBrowser.contentDocument;
     var testplugin = doc.getElementById("test");
     ok(testplugin, "should have test plugin");
     var secondtestplugin = doc.getElementById("secondtest");
     ok(secondtestplugin, "should have second test plugin");
     var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
     ok(notification, "should have popup notification");
     notification.reshow();
-    is(notification.options.centerActions.length, 2, "should be 2 types of plugin in the popup notification");
+    is(notification.options.centerActions.size, 2, "should be 2 types of plugin in the popup notification");
     finish();
   } else {
     ok(false, "if we've gotten here, something is quite wrong");
   }
 }
--- a/browser/base/content/test/general/browser_pluginnotification.js
+++ b/browser/base/content/test/general/browser_pluginnotification.js
@@ -174,17 +174,17 @@ function prepareTest5() {
 function test5() {
   info("test5");
   ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 5, Should not have displayed the missing plugin notification");
   let notification = PopupNotifications.getNotification("click-to-play-plugins");
   ok(notification, "Test 5: There should be a plugin notification for blocked plugins");
   ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default");
 
   notification.reshow();
-  is(notification.options.centerActions.length, 1, "Test 5: Only the blocked plugin should be present in the notification");
+  is(notification.options.centerActions.size, 1, "Test 5: Only the blocked plugin should be present in the notification");
   ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible.");
 
   ok(!gTestBrowser.missingPlugins, "Test 5, Should not be a missing plugin list");
   var pluginNode = gTestBrowser.contentDocument.getElementById("test");
   ok(pluginNode, "Test 5, Found plugin in page");
   var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED");
 
@@ -436,20 +436,21 @@ function test18e() {
 function test18f() {
   var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(notification, "Test 18f, Should have a click-to-play notification");
   ok(notification.dismissed, "Test 18f, notification should start dismissed");
   var plugin = gTestBrowser.contentDocument.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(!objLoadingContent.activated, "Test 18f, Plugin should not be activated");
 
-  // XXXBAD: this code doesn't do what you think it does! it is actually
-  // observing the "removed" event of the old notification, since we create
-  // a *new* one when the plugin is clicked.
-  notification.options.eventCallback = function() { executeSoon(test18g); };
+  var oldEventCallback = notification.options.eventCallback;
+  notification.options.eventCallback = function() {
+    oldEventCallback();
+    executeSoon(test18g);
+  };
   EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
 }
 
 function test18g() {
   var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(notification, "Test 18g, Should have a click-to-play notification");
   ok(!notification.dismissed, "Test 18g, notification should be open");
   notification.options.eventCallback = null;
@@ -598,20 +599,20 @@ function test21a() {
     ok(rect.width == 200, "Test 21a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being clicked");
     ok(rect.height == 200, "Test 21a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being clicked");
     var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
     ok(!objLoadingContent.activated, "Test 21a, Plugin with id=" + plugin.id + " should not be activated");
   }
 
   // we have to actually show the panel to get the bindings to instantiate
   notification.reshow();
-  is(notification.options.centerActions.length, 2, "Test 21a, Should have two types of plugin in the notification");
+  is(notification.options.centerActions.size, 2, "Test 21a, Should have two types of plugin in the notification");
 
   var centerAction = null;
-  for (var action of notification.options.centerActions) {
+  for (var action of notification.options.centerActions.values()) {
     if (action.pluginName == "Test") {
       centerAction = action;
       break;
     }
   }
   ok(centerAction, "Test 21b, found center action for the Test plugin");
 
   var centerItem = null;
@@ -635,17 +636,17 @@ function test21a() {
   waitForCondition(condition, test21c, "Test 21b, Waited too long for plugin to activate");
 }
 
 function test21c() {
   var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(notification, "Test 21c, Should have a click-to-play notification");
 
   notification.reshow();
-  ok(notification.options.centerActions.length == 2, "Test 21c, Should have one type of plugin in the notification");
+  ok(notification.options.centerActions.size == 2, "Test 21c, Should have one type of plugin in the notification");
 
   var doc = gTestBrowser.contentDocument;
   var plugin = doc.getElementById("test");
   var rect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
   ok(rect.width == 0, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 0px width after being clicked");
   ok(rect.height == 0, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 0px height after being clicked");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(objLoadingContent.activated, "Test 21c, Plugin with id=" + plugin.id + " should be activated");
@@ -656,17 +657,17 @@ function test21c() {
     var rect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
     ok(rect.width == 200, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being clicked");
     ok(rect.height == 200, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being clicked");
     var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
     ok(!objLoadingContent.activated, "Test 21c, Plugin with id=" + plugin.id + " should not be activated");
   }
 
   var centerAction = null;
-  for (var action of notification.options.centerActions) {
+  for (var action of notification.options.centerActions.values()) {
     if (action.pluginName == "Second Test") {
       centerAction = action;
       break;
     }
   }
   ok(centerAction, "Test 21d, found center action for the Second Test plugin");
 
   var centerItem = null;
--- a/browser/base/content/test/general/browser_plugins_added_dynamically.js
+++ b/browser/base/content/test/general/browser_plugins_added_dynamically.js
@@ -63,17 +63,17 @@ function testActivateAddSameTypePart2() 
 
   popupNotification.reshow();
   testActivateAddSameTypePart3();
 }
 
 function testActivateAddSameTypePart3() {
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   let centerAction = null;
-  for (let action of popupNotification.options.centerActions) {
+  for (let action of popupNotification.options.centerActions.values()) {
     if (action.pluginName == "Test") {
       centerAction = action;
       break;
     }
   }
   ok(centerAction, "testActivateAddSameTypePart3: found center action for the Test plugin");
 
   let centerItem = null;
@@ -137,17 +137,17 @@ function testActivateAddDifferentTypePar
 
   // we have to actually show the panel to get the bindings to instantiate
   popupNotification.reshow();
   testActivateAddDifferentTypePart3();
 }
 
 function testActivateAddDifferentTypePart3() {
   let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  is(popupNotification.options.centerActions.length, 1, "Should be one plugin action");
+  is(popupNotification.options.centerActions.size, 1, "Should be one plugin action");
 
   let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
   ok(!plugin.activated, "testActivateAddDifferentTypePart3: plugin should not be activated");
 
   // "click" the button to activate the Test plugin
   PopupNotifications.panel.firstChild._primaryButton.click();
 
   let condition = function() plugin.activated;
deleted file mode 100644
--- a/browser/base/content/test/general/plugin_iframe.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="utf-8">
-</head>
-<body>
-<iframe src="plugin_test.html" id="frame" width="300" height="300">
-  This should load plugin_test.html
-</iframe>
-</body>
-</html>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1562,24 +1562,30 @@
         document.getAnonymousElementByAttribute(this, "anonid", "button-container")
       </field>
       <field name="_brandShortName">
         document.getElementById("bundle_brand").getString("brandShortName")
       </field>
       <field name="_items">[]</field>
       <constructor><![CDATA[
         const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-        for (let action of this.notification.options.centerActions) {
+        let sortedActions = [];
+        for (let action of this.notification.options.centerActions.values()) {
+          sortedActions.push(action);
+        }
+        sortedActions.sort((a, b) => a.pluginName.localeCompare(b.pluginName));
+
+        for (let action of sortedActions) {
           let item = document.createElementNS(XUL_NS, "row");
           item.setAttribute("class", "plugin-popupnotification-centeritem");
           item.action = action;
           this.appendChild(item);
           this._items.push(item);
         }
-        switch (this.notification.options.centerActions.length) {
+        switch (this._items.length) {
           case 0:
             PopupNotifications._dismiss();
             break;
           case 1:
             this._setState(this._states.SINGLE);
             break;
           default:
             if (this.notification.options.primaryPlugin) {
@@ -1634,17 +1640,17 @@
             }
             showBox.hidden = true;
           }
           this._setupLink(null);
         ]]></body>
       </method>
       <method name="_setupSingleState">
         <body><![CDATA[
-          var action = this.notification.options.centerActions[0];
+          var action = this._items[0].action;
           var host = action.pluginPermissionHost;
 
           let label, linkLabel, linkUrl, button1, button2;
 
           if (action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
             button1 = {
               label: "pluginBlockNow.label",
               accesskey: "pluginBlockNow.accesskey",
@@ -1798,41 +1804,41 @@
         <body><![CDATA[
           let methodName = aButton.getAttribute("action");
           this[methodName]();
         ]]></body>
       </method>
       <method name="_singleActivateNow">
         <body><![CDATA[
           gPluginHandler._updatePluginPermission(this.notification,
-            this.notification.options.centerActions[0],
+            this._items[0].action,
             "allownow");
           this._cancel();
         ]]></body>
       </method>
       <method name="_singleBlock">
         <body><![CDATA[
           gPluginHandler._updatePluginPermission(this.notification,
-            this.notification.options.centerActions[0],
+            this._items[0].action,
             "block");
             this._cancel();
         ]]></body>
       </method>
       <method name="_singleActivateAlways">
         <body><![CDATA[
           gPluginHandler._updatePluginPermission(this.notification,
-            this.notification.options.centerActions[0],
+            this._items[0].action,
             "allowalways");
           this._cancel();
         ]]></body>
       </method>
       <method name="_singleContinue">
         <body><![CDATA[
           gPluginHandler._updatePluginPermission(this.notification,
-            this.notification.options.centerActions[0],
+            this._items[0].action,
             "continue");
           this._cancel();
         ]]></body>
       </method>
       <method name="_multiAccept">
         <body><![CDATA[
           for (let item of this._items) {
             let action = item.action;
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1291,55 +1291,47 @@ toolbar[iconsize="small"] #webrtc-status
 
 #webapps-notification-icon {
   list-style-image: url(chrome://global/skin/icons/webapps-16.png);
 }
 
 #plugins-notification-icon {
   list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
 }
-
-#alert-plugins-notification-icon {
+#plugins-notification-icon.plugin-hidden {
   list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
 }
-
-#blocked-plugins-notification-icon {
+#plugins-notification-icon.plugin-blocked {
   list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
 }
 
-#plugins-notification-icon,
-#alert-plugins-notification-icon,
-#blocked-plugins-notification-icon {
+#plugins-notification-icon {
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-#plugins-notification-icon:hover,
-#alert-plugins-notification-icon:hover,
-#blocked-plugins-notification-icon:hover {
+#plugins-notification-icon:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-#plugins-notification-icon:active,
-#alert-plugins-notification-icon:active,
-#blocked-plugins-notification-icon:active {
+#plugins-notification-icon:active {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 #plugin-install-notification-icon {
   list-style-image: url(chrome://browser/skin/pluginInstall-16.png);
 }
 
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
 }
 
-#blocked-plugins-notification-icon[showing] {
+#plugins-notification-icon.plugin-blocked[showing] {
   animation: pluginBlockedNotification 500ms ease 0s 5 alternate both;
 }
 
 @keyframes pluginBlockedNotification {
   from {
     opacity: 0;
   }
   to {
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -19,18 +19,21 @@
 
 /* Sources toolbar */
 
 #sources-toolbar {
   border: none; /* Remove the devtools-toolbar's black bottom border. */
   -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
 }
 
+#sources-toolbar .devtools-toolbarbutton {
+  min-width: 32px;
+}
+
 #pretty-print {
-  min-width: 0;
   font-weight: bold;
 }
 
 #black-box {
   list-style-image: url(debugger-blackbox.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3202,70 +3202,58 @@ toolbarbutton.chevron > .toolbarbutton-m
     list-style-image: url(chrome://global/skin/icons/webapps-16@2x.png);
   }
 }
 
 #plugins-notification-icon {
   list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
 }
 
-#alert-plugins-notification-icon {
+#plugins-notification-icon.plugin-hidden {
   list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
 }
 
-#blocked-plugins-notification-icon {
+#plugins-notification-icon.plugin-blocked {
   list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
 }
 
-#plugins-notification-icon,
-#alert-plugins-notification-icon,
-#blocked-plugins-notification-icon {
+#plugins-notification-icon {
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-#plugins-notification-icon:hover,
-#alert-plugins-notification-icon:hover,
-#blocked-plugins-notification-icon:hover {
+#plugins-notification-icon:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-#plugins-notification-icon:active,
-#alert-plugins-notification-icon:active,
-#blocked-plugins-notification-icon:active {
+#plugins-notification-icon:active {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 @media (min-resolution: 2dppx) {
   #plugins-notification-icon {
     list-style-image: url(chrome://browser/skin/notification-pluginNormal@2x.png);
   }
 
-  #alert-plugins-notification-icon {
+  #plugins-notification-icon.plugin-hidden {
     list-style-image: url(chrome://browser/skin/notification-pluginAlert@2x.png);
   }
 
-  #blocked-plugins-notification-icon {
+  #plugins-notification-icon.plugin-blocked {
     list-style-image: url(chrome://browser/skin/notification-pluginBlocked@2x.png);
   }
 
-  #plugins-notification-icon,
-  #alert-plugins-notification-icon,
-  #blocked-plugins-notification-icon {
+  #plugins-notification-icon {
     -moz-image-region: rect(0, 32px, 32px, 0);
   }
 
-  #plugins-notification-icon:hover,
-  #alert-plugins-notification-icon:hover,
-  #blocked-plugins-notification-icon:hover {
+  #plugins-notification-icon:hover {
     -moz-image-region: rect(0, 64px, 32px, 32px);
   }
 
-  #plugins-notification-icon:active,
-  #alert-plugins-notification-icon:active,
-  #blocked-plugins-notification-icon:active {
+  #plugins-notification-icon:active {
     -moz-image-region: rect(0, 96px, 32px, 64px);
   }
 }
 
 #plugin-install-notification-icon {
   list-style-image: url(chrome://browser/skin/pluginInstall-16.png);
 }
 @media (min-resolution: 2dppx) {
@@ -3276,17 +3264,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
 }
 
-#blocked-plugins-notification-icon[showing] {
+#plugins-notification-icon.plugin-blocked[showing] {
   animation: pluginBlockedNotification 500ms ease 0s 5 alternate both;
 }
 
 @keyframes pluginBlockedNotification {
   from {
     opacity: 0;
   }
   to {
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -21,18 +21,21 @@
 
 /* Sources toolbar */
 
 #sources-toolbar {
   border: none; /* Remove the devtools-toolbar's black bottom border. */
   -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
 }
 
+#sources-toolbar .devtools-toolbarbutton {
+  min-width: 32px;
+}
+
 #pretty-print {
-  min-width: 0;
   font-weight: bold;
 }
 
 #black-box {
   list-style-image: url(debugger-blackbox.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2563,55 +2563,48 @@ toolbarbutton.bookmark-item[dragover="tr
 
 #webapps-notification-icon {
   list-style-image: url(chrome://global/skin/icons/webapps-16.png);
 }
 
 #plugins-notification-icon {
   list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
 }
-
-#alert-plugins-notification-icon {
+#plugins-notification-icon.plugin-hidden {
   list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
 }
 
-#blocked-plugins-notification-icon {
+#plugins-notification-icon.plugin-blocked {
   list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
 }
 
-#plugins-notification-icon,
-#alert-plugins-notification-icon,
-#blocked-plugins-notification-icon {
+#plugins-notification-icon {
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-#plugins-notification-icon:hover,
-#alert-plugins-notification-icon:hover,
-#blocked-plugins-notification-icon:hover {
+#plugins-notification-icon:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-#plugins-notification-icon:active,
-#alert-plugins-notification-icon:active,
-#blocked-plugins-notification-icon:active {
+#plugins-notification-icon {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 #plugin-install-notification-icon {
   list-style-image: url(chrome://browser/skin/pluginInstall-16.png);
 }
 
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
 }
 
-#blocked-plugins-notification-icon[showing] {
+#plugins-notification-icon.plugin-blocked[showing] {
   animation: pluginBlockedNotification 500ms ease 0s 5 alternate both;
 }
 
 @keyframes pluginBlockedNotification {
   from {
     opacity: 0;
   }
   to {
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -19,18 +19,21 @@
 
 /* Sources toolbar */
 
 #sources-toolbar {
   border: none; /* Remove the devtools-toolbar's black bottom border. */
   -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
 }
 
+#sources-toolbar .devtools-toolbarbutton {
+  min-width: 32px;
+}
+
 #pretty-print {
-  min-width: 0;
   font-weight: bold;
 }
 
 #black-box {
   list-style-image: url(debugger-blackbox.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }