Bug 1186948 - remove plugins that are click-to-play from navigator.plugins (restricted to Flash). r=jst,mconley,mrbkap
authorBrad Lassey <blassey@mozilla.com>
Mon, 03 Aug 2015 19:24:35 -0400
changeset 331406 21d15da870e8f74b933429f4b3efd651747b8b31
parent 331405 730a8fbf67f951377326d659bc5c4d262700b3a2
child 331407 e461c427a87edfb768b315d8a0ac4e4036c9d4a9
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, mconley, mrbkap
bugs1186948
milestone50.0a1
Bug 1186948 - remove plugins that are click-to-play from navigator.plugins (restricted to Flash). r=jst,mconley,mrbkap
browser/modules/PluginContent.jsm
dom/base/nsPluginArray.cpp
dom/base/nsPluginArray.h
dom/bindings/Bindings.conf
dom/plugins/base/nsIPluginHost.idl
dom/plugins/base/nsPluginHost.cpp
dom/plugins/base/nsPluginTags.cpp
dom/plugins/base/nsPluginTags.h
dom/webidl/HiddenPluginEvent.webidl
dom/webidl/moz.build
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -41,16 +41,17 @@ PluginContent.prototype = {
     global.addEventListener("PluginBindingAttached", this, true, true);
     global.addEventListener("PluginCrashed",         this, true);
     global.addEventListener("PluginOutdated",        this, true);
     global.addEventListener("PluginInstantiated",    this, true);
     global.addEventListener("PluginRemoved",         this, true);
     global.addEventListener("pagehide",              this, true);
     global.addEventListener("pageshow",              this, true);
     global.addEventListener("unload",                this);
+    global.addEventListener("HiddenPlugin",          this, true);
 
     global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.addMessageListener("BrowserPlugins:NotificationShown", this);
     global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
   },
@@ -61,16 +62,17 @@ PluginContent.prototype = {
     global.removeEventListener("PluginBindingAttached", this, true);
     global.removeEventListener("PluginCrashed",         this, true);
     global.removeEventListener("PluginOutdated",        this, true);
     global.removeEventListener("PluginInstantiated",    this, true);
     global.removeEventListener("PluginRemoved",         this, true);
     global.removeEventListener("pagehide",              this, true);
     global.removeEventListener("pageshow",              this, true);
     global.removeEventListener("unload",                this);
+    global.removeEventListener("HiddenPlugin",          this, true);
 
     global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.removeMessageListener("BrowserPlugins:NotificationShown", this);
     global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
     delete this.global;
@@ -189,16 +191,55 @@ PluginContent.prototype = {
              pluginName: pluginName,
              pluginTag: pluginTag,
              permissionString: permissionString,
              fallbackType: fallbackType,
              blocklistState: blocklistState,
            };
   },
 
