bug 746374 - differentiate click-to-play plugin permissions by type and vulnerability status r=jaws r=joshmoz
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 27 Nov 2012 10:09:10 -0800
changeset 114224 79b27ec730c2
parent 114223 567bfba61e51
child 114225 3332c461b997
push id18640
push userdkeeler@mozilla.com
push dateTue, 27 Nov 2012 18:14:33 +0000
treeherdermozilla-inbound@79b27ec730c2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws, joshmoz
bugs746374
milestone20.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 746374 - differentiate click-to-play plugin permissions by type and vulnerability status r=jaws r=joshmoz
browser/base/content/browser-plugins.js
browser/base/content/test/browser_pluginnotification.js
content/base/src/nsObjectLoadingContent.cpp
dom/plugins/base/nsIPluginHost.idl
dom/plugins/base/nsPluginHost.cpp
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -233,17 +233,26 @@ var gPluginHandler = {
     if (eventType != "PluginCrashed") {
       let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
       if (overlay != null && self.isTooSmall(plugin, overlay))
           overlay.style.visibility = "hidden";
     }
   },
 
   canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+    let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+    if (objLoadingContent.actualType) {
+      let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+      let browser = gBrowser.getBrowserForDocument(objLoadingContent.ownerDocument.defaultView.top.document);
+      pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
+    }
+
     return !objLoadingContent.activated &&
+           pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
            objLoadingContent.pluginFallbackType !== Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW;
   },
 
   activatePlugins: function PH_activatePlugins(aContentWindow) {
     let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
     browser._clickToPlayPluginsActivated = true;
     let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
@@ -346,27 +355,34 @@ var gPluginHandler = {
   openHelpPage: function () {
     openHelpLink("plugin-crashed", false);
   },
 
   // Event listener for click-to-play plugins.
   _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) {
     let doc = aPlugin.ownerDocument;
     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
-    let pluginsPermission = Services.perms.testPermission(browser.currentURI, "plugins");
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+    let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+    let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    if (objLoadingContent.actualType) {
+      let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+      pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
+    }
     let overlay = doc.getAnonymousElementByAttribute(aPlugin, "class", "mainBox");
 
+    if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
+      if (overlay)
+        overlay.style.visibility = "hidden";
+      return;
+    }
+
     if (browser._clickToPlayPluginsActivated) {
-      let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
       objLoadingContent.playPlugin();
       return;
-    } else if (pluginsPermission == Ci.nsIPermissionManager.DENY_ACTION) {
-      if (overlay)
-        overlay.style.visibility = "hidden";
-      return;
     }
 
     if (overlay) {
       overlay.addEventListener("click", function(aEvent) {
         // Have to check that the target is not the link to update the plugin
         if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
             aEvent.button == 0 && aEvent.isTrusted) {
           gPluginHandler.activateSinglePlugin(aEvent.target.ownerDocument.defaultView.top, aPlugin);
@@ -412,20 +428,16 @@ var gPluginHandler = {
       let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
       if (iframe)
         previewContent.removeChild(iframe);
     }, true);
   },
 
   reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
     let browser = gBrowser.selectedBrowser;
-    let pluginsPermission = Services.perms.testPermission(browser.currentURI, "plugins");
-    if (pluginsPermission == Ci.nsIPermissionManager.DENY_ACTION)
-      return;
-
     if (gPluginHandler._pluginNeedsActivationExceptThese([]))
       gPluginHandler._showClickToPlayNotification(browser);
   },
 
   // returns true if there is a plugin on this page that needs activation
   // and isn't in the "except these" list
   _pluginNeedsActivationExceptThese: function PH_pluginNeedsActivationExceptThese(aExceptThese) {
     let contentWindow = gBrowser.selectedBrowser.contentWindow;
@@ -508,17 +520,29 @@ var gPluginHandler = {
             notification.remove();
           }
         }
       };
       centerActions.push(action);
     }
 
     return centerActions;
