summaryrefslogtreecommitdiffstats
path: root/dom/base/nsPluginArray.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsPluginArray.cpp')
-rw-r--r--dom/base/nsPluginArray.cpp495
1 files changed, 495 insertions, 0 deletions
diff --git a/dom/base/nsPluginArray.cpp b/dom/base/nsPluginArray.cpp
new file mode 100644
index 0000000000..9749ecd1d8
--- /dev/null
+++ b/dom/base/nsPluginArray.cpp
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsIWebNavigation.h"
+#include "nsPluginHost.h"
+#include "nsPluginTags.h"
+#include "nsIObserverService.h"
+#include "nsIWeakReference.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsContentUtils.h"
+#include "nsIPermissionManager.h"
+#include "mozilla/dom/Document.h"
+#include "nsIBlocklistService.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {}
+
+void nsPluginArray::Init() {
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->AddObserver(this, "plugin-info-updated", true);
+ }
+}
+
+nsPluginArray::~nsPluginArray() = default;
+
+nsPIDOMWindowInner* nsPluginArray::GetParentObject() const {
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+JSObject* nsPluginArray::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PluginArray_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPluginArray)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginArray)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginArray)
+ 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_WEAK(nsPluginArray, mWindow, 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());
+ }
+}
+
+static bool operator<(const RefPtr<nsMimeType>& lhs,
+ const RefPtr<nsMimeType>& rhs) {
+ // Sort MIME types alphabetically by type name.
+ return lhs->Type() < rhs->Type();
+}
+
+void nsPluginArray::GetMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes) {
+ aMimeTypes.Clear();
+
+ if (!AllowPlugins()) {
+ return;
+ }
+
+ EnsurePlugins();
+
+ GetPluginMimeTypes(mPlugins, aMimeTypes);
+
+ // Alphabetize the enumeration order of non-hidden MIME types to reduce
+ // fingerprintable entropy based on plugins' installation file times.
+ aMimeTypes.Sort();
+}
+
+void nsPluginArray::GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes) {
+ aMimeTypes.Clear();
+
+ if (!AllowPlugins()) {
+ return;
+ }
+
+ EnsurePlugins();
+
+ GetPluginMimeTypes(mCTPPlugins, aMimeTypes);
+
+ // Alphabetize the enumeration order of non-hidden MIME types to reduce
+ // fingerprintable entropy based on plugins' installation file times.
+ aMimeTypes.Sort();
+}
+
+nsPluginElement* nsPluginArray::Item(uint32_t aIndex, CallerType aCallerType) {
+ bool unused;
+ return IndexedGetter(aIndex, unused, aCallerType);
+}
+
+nsPluginElement* nsPluginArray::NamedItem(const nsAString& aName,
+ CallerType aCallerType) {
+ bool unused;
+ return NamedGetter(aName, unused, aCallerType);
+}
+
+void nsPluginArray::Refresh(bool aReloadDocuments) {
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+
+ if (!AllowPlugins() || !pluginHost) {
+ return;
+ }
+
+ // NS_ERROR_PLUGINS_PLUGINSNOTCHANGED on reloading plugins indicates
+ // that plugins did not change and was not reloaded
+ if (pluginHost->ReloadPlugins() == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) {
+ nsTArray<nsCOMPtr<nsIInternalPluginTag>> newPluginTags;
+ pluginHost->GetPlugins(newPluginTags);
+
+ // Check if the number of plugins we know about are different from
+ // the number of plugin tags the plugin host knows about. If the
+ // lengths are different, we refresh. This is safe because we're
+ // notified for every plugin enabling/disabling event that
+ // happens, and therefore the lengths will be in sync only when
+ // the both arrays contain the same plugin tags (though as
+ // different types).
+ if (newPluginTags.Length() == mPlugins.Length()) {
+ return;
+ }
+ }
+
+ mPlugins.Clear();
+ mCTPPlugins.Clear();
+
+ RefPtr<Navigator> navigator = mWindow->Navigator();
+ navigator->RefreshMIMEArray();
+
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
+ if (aReloadDocuments && webNav) {
+ webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE);
+ }
+}
+
+nsPluginElement* nsPluginArray::IndexedGetter(uint32_t aIndex, bool& aFound,
+ CallerType aCallerType) {
+ aFound = false;
+
+ if (!AllowPlugins() || nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return nullptr;
+ }
+
+ EnsurePlugins();
+
+ aFound = aIndex < mPlugins.Length();
+
+ if (!aFound) {
+ return nullptr;
+ }
+
+ return mPlugins[aIndex];
+}
+
+void nsPluginArray::Invalidate() {
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->RemoveObserver(this, "plugin-info-updated");
+ }
+}
+
+static nsPluginElement* FindPlugin(
+ const nsTArray<RefPtr<nsPluginElement>>& aPlugins, const nsAString& aName) {
+ for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
+ nsAutoString pluginName;
+ nsPluginElement* plugin = aPlugins[i];
+ plugin->GetName(pluginName);
+
+ if (pluginName.Equals(aName)) {
+ return plugin;
+ }
+ }
+
+ return nullptr;
+}
+
+nsPluginElement* nsPluginArray::NamedGetter(const nsAString& aName,
+ bool& aFound,
+ CallerType aCallerType) {
+ aFound = false;
+
+ if (!AllowPlugins() || nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return nullptr;
+ }
+
+ EnsurePlugins();
+
+ nsPluginElement* plugin = FindPlugin(mPlugins, aName);
+ aFound = (plugin != nullptr);
+ if (!aFound) {
+ nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
+ if (hiddenPlugin) {
+ NotifyHiddenPluginTouched(hiddenPlugin);
+ }
+ }
+ return plugin;
+}
+
+void nsPluginArray::NotifyHiddenPluginTouched(nsPluginElement* aHiddenElement) {
+ HiddenPluginEventInit init;
+ init.mTag = aHiddenElement->PluginTag();
+ nsCOMPtr<Document> doc = aHiddenElement->GetParentObject()->GetDoc();
+ RefPtr<HiddenPluginEvent> event =
+ HiddenPluginEvent::Constructor(doc, u"HiddenPlugin"_ns, init);
+ event->SetTarget(doc);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ doc->DispatchEvent(*event);
+}
+
+uint32_t nsPluginArray::Length(CallerType aCallerType) {
+ if (!AllowPlugins() || nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return 0;
+ }
+
+ EnsurePlugins();
+
+ return mPlugins.Length();
+}
+
+void nsPluginArray::GetSupportedNames(nsTArray<nsString>& aRetval,
+ CallerType aCallerType) {
+ aRetval.Clear();
+
+ if (!AllowPlugins() || nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < mPlugins.Length(); ++i) {
+ nsAutoString pluginName;
+ mPlugins[i]->GetName(pluginName);
+
+ aRetval.AppendElement(pluginName);
+ }
+}
+
+NS_IMETHODIMP
+nsPluginArray::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "plugin-info-updated")) {
+ Refresh(false);
+ }
+
+ return NS_OK;
+}
+
+bool nsPluginArray::AllowPlugins() const {
+ if (!mWindow) {
+ return false;
+ }
+ nsCOMPtr<Document> doc = mWindow->GetDoc();
+ if (!doc) {
+ return false;
+ }
+
+ return doc->GetAllowPlugins();
+}
+
+static bool operator<(const RefPtr<nsPluginElement>& lhs,
+ const RefPtr<nsPluginElement>& rhs) {
+ // Sort plugins alphabetically by name.
+ return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
+}
+
+static bool PluginShouldBeHidden(const nsCString& aName) {
+ // This only supports one hidden plugin
+ nsAutoCString value;
+ Preferences::GetCString("plugins.navigator.hidden_ctp_plugin", value);
+ return value.Equals(aName);
+}
+
+void nsPluginArray::EnsurePlugins() {
+ 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) {
+ 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;
+ uint32_t blocklistState;
+ if (pluginTag->IsClicktoplay() &&
+ NS_SUCCEEDED(pluginTag->GetBlocklistState(&blocklistState)) &&
+ blocklistState == nsIBlocklistService::STATE_NOT_BLOCKED) {
+ nsCString name;
+ pluginTag->GetName(name);
+ if (PluginShouldBeHidden(name)) {
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ nsCString permString;
+ nsresult rv =
+ pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
+ if (rv == NS_OK) {
+ nsCOMPtr<Document> currentDoc = mWindow->GetExtantDoc();
+
+ // The top-level content document gets the final say on whether or
+ // not a plugin is going to be hidden or not, regardless of the
+ // origin that a subframe is hosted at. This is to avoid spamming
+ // the user with the hidden plugin notification bar when third-party
+ // iframes attempt to access navigator.plugins after the user has
+ // already expressed that the top-level document has this
+ // permission.
+ nsCOMPtr<Document> topDoc =
+ currentDoc->GetTopLevelContentDocument();
+
+ if (topDoc) {
+ nsIPrincipal* principal = topDoc->NodePrincipal();
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ services::GetPermissionManager();
+ permMgr->TestPermissionFromPrincipal(principal, permString,
+ &permission);
+ }
+ }
+ }
+ }
+ if (permission == nsIPermissionManager::ALLOW_ACTION) {
+ mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+ } else {
+ mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+ }
+ }
+ }
+
+ if (mPlugins.Length() == 0 && mCTPPlugins.Length() != 0) {
+ nsCOMPtr<nsPluginTag> hiddenTag = new nsPluginTag(
+ "Hidden Plugin", nullptr, "dummy.plugin", nullptr, nullptr, nullptr,
+ nullptr, nullptr, 0, 0, false, nsIBlocklistService::STATE_NOT_BLOCKED);
+ mPlugins.AppendElement(new nsPluginElement(mWindow, hiddenTag));
+ }
+
+ // Alphabetize the enumeration order of non-hidden plugins to reduce
+ // fingerprintable entropy based on plugins' installation file times.
+ mPlugins.Sort();
+}
+// nsPluginElement implementation.
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPluginElement)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPluginElement)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPluginElement)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginElement, mWindow, mMimeTypes)
+
+nsPluginElement::nsPluginElement(nsPIDOMWindowInner* aWindow,
+ nsIInternalPluginTag* aPluginTag)
+ : mWindow(aWindow), mPluginTag(aPluginTag) {}
+
+nsPluginElement::~nsPluginElement() = default;
+
+nsPIDOMWindowInner* nsPluginElement::GetParentObject() const {
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+JSObject* nsPluginElement::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Plugin_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void nsPluginElement::GetDescription(nsString& retval) const {
+ CopyUTF8toUTF16(mPluginTag->Description(), retval);
+}
+
+void nsPluginElement::GetFilename(nsString& retval) const {
+ CopyUTF8toUTF16(mPluginTag->FileName(), retval);
+}
+
+void nsPluginElement::GetVersion(nsString& retval) const {
+ CopyUTF8toUTF16(mPluginTag->Version(), retval);
+}
+
+void nsPluginElement::GetName(nsString& retval) const {
+ CopyUTF8toUTF16(mPluginTag->Name(), retval);
+}
+
+nsMimeType* nsPluginElement::Item(uint32_t aIndex) {
+ EnsurePluginMimeTypes();
+
+ return mMimeTypes.SafeElementAt(aIndex);
+}
+
+nsMimeType* nsPluginElement::NamedItem(const nsAString& aName) {
+ bool unused;
+ return NamedGetter(aName, unused);
+}
+
+nsMimeType* nsPluginElement::IndexedGetter(uint32_t aIndex, bool& aFound) {
+ EnsurePluginMimeTypes();
+
+ aFound = aIndex < mMimeTypes.Length();
+
+ if (!aFound) {
+ return nullptr;
+ }
+
+ return mMimeTypes[aIndex];
+}
+
+nsMimeType* nsPluginElement::NamedGetter(const nsAString& aName, bool& aFound) {
+ EnsurePluginMimeTypes();
+
+ aFound = false;
+
+ for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
+ if (mMimeTypes[i]->Type().Equals(aName)) {
+ aFound = true;
+
+ return mMimeTypes[i];
+ }
+ }
+
+ return nullptr;
+}
+
+uint32_t nsPluginElement::Length() {
+ EnsurePluginMimeTypes();
+
+ return mMimeTypes.Length();
+}
+
+void nsPluginElement::GetSupportedNames(nsTArray<nsString>& retval) {
+ EnsurePluginMimeTypes();
+
+ for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
+ retval.AppendElement(mMimeTypes[i]->Type());
+ }
+}
+
+nsTArray<RefPtr<nsMimeType>>& nsPluginElement::MimeTypes() {
+ EnsurePluginMimeTypes();
+
+ return mMimeTypes;
+}
+
+void nsPluginElement::EnsurePluginMimeTypes() {
+ if (!mMimeTypes.IsEmpty()) {
+ return;
+ }
+
+ if (mPluginTag->MimeTypes().Length() !=
+ mPluginTag->MimeDescriptions().Length() ||
+ mPluginTag->MimeTypes().Length() != mPluginTag->Extensions().Length()) {
+ MOZ_ASSERT(false, "mime type arrays expected to be the same length");
+ return;
+ }
+
+ for (uint32_t i = 0; i < mPluginTag->MimeTypes().Length(); ++i) {
+ NS_ConvertUTF8toUTF16 type(mPluginTag->MimeTypes()[i]);
+ NS_ConvertUTF8toUTF16 description(mPluginTag->MimeDescriptions()[i]);
+ NS_ConvertUTF8toUTF16 extension(mPluginTag->Extensions()[i]);
+
+ mMimeTypes.AppendElement(
+ new nsMimeType(mWindow, this, type, description, extension));
+ }
+}