summaryrefslogtreecommitdiffstats
path: root/dom/plugins/base/nsPluginHost.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/plugins/base/nsPluginHost.cpp')
-rw-r--r--dom/plugins/base/nsPluginHost.cpp2792
1 files changed, 2792 insertions, 0 deletions
diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp
new file mode 100644
index 0000000000..4cc7d89e59
--- /dev/null
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -0,0 +1,2792 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* nsPluginHost.cpp - top-level plugin management code */
+
+#include "nsPluginHost.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <cstdlib>
+#include <new>
+#include <utility>
+#include "GeckoProfiler.h"
+#include "ReferrerInfo.h"
+#include "js/RootingAPI.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/PluginLibrary.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FakePluginTagInitBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/ReferrerPolicyBinding.h"
+#include "mozilla/fallible.h"
+#include "mozilla/ipc/URIParams.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/plugins/PluginTypes.h"
+#include "npapi.h"
+#include "npfunctions.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAsyncShutdown.h"
+#include "nsIBlocklistService.h"
+#include "nsICategoryManager.h"
+#include "nsIChannel.h"
+#include "nsIContent.h"
+#include "nsIContentPolicy.h"
+#include "nsID.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIFile.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsIIDNService.h"
+#include "nsIInputStream.h"
+#include "nsILoadInfo.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIObserverService.h"
+#include "nsIPluginInstanceOwner.h"
+#include "nsIPluginTag.h"
+#include "nsIPrefBranch.h"
+#include "nsIProtocolHandler.h"
+#include "nsIReferrerInfo.h"
+#include "nsIRequest.h"
+#include "nsIScriptChannel.h"
+#include "nsISeekableStream.h"
+#include "nsIStringStream.h"
+#include "nsISupportsUtils.h"
+#include "nsIURI.h"
+#include "nsIUploadChannel.h"
+#include "nsIWeakReference.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsLiteralString.h"
+#include "nsNPAPIPlugin.h"
+#include "nsNPAPIPluginInstance.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsObjectLoadingContent.h"
+#include "nsPluginInstanceOwner.h"
+#include "nsPluginLogging.h"
+#include "nsPluginNativeWindow.h"
+#include "nsPluginStreamListenerPeer.h"
+#include "nsPluginTags.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsTPromiseFlatString.h"
+#include "nsTStringRepr.h"
+#include "nsThreadUtils.h"
+#include "nsVersionComparator.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMCID.h"
+#include "nsXULAppAPI.h"
+#include "nscore.h"
+#include "plstr.h"
+
+#if defined(XP_WIN)
+# include "nsIWindowMediator.h"
+# include "nsIBaseWindow.h"
+# include "windows.h"
+# include "winbase.h"
+#endif
+#if (MOZ_WIDGET_GTK)
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+#endif
+
+using namespace mozilla;
+using mozilla::TimeStamp;
+using mozilla::dom::Document;
+using mozilla::dom::FakePluginMimeEntry;
+using mozilla::dom::FakePluginTagInit;
+using mozilla::dom::Promise;
+using mozilla::plugins::FakePluginTag;
+using mozilla::plugins::PluginTag;
+
+// Null out a strong ref to a linked list iteratively to avoid
+// exhausting the stack (bug 486349).
+#define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_) \
+ { \
+ while (list_) { \
+ type_ temp = list_->mNext_; \
+ list_->mNext_ = nullptr; \
+ list_ = temp; \
+ } \
+ }
+
+static const char* kPrefDisableFullPage =
+ "plugin.disable_full_page_plugin_for_types";
+
+LazyLogModule nsPluginLogging::gNPNLog(NPN_LOG_NAME);
+LazyLogModule nsPluginLogging::gNPPLog(NPP_LOG_NAME);
+LazyLogModule nsPluginLogging::gPluginLog(PLUGIN_LOG_NAME);
+
+// #defines for plugin cache and prefs
+#define NS_PREF_MAX_NUM_CACHED_INSTANCES \
+ "browser.plugins.max_num_cached_plugins"
+// Raise this from '10' to '50' to work around a bug in Apple's current Java
+// plugins on OS X Lion and SnowLeopard. See bug 705931.
+#define DEFAULT_NUMBER_OF_STOPPED_INSTANCES 50
+
+nsIFile* nsPluginHost::sPluginTempDir;
+StaticRefPtr<nsPluginHost> nsPluginHost::sInst;
+
+// Helper to check for a MIME in a comma-delimited preference
+static bool IsTypeInList(const nsCString& aMimeType, nsCString aTypeList) {
+ nsAutoCString searchStr;
+ searchStr.Assign(',');
+ searchStr.Append(aTypeList);
+ searchStr.Append(',');
+
+ nsACString::const_iterator start, end;
+
+ searchStr.BeginReading(start);
+ searchStr.EndReading(end);
+
+ nsAutoCString commaSeparated;
+ commaSeparated.Assign(',');
+ commaSeparated += aMimeType;
+ commaSeparated.Append(',');
+
+ // Lower-case the search string and MIME type to properly handle a mixed-case
+ // type, as MIME types are case insensitive.
+ ToLowerCase(searchStr);
+ ToLowerCase(commaSeparated);
+
+ return FindInReadable(commaSeparated, start, end);
+}
+
+namespace mozilla::plugins {
+class BlocklistPromiseHandler final
+ : public mozilla::dom::PromiseNativeHandler {
+ public:
+ NS_DECL_ISUPPORTS
+
+ BlocklistPromiseHandler(nsPluginTag* aTag, const bool aShouldSoftblock)
+ : mTag(aTag), mShouldDisableWhenSoftblocked(aShouldSoftblock) {
+ MOZ_ASSERT(mTag, "Should always be passed a plugin tag");
+ sPendingBlocklistStateRequests++;
+ }
+
+ void MaybeWriteBlocklistChanges() {
+ // We're called immediately when the promise resolves/rejects, and (as a
+ // backup) when the handler is destroyed. To ensure we only run once, we use
+ // mTag as a sentinel, setting it to nullptr when we run.
+ if (!mTag) {
+ return;
+ }
+ mTag = nullptr;
+ sPendingBlocklistStateRequests--;
+ // If this was the only remaining pending request, check if we need to write
+ // state and if so update the child processes.
+ if (!sPendingBlocklistStateRequests) {
+ if (sPluginBlocklistStatesChangedSinceLastWrite) {
+ sPluginBlocklistStatesChangedSinceLastWrite = false;
+
+ RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+ // We update blocklist info in content processes asynchronously
+ // by just sending a new plugin list to content.
+ host->IncrementChromeEpoch();
+ host->BroadcastPluginsToContent();
+ }
+
+ // Now notify observers that we're done updating plugin state.
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(
+ nullptr, "plugin-blocklist-updates-finished", nullptr);
+ }
+ }
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
+ if (!aValue.isInt32()) {
+ MOZ_ASSERT(false, "Blocklist should always return int32");
+ return;
+ }
+ int32_t newState = aValue.toInt32();
+ MOZ_ASSERT(newState >= 0 && newState < nsIBlocklistService::STATE_MAX,
+ "Shouldn't get an out of bounds blocklist state");
+
+ // Check the old and new state and see if there was a change:
+ uint32_t oldState = mTag->GetBlocklistState();
+ bool changed = oldState != (uint32_t)newState;
+ mTag->SetBlocklistState(newState);
+
+ if (newState == nsIBlocklistService::STATE_SOFTBLOCKED &&
+ mShouldDisableWhenSoftblocked) {
+ mTag->SetEnabledState(nsIPluginTag::STATE_DISABLED);
+ changed = true;
+ }
+ sPluginBlocklistStatesChangedSinceLastWrite |= changed;
+
+ MaybeWriteBlocklistChanges();
+ }
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
+ MOZ_ASSERT(false, "Shouldn't reject plugin blocklist state request");
+ MaybeWriteBlocklistChanges();
+ }
+
+ private:
+ ~BlocklistPromiseHandler() {
+ // If we have multiple plugins and the last pending request is GC'd
+ // and so never resolves/rejects, ensure we still write the blocklist.
+ MaybeWriteBlocklistChanges();
+ }
+
+ RefPtr<nsPluginTag> mTag;
+ bool mShouldDisableWhenSoftblocked;
+
+ // Whether we changed any of the plugins' blocklist states since
+ // we last started fetching them (async). This is reset to false
+ // every time we finish fetching plugin blocklist information.
+ // When this happens, if the previous value was true, we store the
+ // updated list on disk and send it to child processes.
+ static bool sPluginBlocklistStatesChangedSinceLastWrite;
+ // How many pending blocklist state requests we've got
+ static uint32_t sPendingBlocklistStateRequests;
+};
+
+NS_IMPL_ISUPPORTS0(BlocklistPromiseHandler)
+
+bool BlocklistPromiseHandler::sPluginBlocklistStatesChangedSinceLastWrite =
+ false;
+uint32_t BlocklistPromiseHandler::sPendingBlocklistStateRequests = 0;
+} // namespace mozilla::plugins
+
+nsPluginHost::nsPluginHost()
+ : mPluginsLoaded(false),
+ mOverrideInternalTypes(false),
+ mPluginsDisabled(false),
+ mPluginEpoch(0) {
+ // check to see if pref is set at startup to let plugins take over in
+ // full page mode for certain image mime types that we handle internally
+ mOverrideInternalTypes =
+ Preferences::GetBool("plugin.override_internal_types", false);
+
+ bool waylandBackend = false;
+#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11)
+ GdkDisplay* display = gdk_display_get_default();
+ if (display) {
+ waylandBackend = !GDK_IS_X11_DISPLAY(display);
+ }
+#endif
+ mPluginsDisabled =
+ Preferences::GetBool("plugin.disable", false) || waylandBackend;
+ if (!waylandBackend) {
+ Preferences::AddStrongObserver(this, "plugin.disable");
+ }
+
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (XRE_IsParentProcess()) {
+ obsService->AddObserver(this, "plugin-blocklist-updated", false);
+ }
+ }
+
+#ifdef PLUGIN_LOGGING
+ MOZ_LOG(nsPluginLogging::gNPNLog, PLUGIN_LOG_ALWAYS,
+ ("NPN Logging Active!\n"));
+ MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_ALWAYS,
+ ("General Plugin Logging Active! (nsPluginHost::ctor)\n"));
+ MOZ_LOG(nsPluginLogging::gNPPLog, PLUGIN_LOG_ALWAYS,
+ ("NPP Logging Active!\n"));
+
+ PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("nsPluginHost::ctor\n"));
+ PR_LogFlush();
+#endif
+ // We need to ensure that plugin tag sandbox info is available. This needs to
+ // be done from the main thread:
+ nsPluginTag::EnsureSandboxInformation();
+
+ // Load plugins on creation, as there's a good chance we'll need to send them
+ // to content processes directly after creation.
+ if (XRE_IsParentProcess()) {
+ // Always increment the chrome epoch when we bring up the nsPluginHost in
+ // the parent process. If the only plugins we have are cached in
+ // pluginreg.dat, we won't see any plugin changes in LoadPlugins and the
+ // epoch will stay the same between the parent and child process, meaning
+ // plugins will never update in the child process.
+ IncrementChromeEpoch();
+ LoadPlugins();
+ }
+}
+
+nsPluginHost::~nsPluginHost() {
+ PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("nsPluginHost::dtor\n"));
+
+ UnloadPlugins();
+}
+
+NS_IMPL_ISUPPORTS(nsPluginHost, nsIPluginHost, nsIObserver, nsITimerCallback,
+ nsISupportsWeakReference, nsINamed)
+
+already_AddRefed<nsPluginHost> nsPluginHost::GetInst() {
+ if (!sInst) {
+ sInst = new nsPluginHost();
+ ClearOnShutdown(&sInst);
+ }
+
+ return do_AddRef(sInst);
+}
+
+bool nsPluginHost::IsRunningPlugin(nsPluginTag* aPluginTag) {
+ if (!aPluginTag || !aPluginTag->mPlugin) {
+ return false;
+ }
+
+ if (aPluginTag->mContentProcessRunningCount) {
+ return true;
+ }
+
+ for (uint32_t i = 0; i < mInstances.Length(); i++) {
+ nsNPAPIPluginInstance* instance = mInstances[i].get();
+ if (instance && instance->GetPlugin() == aPluginTag->mPlugin &&
+ instance->IsRunning()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult nsPluginHost::ReloadPlugins() {
+ PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::ReloadPlugins\n"));
+ return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED;
+}
+
+void nsPluginHost::ClearNonRunningPlugins() {
+ // shutdown plugins and kill the list if there are no running plugins
+ RefPtr<nsPluginTag> prev;
+ RefPtr<nsPluginTag> next;
+
+ for (RefPtr<nsPluginTag> p = mPlugins; p != nullptr;) {
+ next = p->mNext;
+
+ // only remove our plugin from the list if it's not running.
+ if (!IsRunningPlugin(p)) {
+ if (p == mPlugins)
+ mPlugins = next;
+ else
+ prev->mNext = next;
+
+ p->mNext = nullptr;
+
+ // attempt to unload plugins whenever they are removed from the list
+ p->TryUnloadPlugin(false);
+
+ p = next;
+ continue;
+ }
+
+ prev = p;
+ p = next;
+ }
+}
+
+nsresult nsPluginHost::ActuallyReloadPlugins() {
+ nsresult rv = NS_OK;
+ ClearNonRunningPlugins();
+
+ // set flags
+ mPluginsLoaded = false;
+
+ // load them again
+ rv = LoadPlugins();
+
+ if (XRE_IsParentProcess()) {
+ // If the plugin list changed, update content. If the plugin list changed
+ // for the content process, it will also reload plugins.
+ BroadcastPluginsToContent();
+ }
+
+ PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::ReloadPlugins End\n"));
+
+ return rv;
+}
+
+#define NS_RETURN_UASTRING_SIZE 128
+
+nsresult nsPluginHost::UserAgent(const char** retstring) {
+ static char resultString[NS_RETURN_UASTRING_SIZE];
+ nsresult res;
+
+ nsCOMPtr<nsIHttpProtocolHandler> http =
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
+ if (NS_FAILED(res)) return res;
+
+ nsAutoCString uaString;
+ res = http->GetUserAgent(uaString);
+
+ if (NS_SUCCEEDED(res)) {
+ if (NS_RETURN_UASTRING_SIZE > uaString.Length()) {
+ PL_strcpy(resultString, uaString.get());
+ } else {
+ // Copy as much of UA string as we can (terminate at right-most space).
+ PL_strncpy(resultString, uaString.get(), NS_RETURN_UASTRING_SIZE);
+ for (int i = NS_RETURN_UASTRING_SIZE - 1; i >= 0; i--) {
+ if (i == 0) {
+ resultString[NS_RETURN_UASTRING_SIZE - 1] = '\0';
+ } else if (resultString[i] == ' ') {
+ resultString[i] = '\0';
+ break;
+ }
+ }
+ }
+ *retstring = resultString;
+ } else {
+ *retstring = nullptr;
+ }
+
+ PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+ ("nsPluginHost::UserAgent return=%s\n", *retstring));
+
+ return res;
+}
+
+nsresult nsPluginHost::GetURL(nsISupports* pluginInst, const char* url,
+ const char* target,
+ nsNPAPIPluginStreamListener* streamListener,
+ const char* altHost, const char* referrer,
+ bool forceJSEnabled) {
+ return GetURLWithHeaders(static_cast<nsNPAPIPluginInstance*>(pluginInst), url,
+ target, streamListener, altHost, referrer,
+ forceJSEnabled, 0, nullptr);
+}
+
+nsresult nsPluginHost::GetURLWithHeaders(
+ nsNPAPIPluginInstance* pluginInst, const char* url, const char* target,
+ nsNPAPIPluginStreamListener* streamListener, const char* altHost,
+ const char* referrer, bool forceJSEnabled, uint32_t getHeadersLength,
+ const char* getHeaders) {
+ // we can only send a stream back to the plugin (as specified by a
+ // null target) if we also have a nsNPAPIPluginStreamListener to talk to
+ if (!target && !streamListener) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = NS_OK;
+
+ if (target) {
+ RefPtr<nsPluginInstanceOwner> owner = pluginInst->GetOwner();
+ if (owner) {
+ rv = owner->GetURL(url, target, nullptr, nullptr, 0, true);
+ }
+ }
+
+ if (streamListener) {
+ rv = NewPluginURLStream(NS_ConvertUTF8toUTF16(url), pluginInst,
+ streamListener, nullptr, getHeaders,
+ getHeadersLength);
+ }
+ return rv;
+}
+
+nsresult nsPluginHost::PostURL(nsISupports* pluginInst, const char* url,
+ uint32_t postDataLen, const char* postData,
+ const char* target,
+ nsNPAPIPluginStreamListener* streamListener,
+ const char* altHost, const char* referrer,
+ bool forceJSEnabled, uint32_t postHeadersLength,
+ const char* postHeaders) {
+ nsresult rv;
+
+ // we can only send a stream back to the plugin (as specified
+ // by a null target) if we also have a nsNPAPIPluginStreamListener
+ // to talk to also
+ if (!target && !streamListener) return NS_ERROR_ILLEGAL_VALUE;
+
+ nsNPAPIPluginInstance* instance =
+ static_cast<nsNPAPIPluginInstance*>(pluginInst);
+
+ nsCOMPtr<nsIInputStream> postStream;
+ char* dataToPost;
+ uint32_t newDataToPostLen;
+ ParsePostBufferToFixHeaders(postData, postDataLen, &dataToPost,
+ &newDataToPostLen);
+ if (!dataToPost) return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIStringInputStream> sis =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ if (!sis) {
+ free(dataToPost);
+ return rv;
+ }
+
+ // data allocated by ParsePostBufferToFixHeaders() is managed and
+ // freed by the string stream.
+ postDataLen = newDataToPostLen;
+ sis->AdoptData(dataToPost, postDataLen);
+ postStream = sis;
+
+ if (target) {
+ RefPtr<nsPluginInstanceOwner> owner = instance->GetOwner();
+ if (owner) {
+ rv = owner->GetURL(url, target, postStream, (void*)postHeaders,
+ postHeadersLength, true);
+ }
+ }
+
+ // if we don't have a target, just create a stream.
+ if (streamListener) {
+ rv =
+ NewPluginURLStream(NS_ConvertUTF8toUTF16(url), instance, streamListener,
+ postStream, postHeaders, postHeadersLength);
+ }
+ return rv;
+}
+
+nsresult nsPluginHost::UnloadPlugins() {
+ PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::UnloadPlugins Called\n"));
+
+ if (!mPluginsLoaded) return NS_OK;
+
+ // we should call nsIPluginInstance::Stop and nsIPluginInstance::SetWindow
+ // for those plugins who want it
+ DestroyRunningInstances(nullptr);
+
+ nsPluginTag* pluginTag;
+ for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) {
+ pluginTag->TryUnloadPlugin(true);
+ }
+
+ NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mPlugins, mNext);
+
+ // Lets remove any of the temporary files that we created.
+ if (sPluginTempDir) {
+ sPluginTempDir->Remove(true);
+ NS_RELEASE(sPluginTempDir);
+ }
+ mSerializablePlugins.Clear();
+ mSerializableFakePlugins.Clear();
+
+ mPluginsLoaded = false;
+
+ return NS_OK;
+}
+
+void nsPluginHost::OnPluginInstanceDestroyed(nsPluginTag* aPluginTag) {
+ bool hasInstance = false;
+ for (uint32_t i = 0; i < mInstances.Length(); i++) {
+ if (TagForPlugin(mInstances[i]->GetPlugin()) == aPluginTag) {
+ hasInstance = true;
+ break;
+ }
+ }
+
+ if (!hasInstance) {
+ aPluginTag->TryUnloadPlugin(false);
+ }
+}
+
+nsresult nsPluginHost::InstantiatePluginInstance(
+ const nsACString& aMimeType, nsIURI* aURL, nsObjectLoadingContent* aContent,
+ nsPluginInstanceOwner** aOwner) {
+ NS_ENSURE_ARG_POINTER(aOwner);
+
+#ifdef PLUGIN_LOGGING
+ nsAutoCString urlSpec;
+ if (aURL) aURL->GetAsciiSpec(urlSpec);
+
+ MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL,
+ ("nsPluginHost::InstantiatePlugin Begin mime=%s, url=%s\n",
+ PromiseFlatCString(aMimeType).get(), urlSpec.get()));
+
+ PR_LogFlush();
+#endif
+
+ if (aMimeType.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE("Attempting to spawn a plugin with no mime type");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsPluginInstanceOwner> instanceOwner = new nsPluginInstanceOwner();
+ if (!instanceOwner) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCOMPtr<nsIContent> ourContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(aContent));
+ nsresult rv = instanceOwner->Init(ourContent);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsPluginTagType tagType;
+ rv = instanceOwner->GetTagType(&tagType);
+ if (NS_FAILED(rv)) {
+ instanceOwner->Destroy();
+ return rv;
+ }
+
+ if (tagType != nsPluginTagType_Embed && tagType != nsPluginTagType_Object) {
+ instanceOwner->Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = SetUpPluginInstance(aMimeType, aURL, instanceOwner);
+ if (NS_FAILED(rv)) {
+ instanceOwner->Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsNPAPIPluginInstance> instance = instanceOwner->GetInstance();
+
+ if (instance) {
+ CreateWidget(instanceOwner);
+ }
+
+ // At this point we consider instantiation to be successful. Do not return an
+ // error.
+ instanceOwner.forget(aOwner);
+
+#ifdef PLUGIN_LOGGING
+ nsAutoCString urlSpec2;
+ if (aURL != nullptr) aURL->GetAsciiSpec(urlSpec2);
+
+ MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL,
+ ("nsPluginHost::InstantiatePlugin Finished mime=%s, rv=%" PRIu32
+ ", url=%s\n",
+ PromiseFlatCString(aMimeType).get(), static_cast<uint32_t>(rv),
+ urlSpec2.get()));
+
+ PR_LogFlush();
+#endif
+
+ return NS_OK;
+}
+
+nsPluginTag* nsPluginHost::FindTagForLibrary(PRLibrary* aLibrary) {
+ nsPluginTag* pluginTag;
+ for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) {
+ if (pluginTag->mLibrary == aLibrary) {
+ return pluginTag;
+ }
+ }
+ return nullptr;
+}
+
+nsPluginTag* nsPluginHost::TagForPlugin(nsNPAPIPlugin* aPlugin) {
+ nsPluginTag* pluginTag;
+ for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) {
+ if (pluginTag->mPlugin == aPlugin) {
+ return pluginTag;
+ }
+ }
+ // a plugin should never exist without a corresponding tag
+ NS_ERROR("TagForPlugin has failed");
+ return nullptr;
+}
+
+nsresult nsPluginHost::SetUpPluginInstance(const nsACString& aMimeType,
+ nsIURI* aURL,
+ nsPluginInstanceOwner* aOwner) {
+ NS_ENSURE_ARG_POINTER(aOwner);
+
+ nsresult rv = TrySetUpPluginInstance(aMimeType, aURL, aOwner);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ // If we failed to load a plugin instance we'll try again after
+ // reloading our plugin list. Only do that once per document to
+ // avoid redundant high resource usage on pages with multiple
+ // unkown instance types. We'll do that by caching the document.
+ nsCOMPtr<Document> document;
+ aOwner->GetDocument(getter_AddRefs(document));
+
+ nsCOMPtr<Document> currentdocument = do_QueryReferent(mCurrentDocument);
+ if (document == currentdocument) {
+ return rv;
+ }
+
+ mCurrentDocument = do_GetWeakReference(document);
+
+ // Don't try to set up an instance again if nothing changed.
+ if (ReloadPlugins() == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) {
+ return rv;
+ }
+
+ return TrySetUpPluginInstance(aMimeType, aURL, aOwner);
+}
+
+nsresult nsPluginHost::TrySetUpPluginInstance(const nsACString& aMimeType,
+ nsIURI* aURL,
+ nsPluginInstanceOwner* aOwner) {
+#ifdef PLUGIN_LOGGING
+ MOZ_LOG(
+ nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL,
+ ("nsPluginHost::TrySetupPluginInstance Begin mime=%s, owner=%p, url=%s\n",
+ PromiseFlatCString(aMimeType).get(), aOwner,
+ aURL ? aURL->GetSpecOrDefault().get() : ""));
+
+ PR_LogFlush();
+#endif
+
+#ifdef XP_WIN
+ bool changed;
+ if ((mRegKeyHKLM && NS_SUCCEEDED(mRegKeyHKLM->HasChanged(&changed)) &&
+ changed) ||
+ (mRegKeyHKCU && NS_SUCCEEDED(mRegKeyHKCU->HasChanged(&changed)) &&
+ changed)) {
+ ReloadPlugins();
+ }
+#endif
+
+ RefPtr<nsNPAPIPlugin> plugin;
+ GetPlugin(aMimeType, getter_AddRefs(plugin));
+ if (!plugin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true);
+
+ NS_ASSERTION(pluginTag, "Must have plugin tag here!");
+
+ plugin->GetLibrary()->SetHasLocalInstance();
+
+ RefPtr<nsNPAPIPluginInstance> instance = new nsNPAPIPluginInstance();
+
+ // This will create the owning reference. The connection must be made between
+ // the instance and the instance owner before initialization. Plugins can call
+ // into the browser during initialization.
+ aOwner->SetInstance(instance.get());
+
+ // Add the instance to the instances list before we call NPP_New so that
+ // it is "in play" before NPP_New happens. Take it out if NPP_New fails.
+ mInstances.AppendElement(instance.get());
+
+ // this should not addref the instance or owner
+ // except in some cases not Java, see bug 140931
+ // our COM pointer will free the peer
+ nsresult rv = instance->Initialize(plugin.get(), aOwner, aMimeType);
+ if (NS_FAILED(rv)) {
+ mInstances.RemoveElement(instance.get());
+ aOwner->SetInstance(nullptr);
+ return rv;
+ }
+
+ // Cancel the plugin unload timer since we are creating
+ // an instance for it.
+ if (pluginTag->mUnloadTimer) {
+ pluginTag->mUnloadTimer->Cancel();
+ }
+
+#ifdef PLUGIN_LOGGING
+ MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC,
+ ("nsPluginHost::TrySetupPluginInstance Finished mime=%s, rv=%" PRIu32
+ ", owner=%p, url=%s\n",
+ PromiseFlatCString(aMimeType).get(), static_cast<uint32_t>(rv),
+ aOwner, aURL ? aURL->GetSpecOrDefault().get() : ""));
+
+ PR_LogFlush();
+#endif
+
+ return rv;
+}
+
+bool nsPluginHost::HavePluginForType(const nsACString& aMimeType,
+ PluginFilter aFilter) {
+ bool checkEnabled = aFilter & eExcludeDisabled;
+ bool allowFake = !(aFilter & eExcludeFake);
+ return FindPluginForType(aMimeType, allowFake, checkEnabled);
+}
+
+nsIInternalPluginTag* nsPluginHost::FindPluginForType(
+ const nsACString& aMimeType, bool aIncludeFake, bool aCheckEnabled) {
+ if (aIncludeFake) {
+ nsFakePluginTag* fakeTag = FindFakePluginForType(aMimeType, aCheckEnabled);
+ if (fakeTag) {
+ return fakeTag;
+ }
+ }
+
+ return FindNativePluginForType(aMimeType, aCheckEnabled);
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetPluginTagForType(const nsACString& aMimeType,
+ uint32_t aExcludeFlags,
+ nsIPluginTag** aResult) {
+ bool includeFake = !(aExcludeFlags & eExcludeFake);
+ bool includeDisabled = !(aExcludeFlags & eExcludeDisabled);
+
+ // First look for an enabled plugin.
+ RefPtr<nsIInternalPluginTag> tag =
+ FindPluginForType(aMimeType, includeFake, true);
+ if (!tag && includeDisabled) {
+ tag = FindPluginForType(aMimeType, includeFake, false);
+ }
+
+ if (tag) {
+ tag.forget(aResult);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetStateForType(const nsACString& aMimeType,
+ uint32_t aExcludeFlags, uint32_t* aResult) {
+ nsCOMPtr<nsIPluginTag> tag;
+ nsresult rv =
+ GetPluginTagForType(aMimeType, aExcludeFlags, getter_AddRefs(tag));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return tag->GetEnabledState(aResult);
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetBlocklistStateForType(const nsACString& aMimeType,
+ uint32_t aExcludeFlags,
+ uint32_t* aState) {
+ nsCOMPtr<nsIPluginTag> tag;
+ nsresult rv =
+ GetPluginTagForType(aMimeType, aExcludeFlags, getter_AddRefs(tag));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return tag->GetBlocklistState(aState);
+}
+
+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);
+ 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;
+ 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 = aTag->GetNiceName(niceName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE);
+
+ aPermissionString.Append(niceName);
+
+ return NS_OK;
+}
+
+bool nsPluginHost::HavePluginForExtension(const nsACString& aExtension,
+ /* out */ nsACString& aMimeType,
+ PluginFilter aFilter) {
+ // As of FF 52, we only support flash and test plugins, so if the extension
+ // types don't match for that, exit before we start loading plugins.
+ //
+ // XXX: Remove tst case when bug 1351885 lands.
+ if (!aExtension.LowerCaseEqualsLiteral("swf") &&
+ !aExtension.LowerCaseEqualsLiteral("tst")) {
+ return false;
+ }
+
+ bool checkEnabled = aFilter & eExcludeDisabled;
+ bool allowFake = !(aFilter & eExcludeFake);
+ return FindNativePluginForExtension(aExtension, aMimeType, checkEnabled) ||
+ (allowFake &&
+ FindFakePluginForExtension(aExtension, aMimeType, checkEnabled));
+}
+
+void nsPluginHost::GetPlugins(
+ nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray,
+ bool aIncludeDisabled) {
+ aPluginArray.Clear();
+
+ LoadPlugins();
+
+ // Append fake plugins, then normal plugins.
+
+ uint32_t numFake = mFakePlugins.Length();
+ for (uint32_t i = 0; i < numFake; i++) {
+ aPluginArray.AppendElement(mFakePlugins[i]);
+ }
+
+ // Regular plugins
+ nsPluginTag* plugin = mPlugins;
+ while (plugin != nullptr) {
+ if (plugin->IsEnabled() || aIncludeDisabled) {
+ aPluginArray.AppendElement(plugin);
+ }
+ plugin = plugin->mNext;
+ }
+}
+
+// FIXME-jsplugins Check users for order of fake v non-fake
+NS_IMETHODIMP
+nsPluginHost::GetPluginTags(nsTArray<RefPtr<nsIPluginTag>>& aResults) {
+ LoadPlugins();
+
+ for (nsPluginTag* plugin = mPlugins; plugin; plugin = plugin->mNext) {
+ aResults.AppendElement(plugin);
+ }
+
+ for (nsIInternalPluginTag* plugin : mFakePlugins) {
+ aResults.AppendElement(plugin);
+ }
+
+ return NS_OK;
+}
+
+nsPluginTag* nsPluginHost::FindPreferredPlugin(
+ const nsTArray<nsPluginTag*>& matches) {
+ // We prefer the plugin with the highest version number.
+ /// XXX(johns): This seems to assume the only time multiple plugins will have
+ /// the same MIME type is if they're multiple versions of the same
+ /// plugin -- but since plugin filenames and pretty names can both
+ /// update, it's probably less arbitrary than just going at it
+ /// alphabetically.
+
+ if (matches.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsPluginTag* preferredPlugin = matches[0];
+ for (unsigned int i = 1; i < matches.Length(); i++) {
+ if (mozilla::Version(matches[i]->Version().get()) >
+ preferredPlugin->Version().get()) {
+ preferredPlugin = matches[i];
+ }
+ }
+
+ return preferredPlugin;
+}
+
+nsFakePluginTag* nsPluginHost::FindFakePluginForExtension(
+ const nsACString& aExtension,
+ /* out */ nsACString& aMimeType, bool aCheckEnabled) {
+ if (aExtension.IsEmpty()) {
+ return nullptr;
+ }
+
+ int32_t numFakePlugins = mFakePlugins.Length();
+ for (int32_t i = 0; i < numFakePlugins; i++) {
+ nsFakePluginTag* plugin = mFakePlugins[i];
+ bool active;
+ if ((!aCheckEnabled ||
+ (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) &&
+ plugin->HasExtension(aExtension, aMimeType)) {
+ return plugin;
+ }
+ }
+
+ return nullptr;
+}
+
+nsFakePluginTag* nsPluginHost::FindFakePluginForType(
+ const nsACString& aMimeType, bool aCheckEnabled) {
+ int32_t numFakePlugins = mFakePlugins.Length();
+ for (int32_t i = 0; i < numFakePlugins; i++) {
+ nsFakePluginTag* plugin = mFakePlugins[i];
+ bool active;
+ if ((!aCheckEnabled ||
+ (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) &&
+ plugin->HasMimeType(aMimeType)) {
+ return plugin;
+ }
+ }
+
+ return nullptr;
+}
+
+nsPluginTag* nsPluginHost::FindNativePluginForType(const nsACString& aMimeType,
+ bool aCheckEnabled) {
+ if (aMimeType.IsEmpty()) {
+ return nullptr;
+ }
+
+ // As of FF 52, we only support flash and test plugins, so if the mime types
+ // don't match for that, exit before we start loading plugins.
+ if (!nsPluginHost::CanUsePluginForMIMEType(aMimeType)) {
+ return nullptr;
+ }
+
+ LoadPlugins();
+
+ nsTArray<nsPluginTag*> matchingPlugins;
+
+ nsPluginTag* plugin = mPlugins;
+ while (plugin) {
+ if ((!aCheckEnabled || plugin->IsActive()) &&
+ plugin->HasMimeType(aMimeType)) {
+ matchingPlugins.AppendElement(plugin);
+ }
+ plugin = plugin->mNext;
+ }
+
+ return FindPreferredPlugin(matchingPlugins);
+}
+
+nsPluginTag* nsPluginHost::FindNativePluginForExtension(
+ const nsACString& aExtension,
+ /* out */ nsACString& aMimeType, bool aCheckEnabled) {
+ if (aExtension.IsEmpty()) {
+ return nullptr;
+ }
+
+ LoadPlugins();
+
+ nsTArray<nsPluginTag*> matchingPlugins;
+ nsCString matchingMime; // Don't mutate aMimeType unless returning a match
+ nsPluginTag* plugin = mPlugins;
+
+ while (plugin) {
+ if (!aCheckEnabled || plugin->IsActive()) {
+ if (plugin->HasExtension(aExtension, matchingMime)) {
+ matchingPlugins.AppendElement(plugin);
+ }
+ }
+ plugin = plugin->mNext;
+ }
+
+ nsPluginTag* preferredPlugin = FindPreferredPlugin(matchingPlugins);
+ if (!preferredPlugin) {
+ return nullptr;
+ }
+
+ // Re-fetch the matching type because of how FindPreferredPlugin works...
+ preferredPlugin->HasExtension(aExtension, aMimeType);
+ return preferredPlugin;
+}
+
+static nsresult CreateNPAPIPlugin(nsPluginTag* aPluginTag,
+ nsNPAPIPlugin** aOutNPAPIPlugin) {
+ nsresult rv;
+ rv = nsNPAPIPlugin::CreatePlugin(aPluginTag, aOutNPAPIPlugin);
+
+ return rv;
+}
+
+nsresult nsPluginHost::EnsurePluginLoaded(nsPluginTag* aPluginTag) {
+ RefPtr<nsNPAPIPlugin> plugin = aPluginTag->mPlugin;
+ if (!plugin) {
+ nsresult rv = CreateNPAPIPlugin(aPluginTag, getter_AddRefs(plugin));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aPluginTag->mPlugin = plugin;
+ }
+ return NS_OK;
+}
+
+nsresult nsPluginHost::GetPluginForContentProcess(uint32_t aPluginId,
+ nsNPAPIPlugin** aPlugin) {
+ AUTO_PROFILER_LABEL("nsPluginHost::GetPluginForContentProcess", OTHER);
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // If plugins haven't been scanned yet, do so now
+ LoadPlugins();
+
+ nsPluginTag* pluginTag = PluginWithId(aPluginId);
+ if (pluginTag) {
+ // When setting up a bridge, double check with chrome to see if this plugin
+ // is blocked hard. Note this does not protect against vulnerable plugins
+ // that the user has explicitly allowed. :(
+ if (pluginTag->IsBlocklisted()) {
+ return NS_ERROR_PLUGIN_BLOCKLISTED;
+ }
+
+ nsresult rv = EnsurePluginLoaded(pluginTag);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We only get here if a content process doesn't have a PluginModuleParent
+ // for the given plugin already. Therefore, this counter is counting the
+ // number of outstanding PluginModuleParents for the plugin, excluding the
+ // one from the chrome process.
+ pluginTag->mContentProcessRunningCount++;
+ NS_ADDREF(*aPlugin = pluginTag->mPlugin);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+class nsPluginUnloadRunnable : public Runnable {
+ public:
+ explicit nsPluginUnloadRunnable(uint32_t aPluginId)
+ : Runnable("nsPluginUnloadRunnable"), mPluginId(aPluginId) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+ if (!host) {
+ return NS_OK;
+ }
+ nsPluginTag* pluginTag = host->PluginWithId(mPluginId);
+ if (!pluginTag) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(pluginTag->mContentProcessRunningCount > 0);
+ pluginTag->mContentProcessRunningCount--;
+
+ if (!pluginTag->mContentProcessRunningCount) {
+ if (!host->IsRunningPlugin(pluginTag)) {
+ pluginTag->TryUnloadPlugin(false);
+ }
+ }
+ return NS_OK;
+ }
+
+ protected:
+ uint32_t mPluginId;
+};
+
+void nsPluginHost::NotifyContentModuleDestroyed(uint32_t aPluginId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // This is called in response to a message from the plugin. Don't unload the
+ // plugin until the message handler is off the stack.
+ RefPtr<nsPluginUnloadRunnable> runnable =
+ new nsPluginUnloadRunnable(aPluginId);
+ NS_DispatchToMainThread(runnable);
+}
+
+nsresult nsPluginHost::GetPlugin(const nsACString& aMimeType,
+ nsNPAPIPlugin** aPlugin) {
+ nsresult rv = NS_ERROR_FAILURE;
+ *aPlugin = nullptr;
+
+ // If plugins haven't been scanned yet, do so now
+ LoadPlugins();
+
+ nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true);
+ if (pluginTag) {
+ rv = NS_OK;
+ PLUGIN_LOG(
+ PLUGIN_LOG_BASIC,
+ ("nsPluginHost::GetPlugin Begin mime=%s, plugin=%s\n",
+ PromiseFlatCString(aMimeType).get(), pluginTag->FileName().get()));
+
+#ifdef DEBUG
+ if (!pluginTag->FileName().IsEmpty())
+ printf("For %s found plugin %s\n", PromiseFlatCString(aMimeType).get(),
+ pluginTag->FileName().get());
+#endif
+
+ rv = EnsurePluginLoaded(pluginTag);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_ADDREF(*aPlugin = pluginTag->mPlugin);
+ return NS_OK;
+ }
+
+ PLUGIN_LOG(
+ PLUGIN_LOG_NORMAL,
+ ("nsPluginHost::GetPlugin End mime=%s, rv=%" PRIu32
+ ", plugin=%p name=%s\n",
+ PromiseFlatCString(aMimeType).get(), static_cast<uint32_t>(rv), *aPlugin,
+ (pluginTag ? pluginTag->FileName().get() : "(not found)")));
+
+ return rv;
+}
+
+// Normalize 'host' to ACE.
+nsresult nsPluginHost::NormalizeHostname(nsCString& host) {
+ if (IsAscii(host)) {
+ ToLowerCase(host);
+ return NS_OK;
+ }
+
+ if (!mIDNService) {
+ nsresult rv;
+ mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mIDNService->ConvertUTF8toACE(host, host);
+}
+
+// Enumerate a 'sites' array returned by GetSitesWithData and determine if
+// any of them have a base domain in common with 'domain'; if so, append them
+// to the 'result' array. If 'firstMatchOnly' is true, return after finding the
+// first match.
+nsresult nsPluginHost::EnumerateSiteData(const nsACString& domain,
+ const nsTArray<nsCString>& sites,
+ nsTArray<nsCString>& result,
+ bool firstMatchOnly) {
+ NS_ASSERTION(!domain.IsVoid(), "null domain string");
+
+ nsresult rv;
+ if (!mTLDService) {
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Get the base domain from the domain.
+ nsCString baseDomain;
+ rv = mTLDService->GetBaseDomainFromHost(domain, 0, baseDomain);
+ bool isIP = rv == NS_ERROR_HOST_IS_IP_ADDRESS;
+ if (isIP || rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ // The base domain is the site itself. However, we must be careful to
+ // normalize.
+ baseDomain = domain;
+ rv = NormalizeHostname(baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Enumerate the array of sites with data.
+ for (uint32_t i = 0; i < sites.Length(); ++i) {
+ const nsCString& site = sites[i];
+
+ // Check if the site is an IP address.
+ bool siteIsIP =
+ site.Length() >= 2 && site.First() == '[' && site.Last() == ']';
+ if (siteIsIP != isIP) continue;
+
+ nsCString siteBaseDomain;
+ if (siteIsIP) {
+ // Strip the '[]'.
+ siteBaseDomain = Substring(site, 1, site.Length() - 2);
+ } else {
+ // Determine the base domain of the site.
+ rv = mTLDService->GetBaseDomainFromHost(site, 0, siteBaseDomain);
+ if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ // The base domain is the site itself. However, we must be careful to
+ // normalize.
+ siteBaseDomain = site;
+ rv = NormalizeHostname(siteBaseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // At this point, we can do an exact comparison of the two domains.
+ if (baseDomain != siteBaseDomain) {
+ continue;
+ }
+
+ // Append the site to the result array.
+ result.AppendElement(site);
+
+ // If we're supposed to return early, do so.
+ if (firstMatchOnly) {
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+static bool MimeTypeIsAllowedForFakePlugin(const nsString& aMimeType) {
+ static const char* const allowedFakePlugins[] = {
+ // Flash
+ "application/x-shockwave-flash",
+ // PDF
+ "application/pdf",
+ "application/vnd.adobe.pdf",
+ "application/vnd.adobe.pdfxml",
+ "application/vnd.adobe.x-mars",
+ "application/vnd.adobe.xdp+xml",
+ "application/vnd.adobe.xfdf",
+ "application/vnd.adobe.xfd+xml",
+ "application/vnd.fdf",
+ };
+
+ for (const auto allowed : allowedFakePlugins) {
+ if (aMimeType.EqualsASCII(allowed)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+nsPluginHost::RegisterFakePlugin(JS::Handle<JS::Value> aInitDictionary,
+ JSContext* aCx, nsIFakePluginTag** aResult) {
+ FakePluginTagInit initDictionary;
+ if (!initDictionary.Init(aCx, aInitDictionary)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const FakePluginMimeEntry& mimeEntry : initDictionary.mMimeEntries) {
+ if (!MimeTypeIsAllowedForFakePlugin(mimeEntry.mType)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ RefPtr<nsFakePluginTag> newTag;
+ nsresult rv = nsFakePluginTag::Create(initDictionary, getter_AddRefs(newTag));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (const auto& existingTag : mFakePlugins) {
+ if (newTag->HandlerURIMatches(existingTag->HandlerURI())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ mFakePlugins.AppendElement(newTag);
+
+ nsAutoCString disableFullPage;
+ Preferences::GetCString(kPrefDisableFullPage, disableFullPage);
+ for (uint32_t i = 0; i < newTag->MimeTypes().Length(); i++) {
+ if (!IsTypeInList(newTag->MimeTypes()[i], disableFullPage)) {
+ RegisterWithCategoryManager(newTag->MimeTypes()[i], ePluginRegister);
+ }
+ }
+
+ newTag.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginHost::CreateFakePlugin(JS::Handle<JS::Value> aInitDictionary,
+ JSContext* aCx, nsIFakePluginTag** aResult) {
+ FakePluginTagInit initDictionary;
+ if (!initDictionary.Init(aCx, aInitDictionary)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsFakePluginTag> newTag;
+ nsresult rv = nsFakePluginTag::Create(initDictionary, getter_AddRefs(newTag));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newTag.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginHost::UnregisterFakePlugin(const nsACString& aHandlerURI) {
+ nsCOMPtr<nsIURI> handlerURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(handlerURI), aHandlerURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < mFakePlugins.Length(); ++i) {
+ if (mFakePlugins[i]->HandlerURIMatches(handlerURI)) {
+ mFakePlugins.RemoveElementAt(i);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+// FIXME-jsplugins Is this method actually needed?
+NS_IMETHODIMP
+nsPluginHost::GetFakePlugin(const nsACString& aMimeType,
+ nsIFakePluginTag** aResult) {
+ RefPtr<nsFakePluginTag> result = FindFakePluginForType(aMimeType, false);
+ if (result) {
+ result.forget(aResult);
+ return NS_OK;
+ }
+
+ *aResult = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+#define ClearDataFromSitesClosure_CID \
+ { \
+ 0x9fb21761, 0x2403, 0x41ad, { \
+ 0x9e, 0xfd, 0x36, 0x7e, 0xc4, 0x4f, 0xa4, 0x5e \
+ } \
+ }
+
+// Class to hold all the data we need need for IterateMatchesAndClear and
+// ClearDataFromSites
+class ClearDataFromSitesClosure : public nsIClearSiteDataCallback,
+ public nsIGetSitesWithDataCallback {
+ public:
+ ClearDataFromSitesClosure(nsIPluginTag* plugin, const nsACString& domain,
+ uint64_t flags, int64_t maxAge,
+ nsCOMPtr<nsIClearSiteDataCallback> callback,
+ nsPluginHost* host)
+ : domain(domain),
+ callback(callback),
+ tag(plugin),
+ flags(flags),
+ maxAge(maxAge),
+ host(host) {}
+ NS_DECL_ISUPPORTS
+
+ // Callback from NPP_ClearSiteData, continue to iterate the matches and clear
+ NS_IMETHOD Callback(nsresult rv) override {
+ if (NS_FAILED(rv)) {
+ callback->Callback(rv);
+ return NS_OK;
+ }
+ if (!matches.Length()) {
+ callback->Callback(NS_OK);
+ return NS_OK;
+ }
+
+ const nsCString match(matches[0]);
+ matches.RemoveElement(match);
+ PluginLibrary* library =
+ static_cast<nsPluginTag*>(tag)->mPlugin->GetLibrary();
+ rv = library->NPP_ClearSiteData(match.get(), flags, maxAge, this);
+ if (NS_FAILED(rv)) {
+ callback->Callback(rv);
+ return NS_OK;
+ }
+ return NS_OK;
+ }
+
+ // Callback from NPP_GetSitesWithData, kick the iteration off to clear the
+ // data
+ NS_IMETHOD SitesWithData(nsTArray<nsCString>& sites) override {
+ // Enumerate the sites and build a list of matches.
+ nsresult rv = host->EnumerateSiteData(domain, sites, matches, false);
+ Callback(rv);
+ return NS_OK;
+ }
+
+ nsCString domain;
+ nsCOMPtr<nsIClearSiteDataCallback> callback;
+ nsTArray<nsCString> matches;
+ nsIPluginTag* tag;
+ uint64_t flags;
+ int64_t maxAge;
+ nsPluginHost* host;
+ NS_DECLARE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure_CID)
+ private:
+ virtual ~ClearDataFromSitesClosure() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure,
+ ClearDataFromSitesClosure_CID)
+
+NS_IMPL_ADDREF(ClearDataFromSitesClosure)
+NS_IMPL_RELEASE(ClearDataFromSitesClosure)
+
+NS_INTERFACE_MAP_BEGIN(ClearDataFromSitesClosure)
+ NS_INTERFACE_MAP_ENTRY(nsIClearSiteDataCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIGetSitesWithDataCallback)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClearSiteDataCallback)
+NS_INTERFACE_MAP_END
+
+// FIXME-jsplugins what should this do for fake plugins?
+NS_IMETHODIMP
+nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain,
+ uint64_t flags, int64_t maxAge,
+ nsIClearSiteDataCallback* callbackFunc) {
+ nsCOMPtr<nsIClearSiteDataCallback> callback(callbackFunc);
+ // maxAge must be either a nonnegative integer or -1.
+ NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1);
+
+ // Caller may give us a tag object that is no longer live.
+ if (!IsLiveTag(plugin)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsPluginTag* tag = static_cast<nsPluginTag*>(plugin);
+
+ if (!tag->IsEnabled()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We only ensure support for clearing Flash site data for now.
+ // We will also attempt to clear data for any plugin that happens
+ // to be loaded already.
+ if (!tag->mIsFlashPlugin && !tag->mPlugin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure the plugin is loaded.
+ nsresult rv = EnsurePluginLoaded(tag);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ PluginLibrary* library = tag->mPlugin->GetLibrary();
+
+ // If 'domain' is the null string, clear everything.
+ if (domain.IsVoid()) {
+ return library->NPP_ClearSiteData(nullptr, flags, maxAge, callback);
+ }
+ nsCOMPtr<nsIGetSitesWithDataCallback> closure(new ClearDataFromSitesClosure(
+ plugin, domain, flags, maxAge, callback, this));
+ rv = library->NPP_GetSitesWithData(closure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+#define GetSitesClosure_CID \
+ { \
+ 0x4c9268ac, 0x2fd1, 0x4f2a, { \
+ 0x9a, 0x10, 0x7a, 0x09, 0xf1, 0xb7, 0x60, 0x3a \
+ } \
+ }
+
+// Closure to contain the data needed to handle the callback from
+// NPP_GetSitesWithData
+class GetSitesClosure : public nsIGetSitesWithDataCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ GetSitesClosure(const nsACString& domain, nsPluginHost* host)
+ : domain(domain),
+ host(host),
+ result{false},
+ keepWaiting(true),
+ retVal(NS_ERROR_NOT_INITIALIZED) {}
+
+ NS_IMETHOD SitesWithData(nsTArray<nsCString>& sites) override {
+ retVal = HandleGetSites(sites);
+ keepWaiting = false;
+ return NS_OK;
+ }
+
+ nsresult HandleGetSites(nsTArray<nsCString>& sites) {
+ // If there's no data, we're done.
+ if (sites.IsEmpty()) {
+ result = false;
+ return NS_OK;
+ }
+
+ // If 'domain' is the null string, and there's data for at least one site,
+ // we're done.
+ if (domain.IsVoid()) {
+ result = true;
+ return NS_OK;
+ }
+
+ // Enumerate the sites and determine if there's a match.
+ nsTArray<nsCString> matches;
+ nsresult rv = host->EnumerateSiteData(domain, sites, matches, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ result = !matches.IsEmpty();
+ return NS_OK;
+ }
+
+ nsCString domain;
+ RefPtr<nsPluginHost> host;
+ bool result;
+ bool keepWaiting;
+ nsresult retVal;
+ NS_DECLARE_STATIC_IID_ACCESSOR(GetSitesClosure_CID)
+ private:
+ virtual ~GetSitesClosure() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(GetSitesClosure, GetSitesClosure_CID)
+
+NS_IMPL_ISUPPORTS(GetSitesClosure, GetSitesClosure, nsIGetSitesWithDataCallback)
+
+// This will spin the event loop while waiting on an async
+// call to GetSitesWithData
+NS_IMETHODIMP
+nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain,
+ bool* result) {
+ // Caller may give us a tag object that is no longer live.
+ if (!IsLiveTag(plugin)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // FIXME-jsplugins audit casts
+ nsPluginTag* tag = static_cast<nsPluginTag*>(plugin);
+
+ // We only ensure support for clearing Flash site data for now.
+ // We will also attempt to clear data for any plugin that happens
+ // to be loaded already.
+ if (!tag->mIsFlashPlugin && !tag->mPlugin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure the plugin is loaded.
+ nsresult rv = EnsurePluginLoaded(tag);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ PluginLibrary* library = tag->mPlugin->GetLibrary();
+
+ // Get the list of sites from the plugin
+ nsCOMPtr<GetSitesClosure> closure(new GetSitesClosure(domain, this));
+ rv = library->NPP_GetSitesWithData(
+ nsCOMPtr<nsIGetSitesWithDataCallback>(closure));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Spin the event loop while we wait for the async call to GetSitesWithData
+ SpinEventLoopUntil([&]() { return !closure->keepWaiting; });
+ *result = closure->result;
+ return closure->retVal;
+}
+
+nsPluginHost::SpecialType nsPluginHost::GetSpecialType(
+ const nsACString& aMIMEType) {
+ if (aMIMEType.LowerCaseEqualsASCII("application/x-test")) {
+ return eSpecialType_Test;
+ }
+
+ if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") ||
+ aMIMEType.LowerCaseEqualsASCII("application/futuresplash") ||
+ aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash-test")) {
+ return eSpecialType_Flash;
+ }
+
+ return eSpecialType_None;
+}
+
+// Check whether or not a tag is a live, valid tag, and that it's loaded.
+bool nsPluginHost::IsLiveTag(nsIPluginTag* aPluginTag) {
+ nsCOMPtr<nsIInternalPluginTag> internalTag(do_QueryInterface(aPluginTag));
+ uint32_t fakeCount = mFakePlugins.Length();
+ for (uint32_t i = 0; i < fakeCount; i++) {
+ if (mFakePlugins[i] == internalTag) {
+ return true;
+ }
+ }
+
+ nsPluginTag* tag;
+ for (tag = mPlugins; tag; tag = tag->mNext) {
+ if (tag == internalTag) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// FIXME-jsplugins what should happen with jsplugins here, if anything?
+nsPluginTag* nsPluginHost::HaveSamePlugin(const nsPluginTag* aPluginTag) {
+ for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) {
+ if (tag->HasSameNameAndMimes(aPluginTag)) {
+ return tag;
+ }
+ }
+ return nullptr;
+}
+
+nsPluginTag* nsPluginHost::PluginWithId(uint32_t aId) {
+ for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) {
+ if (tag->mId == aId) {
+ return tag;
+ }
+ }
+ return nullptr;
+}
+
+void nsPluginHost::AddPluginTag(nsPluginTag* aPluginTag) {
+ aPluginTag->mNext = mPlugins;
+ mPlugins = aPluginTag;
+
+ if (aPluginTag->IsActive()) {
+ nsAutoCString disableFullPage;
+ Preferences::GetCString(kPrefDisableFullPage, disableFullPage);
+ for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) {
+ if (!IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) {
+ RegisterWithCategoryManager(aPluginTag->MimeTypes()[i],
+ ePluginRegister);
+ }
+ }
+ }
+}
+
+typedef NS_NPAPIPLUGIN_CALLBACK(char*, NP_GETMIMEDESCRIPTION)(void);
+
+void nsPluginHost::UpdatePluginBlocklistState(nsPluginTag* aPluginTag,
+ bool aShouldSoftblock) {
+ nsCOMPtr<nsIBlocklistService> blocklist =
+ do_GetService("@mozilla.org/extensions/blocklist;1");
+ MOZ_ASSERT(blocklist, "Should be able to access the blocklist");
+ if (!blocklist) {
+ return;
+ }
+ // Asynchronously get the blocklist state.
+ RefPtr<Promise> promise;
+ blocklist->GetPluginBlocklistState(aPluginTag, u""_ns, u""_ns,
+ getter_AddRefs(promise));
+ MOZ_ASSERT(promise,
+ "Should always get a promise for plugin blocklist state.");
+ if (promise) {
+ promise->AppendNativeHandler(new mozilla::plugins::BlocklistPromiseHandler(
+ aPluginTag, aShouldSoftblock));
+ }
+}
+
+void nsPluginHost::IncrementChromeEpoch() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ mPluginEpoch++;
+}
+
+uint32_t nsPluginHost::ChromeEpoch() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return mPluginEpoch;
+}
+
+uint32_t nsPluginHost::ChromeEpochForContent() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ return mPluginEpoch;
+}
+
+void nsPluginHost::SetChromeEpochForContent(uint32_t aEpoch) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ mPluginEpoch = aEpoch;
+}
+
+already_AddRefed<nsIAsyncShutdownClient> GetProfileChangeTeardownPhase() {
+ nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc =
+ services::GetAsyncShutdownService();
+ MOZ_ASSERT(asyncShutdownSvc);
+ if (NS_WARN_IF(!asyncShutdownSvc)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
+ DebugOnly<nsresult> rv =
+ asyncShutdownSvc->GetProfileChangeTeardown(getter_AddRefs(shutdownPhase));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return shutdownPhase.forget();
+}
+
+nsresult nsPluginHost::LoadPlugins() { return NS_OK; }
+
+void nsPluginHost::FindingFinished() {}
+
+nsresult nsPluginHost::SetPluginsInContent(
+ uint32_t aPluginEpoch, nsTArray<mozilla::plugins::PluginTag>& aPlugins,
+ nsTArray<mozilla::plugins::FakePluginTag>& aFakePlugins) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ nsTArray<PluginTag> plugins;
+
+ nsTArray<FakePluginTag> fakePlugins;
+
+ if (aPluginEpoch != ChromeEpochForContent()) {
+ // Since we know we're going to be repopulating the lists anyways,
+ // clear out all old entries.
+ ClearNonRunningPlugins();
+
+ SetChromeEpochForContent(aPluginEpoch);
+
+ for (auto tag : aPlugins) {
+ // Don't add the same plugin again.
+ if (nsPluginTag* existing = PluginWithId(tag.id())) {
+ UpdateInMemoryPluginInfo(existing);
+ existing->SetBlocklistState(tag.blocklistState());
+ continue;
+ }
+
+ nsPluginTag* pluginTag = new nsPluginTag(
+ tag.id(), tag.name().get(), tag.description().get(),
+ tag.filename().get(),
+ "", // aFullPath
+ tag.version().get(), tag.mimeTypes().Clone(),
+ tag.mimeDescriptions().Clone(), tag.extensions().Clone(),
+ tag.isFlashPlugin(), tag.supportsAsyncRender(),
+ tag.lastModifiedTime(), tag.sandboxLevel(), tag.blocklistState());
+ AddPluginTag(pluginTag);
+ }
+
+ for (const auto& tag : aFakePlugins) {
+ // Don't add the same plugin again.
+ for (const auto& existingTag : mFakePlugins) {
+ if (existingTag->Id() == tag.id()) {
+ continue;
+ }
+ }
+
+ RefPtr<nsFakePluginTag> pluginTag =
+ *mFakePlugins.AppendElement(new nsFakePluginTag(
+ tag.id(), mozilla::ipc::DeserializeURI(tag.handlerURI()),
+ tag.name().get(), tag.description().get(), tag.mimeTypes(),
+ tag.mimeDescriptions(), tag.extensions(), tag.niceName(),
+ tag.sandboxScript()));
+ nsAutoCString disableFullPage;
+ Preferences::GetCString(kPrefDisableFullPage, disableFullPage);
+ for (uint32_t i = 0; i < pluginTag->MimeTypes().Length(); i++) {
+ if (!IsTypeInList(pluginTag->MimeTypes()[i], disableFullPage)) {
+ RegisterWithCategoryManager(pluginTag->MimeTypes()[i],
+ ePluginRegister);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr);
+ }
+ }
+
+ mPluginsLoaded = true;
+ return NS_OK;
+}
+
+nsresult nsPluginHost::UpdateCachedSerializablePluginList() {
+ nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins;
+ GetPlugins(plugins, true);
+ mSerializablePlugins.Clear();
+ mSerializableFakePlugins.Clear();
+
+ for (size_t i = 0; i < plugins.Length(); i++) {
+ nsCOMPtr<nsIInternalPluginTag> basetag = plugins[i];
+
+ nsCOMPtr<nsIFakePluginTag> faketag = do_QueryInterface(basetag);
+ if (faketag) {
+ /// FIXME-jsplugins - We need to add a nsIInternalPluginTag->AsNative() to
+ /// avoid this hacky static cast
+ nsFakePluginTag* tag = static_cast<nsFakePluginTag*>(basetag.get());
+ mozilla::ipc::URIParams handlerURI;
+ SerializeURI(tag->HandlerURI(), handlerURI);
+ mSerializableFakePlugins.AppendElement(FakePluginTag(
+ tag->Id(), handlerURI, tag->Name(), tag->Description(),
+ tag->MimeTypes(), tag->MimeDescriptions(), tag->Extensions(),
+ tag->GetNiceFileName(), tag->SandboxScript()));
+ continue;
+ }
+
+ /// FIXME-jsplugins - We need to cleanup the various plugintag classes
+ /// to be more sane and avoid this dance
+ nsPluginTag* tag = static_cast<nsPluginTag*>(basetag.get());
+
+ uint32_t blocklistState;
+ if (NS_WARN_IF(NS_FAILED(tag->GetBlocklistState(&blocklistState)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mSerializablePlugins.AppendElement(PluginTag(
+ tag->mId, tag->Name(), tag->Description(), tag->MimeTypes(),
+ tag->MimeDescriptions(), tag->Extensions(), tag->mIsFlashPlugin,
+ tag->mSupportsAsyncRender, tag->FileName(), tag->Version(),
+ tag->mLastModifiedTime, tag->mSandboxLevel, blocklistState));
+ }
+ return NS_OK;
+}
+
+nsresult nsPluginHost::BroadcastPluginsToContent() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // Load plugins so that the epoch is correct.
+ nsresult rv = LoadPlugins();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = UpdateCachedSerializablePluginList();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t newPluginEpoch = ChromeEpoch();
+
+ nsTArray<dom::ContentParent*> parents;
+ dom::ContentParent::GetAll(parents);
+ for (auto p : parents) {
+ Unused << p->SendSetPluginList(newPluginEpoch, mSerializablePlugins,
+ mSerializableFakePlugins);
+ }
+ return NS_OK;
+}
+
+nsresult nsPluginHost::SendPluginsToContent(dom::ContentParent* parent) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(parent);
+ // Load plugins so that the epoch is correct.
+ nsresult rv = LoadPlugins();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Unused << parent->SendSetPluginList(ChromeEpoch(), mSerializablePlugins,
+ mSerializableFakePlugins);
+ return NS_OK;
+}
+
+void nsPluginHost::UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag) {
+ if (!aPluginTag) {
+ return;
+ }
+
+ // Update types with category manager
+ nsAutoCString disableFullPage;
+ Preferences::GetCString(kPrefDisableFullPage, disableFullPage);
+ for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) {
+ nsRegisterType shouldRegister;
+
+ if (IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) {
+ shouldRegister = ePluginUnregister;
+ } else {
+ nsPluginTag* plugin =
+ FindNativePluginForType(aPluginTag->MimeTypes()[i], true);
+ shouldRegister = plugin ? ePluginRegister : ePluginUnregister;
+ }
+
+ RegisterWithCategoryManager(aPluginTag->MimeTypes()[i], shouldRegister);
+ }
+
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService)
+ obsService->NotifyObservers(nullptr, "plugin-info-updated", nullptr);
+}
+
+// This function is not relevant for fake plugins.
+void nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ IncrementChromeEpoch();
+
+ UpdateInMemoryPluginInfo(aPluginTag);
+}
+
+void nsPluginHost::RegisterWithCategoryManager(const nsCString& aMimeType,
+ nsRegisterType aType) {
+ PLUGIN_LOG(
+ PLUGIN_LOG_NORMAL,
+ ("nsPluginTag::RegisterWithCategoryManager type = %s, removing = %s\n",
+ aMimeType.get(), aType == ePluginUnregister ? "yes" : "no"));
+
+ nsCOMPtr<nsICategoryManager> catMan =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ if (!catMan) {
+ return;
+ }
+
+ constexpr auto contractId =
+ "@mozilla.org/content/plugin/document-loader-factory;1"_ns;
+
+ if (aType == ePluginRegister) {
+ catMan->AddCategoryEntry("Gecko-Content-Viewers", aMimeType, contractId,
+ false, /* persist: broken by bug 193031 */
+ mOverrideInternalTypes);
+ } else {
+ if (aType == ePluginMaybeUnregister) {
+ // Bail out if this type is still used by an enabled plugin
+ if (HavePluginForType(aMimeType)) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT(aType == ePluginUnregister, "Unknown nsRegisterType");
+ }
+
+ // Only delete the entry if a plugin registered for it
+ nsCString value;
+ nsresult rv =
+ catMan->GetCategoryEntry("Gecko-Content-Viewers", aMimeType, value);
+ if (NS_SUCCEEDED(rv) && value == contractId) {
+ catMan->DeleteCategoryEntry("Gecko-Content-Viewers", aMimeType, true);
+ }
+ }
+}
+
+nsresult nsPluginHost::NewPluginURLStream(
+ const nsString& aURL, nsNPAPIPluginInstance* aInstance,
+ nsNPAPIPluginStreamListener* aListener, nsIInputStream* aPostStream,
+ const char* aHeadersData, uint32_t aHeadersDataLen) {
+ nsCOMPtr<nsIURI> url;
+ nsAutoString absUrl;
+
+ if (aURL.Length() <= 0) return NS_OK;
+
+ // get the base URI for the plugin to create an absolute url
+ // in case aURL is relative
+ RefPtr<nsPluginInstanceOwner> owner = aInstance->GetOwner();
+ if (owner) {
+ NS_MakeAbsoluteURI(absUrl, aURL, owner->GetBaseURI());
+ }
+
+ if (absUrl.IsEmpty()) absUrl.Assign(aURL);
+
+ nsresult rv = NS_NewURI(getter_AddRefs(url), absUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsPluginStreamListenerPeer> listenerPeer =
+ new nsPluginStreamListenerPeer();
+ NS_ENSURE_TRUE(listenerPeer, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = listenerPeer->Initialize(url, aInstance, aListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<dom::Element> element;
+ nsCOMPtr<Document> doc;
+ if (owner) {
+ owner->GetDOMElement(getter_AddRefs(element));
+ owner->GetDocument(getter_AddRefs(doc));
+ }
+
+ NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIChannel> channel;
+ // @arg loadgroup:
+ // do not add this internal plugin's channel on the
+ // load group otherwise this channel could be canceled
+ // form |nsDocShell::OnLinkClickSync| bug 166613
+ rv = NS_NewChannel(
+ getter_AddRefs(channel), url, element,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+ nsIContentPolicy::TYPE_OBJECT_SUBREQUEST,
+ nullptr, // aPerformanceStorage
+ nullptr, // aLoadGroup
+ listenerPeer,
+ nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (doc) {
+ // And if it's a script allow it to execute against the
+ // document's script context.
+ nsCOMPtr<nsIScriptChannel> scriptChannel(do_QueryInterface(channel));
+ if (scriptChannel) {
+ scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
+ // Plug-ins seem to depend on javascript: URIs running synchronously
+ scriptChannel->SetExecuteAsync(false);
+ }
+ }
+
+ // deal with headers and post data
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ if (!aPostStream) {
+ // Only set the Referer header for GET requests because IIS throws
+ // errors about malformed requests if we include it in POSTs. See
+ // bug 724465.
+ nsCOMPtr<nsIURI> referer;
+ dom::ReferrerPolicy referrerPolicy = dom::ReferrerPolicy::_empty;
+
+ nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(element);
+ if (olc) olc->GetSrcURI(getter_AddRefs(referer));
+
+ if (!referer) {
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+ referer = doc->GetDocumentURIAsReferrer();
+ referrerPolicy = doc->GetReferrerPolicy();
+ }
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ new dom::ReferrerInfo(referer, referrerPolicy);
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aPostStream) {
+ // XXX it's a bit of a hack to rewind the postdata stream
+ // here but it has to be done in case the post data is
+ // being reused multiple times.
+ nsCOMPtr<nsISeekableStream> postDataSeekable(
+ do_QueryInterface(aPostStream));
+ if (postDataSeekable)
+ postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
+
+ uploadChannel->SetUploadStream(aPostStream, ""_ns, -1);
+ }
+
+ if (aHeadersData) {
+ rv = AddHeadersToChannel(aHeadersData, aHeadersDataLen, httpChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ rv = channel->AsyncOpen(listenerPeer);
+ if (NS_SUCCEEDED(rv)) listenerPeer->TrackRequest(channel);
+ return rv;
+}
+
+nsresult nsPluginHost::AddHeadersToChannel(const char* aHeadersData,
+ uint32_t aHeadersDataLen,
+ nsIChannel* aGenericChannel) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIHttpChannel> aChannel = do_QueryInterface(aGenericChannel);
+ if (!aChannel) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // used during the manipulation of the String from the aHeadersData
+ nsAutoCString headersString;
+ nsAutoCString oneHeader;
+ nsAutoCString headerName;
+ nsAutoCString headerValue;
+ int32_t crlf = 0;
+ int32_t colon = 0;
+
+ // Turn the char * buffer into an nsString.
+ headersString = aHeadersData;
+
+ // Iterate over the nsString: for each "\r\n" delimited chunk,
+ // add the value as a header to the nsIHTTPChannel
+ while (true) {
+ crlf = headersString.Find("\r\n", true);
+ if (-1 == crlf) {
+ rv = NS_OK;
+ return rv;
+ }
+ headersString.Mid(oneHeader, 0, crlf);
+ headersString.Cut(0, crlf + 2);
+ oneHeader.StripWhitespace();
+ colon = oneHeader.Find(":");
+ if (-1 == colon) {
+ rv = NS_ERROR_NULL_POINTER;
+ return rv;
+ }
+ oneHeader.Left(headerName, colon);
+ colon++;
+ oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon);
+
+ // FINALLY: we can set the header!
+
+ rv = aChannel->SetRequestHeader(headerName, headerValue, true);
+ if (NS_FAILED(rv)) {
+ rv = NS_ERROR_NULL_POINTER;
+ return rv;
+ }
+ }
+}
+
+nsresult nsPluginHost::StopPluginInstance(nsNPAPIPluginInstance* aInstance) {
+ AUTO_PROFILER_LABEL("nsPluginHost::StopPluginInstance", OTHER);
+ if (PluginDestructionGuard::DelayDestroy(aInstance)) {
+ return NS_OK;
+ }
+
+ PLUGIN_LOG(
+ PLUGIN_LOG_NORMAL,
+ ("nsPluginHost::StopPluginInstance called instance=%p\n", aInstance));
+
+ if (aInstance->HasStartedDestroying()) {
+ return NS_OK;
+ }
+
+ Telemetry::AutoTimer<Telemetry::PLUGIN_SHUTDOWN_MS> timer;
+ aInstance->Stop();
+
+ // if the instance does not want to be 'cached' just remove it
+ bool doCache = aInstance->ShouldCache();
+ if (doCache) {
+ // try to get the max cached instances from a pref or use default
+ uint32_t cachedInstanceLimit = Preferences::GetUint(
+ NS_PREF_MAX_NUM_CACHED_INSTANCES, DEFAULT_NUMBER_OF_STOPPED_INSTANCES);
+ if (StoppedInstanceCount() >= cachedInstanceLimit) {
+ nsNPAPIPluginInstance* oldestInstance = FindOldestStoppedInstance();
+ if (oldestInstance) {
+ nsPluginTag* pluginTag = TagForPlugin(oldestInstance->GetPlugin());
+ oldestInstance->Destroy();
+ mInstances.RemoveElement(oldestInstance);
+ // TODO: Remove this check once bug 752422 was investigated
+ if (pluginTag) {
+ OnPluginInstanceDestroyed(pluginTag);
+ }
+ }
+ }
+ } else {
+ nsPluginTag* pluginTag = TagForPlugin(aInstance->GetPlugin());
+ aInstance->Destroy();
+ mInstances.RemoveElement(aInstance);
+ // TODO: Remove this check once bug 752422 was investigated
+ if (pluginTag) {
+ OnPluginInstanceDestroyed(pluginTag);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsPluginHost::NewPluginStreamListener(
+ nsIURI* aURI, nsNPAPIPluginInstance* aInstance,
+ nsIStreamListener** aStreamListener) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aStreamListener);
+
+ RefPtr<nsPluginStreamListenerPeer> listener =
+ new nsPluginStreamListenerPeer();
+ nsresult rv = listener->Initialize(aURI, aInstance, nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ listener.forget(aStreamListener);
+
+ return NS_OK;
+}
+
+void nsPluginHost::CreateWidget(nsPluginInstanceOwner* aOwner) {
+ aOwner->CreateWidget();
+
+ // If we've got a native window, the let the plugin know about it.
+ aOwner->CallSetWindow();
+}
+
+NS_IMETHODIMP nsPluginHost::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* someData) {
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
+ UnloadPlugins();
+ }
+ if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
+ mPluginsDisabled = Preferences::GetBool("plugin.disable", false);
+ // Unload or load plugins as needed
+ if (mPluginsDisabled) {
+ UnloadPlugins();
+ } else {
+ LoadPlugins();
+ }
+ }
+ if (XRE_IsParentProcess() && !strcmp("plugin-blocklist-updated", aTopic)) {
+ // The blocklist has updated. Asynchronously get blocklist state for all
+ // items. The promise resolution handler takes care of checking if anything
+ // changed, and writing an updated state to file, as well as sending data to
+ // child processes.
+ nsPluginTag* plugin = mPlugins;
+ while (plugin) {
+ UpdatePluginBlocklistState(plugin);
+ plugin = plugin->mNext;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsPluginHost::ParsePostBufferToFixHeaders(const char* inPostData,
+ uint32_t inPostDataLen,
+ char** outPostData,
+ uint32_t* outPostDataLen) {
+ if (!inPostData || !outPostData || !outPostDataLen)
+ return NS_ERROR_NULL_POINTER;
+
+ *outPostData = 0;
+ *outPostDataLen = 0;
+
+ const char CR = '\r';
+ const char LF = '\n';
+ const char CRLFCRLF[] = {CR, LF, CR, LF, '\0'}; // C string"\r\n\r\n"
+ const char ContentLenHeader[] = "Content-length";
+
+ AutoTArray<const char*, 8> singleLF;
+ const char* pSCntlh =
+ 0; // pointer to start of ContentLenHeader in inPostData
+ const char* pSod = 0; // pointer to start of data in inPostData
+ const char* pEoh = 0; // pointer to end of headers in inPostData
+ const char* pEod =
+ inPostData + inPostDataLen; // pointer to end of inPostData
+ if (*inPostData == LF) {
+ // If no custom headers are required, simply add a blank
+ // line ('\n') to the beginning of the file or buffer.
+ // so *inPostData == '\n' is valid
+ pSod = inPostData + 1;
+ } else {
+ const char* s = inPostData; // tmp pointer to sourse inPostData
+ while (s < pEod) {
+ if (!pSCntlh && (*s == 'C' || *s == 'c') &&
+ (s + sizeof(ContentLenHeader) - 1 < pEod) &&
+ (!PL_strncasecmp(s, ContentLenHeader,
+ sizeof(ContentLenHeader) - 1))) {
+ // lets assume this is ContentLenHeader for now
+ const char* p = pSCntlh = s;
+ p += sizeof(ContentLenHeader) - 1;
+ // search for first CR or LF == end of ContentLenHeader
+ for (; p < pEod; p++) {
+ if (*p == CR || *p == LF) {
+ // got delimiter,
+ // one more check; if previous char is a digit
+ // most likely pSCntlh points to the start of ContentLenHeader
+ if (*(p - 1) >= '0' && *(p - 1) <= '9') {
+ s = p;
+ }
+ break; // for loop
+ }
+ }
+ if (pSCntlh == s) { // curret ptr is the same
+ pSCntlh = 0; // that was not ContentLenHeader
+ break; // there is nothing to parse, break *WHILE LOOP* here
+ }
+ }
+
+ if (*s == CR) {
+ if (pSCntlh && // only if ContentLenHeader is found we are looking for
+ // end of headers
+ ((s + sizeof(CRLFCRLF) - 1) <= pEod) &&
+ !memcmp(s, CRLFCRLF, sizeof(CRLFCRLF) - 1)) {
+ s += sizeof(CRLFCRLF) - 1;
+ pEoh = pSod = s; // data stars here
+ break;
+ }
+ } else if (*s == LF) {
+ if (*(s - 1) != CR) {
+ singleLF.AppendElement(s);
+ }
+ if (pSCntlh && (s + 1 < pEod) && (*(s + 1) == LF)) {
+ s++;
+ singleLF.AppendElement(s);
+ s++;
+ pEoh = pSod = s; // data stars here
+ break;
+ }
+ }
+ s++;
+ }
+ }
+
+ // deal with output buffer
+ if (!pSod) { // lets assume whole buffer is a data
+ pSod = inPostData;
+ }
+
+ uint32_t newBufferLen = 0;
+ uint32_t dataLen = pEod - pSod;
+ uint32_t headersLen = pEoh ? pSod - inPostData : 0;
+
+ char* p; // tmp ptr into new output buf
+ if (headersLen) { // we got a headers
+ // this function does not make any assumption on correctness
+ // of ContentLenHeader value in this case.
+
+ newBufferLen = dataLen + headersLen;
+ // in case there were single LFs in headers
+ // reserve an extra space for CR will be added before each single LF
+ int cntSingleLF = singleLF.Length();
+ newBufferLen += cntSingleLF;
+
+ *outPostData = p = (char*)moz_xmalloc(newBufferLen);
+
+ // deal with single LF
+ const char* s = inPostData;
+ if (cntSingleLF) {
+ for (int i = 0; i < cntSingleLF; i++) {
+ const char* plf = singleLF.ElementAt(i); // ptr to single LF in headers
+ int n = plf - s; // bytes to copy
+ if (n) { // for '\n\n' there is nothing to memcpy
+ memcpy(p, s, n);
+ p += n;
+ }
+ *p++ = CR;
+ s = plf;
+ *p++ = *s++;
+ }
+ }
+ // are we done with headers?
+ headersLen = pEoh - s;
+ if (headersLen) { // not yet
+ memcpy(p, s, headersLen); // copy the rest
+ p += headersLen;
+ }
+ } else if (dataLen) { // no ContentLenHeader is found but there is a data
+ // make new output buffer big enough
+ // to keep ContentLenHeader+value followed by data
+ uint32_t l = sizeof(ContentLenHeader) + sizeof(CRLFCRLF) + 32;
+ newBufferLen = dataLen + l;
+ *outPostData = p = (char*)moz_xmalloc(newBufferLen);
+ headersLen =
+ snprintf(p, l, "%s: %u%s", ContentLenHeader, dataLen, CRLFCRLF);
+ if (headersLen ==
+ l) { // if snprintf has ate all extra space consider this as an error
+ free(p);
+ *outPostData = 0;
+ return NS_ERROR_FAILURE;
+ }
+ p += headersLen;
+ newBufferLen = headersLen + dataLen;
+ }
+ // at this point we've done with headers.
+ // there is a possibility that input buffer has only headers info in it
+ // which already parsed and copied into output buffer.
+ // copy the data
+ if (dataLen) {
+ memcpy(p, pSod, dataLen);
+ }
+
+ *outPostDataLen = newBufferLen;
+
+ return NS_OK;
+}
+
+nsresult nsPluginHost::NewPluginNativeWindow(
+ nsPluginNativeWindow** aPluginNativeWindow) {
+ return PLUG_NewPluginNativeWindow(aPluginNativeWindow);
+}
+
+nsresult nsPluginHost::GetPluginName(nsNPAPIPluginInstance* aPluginInstance,
+ const char** aPluginName) {
+ nsNPAPIPluginInstance* instance =
+ static_cast<nsNPAPIPluginInstance*>(aPluginInstance);
+ if (!instance) return NS_ERROR_FAILURE;
+
+ nsNPAPIPlugin* plugin = instance->GetPlugin();
+ if (!plugin) return NS_ERROR_FAILURE;
+
+ *aPluginName = TagForPlugin(plugin)->Name().get();
+
+ return NS_OK;
+}
+
+nsresult nsPluginHost::GetPluginTagForInstance(
+ nsNPAPIPluginInstance* aPluginInstance, nsIPluginTag** aPluginTag) {
+ NS_ENSURE_ARG_POINTER(aPluginInstance);
+ NS_ENSURE_ARG_POINTER(aPluginTag);
+
+ nsNPAPIPlugin* plugin = aPluginInstance->GetPlugin();
+ if (!plugin) return NS_ERROR_FAILURE;
+
+ *aPluginTag = TagForPlugin(plugin);
+
+ NS_ADDREF(*aPluginTag);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPluginHost::Notify(nsITimer* timer) {
+ RefPtr<nsPluginTag> pluginTag = mPlugins;
+ while (pluginTag) {
+ if (pluginTag->mUnloadTimer == timer) {
+ if (!IsRunningPlugin(pluginTag)) {
+ pluginTag->TryUnloadPlugin(false);
+ }
+ return NS_OK;
+ }
+ pluginTag = pluginTag->mNext;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsPluginHost");
+ return NS_OK;
+}
+
+#ifdef XP_WIN
+// Re-enable any top level browser windows that were disabled by modal dialogs
+// displayed by the crashed plugin.
+static void CheckForDisabledWindows() {
+ nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!wm) return;
+
+ nsCOMPtr<nsISimpleEnumerator> windowList;
+ wm->GetAppWindowEnumerator(nullptr, getter_AddRefs(windowList));
+ if (!windowList) return;
+
+ bool haveWindows;
+ do {
+ windowList->HasMoreElements(&haveWindows);
+ if (!haveWindows) return;
+
+ nsCOMPtr<nsISupports> supportsWindow;
+ windowList->GetNext(getter_AddRefs(supportsWindow));
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(supportsWindow));
+ if (baseWin) {
+ nsCOMPtr<nsIWidget> widget;
+ baseWin->GetMainWidget(getter_AddRefs(widget));
+ if (widget && !widget->GetParent() && widget->IsVisible() &&
+ !widget->IsEnabled()) {
+ nsIWidget* child = widget->GetFirstChild();
+ bool enable = true;
+ while (child) {
+ if (child->WindowType() == eWindowType_dialog) {
+ enable = false;
+ break;
+ }
+ child = child->GetNextSibling();
+ }
+ if (enable) {
+ widget->Enable(true);
+ }
+ }
+ }
+ } while (haveWindows);
+}
+#endif
+
+void nsPluginHost::PluginCrashed(nsNPAPIPlugin* aPlugin,
+ const nsAString& aPluginDumpID,
+ const nsACString& aAdditionalMinidumps) {
+ nsPluginTag* crashedPluginTag = TagForPlugin(aPlugin);
+ MOZ_ASSERT(crashedPluginTag);
+
+ // Notify the app's observer that a plugin crashed so it can submit
+ // a crashreport.
+ bool submittedCrashReport = false;
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ nsCOMPtr<nsIWritablePropertyBag2> propbag =
+ do_CreateInstance("@mozilla.org/hash-property-bag;1");
+ if (obsService && propbag) {
+ uint32_t runID = 0;
+ PluginLibrary* library = aPlugin->GetLibrary();
+
+ if (!NS_WARN_IF(!library)) {
+ library->GetRunID(&runID);
+ }
+ propbag->SetPropertyAsUint32(u"runID"_ns, runID);
+
+ nsCString pluginName;
+ crashedPluginTag->GetName(pluginName);
+ propbag->SetPropertyAsAString(u"pluginName"_ns,
+ NS_ConvertUTF8toUTF16(pluginName));
+ propbag->SetPropertyAsAString(u"pluginDumpID"_ns, aPluginDumpID);
+ propbag->SetPropertyAsACString(u"additionalMinidumps"_ns,
+ aAdditionalMinidumps);
+ propbag->SetPropertyAsBool(u"submittedCrashReport"_ns,
+ submittedCrashReport);
+ obsService->NotifyObservers(propbag, "plugin-crashed", nullptr);
+ // see if an observer submitted a crash report.
+ propbag->GetPropertyAsBool(u"submittedCrashReport"_ns,
+ &submittedCrashReport);
+ }
+
+ // Invalidate each nsPluginInstanceTag for the crashed plugin
+
+ for (uint32_t i = mInstances.Length(); i > 0; i--) {
+ nsNPAPIPluginInstance* instance = mInstances[i - 1];
+ if (instance->GetPlugin() == aPlugin) {
+ // notify the content node (nsIObjectLoadingContent) that the
+ // plugin has crashed
+ RefPtr<dom::Element> domElement;
+ instance->GetDOMElement(getter_AddRefs(domElement));
+ nsCOMPtr<nsIObjectLoadingContent> objectContent(
+ do_QueryInterface(domElement));
+ if (objectContent) {
+ objectContent->PluginCrashed(crashedPluginTag, aPluginDumpID,
+ submittedCrashReport);
+ }
+
+ instance->Destroy();
+ mInstances.RemoveElement(instance);
+ OnPluginInstanceDestroyed(crashedPluginTag);
+ }
+ }
+
+ // Only after all instances have been invalidated is it safe to null
+ // out nsPluginTag.mPlugin. The next time we try to create an
+ // instance of this plugin we reload it (launch a new plugin process).
+
+ crashedPluginTag->mPlugin = nullptr;
+ crashedPluginTag->mContentProcessRunningCount = 0;
+
+#ifdef XP_WIN
+ CheckForDisabledWindows();
+#endif
+}
+
+nsNPAPIPluginInstance* nsPluginHost::FindInstance(const char* mimetype) {
+ for (uint32_t i = 0; i < mInstances.Length(); i++) {
+ nsNPAPIPluginInstance* instance = mInstances[i];
+
+ const char* mt;
+ nsresult rv = instance->GetMIMEType(&mt);
+ if (NS_FAILED(rv)) continue;
+
+ if (PL_strcasecmp(mt, mimetype) == 0) return instance;
+ }
+
+ return nullptr;
+}
+
+nsNPAPIPluginInstance* nsPluginHost::FindOldestStoppedInstance() {
+ nsNPAPIPluginInstance* oldestInstance = nullptr;
+ TimeStamp oldestTime = TimeStamp::Now();
+ for (uint32_t i = 0; i < mInstances.Length(); i++) {
+ nsNPAPIPluginInstance* instance = mInstances[i];
+ if (instance->IsRunning()) continue;
+
+ TimeStamp time = instance->StopTime();
+ if (time < oldestTime) {
+ oldestTime = time;
+ oldestInstance = instance;
+ }
+ }
+
+ return oldestInstance;
+}
+
+uint32_t nsPluginHost::StoppedInstanceCount() {
+ uint32_t stoppedCount = 0;
+ for (uint32_t i = 0; i < mInstances.Length(); i++) {
+ nsNPAPIPluginInstance* instance = mInstances[i];
+ if (!instance->IsRunning()) stoppedCount++;
+ }
+ return stoppedCount;
+}
+
+nsTArray<RefPtr<nsNPAPIPluginInstance>>* nsPluginHost::InstanceArray() {
+ return &mInstances;
+}
+
+void nsPluginHost::DestroyRunningInstances(nsPluginTag* aPluginTag) {
+ for (int32_t i = mInstances.Length(); i > 0; i--) {
+ nsNPAPIPluginInstance* instance = mInstances[i - 1];
+ if (instance->IsRunning() &&
+ (!aPluginTag || aPluginTag == TagForPlugin(instance->GetPlugin()))) {
+ instance->SetWindow(nullptr);
+ instance->Stop();
+
+ // Get rid of all the instances without the possibility of caching.
+ nsPluginTag* pluginTag = TagForPlugin(instance->GetPlugin());
+ instance->SetWindow(nullptr);
+
+ RefPtr<dom::Element> domElement;
+ instance->GetDOMElement(getter_AddRefs(domElement));
+ nsCOMPtr<nsIObjectLoadingContent> objectContent =
+ do_QueryInterface(domElement);
+
+ instance->Destroy();
+
+ mInstances.RemoveElement(instance);
+ OnPluginInstanceDestroyed(pluginTag);
+
+ // Notify owning content that we destroyed its plugin out from under it
+ if (objectContent) {
+ objectContent->PluginDestroyed();
+ }
+ }
+ }
+}
+
+/* static */
+bool nsPluginHost::CanUsePluginForMIMEType(const nsACString& aMIMEType) {
+ // We only support flash as a plugin, so if the mime types don't match for
+ // those, exit before we start loading plugins.
+ //
+ // XXX: Remove test/java cases when bug 1351885 lands.
+ if (nsPluginHost::GetSpecialType(aMIMEType) ==
+ nsPluginHost::eSpecialType_Flash ||
+ MimeTypeIsAllowedForFakePlugin(NS_ConvertUTF8toUTF16(aMIMEType)) ||
+ aMIMEType.LowerCaseEqualsLiteral("application/x-test")) {
+ return true;
+ }
+
+ return false;
+}
+
+// Runnable that does an async destroy of a plugin.
+
+class nsPluginDestroyRunnable
+ : public Runnable,
+ public mozilla::LinkedListElement<nsPluginDestroyRunnable> {
+ public:
+ explicit nsPluginDestroyRunnable(nsNPAPIPluginInstance* aInstance)
+ : Runnable("nsPluginDestroyRunnable"), mInstance(aInstance) {
+ sRunnableList.insertBack(this);
+ }
+
+ ~nsPluginDestroyRunnable() override { this->remove(); }
+
+ NS_IMETHOD Run() override {
+ RefPtr<nsNPAPIPluginInstance> instance;
+
+ // Null out mInstance to make sure this code in another runnable
+ // will do the right thing even if someone was holding on to this
+ // runnable longer than we expect.
+ instance.swap(mInstance);
+
+ if (PluginDestructionGuard::DelayDestroy(instance)) {
+ // It's still not safe to destroy the plugin, it's now up to the
+ // outermost guard on the stack to take care of the destruction.
+ return NS_OK;
+ }
+
+ for (auto r : sRunnableList) {
+ if (r != this && r->mInstance == instance) {
+ // There's another runnable scheduled to tear down
+ // instance. Let it do the job.
+ return NS_OK;
+ }
+ }
+
+ PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+ ("Doing delayed destroy of instance %p\n", instance.get()));
+
+ RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+ if (host) host->StopPluginInstance(instance);
+
+ PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+ ("Done with delayed destroy of instance %p\n", instance.get()));
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<nsNPAPIPluginInstance> mInstance;
+
+ static mozilla::LinkedList<nsPluginDestroyRunnable> sRunnableList;
+};
+
+mozilla::LinkedList<nsPluginDestroyRunnable>
+ nsPluginDestroyRunnable::sRunnableList;
+
+mozilla::LinkedList<PluginDestructionGuard> PluginDestructionGuard::sList;
+
+PluginDestructionGuard::PluginDestructionGuard(nsNPAPIPluginInstance* aInstance)
+ : mInstance(aInstance) {
+ Init();
+}
+
+PluginDestructionGuard::PluginDestructionGuard(NPP npp)
+ : mInstance(npp ? static_cast<nsNPAPIPluginInstance*>(npp->ndata)
+ : nullptr) {
+ Init();
+}
+
+PluginDestructionGuard::~PluginDestructionGuard() {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread");
+
+ this->remove();
+
+ if (mDelayedDestroy) {
+ // We've attempted to destroy the plugin instance we're holding on
+ // to while we were guarding it. Do the actual destroy now, off of
+ // a runnable.
+ RefPtr<nsPluginDestroyRunnable> evt =
+ new nsPluginDestroyRunnable(mInstance);
+
+ NS_DispatchToMainThread(evt);
+ }
+}
+
+// static
+bool PluginDestructionGuard::DelayDestroy(nsNPAPIPluginInstance* aInstance) {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread");
+ NS_ASSERTION(aInstance, "Uh, I need an instance!");
+
+ // Find the first guard on the stack and make it do a delayed
+ // destroy upon destruction.
+
+ for (auto g : sList) {
+ if (g->mInstance == aInstance) {
+ g->mDelayedDestroy = true;
+
+ return true;
+ }
+ }
+
+ return false;
+}