-   },
+  },
+
+  _setPermissionForPlugins: function PH_setPermissionForPlugins(aBrowser, aPermission, aPluginList) {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+    for (let plugin of aPluginList) {
+      let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+      if (gPluginHandler.canActivatePlugin(objLoadingContent) &&
+          objLoadingContent.actualType) {
+        let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+        Services.perms.add(aBrowser.currentURI, permissionString, aPermission);
+      }
+    }
+  },
 
   _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser) {
     aBrowser._clickToPlayDoorhangerShown = true;
     let contentWindow = aBrowser.contentWindow;
 
     let messageString = gNavigatorBundle.getString("activatePluginsMessage.message");
     let mainAction = {
       label: gNavigatorBundle.getString("activateAllPluginsMessage.label"),
@@ -536,24 +560,24 @@ var gPluginHandler = {
     });
     if (haveVulnerablePlugin) {
       messageString = gNavigatorBundle.getString("vulnerablePluginsMessage");
     }
     let secondaryActions = [{
       label: gNavigatorBundle.getString("activatePluginsMessage.always"),
       accessKey: gNavigatorBundle.getString("activatePluginsMessage.always.accesskey"),
       callback: function () {
-        Services.perms.add(aBrowser.currentURI, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION);
+        gPluginHandler._setPermissionForPlugins(aBrowser, Ci.nsIPermissionManager.ALLOW_ACTION, cwu.plugins);
         gPluginHandler.activatePlugins(contentWindow);
       }
     },{
       label: gNavigatorBundle.getString("activatePluginsMessage.never"),
       accessKey: gNavigatorBundle.getString("activatePluginsMessage.never.accesskey"),
       callback: function () {
-        Services.perms.add(aBrowser.currentURI, "plugins", Ci.nsIPermissionManager.DENY_ACTION);
+        gPluginHandler._setPermissionForPlugins(aBrowser, Ci.nsIPermissionManager.DENY_ACTION, cwu.plugins);
         let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
         if (notification)
           notification.remove();
         gPluginHandler._removeClickToPlayOverlays(contentWindow);
       }
     }];
     let options = { dismissed: true, centerActions: centerActions };
     PopupNotifications.show(aBrowser, "click-to-play-plugins",
@@ -562,17 +586,19 @@ var gPluginHandler = {
   },
 
   _removeClickToPlayOverlays: function PH_removeClickToPlayOverlays(aContentWindow) {
     let doc = aContentWindow.document;
     let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
     for (let plugin of cwu.plugins) {
       let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
-      overlay.style.visibility = "hidden";
+      // for already activated plugins, there will be no overlay
+      if (overlay)
+        overlay.style.visibility = "hidden";
     }
   },
 
   // event listener for missing/blocklisted/outdated/carbonFailure plugins.
   pluginUnavailable: function (plugin, eventType) {
     let browser = gBrowser.getBrowserForDocument(plugin.ownerDocument
                                                        .defaultView.top.document);
     if (!browser.missingPlugins)
--- a/browser/base/content/test/browser_pluginnotification.js
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -397,19 +397,36 @@ function test12b() {
 // Tests that the "Always" permission works for click-to-play plugins (part 3/3)
 function test12c() {
   var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(!popupNotification, "Test 12c, Should not have a click-to-play notification");
   var plugin = gTestBrowser.contentDocument.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(objLoadingContent.activated, "Test 12c, Plugin should be activated");
 
+  prepareTest(test12d, gHttpTestRoot + "plugin_two_types.html");
+}
+
+// Test that the "Always" permission, when set for just the Test plugin,
+// does not also allow the Second Test plugin.
+function test12d() {
+  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "Test 12d, Should have a click-to-play notification");
+  var test = gTestBrowser.contentDocument.getElementById("test");
+  var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA");
+  var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB");
+  var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 12d, Test plugin should be activated");
+  var objLoadingContent = secondtestA.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "Test 12d, Second Test plugin (A) should not be activated");
+  var objLoadingContent = secondtestB.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "Test 12d, Second Test plugin (B) should not be activated");
+
   Services.perms.removeAll();
-  gNextTest = test13a;
-  gTestBrowser.reload();
+  prepareTest(test13a, gHttpTestRoot + "plugin_clickToPlayDeny.html");
 }
 
 // Tests that the "Deny Always" permission works for click-to-play plugins (part 1/3)
 function test13a() {
   var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(popupNotification, "Test 13a, Should have a click-to-play notification");
   var plugin = gTestBrowser.contentDocument.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
@@ -439,16 +456,65 @@ function test13c() {
   var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(!popupNotification, "Test 13c, Should not have a click-to-play notification");
   var plugin = gTestBrowser.contentDocument.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(!objLoadingContent.activated, "Test 13c, Plugin should not be activated");
   var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox");
   ok(overlay.style.visibility == "hidden", "Test 13c, Plugin should not have visible overlay");
 
+  prepareTest(test13d, gHttpTestRoot + "plugin_two_types.html");
+}
+
+// Test that the "Deny Always" permission, when set for just the Test plugin,
+// does not also block the Second Test plugin (i.e. it gets an overlay and
+// there's a notification and everything).
+function test13d() {
+  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "Test 13d, Should have a click-to-play notification");
+
+  var test = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+  var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(test, "class", "mainBox");
+  ok(overlay.style.visibility == "hidden", "Test 13d, Test plugin should not have visible overlay");
+  ok(!objLoadingContent.activated, "Test 13d, Test plugin should not be activated");
+
+  var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA");
+  var objLoadingContent = secondtestA.QueryInterface(Ci.nsIObjectLoadingContent);
+  var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(secondtestA, "class", "mainBox");
+  ok(overlay.style.visibility != "hidden", "Test 13d, Test plugin should have visible overlay");
+  ok(!objLoadingContent.activated, "Test 13d, Second Test plugin (A) should not be activated");
+
+  var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB");
+  var objLoadingContent = secondtestB.QueryInterface(Ci.nsIObjectLoadingContent);
+  var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(secondtestB, "class", "mainBox");
+  ok(overlay.style.visibility != "hidden", "Test 13d, Test plugin should have visible overlay");
+  ok(!objLoadingContent.activated, "Test 13d, Second Test plugin (B) should not be activated");
+
+  var condition = function() objLoadingContent.activated;
+  // "click" "Activate All Plugins"
+  popupNotification.mainAction.callback();
+  waitForCondition(condition, test13e, "Test 13d, Waited too long for plugin to activate");
+}
+
+// Test that clicking "Activate All Plugins" won't activate plugins that
+// have previously been "Deny Always"-ed.
+function test13e() {
+  var test = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "Test 13e, Test plugin should not be activated");
+
+  var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA");
+  var objLoadingContent = secondtestA.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 13e, Second Test plugin (A) should be activated");
+
+  var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB");
+  var objLoadingContent = secondtestB.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 13e, Second Test plugin (B) should be activated");
+
   Services.perms.removeAll();
   Services.prefs.setBoolPref("plugins.click_to_play", false);
   prepareTest(test14, gTestRoot + "plugin_test2.html");
 }
 
 // Tests that the plugin's "activated" property is true for working plugins with click-to-play disabled.
 function test14() {
   var plugin = gTestBrowser.contentDocument.getElementById("test1");
@@ -927,10 +993,128 @@ function test23() {
   pluginNode.src = pluginNode.src; // We currently don't properly change state just on type change, bug 767631
   is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be unloaded");
   pluginNode.type = "application/x-test";
   pluginNode.src = pluginNode.src;
   is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, Plugin should not have activated");
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 23, Plugin should be click-to-play");
   ok(!pluginNode.activated, "Test 23, plugin node should not be activated");
 
+  prepareTest(test24a, gHttpTestRoot + "plugin_test.html");
+}
+
+// Test that "always allow"-ing a plugin will not allow it when it becomes
+// blocklisted.
+function test24a() {
+  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 24a, Should have a click-to-play notification");
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  ok(plugin, "Test 24a, Found plugin in page");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 24a, Plugin should be click-to-play");
+  ok(!objLoadingContent.activated, "Test 24a, plugin should not be activated");
+
+  // simulate "always allow"
+  notification.secondaryActions[0].callback();
+  prepareTest(test24b, gHttpTestRoot + "plugin_test.html");
+}
+
+// did the "always allow" work as intended?
+function test24b() {
+  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!notification, "Test 24b, Should not have a click-to-play notification");
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  ok(plugin, "Test 24b, Found plugin in page");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 24b, plugin should be activated");
+  setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
+  function() {
+    prepareTest(test24c, gHttpTestRoot + "plugin_test.html");
+  });
+}
+
+// the plugin is now blocklisted, so it should not automatically load
+function test24c() {
+  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 24c, Should have a click-to-play notification");
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  ok(plugin, "Test 24c, Found plugin in page");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "Test 24c, Plugin should be vulnerable/updatable");
+  ok(!objLoadingContent.activated, "Test 24c, plugin should not be activated");
+
+  // simulate "always allow"
+  notification.secondaryActions[0].callback();
+  prepareTest(test24d, gHttpTestRoot + "plugin_test.html");
+}
+
+// We should still be able to always allow a plugin after we've seen that it's
+// blocklisted.
+function test24d() {
+  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!notification, "Test 24d, Should not have a click-to-play notification");
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  ok(plugin, "Test 24d, Found plugin in page");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 24d, plugin should be activated");
+
+  Services.perms.removeAll();
+  resetBlocklist(function () {
+    prepareTest(test25a, gHttpTestRoot + "plugin_test.html");
+  });
+}
+
+// Test that clicking "always allow" or "always deny" doesn't affect plugins
+// that already have permission given to them
+function test25a() {
+  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 25a, Should have a click-to-play notification");
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  ok(plugin, "Test 25a, Found plugin in page");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "Test 25a, plugin should not be activated");
+
+  // simulate "always allow"
+  notification.secondaryActions[0].callback();
+  prepareTest(test25b, gHttpTestRoot + "plugin_two_types.html");
+}
+
+function test25b() {
+  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 25b, Should have a click-to-play notification");
+
+  var test = gTestBrowser.contentDocument.getElementById("test");
+  ok(test, "Test 25b, Found test plugin in page");
+  var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 25b, test plugin should be activated");
+
+  var secondtest = gTestBrowser.contentDocument.getElementById("secondtestA");
+  ok(secondtest, "Test 25b, Found second test plugin in page");
+  var objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "Test 25b, second test plugin should not be activated");
+
+  // simulate "always deny"
+  notification.secondaryActions[1].callback();
+  prepareTest(test25c, gHttpTestRoot + "plugin_two_types.html");
+}
+
+// we should have one plugin allowed to activate and the other plugin(s) denied
+// (so it should have an invisible overlay)
+function test25c() {
+  var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(!notification, "Test 25c, Should not have a click-to-play notification");
+
+  var test = gTestBrowser.contentDocument.getElementById("test");
+  ok(test, "Test 25c, Found test plugin in page");
+  var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 25c, test plugin should be activated");
+
+  var secondtest = gTestBrowser.contentDocument.getElementById("secondtestA");
+  ok(secondtest, "Test 25c, Found second test plugin in page");
+  var objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "Test 25c, second test plugin should not be activated");
+  var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(secondtest, "class", "mainBox");
+  ok(overlay.style.visibility == "hidden", "Test 25c, second test plugin should not have visible overlay");
+
+  Services.perms.removeAll();
+
   finishTest();
 }
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -2556,19 +2556,22 @@ nsObjectLoadingContent::ShouldPlay(Fallb
 
   bool allowPerm = false;
   // For now we always say that the system principal uses click-to-play since
   // that maintains current behavior and we have tests that expect this.
   // What we really should do is disable plugins entirely in pages that use
   // the system principal, i.e. in chrome pages. That way the click-to-play
   // code here wouldn't matter at all. Bug 775301 is tracking this.
   if (!nsContentUtils::IsSystemPrincipal(topDoc->NodePrincipal())) {
+    nsAutoCString permissionString;
+    rv = pluginHost->GetPermissionStringForType(mContentType, permissionString);
+    NS_ENSURE_SUCCESS(rv, false);
     uint32_t permission;
     rv = permissionManager->TestPermissionFromPrincipal(topDoc->NodePrincipal(),
-                                                        "plugins",
+                                                        permissionString.Data(),
                                                         &permission);
     NS_ENSURE_SUCCESS(rv, false);
     allowPerm = permission == nsIPermissionManager::ALLOW_ACTION;
   }
 
   return allowPerm;
 }
 
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -7,17 +7,17 @@
 #include "nsISupports.idl"
 #include "nsIPluginTag.idl"
 
 %{C++
 #define MOZ_PLUGIN_HOST_CONTRACTID \
   "@mozilla.org/plugin/host;1"
 %}
 