+  _getPluginInfoForTag: function (pluginTag, tagMimetype) {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+    let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
+    let permissionString = null;
+    let blocklistState = null;
+
+    if (pluginTag) {
+      pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
+
+      permissionString = pluginHost.getPermissionStringForTag(pluginTag);
+      blocklistState = pluginTag.blocklistState;
+
+      // Convert this from nsIPluginTag so it can be serialized.
+      let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
+      let pluginTagCopy = {};
+      for (let prop of properties) {
+        pluginTagCopy[prop] = pluginTag[prop];
+      }
+      pluginTag = pluginTagCopy;
+
+      // Make state-softblocked == state-notblocked for our purposes,
+      // they have the same UI. STATE_OUTDATED should not exist for plugin
+      // items, but let's alias it anyway, just in case.
+      if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+          blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+        blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+      }
+    }
+
+    return { mimetype: tagMimetype,
+             pluginName: pluginName,
+             pluginTag: pluginTag,
+             permissionString: permissionString,
+             fallbackType: null,
+             blocklistState: blocklistState,
+           };
+  },
+
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility : function (plugin, overlay, shouldShow) {
     overlay.classList.toggle("visible", shouldShow);
     if (shouldShow) {
       overlay.removeAttribute("dismissed");
     }
@@ -348,16 +389,24 @@ PluginContent.prototype = {
     if (eventType == "PluginCrashed" &&
         !(event.target instanceof Ci.nsIObjectLoadingContent)) {
       // If the event target is not a plugin object (i.e., an <object> or
       // <embed> element), this call is for a window-global plugin.
       this.onPluginCrashed(event.target, event);
       return;
     }
 
+    if (eventType == "HiddenPlugin") {
+      let pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag);
+      if (event.target.defaultView.top.document != this.content.document) {
+	return;
+      }
+      this._showClickToPlayNotification(pluginTag, true);
+    }
+
     let plugin = event.target;
     let doc = plugin.ownerDocument;
 
     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
       return;
 
     if (eventType == "PluginBindingAttached") {
       // The plugin binding fires this event when it is created.
@@ -708,17 +757,23 @@ PluginContent.prototype = {
     }
 
     let pluginData = this.pluginData;
 
     let principal = this.content.document.nodePrincipal;
     let location = this.content.document.location.href;
 
     for (let p of plugins) {
-      let pluginInfo = this._getPluginInfo(p);
+      let pluginInfo;
+      if (p instanceof Ci.nsIPluginTag) {
+        let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
+        pluginInfo = this._getPluginInfoForTag(p, mimeType);
+      } else {
+        pluginInfo = this._getPluginInfo(p);
+      }
       if (pluginInfo.permissionString === null) {
         Cu.reportError("No permission string for active plugin.");
         continue;
       }
       if (pluginData.has(pluginInfo.permissionString)) {
         continue;
       }
 
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -3,28 +3,31 @@
 /* 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/. */
 
 #include "nsPluginArray.h"
 
 #include "mozilla/dom/PluginArrayBinding.h"
 #include "mozilla/dom/PluginBinding.h"
+#include "mozilla/dom/HiddenPluginEvent.h"
 
 #include "nsMimeTypeArray.h"
 #include "Navigator.h"
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIObserverService.h"
 #include "nsIWeakReference.h"
 #include "mozilla/Services.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsContentUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 {
 }
@@ -68,17 +71,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
                                       mWindow,
-                                      mPlugins)
+                                      mPlugins,
+                                      mCTPPlugins)
 
 static void
 GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
                    nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
 {
   for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
     nsPluginElement *plugin = aPlugins[i];
     aMimeTypes.AppendElements(plugin->MimeTypes());
@@ -148,16 +152,17 @@ nsPluginArray::Refresh(bool aReloadDocum
     // the both arrays contain the same plugin tags (though as
     // different types).
     if (newPluginTags.Length() == mPlugins.Length()) {
       return;
     }
   }
 
   mPlugins.Clear();
+  mCTPPlugins.Clear();
 
   nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
 
   if (!navigator) {
     return;
   }
 
   static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
@@ -223,16 +228,31 @@ nsPluginArray::NamedGetter(const nsAStri
   if (!AllowPlugins() || ResistFingerprinting()) {
     return nullptr;
   }
 
   EnsurePlugins();
 
   nsPluginElement* plugin = FindPlugin(mPlugins, aName);
   aFound = (plugin != nullptr);
+  if (!aFound) {
+    nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
+    if (hiddenPlugin) {
+      HiddenPluginEventInit init;
+      init.mTag = hiddenPlugin->PluginTag();
+      nsCOMPtr<nsIDocument> doc = hiddenPlugin->GetParentObject()->GetDoc();
+      RefPtr<HiddenPluginEvent> event =
+        HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
+      event->SetTarget(doc);
+      event->SetTrusted(true);
+      event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+      bool dummy;
+      doc->DispatchEvent(event, &dummy);
+    }
+  }
   return plugin;
 }
 
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins() || ResistFingerprinting()) {
     return 0;
@@ -284,34 +304,58 @@ operator<(const RefPtr<nsPluginElement>&
 {
   // Sort plugins alphabetically by name.
   return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
 }
 
 void
 nsPluginArray::EnsurePlugins()
 {
-  if (!mPlugins.IsEmpty()) {
+  if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
+    if (!pluginTag) {
+      mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    } else if (pluginTag->IsActive()) {
+      uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
+      if (pluginTag->IsClicktoplay()) {
+        nsCString name;
+        pluginTag->GetName(name);
+        if (NS_LITERAL_CSTRING("Shockwave Flash").Equals(name)) {
+          RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+          nsCString permString;
+          nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
+          if (rv == NS_OK) {
+            nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
+            nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+            permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
+          }
+        }
+      }
+      if (permission == nsIPermissionManager::ALLOW_ACTION) {
+        mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      } else {
+        mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      }
+    }
   }
 
   // Alphabetize the enumeration order of non-hidden plugins to reduce
   // fingerprintable entropy based on plugins' installation file times.
   mPlugins.Sort();
 }
 
 // nsPluginElement implementation.
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -55,16 +55,20 @@ public:
 private:
   virtual ~nsPluginArray();
 
   bool AllowPlugins() const;
   void EnsurePlugins();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsTArray<RefPtr<nsPluginElement> > mPlugins;
+  /* A separate list of click-to-play plugins that we don't tell content
+   * about but keep track of so we can still prompt the user to click to play.
+   */
+  nsTArray<RefPtr<nsPluginElement> > mCTPPlugins;
 };
 
 class nsPluginElement final : public nsISupports,
                               public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -840,16 +840,20 @@ DOMInterfaces = {
     'headerFile' : 'nsPluginArray.h',
     'nativeType': 'nsPluginElement',
 },
 
 'PluginArray': {
     'nativeType': 'nsPluginArray',
 },
 
+'PluginTag': {
+    'nativeType': 'nsIPluginTag',
+},
+
 'PopupBoxObject': {
     'resultNotAddRefed': ['triggerNode', 'anchorNode'],
 },
 
 'Position': {
     'headerFile': 'nsGeoPosition.h'
 },
 
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -95,16 +95,28 @@ interface nsIPluginHost : nsISupports
    *
    * @mimeType The MIME type we're interested in.
    * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   ACString getPermissionStringForType(in AUTF8String mimeType,
                                       [optional] in uint32_t excludeFlags);
 
   /**
+   * Get the "permission string" for the plugin.  This is a string that can be
+   * passed to the permission manager to see whether the plugin is allowed to
+   * run, for example.  This will typically be based on the plugin's "nice name"
+   * and its blocklist state.
+   *
+   * @tag The tage we're interested in
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
+   */
+  ACString getPermissionStringForTag(in nsIPluginTag tag,
+                                     [optional] in uint32_t excludeFlags);
+
+  /**
    * Get the nsIPluginTag for this MIME type. This method works with both
    * enabled and disabled/blocklisted plugins, but an enabled plugin will
    * always be returned if available.
    *
    * A fake plugin tag, if one exists and is available, will be returned in
    * preference to NPAPI plugin tags unless excluded by the excludeFlags.
    *
    * @mimeType The MIME type we're interested in.
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1105,33 +1105,41 @@ NS_IMETHODIMP
 nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType,
                                          uint32_t aExcludeFlags,
                                          nsACString &aPermissionString)
 {
   nsCOMPtr<nsIPluginTag> tag;
   nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags,
                                     getter_AddRefs(tag));
   NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(tag, NS_ERROR_FAILURE);
+  return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString);
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag,
+                                        uint32_t aExcludeFlags,
+                                        nsACString &aPermissionString)
+{
+  NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE);
 
   aPermissionString.Truncate();
   uint32_t blocklistState;
-  rv = tag->GetBlocklistState(&blocklistState);
+  nsresult rv = aTag->GetBlocklistState(&blocklistState);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
     aPermissionString.AssignLiteral("plugin-vulnerable:");
   }
   else {
     aPermissionString.AssignLiteral("plugin:");
   }
 
   nsCString niceName;
-  rv = tag->GetNiceName(niceName);
+  rv = aTag->GetNiceName(niceName);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE);
 
   aPermissionString.Append(niceName);
 
   return NS_OK;
 }
 
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -318,17 +318,17 @@ nsPluginTag::nsPluginTag(uint32_t aId,
 {
 }
 
 nsPluginTag::~nsPluginTag()
 {
   NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349");
 }
 
-NS_IMPL_ISUPPORTS(nsPluginTag, nsIPluginTag, nsIInternalPluginTag)
+NS_IMPL_ISUPPORTS(nsPluginTag, nsPluginTag,  nsIInternalPluginTag, nsIPluginTag)
 
 void nsPluginTag::InitMime(const char* const* aMimeTypes,
                            const char* const* aMimeDescriptions,
                            const char* const* aExtensions,
                            uint32_t aVariantCount)
 {
   if (!aMimeTypes) {
     return;
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -25,16 +25,19 @@ struct FakePluginTagInit;
 } // namespace dom
 } // namespace mozilla
 
 // An interface representing plugin tags internally.
 #define NS_IINTERNALPLUGINTAG_IID \
 { 0xe8fdd227, 0x27da, 0x46ee,     \
   { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } }
 
+#define NS_PLUGINTAG_IID \
+  { 0xcce2e8b9, 0x9702, 0x4d4b, \
+   { 0xbe, 0xa4, 0x7c, 0x1e, 0x13, 0x1f, 0xaf, 0x78 } }
 class nsIInternalPluginTag : public nsIPluginTag
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID)
 
   nsIInternalPluginTag();
   nsIInternalPluginTag(const char* aName, const char* aDescription,
                        const char* aFileName, const char* aVersion);
@@ -85,16 +88,18 @@ protected:
 };
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID)
 
 // A linked-list of plugin information that is used for instantiating plugins
 // and reflecting plugin information into JavaScript.
 class nsPluginTag final : public nsIInternalPluginTag
 {
 public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_PLUGINTAG_IID)
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGINTAG
 
   // These must match the STATE_* values in nsIPluginTag.idl
   enum PluginState {
     ePluginState_Disabled = 0,
     ePluginState_Clicktoplay = 1,
     ePluginState_Enabled = 2,
@@ -187,16 +192,17 @@ private:
                 const char* const* aExtensions,
                 uint32_t aVariantCount);
   void InitSandboxLevel();
   nsresult EnsureMembersAreUTF8();
   void FixupVersion();
 
   static uint32_t sNextId;
 };
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPluginTag, NS_PLUGINTAG_IID)
 
 // A class representing "fake" plugin tags; that is plugin tags not
 // corresponding to actual NPAPI plugins.  In practice these are all
 // JS-implemented plugins; maybe we want a better name for this class?
 class nsFakePluginTag : public nsIInternalPluginTag,
                         public nsIFakePluginTag
 {
 public:
new file mode 100644
--- /dev/null
+++ b/dom/webidl/HiddenPluginEvent.webidl
@@ -0,0 +1,12 @@
+interface PluginTag;
+
+[Constructor(DOMString type, optional HiddenPluginEventInit eventInit), ChromeOnly]
+interface HiddenPluginEvent : Event
+{
+  readonly attribute PluginTag? tag;
+};
+
+dictionary HiddenPluginEventInit : EventInit
+{
+  PluginTag? tag = null;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -785,16 +785,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'DeviceProximityEvent.webidl',
     'DeviceStorageAreaChangedEvent.webidl',
     'DeviceStorageChangeEvent.webidl',
     'DOMTransactionEvent.webidl',
     'DownloadEvent.webidl',
     'ErrorEvent.webidl',
     'FontFaceSetLoadEvent.webidl',
     'HashChangeEvent.webidl',
+    'HiddenPluginEvent.webidl',
     'IccChangeEvent.webidl',
     'ImageCaptureErrorEvent.webidl',
     'MediaStreamEvent.webidl',
     'MozCellBroadcastEvent.webidl',
     'MozClirModeEvent.webidl',
     'MozContactChangeEvent.webidl',
     'MozEmergencyCbModeEvent.webidl',
     'MozMessageDeletedEvent.webidl',