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, a=lsblakk
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -872,34 +872,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;
@@ -907,32 +909,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);