-[scriptable, uuid(d70af999-cb1f-4429-b85e-f18cdbabc43c)]
+[scriptable, uuid(3ac8fe33-c38c-4123-b2f0-0e8a2824b9c5)]
 interface nsIPluginHost : nsISupports
 {
   /**
    * Causes the plugins directory to be searched again for new plugin 
    * libraries.
    *
    * @param reloadPages - indicates whether currently visible pages should 
    * also be reloaded
@@ -77,11 +77,13 @@ interface nsIPluginHost : nsISupports
    * Registers the play preview plugin mode for specific mime type
    *
    * @param mimeType - specified mime type
    */
   void registerPlayPreviewMimeType(in AUTF8String mimeType);
 
   void unregisterPlayPreviewMimeType(in AUTF8String mimeType);
 
+  ACString getPermissionStringForType(in AUTF8String mimeType);
+
   bool isPluginClickToPlayForType(in AUTF8String mimeType);
 };
 
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1342,16 +1342,41 @@ nsPluginHost::GetBlocklistStateForType(c
       return blocklist->GetPluginBlocklistState(plugin, EmptyString(),
                                                 EmptyString(), aState);
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
+NS_IMETHODIMP
+nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType, nsACString &aPermissionString)
+{
+  aPermissionString.Truncate();
+  uint32_t blocklistState;
+  nsresult rv = GetBlocklistStateForType(aMimeType.Data(), &blocklistState);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsPluginTag *tag = FindPluginForType(aMimeType.Data(), true);
+  if (!tag) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
+      blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
+    aPermissionString.AssignLiteral("plugin-vulnerable:");
+  }
+  else {
+    aPermissionString.AssignLiteral("plugin:");
+  }
+
+  aPermissionString.Append(tag->mFileName);
+
+  return NS_OK;
+}
+
 // check comma delimitered extensions
 static int CompareExtensions(const char *aExtensionList, const char *aExtension)
 {
   if (!aExtensionList || !aExtension)
     return -1;
 
   const char *pExt = aExtensionList;
   const char *pComma = strchr(pExt, ',');