summaryrefslogtreecommitdiffstats
path: root/dom/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/plugins
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/plugins')
-rw-r--r--dom/plugins/base/moz.build45
-rw-r--r--dom/plugins/base/nsIPluginTag.idl97
-rw-r--r--dom/plugins/base/nsPluginHost.cpp223
-rw-r--r--dom/plugins/base/nsPluginHost.h111
-rw-r--r--dom/plugins/base/nsPluginLogging.h54
-rw-r--r--dom/plugins/base/nsPluginTags.cpp391
-rw-r--r--dom/plugins/base/nsPluginTags.h149
-rw-r--r--dom/plugins/test/crashtests/110650-1.html11
-rw-r--r--dom/plugins/test/crashtests/41276-1.html28
-rw-r--r--dom/plugins/test/crashtests/48856-1.html10
-rw-r--r--dom/plugins/test/crashtests/752340.html19
-rw-r--r--dom/plugins/test/crashtests/843086.xhtml1
-rw-r--r--dom/plugins/test/crashtests/crashtests.list5
-rw-r--r--dom/plugins/test/mochitest/block_all_plugins.html10
-rw-r--r--dom/plugins/test/mochitest/browser.ini5
-rw-r--r--dom/plugins/test/mochitest/browser_blockallplugins.js64
-rw-r--r--dom/plugins/test/mochitest/large-pic.jpgbin0 -> 98570 bytes
-rw-r--r--dom/plugins/test/mochitest/mixed_case_mime.sjs7
-rw-r--r--dom/plugins/test/mochitest/mochitest.ini10
-rw-r--r--dom/plugins/test/mochitest/test_mixed_case_mime.html25
-rw-r--r--dom/plugins/test/mochitest/test_plugin_fallback_focus.html80
-rw-r--r--dom/plugins/test/moz.build9
22 files changed, 1354 insertions, 0 deletions
diff --git a/dom/plugins/base/moz.build b/dom/plugins/base/moz.build
new file mode 100644
index 0000000000..1e13483336
--- /dev/null
+++ b/dom/plugins/base/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIPluginTag.idl",
+]
+
+XPIDL_MODULE = "plugin"
+
+EXPORTS += [
+ "nsPluginHost.h",
+ "nsPluginLogging.h",
+ "nsPluginTags.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsPluginHost.cpp",
+ "nsPluginTags.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/gfx/cairo/cairo/src",
+ "/layout/generic",
+ "/layout/xul",
+ "/netwerk/base",
+ "/widget",
+ "/widget/cocoa",
+ "/xpcom/base",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ LOCAL_INCLUDES += [
+ "/xpcom/base",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
diff --git a/dom/plugins/base/nsIPluginTag.idl b/dom/plugins/base/nsIPluginTag.idl
new file mode 100644
index 0000000000..68f8da1dc8
--- /dev/null
+++ b/dom/plugins/base/nsIPluginTag.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+[builtinclass, scriptable, uuid(5daa99d5-265a-4397-b429-c943803e2619)]
+interface nsIPluginTag : nsISupports
+{
+ // enabledState is stored as one of the following as an integer in prefs,
+ // so if new states are added, they must not renumber the existing states.
+ const unsigned long STATE_DISABLED = 0;
+ const unsigned long STATE_CLICKTOPLAY = 1;
+ const unsigned long STATE_ENABLED = 2;
+
+ readonly attribute AUTF8String description;
+ readonly attribute AUTF8String filename;
+ readonly attribute AUTF8String fullpath;
+ readonly attribute AUTF8String version;
+ readonly attribute AUTF8String name;
+
+ // The 'nice' name of this plugin, e.g. 'flash' 'java'
+ readonly attribute AUTF8String niceName;
+
+ /**
+ * true only if this plugin is "hardblocked" and cannot be enabled.
+ */
+ // FIXME-jsplugins QI to fakePluginTag possible
+ // FIXME-jsplugins implement missing + tests (whatever that means)
+ [infallible]
+ readonly attribute boolean blocklisted;
+
+ /**
+ * true if the state is non-default and locked, false otherwise.
+ */
+ [infallible]
+ readonly attribute boolean isEnabledStateLocked;
+
+ // If this plugin is capable of being used (not disabled, blocklisted, etc)
+ [infallible]
+ readonly attribute boolean active;
+
+ // Get a specific nsIBlocklistService::STATE_*
+ [infallible]
+ readonly attribute unsigned long blocklistState;
+
+ [infallible]
+ readonly attribute boolean disabled;
+ [infallible]
+ readonly attribute boolean clicktoplay;
+ [infallible]
+ readonly attribute boolean loaded;
+ // See the STATE_* values above.
+ attribute unsigned long enabledState;
+
+ readonly attribute PRTime lastModifiedTime;
+
+ readonly attribute boolean isFlashPlugin;
+
+ Array<AUTF8String> getMimeTypes();
+ Array<AUTF8String> getMimeDescriptions();
+ Array<AUTF8String> getExtensions();
+
+ /**
+ * An id for this plugin. 0 is a valid id.
+ */
+ readonly attribute unsigned long id;
+};
+
+/**
+ * An interface representing a "fake" plugin: one implemented in JavaScript, not
+ * as a NPAPI plug-in. See nsIPluginHost.registerFakePlugin and the
+ * documentation for the FakePluginTagInit dictionary.
+ */
+[builtinclass, scriptable, uuid(6d22c968-226d-4156-b230-da6ad6bbf6e8)]
+interface nsIFakePluginTag : nsIPluginTag
+{
+ /**
+ * The URI that should be loaded into the tag (as a frame) to handle the
+ * plugin. Note that the original data/src value for the plugin is not loaded
+ * and will need to be requested by the handler via XHR or similar if desired.
+ */
+ readonly attribute nsIURI handlerURI;
+
+ /**
+ * Optional script to run in a sandbox when instantiating a plugin. If this
+ * value is an empty string then no such script will be run.
+ * The script runs in a sandbox with system principal in the process that
+ * contains the element that instantiates the plugin (ie the EMBED or OBJECT
+ * element). The sandbox global has a 'pluginElement' property that the script
+ * can use to access the element that instantiates the plugin.
+ */
+ readonly attribute AString sandboxScript;
+};
diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp
new file mode 100644
index 0000000000..1c94e60002
--- /dev/null
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -0,0 +1,223 @@
+/* -*- 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 "nscore.h"
+
+#include <cstdlib>
+#include <stdio.h>
+#include "nsPluginLogging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "nsIBlocklistService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+
+LazyLogModule nsPluginLogging::gNPNLog(NPN_LOG_NAME);
+LazyLogModule nsPluginLogging::gNPPLog(NPP_LOG_NAME);
+LazyLogModule nsPluginLogging::gPluginLog(PLUGIN_LOG_NAME);
+
+StaticRefPtr<nsPluginHost> nsPluginHost::sInst;
+
+nsPluginHost::nsPluginHost() : mPluginEpoch(0) {
+#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
+
+ // 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.
+ IncrementChromeEpoch();
+ }
+}
+
+nsPluginHost::~nsPluginHost() {
+ PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("nsPluginHost::dtor\n"));
+}
+
+NS_IMPL_ISUPPORTS(nsPluginHost, nsISupportsWeakReference)
+
+already_AddRefed<nsPluginHost> nsPluginHost::GetInst() {
+ if (!sInst) {
+ sInst = new nsPluginHost();
+ ClearOnShutdown(&sInst);
+ }
+
+ return do_AddRef(sInst);
+}
+
+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) {
+ return FindFakePluginForType(aMimeType, aCheckEnabled);
+ }
+
+ return nullptr;
+}
+
+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::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);
+
+ 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;
+}
+
+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;
+}
+
+static bool MimeTypeIsAllowedForFakePlugin(const nsString& aMimeType) {
+ static const char* const allowedFakePlugins[] = {
+ // 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;
+}
+
+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;
+ }
+ }
+ return false;
+}
+
+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;
+}
+
+/* static */
+bool nsPluginHost::CanUsePluginForMIMEType(const nsACString& aMIMEType) {
+ // We "support" these in the sense that we show a special transparent
+ // fallback element in their place.
+ if (nsPluginHost::GetSpecialType(aMIMEType) ==
+ nsPluginHost::eSpecialType_Flash ||
+ MimeTypeIsAllowedForFakePlugin(NS_ConvertUTF8toUTF16(aMIMEType)) ||
+ aMIMEType.LowerCaseEqualsLiteral("application/x-test")) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h
new file mode 100644
index 0000000000..6566e979f3
--- /dev/null
+++ b/dom/plugins/base/nsPluginHost.h
@@ -0,0 +1,111 @@
+/* -*- 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/. */
+
+#ifndef nsPluginHost_h_
+#define nsPluginHost_h_
+
+#include "mozilla/StaticPtr.h"
+
+#include "nsCOMPtr.h"
+#include "prlink.h"
+#include "nsIPluginTag.h"
+#include "nsWeakReference.h"
+#include "nsTArray.h"
+#include "nsPluginTags.h"
+
+class nsIFile;
+
+class nsPluginHost final : public nsSupportsWeakReference {
+ friend class nsFakePluginTag;
+ virtual ~nsPluginHost();
+
+ public:
+ nsPluginHost();
+
+ static already_AddRefed<nsPluginHost> GetInst();
+
+ NS_DECL_ISUPPORTS
+
+ // Acts like a bitfield
+ enum PluginFilter { eExcludeNone, eExcludeDisabled, eExcludeFake };
+
+ NS_IMETHOD GetPluginTagForType(const nsACString& aMimeType,
+ uint32_t aExcludeFlags,
+ nsIPluginTag** aResult);
+ NS_IMETHOD GetPermissionStringForTag(nsIPluginTag* aTag,
+ uint32_t aExcludeFlags,
+ nsACString& aPermissionString);
+
+ // FIXME-jsplugins comment about fake
+ bool HavePluginForType(const nsACString& aMimeType,
+ PluginFilter aFilter = eExcludeDisabled);
+
+ void GetPlugins(nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray,
+ bool aIncludeDisabled = false);
+
+ /**
+ * Returns true if a plugin can be used to load the requested MIME type. Used
+ * for short circuiting before sending things to plugin code.
+ */
+ static bool CanUsePluginForMIMEType(const nsACString& aMIMEType);
+
+ // checks whether aType is a type we recognize for potential special handling
+ enum SpecialType {
+ eSpecialType_None,
+ // Needed to whitelist for async init support
+ eSpecialType_Test,
+ // Informs some decisions about OOP and quirks
+ eSpecialType_Flash
+ };
+ static SpecialType GetSpecialType(const nsACString& aMIMEType);
+
+ private:
+ // Find a plugin for the given type. If aIncludeFake is true a fake plugin
+ // will be preferred if one exists; otherwise a fake plugin will never be
+ // returned. If aCheckEnabled is false, disabled plugins can be returned.
+ nsIInternalPluginTag* FindPluginForType(const nsACString& aMimeType,
+ bool aIncludeFake,
+ bool aCheckEnabled);
+
+ // Find specifically a fake plugin for the given type. If aCheckEnabled is
+ // false, disabled plugins can be returned.
+ nsFakePluginTag* FindFakePluginForType(const nsACString& aMimeType,
+ bool aCheckEnabled);
+
+ // Find specifically a fake plugin for the given extension. If aCheckEnabled
+ // is false, disabled plugins can be returned. aMimeType will be filled in
+ // with the MIME type the plugin is registered for.
+ nsFakePluginTag* FindFakePluginForExtension(const nsACString& aExtension,
+ /* out */ nsACString& aMimeType,
+ bool aCheckEnabled);
+
+ // Checks to see if a tag object is in our list of live tags.
+ bool IsLiveTag(nsIPluginTag* tag);
+
+ // To be used by the chrome process whenever the set of plugins changes.
+ void IncrementChromeEpoch();
+
+ // To be used by the chrome process; returns the current epoch.
+ uint32_t ChromeEpoch();
+
+ // To be used by the content process to get/set the last observed epoch value
+ // from the chrome process.
+ uint32_t ChromeEpochForContent();
+ void SetChromeEpochForContent(uint32_t aEpoch);
+
+ nsTArray<RefPtr<nsFakePluginTag>> mFakePlugins;
+
+ // This epoch increases each time we load the list of plugins from disk.
+ // In the chrome process, this stores the actual epoch.
+ // In the content process, this stores the last epoch value observed
+ // when reading plugins from chrome.
+ uint32_t mPluginEpoch;
+
+ // We need to hold a global ptr to ourselves because we register for
+ // two different CIDs for some reason...
+ static mozilla::StaticRefPtr<nsPluginHost> sInst;
+};
+
+#endif // nsPluginHost_h_
diff --git a/dom/plugins/base/nsPluginLogging.h b/dom/plugins/base/nsPluginLogging.h
new file mode 100644
index 0000000000..029a073308
--- /dev/null
+++ b/dom/plugins/base/nsPluginLogging.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+/* Plugin Module Logging usage instructions and includes */
+////////////////////////////////////////////////////////////////////////////////
+#ifndef nsPluginLogging_h__
+#define nsPluginLogging_h__
+
+#include "mozilla/Logging.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Basic Plugin Logging Usage Instructions
+//
+// 1. Set this environment variable: MOZ_LOG=<name>:<level>
+
+// Choose the <name> and <level> from this list (no quotes):
+
+// Log Names <name>
+#define NPN_LOG_NAME "PluginNPN"
+#define NPP_LOG_NAME "PluginNPP"
+#define PLUGIN_LOG_NAME "Plugin"
+
+// Levels <level>
+#define PLUGIN_LOG_ALWAYS mozilla::LogLevel::Error
+#define PLUGIN_LOG_BASIC mozilla::LogLevel::Info
+#define PLUGIN_LOG_NORMAL mozilla::LogLevel::Debug
+#define PLUGIN_LOG_NOISY mozilla::LogLevel::Verbose
+
+// 2. You can combine logs and levels by separating them with a comma:
+// My favorite Win32 Example: SET MOZ_LOG=Plugin:5,PluginNPP:5,PluginNPN:5
+
+// 3. Instead of output going to the console, you can log to a file.
+// Additionally, set the MOZ_LOG_FILE environment variable to point to the
+// full path of a file.
+// My favorite Win32 Example: SET MOZ_LOG_FILE=c:\temp\pluginLog.txt
+
+// 4. For complete information see the Gecko Developer guide:
+// https://firefox-source-docs.mozilla.org/xpcom/logging.html
+
+class nsPluginLogging {
+ public:
+ static mozilla::LazyLogModule gNPNLog; // 4.x NP API, calls into navigator
+ static mozilla::LazyLogModule gNPPLog; // 4.x NP API, calls into plugin
+ static mozilla::LazyLogModule gPluginLog; // general plugin log
+};
+
+// Quick-use macros
+#define NPN_PLUGIN_LOG(a, b) MOZ_LOG(nsPluginLogging::gNPNLog, a, b)
+#define NPP_PLUGIN_LOG(a, b) MOZ_LOG(nsPluginLogging::gNPPLog, a, b)
+#define PLUGIN_LOG(a, b) MOZ_LOG(nsPluginLogging::gPluginLog, a, b)
+
+#endif // nsPluginLogging_h__
diff --git a/dom/plugins/base/nsPluginTags.cpp b/dom/plugins/base/nsPluginTags.cpp
new file mode 100644
index 0000000000..37e9f742ac
--- /dev/null
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -0,0 +1,391 @@
+/* -*- 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/. */
+
+#include "nsPluginTags.h"
+
+#include "prlink.h"
+#include "prenv.h"
+#include "nsPluginHost.h"
+#include "nsIBlocklistService.h"
+#include "nsPluginLogging.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsNetUtil.h"
+#include <cctype>
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/FakePluginTagInitBinding.h"
+
+using mozilla::dom::FakePluginTagInit;
+using namespace mozilla;
+
+// check comma delimited extensions
+static bool ExtensionInList(const nsCString& aExtensionList,
+ const nsACString& aExtension) {
+ for (const nsACString& extension :
+ nsCCharSeparatedTokenizer(aExtensionList, ',').ToRange()) {
+ if (extension.Equals(aExtension, nsCaseInsensitiveCStringComparator)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Search for an extension in an extensions array, and return its
+// matching mime type
+static bool SearchExtensions(const nsTArray<nsCString>& aExtensions,
+ const nsTArray<nsCString>& aMimeTypes,
+ const nsACString& aFindExtension,
+ nsACString& aMatchingType) {
+ uint32_t mimes = aMimeTypes.Length();
+ MOZ_ASSERT(mimes == aExtensions.Length(),
+ "These arrays should have matching elements");
+
+ aMatchingType.Truncate();
+
+ for (uint32_t i = 0; i < mimes; i++) {
+ if (ExtensionInList(aExtensions[i], aFindExtension)) {
+ aMatchingType = aMimeTypes[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static nsCString MakeNiceFileName(const nsCString& aFileName) {
+ nsCString niceName = aFileName;
+ int32_t niceNameLength = aFileName.RFind(".");
+ NS_ASSERTION(niceNameLength != kNotFound, "aFileName doesn't have a '.'?");
+ while (niceNameLength > 0) {
+ char chr = aFileName[niceNameLength - 1];
+ if (!std::isalpha(chr))
+ niceNameLength--;
+ else
+ break;
+ }
+
+ // If it turns out that niceNameLength <= 0, we'll fall back and use the
+ // entire aFileName (which we've already taken care of, a few lines back).
+ if (niceNameLength > 0) {
+ niceName.Truncate(niceNameLength);
+ }
+
+ ToLowerCase(niceName);
+ return niceName;
+}
+
+static nsCString MakePrefNameForPlugin(const char* const subname,
+ nsIInternalPluginTag* aTag) {
+ nsCString pref;
+ nsAutoCString pluginName(aTag->GetNiceFileName());
+
+ if (pluginName.IsEmpty()) {
+ // Use filename if nice name fails
+ pluginName = aTag->FileName();
+ if (pluginName.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE("Plugin with no filename or nice name in list");
+ pluginName.AssignLiteral("unknown-plugin-name");
+ }
+ }
+
+ pref.AssignLiteral("plugin.");
+ pref.Append(subname);
+ pref.Append('.');
+ pref.Append(pluginName);
+
+ return pref;
+}
+
+static nsCString GetStatePrefNameForPlugin(nsIInternalPluginTag* aTag) {
+ return MakePrefNameForPlugin("state", aTag);
+}
+
+static nsresult IsEnabledStateLockedForPlugin(nsIInternalPluginTag* aTag,
+ bool* aIsEnabledStateLocked) {
+ *aIsEnabledStateLocked = false;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ if (NS_WARN_IF(!prefs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Unused << prefs->PrefIsLocked(GetStatePrefNameForPlugin(aTag).get(),
+ aIsEnabledStateLocked);
+
+ return NS_OK;
+}
+
+/* nsIInternalPluginTag */
+
+uint32_t nsIInternalPluginTag::sNextId;
+
+nsIInternalPluginTag::nsIInternalPluginTag() = default;
+
+nsIInternalPluginTag::nsIInternalPluginTag(const char* aName,
+ const char* aDescription,
+ const char* aFileName,
+ const char* aVersion)
+ : mName(aName),
+ mDescription(aDescription),
+ mFileName(aFileName),
+ mVersion(aVersion) {}
+
+nsIInternalPluginTag::nsIInternalPluginTag(
+ const char* aName, const char* aDescription, const char* aFileName,
+ const char* aVersion, const nsTArray<nsCString>& aMimeTypes,
+ const nsTArray<nsCString>& aMimeDescriptions,
+ const nsTArray<nsCString>& aExtensions)
+ : mName(aName),
+ mDescription(aDescription),
+ mFileName(aFileName),
+ mVersion(aVersion),
+ mMimeTypes(aMimeTypes.Clone()),
+ mMimeDescriptions(aMimeDescriptions.Clone()),
+ mExtensions(aExtensions.Clone()) {}
+
+nsIInternalPluginTag::~nsIInternalPluginTag() = default;
+
+bool nsIInternalPluginTag::HasExtension(const nsACString& aExtension,
+ nsACString& aMatchingType) const {
+ return SearchExtensions(mExtensions, mMimeTypes, aExtension, aMatchingType);
+}
+
+bool nsIInternalPluginTag::HasMimeType(const nsACString& aMimeType) const {
+ return mMimeTypes.Contains(aMimeType,
+ nsCaseInsensitiveCStringArrayComparator());
+}
+
+/* nsFakePluginTag */
+
+nsFakePluginTag::nsFakePluginTag()
+ : mId(sNextId++), mState(ePluginState_Disabled) {}
+
+nsFakePluginTag::nsFakePluginTag(uint32_t aId,
+ already_AddRefed<nsIURI>&& aHandlerURI,
+ const char* aName, const char* aDescription,
+ const nsTArray<nsCString>& aMimeTypes,
+ const nsTArray<nsCString>& aMimeDescriptions,
+ const nsTArray<nsCString>& aExtensions,
+ const nsCString& aNiceName,
+ const nsString& aSandboxScript)
+ : nsIInternalPluginTag(aName, aDescription, nullptr, nullptr, aMimeTypes,
+ aMimeDescriptions, aExtensions),
+ mId(aId),
+ mHandlerURI(aHandlerURI),
+ mNiceName(aNiceName),
+ mSandboxScript(aSandboxScript),
+ mState(ePluginState_Enabled) {}
+
+nsFakePluginTag::~nsFakePluginTag() = default;
+
+NS_IMPL_ADDREF(nsFakePluginTag)
+NS_IMPL_RELEASE(nsFakePluginTag)
+NS_INTERFACE_TABLE_HEAD(nsFakePluginTag)
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsFakePluginTag, nsIPluginTag,
+ nsIInternalPluginTag)
+ NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIInternalPluginTag)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsFakePluginTag, nsISupports,
+ nsIInternalPluginTag)
+ NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIFakePluginTag)
+ NS_INTERFACE_TABLE_END
+NS_INTERFACE_TABLE_TAIL
+
+/* static */
+nsresult nsFakePluginTag::Create(const FakePluginTagInit& aInitDictionary,
+ nsFakePluginTag** aPluginTag) {
+ NS_ENSURE_TRUE(sNextId <= PR_INT32_MAX, NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(!aInitDictionary.mMimeEntries.IsEmpty(), NS_ERROR_INVALID_ARG);
+
+ RefPtr<nsFakePluginTag> tag = new nsFakePluginTag();
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(tag->mHandlerURI), aInitDictionary.mHandlerURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF16toUTF8(aInitDictionary.mNiceName, tag->mNiceName);
+ CopyUTF16toUTF8(aInitDictionary.mFullPath, tag->mFullPath);
+ CopyUTF16toUTF8(aInitDictionary.mName, tag->mName);
+ CopyUTF16toUTF8(aInitDictionary.mDescription, tag->mDescription);
+ CopyUTF16toUTF8(aInitDictionary.mFileName, tag->mFileName);
+ CopyUTF16toUTF8(aInitDictionary.mVersion, tag->mVersion);
+ tag->mSandboxScript = aInitDictionary.mSandboxScript;
+
+ for (const mozilla::dom::FakePluginMimeEntry& mimeEntry :
+ aInitDictionary.mMimeEntries) {
+ CopyUTF16toUTF8(mimeEntry.mType, *tag->mMimeTypes.AppendElement());
+ CopyUTF16toUTF8(mimeEntry.mDescription,
+ *tag->mMimeDescriptions.AppendElement());
+ CopyUTF16toUTF8(mimeEntry.mExtension, *tag->mExtensions.AppendElement());
+ }
+
+ tag.forget(aPluginTag);
+ return NS_OK;
+}
+
+bool nsFakePluginTag::HandlerURIMatches(nsIURI* aURI) {
+ bool equals = false;
+ return NS_SUCCEEDED(mHandlerURI->Equals(aURI, &equals)) && equals;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetHandlerURI(nsIURI** aResult) {
+ NS_IF_ADDREF(*aResult = mHandlerURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetSandboxScript(nsAString& aSandboxScript) {
+ aSandboxScript = mSandboxScript;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetDescription(/* utf-8 */ nsACString& aResult) {
+ aResult = mDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetIsFlashPlugin(bool* aIsFlash) {
+ *aIsFlash = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetFilename(/* utf-8 */ nsACString& aResult) {
+ aResult = mFileName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetFullpath(/* utf-8 */ nsACString& aResult) {
+ aResult = mFullPath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetVersion(/* utf-8 */ nsACString& aResult) {
+ aResult = mVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetName(/* utf-8 */ nsACString& aResult) {
+ aResult = mName;
+ return NS_OK;
+}
+
+const nsCString& nsFakePluginTag::GetNiceFileName() {
+ // We don't try to mimic the special-cased flash/java names if the fake plugin
+ // claims one of their MIME types, but do allow directly setting niceName if
+ // emulating those is desired.
+ if (mNiceName.IsEmpty() && !mFileName.IsEmpty()) {
+ mNiceName = MakeNiceFileName(mFileName);
+ }
+
+ return mNiceName;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetNiceName(/* utf-8 */ nsACString& aResult) {
+ aResult = GetNiceFileName();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetBlocklistState(uint32_t* aResult) {
+ // Fake tags don't currently support blocklisting
+ *aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetBlocklisted(bool* aBlocklisted) {
+ // Fake tags can't be blocklisted
+ *aBlocklisted = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetIsEnabledStateLocked(bool* aIsEnabledStateLocked) {
+ return IsEnabledStateLockedForPlugin(this, aIsEnabledStateLocked);
+}
+
+bool nsFakePluginTag::IsEnabled() {
+ return mState == ePluginState_Enabled || mState == ePluginState_Clicktoplay;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetDisabled(bool* aDisabled) {
+ *aDisabled = !IsEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetClicktoplay(bool* aClicktoplay) {
+ *aClicktoplay = (mState == ePluginState_Clicktoplay);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetEnabledState(uint32_t* aEnabledState) {
+ *aEnabledState = (uint32_t)mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::SetEnabledState(uint32_t aEnabledState) {
+ // There are static asserts above enforcing that this enum matches
+ mState = (PluginState)aEnabledState;
+ // FIXME-jsplugins update
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetMimeTypes(nsTArray<nsCString>& aResults) {
+ aResults = mMimeTypes.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetMimeDescriptions(nsTArray<nsCString>& aResults) {
+ aResults = mMimeDescriptions.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetExtensions(nsTArray<nsCString>& aResults) {
+ aResults = mExtensions.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetActive(bool* aResult) {
+ // Fake plugins can't be blocklisted, so this is just !Disabled
+ *aResult = IsEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetLastModifiedTime(PRTime* aLastModifiedTime) {
+ // FIXME-jsplugins What should this return, if anything?
+ MOZ_ASSERT(aLastModifiedTime);
+ *aLastModifiedTime = 0;
+ return NS_OK;
+}
+
+// We don't load fake plugins out of a library, so they should always be there.
+NS_IMETHODIMP
+nsFakePluginTag::GetLoaded(bool* ret) {
+ *ret = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFakePluginTag::GetId(uint32_t* aId) {
+ *aId = mId;
+ return NS_OK;
+}
diff --git a/dom/plugins/base/nsPluginTags.h b/dom/plugins/base/nsPluginTags.h
new file mode 100644
index 0000000000..2219112d26
--- /dev/null
+++ b/dom/plugins/base/nsPluginTags.h
@@ -0,0 +1,149 @@
+/* -*- 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/. */
+
+#ifndef nsPluginTags_h_
+#define nsPluginTags_h_
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIPluginTag.h"
+#include "nsITimer.h"
+#include "nsString.h"
+
+class nsIURI;
+
+namespace mozilla::dom {
+struct FakePluginTagInit;
+} // namespace mozilla::dom
+
+// An interface representing plugin tags internally.
+#define NS_IINTERNALPLUGINTAG_IID \
+ { \
+ 0xe8fdd227, 0x27da, 0x46ee, { \
+ 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 \
+ } \
+ }
+
+class nsIInternalPluginTag : public nsIPluginTag {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID)
+
+ nsIInternalPluginTag();
+ nsIInternalPluginTag(const char* aName, const char* aDescription,
+ const char* aFileName, const char* aVersion);
+ nsIInternalPluginTag(const char* aName, const char* aDescription,
+ const char* aFileName, const char* aVersion,
+ const nsTArray<nsCString>& aMimeTypes,
+ const nsTArray<nsCString>& aMimeDescriptions,
+ const nsTArray<nsCString>& aExtensions);
+
+ virtual bool IsEnabled() = 0;
+ virtual const nsCString& GetNiceFileName() = 0;
+
+ const nsCString& Name() const { return mName; }
+ const nsCString& Description() const { return mDescription; }
+
+ const nsTArray<nsCString>& MimeTypes() const { return mMimeTypes; }
+
+ const nsTArray<nsCString>& MimeDescriptions() const {
+ return mMimeDescriptions;
+ }
+
+ const nsTArray<nsCString>& Extensions() const { return mExtensions; }
+
+ const nsCString& FileName() const { return mFileName; }
+
+ const nsCString& Version() const { return mVersion; }
+
+ // Returns true if this plugin claims it supports this MIME type. The
+ // comparison is done ASCII-case-insensitively.
+ bool HasMimeType(const nsACString& aMimeType) const;
+
+ // Returns true if this plugin claims it supports the given extension. In
+ // that case, aMatchingType is set to the MIME type the plugin claims
+ // corresponds to this extension. The match on aExtension is done
+ // ASCII-case-insensitively.
+ bool HasExtension(const nsACString& aExtension,
+ /* out */ nsACString& aMatchingType) const;
+
+ // These must match the STATE_* values in nsIPluginTag.idl
+ enum PluginState {
+ ePluginState_Disabled = 0,
+ ePluginState_Clicktoplay = 1,
+ ePluginState_Enabled = 2,
+ ePluginState_MaxValue = 3,
+ };
+
+ protected:
+ ~nsIInternalPluginTag();
+
+ nsCString mName; // UTF-8
+ nsCString mDescription; // UTF-8
+ nsCString mFileName; // UTF-8
+ nsCString mVersion; // UTF-8
+ nsTArray<nsCString> mMimeTypes; // UTF-8
+ nsTArray<nsCString> mMimeDescriptions; // UTF-8
+ nsTArray<nsCString> mExtensions; // UTF-8
+
+ static uint32_t sNextId;
+};
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID)
+
+// A class representing "fake" plugin tags for Javascript-based plugins.
+// There are currently no other types of supported plugins.
+class nsFakePluginTag : public nsIInternalPluginTag, public nsIFakePluginTag {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPLUGINTAG
+ NS_DECL_NSIFAKEPLUGINTAG
+
+ static nsresult Create(const mozilla::dom::FakePluginTagInit& aInitDictionary,
+ nsFakePluginTag** aPluginTag);
+ nsFakePluginTag(uint32_t aId, already_AddRefed<nsIURI>&& aHandlerURI,
+ const char* aName, const char* aDescription,
+ const nsTArray<nsCString>& aMimeTypes,
+ const nsTArray<nsCString>& aMimeDescriptions,
+ const nsTArray<nsCString>& aExtensions,
+ const nsCString& aNiceName, const nsString& aSandboxScript);
+
+ bool IsEnabled() override;
+ const nsCString& GetNiceFileName() override;
+
+ bool HandlerURIMatches(nsIURI* aURI);
+
+ nsIURI* HandlerURI() const { return mHandlerURI; }
+
+ uint32_t Id() const { return mId; }
+
+ const nsString& SandboxScript() const { return mSandboxScript; }
+
+ static const int32_t NOT_JSPLUGIN = -1;
+
+ private:
+ nsFakePluginTag();
+ virtual ~nsFakePluginTag();
+
+ // A unique id for this JS-implemented plugin. Registering a plugin through
+ // nsPluginHost::RegisterFakePlugin assigns a new id. The id is transferred
+ // through IPC when getting the list of JS-implemented plugins from child
+ // processes, so it should be consistent across processes.
+ // 0 is a valid id.
+ uint32_t mId;
+
+ // The URI of the handler for our fake plugin.
+ // FIXME-jsplugins do we need to sanity check these?
+ nsCOMPtr<nsIURI> mHandlerURI;
+
+ nsCString mFullPath;
+ nsCString mNiceName;
+
+ nsString mSandboxScript;
+
+ PluginState mState;
+};
+
+#endif // nsPluginTags_h_
diff --git a/dom/plugins/test/crashtests/110650-1.html b/dom/plugins/test/crashtests/110650-1.html
new file mode 100644
index 0000000000..9826227b03
--- /dev/null
+++ b/dom/plugins/test/crashtests/110650-1.html
@@ -0,0 +1,11 @@
+<HTML>
+<HEAD>
+<TITLE>123246 testcase</TITLE>
+</HEAD>
+<BODY>
+<object align="right" width=100>
+</object>
+</BODY >
+</HTML>
+
+
diff --git a/dom/plugins/test/crashtests/41276-1.html b/dom/plugins/test/crashtests/41276-1.html
new file mode 100644
index 0000000000..ba35c34fa0
--- /dev/null
+++ b/dom/plugins/test/crashtests/41276-1.html
@@ -0,0 +1,28 @@
+<HTML><HEAD><TITLE>Plugin Limit</TITLE></HEAD>
+<BODY>
+
+Mozilla has a hardcoded limit of 10 simultaneously embedded plugins.<br>
+If that limit is exceeded, plugin instances are prematurely destroyed (see the empty boxes below).<br>
+Leave or reload a page that has a prematurely destroyed plugin and mozilla will crash.<br>
+Sometimes, just loading this page will cause mozilla to crash.<br>
+
+
+<br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<br>
+</BODY></HTML>
diff --git a/dom/plugins/test/crashtests/48856-1.html b/dom/plugins/test/crashtests/48856-1.html
new file mode 100644
index 0000000000..cd0de2ab94
--- /dev/null
+++ b/dom/plugins/test/crashtests/48856-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+<HTML>
+ <HEAD>
+ <TITLE>Mozilla Bug 48856</TITLE>
+ </HEAD>
+ <BODY>
+ <EMBED></EMBED>
+ </BODY>
+</HTML>
diff --git a/dom/plugins/test/crashtests/752340.html b/dom/plugins/test/crashtests/752340.html
new file mode 100644
index 0000000000..c4c8c464f5
--- /dev/null
+++ b/dom/plugins/test/crashtests/752340.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript">
+ // Failures in this file can manifest as ###!!! ASSERTION: scope has non-empty map: '0 == mWrappedNativeMap->Count()'
+ // followed by an Assertion failure: allocated() crash during the next GC.
+ // It can also manifest as a leak.
+ function breakthings() {
+ var e = document.createElement("embed");
+ var i = document.getElementById("i");
+ i.contentDocument.body.appendChild(e);
+ i.src = "about:blank";
+ }
+ </script>
+</head>
+<body onload="javascript:breakthings();">
+<iframe id="i" />
+</body>
+</html>
diff --git a/dom/plugins/test/crashtests/843086.xhtml b/dom/plugins/test/crashtests/843086.xhtml
new file mode 100644
index 0000000000..a88e2193fc
--- /dev/null
+++ b/dom/plugins/test/crashtests/843086.xhtml
@@ -0,0 +1 @@
+<applet xmlns="http://www.w3.org/1999/xhtml" />
diff --git a/dom/plugins/test/crashtests/crashtests.list b/dom/plugins/test/crashtests/crashtests.list
new file mode 100644
index 0000000000..568cd8fca5
--- /dev/null
+++ b/dom/plugins/test/crashtests/crashtests.list
@@ -0,0 +1,5 @@
+HTTP load 41276-1.html
+HTTP load 48856-1.html
+HTTP load 110650-1.html
+HTTP load 752340.html
+HTTP load 843086.xhtml
diff --git a/dom/plugins/test/mochitest/block_all_plugins.html b/dom/plugins/test/mochitest/block_all_plugins.html
new file mode 100644
index 0000000000..3ccdda1373
--- /dev/null
+++ b/dom/plugins/test/mochitest/block_all_plugins.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+ <object id="object" type="application/x-shockwave-flash"></object>
+ <embed id="embed" type="application/x-shockwave-flash"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/mochitest/browser.ini b/dom/plugins/test/mochitest/browser.ini
new file mode 100644
index 0000000000..d5190abce7
--- /dev/null
+++ b/dom/plugins/test/mochitest/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ block_all_plugins.html
+
+[browser_blockallplugins.js]
diff --git a/dom/plugins/test/mochitest/browser_blockallplugins.js b/dom/plugins/test/mochitest/browser_blockallplugins.js
new file mode 100644
index 0000000000..346db9d949
--- /dev/null
+++ b/dom/plugins/test/mochitest/browser_blockallplugins.js
@@ -0,0 +1,64 @@
+var gTestRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://127.0.0.1:8888/"
+);
+
+add_task(async function () {
+ registerCleanupFunction(function () {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+// simple tab load helper, pilfered from browser plugin tests
+function promiseTabLoadEvent(tab, url) {
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url) {
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ }
+
+ return loaded;
+}
+
+add_task(async function () {
+ let pluginTab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ await promiseTabLoadEvent(pluginTab, gTestRoot + "block_all_plugins.html");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let doc = content.document;
+
+ let objectElt = doc.getElementById("object");
+ Assert.ok(!!objectElt, "object should exist");
+ Assert.ok(
+ objectElt instanceof Ci.nsIObjectLoadingContent,
+ "object should be an nsIObjectLoadingContent"
+ );
+ Assert.ok(
+ objectElt.displayedType == Ci.nsIObjectLoadingContent.TYPE_FALLBACK,
+ "object should be blocked"
+ );
+
+ let embedElt = doc.getElementById("embed");
+ Assert.ok(!!embedElt, "embed should exist");
+ Assert.ok(
+ embedElt instanceof Ci.nsIObjectLoadingContent,
+ "embed should be an nsIObjectLoadingContent"
+ );
+ Assert.ok(
+ embedElt.displayedType == Ci.nsIObjectLoadingContent.TYPE_FALLBACK,
+ "embed should be blocked"
+ );
+ });
+});
diff --git a/dom/plugins/test/mochitest/large-pic.jpg b/dom/plugins/test/mochitest/large-pic.jpg
new file mode 100644
index 0000000000..b167f6b9ba
--- /dev/null
+++ b/dom/plugins/test/mochitest/large-pic.jpg
Binary files differ
diff --git a/dom/plugins/test/mochitest/mixed_case_mime.sjs b/dom/plugins/test/mochitest/mixed_case_mime.sjs
new file mode 100644
index 0000000000..57b48b4b96
--- /dev/null
+++ b/dom/plugins/test/mochitest/mixed_case_mime.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "image/pNG", false);
+
+ response.write("Hello world.\n");
+ response.finish();
+}
diff --git a/dom/plugins/test/mochitest/mochitest.ini b/dom/plugins/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..d4b4940ef1
--- /dev/null
+++ b/dom/plugins/test/mochitest/mochitest.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+skip-if = headless # crash on shutdown, no other failures
+support-files =
+ large-pic.jpg
+ mixed_case_mime.sjs
+
+[test_mixed_case_mime.html]
+skip-if = (processor == 'aarch64' && os == 'win')
+reason = Plugins are not supported on Windows/AArch64
+[test_plugin_fallback_focus.html]
diff --git a/dom/plugins/test/mochitest/test_mixed_case_mime.html b/dom/plugins/test/mochitest/test_mixed_case_mime.html
new file mode 100644
index 0000000000..fac5e386f1
--- /dev/null
+++ b/dom/plugins/test/mochitest/test_mixed_case_mime.html
@@ -0,0 +1,25 @@
+<body>
+<head>
+ <title>Test mixed case mimetype for plugins</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script>
+ SimpleTest.expectAssertions(0, 1);
+
+ SimpleTest.waitForExplicitFinish();
+
+ function frameLoaded() {
+ var contentDocument = document.getElementById("testframe").contentDocument;
+ ok(contentDocument.body.innerHTML.length, "Frame content shouldn't be empty.");
+ ok(contentDocument.images.length, "Frame content should have an image.");
+ SimpleTest.finish();
+ }
+</script>
+</head>
+<body>
+ <p id="display"></p>
+
+ <iframe id="testframe" name="testframe" onload="frameLoaded()" src="mixed_case_mime.sjs"></iframe>
+
+</body>
+</html>
diff --git a/dom/plugins/test/mochitest/test_plugin_fallback_focus.html b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html
new file mode 100644
index 0000000000..e89abb44df
--- /dev/null
+++ b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test that plugins reject focus</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="content">
+ <object id="obj_elt" type="application/x-shockwave-flash"></object>
+ <object tabindex='0' id="obj_elt_with_idx" type="application/x-shockwave-flash"></object>
+ <embed id="embed_elt" type="application/x-shockwave-flash"></embed>
+</div>
+<script type="application/javascript">
+var objElt = document.getElementById('obj_elt');
+var objEltWithIdx = document.getElementById('obj_elt_with_idx');
+var embedElt = document.getElementById('embed_elt');
+
+function checkHasFocus(expected, typeOfElts, elt) {
+ ok((document.activeElement == elt) == expected,
+ typeOfElts + " element should " + (expected ? "" : "not ") + "accept focus");
+}
+
+function checkNoneHasFocus(typeOfElts) {
+ checkHasFocus(false, typeOfElts + " <object>", objElt);
+ checkHasFocus(false, typeOfElts + " <object> with tabindex", objEltWithIdx);
+ checkHasFocus(false, typeOfElts + " <embed>", embedElt);
+}
+
+function checkFocusable(expected, typeOfElts, elt) {
+ elt.focus();
+ checkHasFocus(expected, typeOfElts, elt);
+}
+
+// As plugins, object and embed elements are not given focus
+ok(objElt != null, "object element should exist");
+ok(objEltWithIdx != null, "object element with tabindex should exist");
+ok(embedElt != null, "embed element should exist");
+
+// As plugins, obj/embed_elt can not take focus
+checkNoneHasFocus("plugin");
+
+// Switch obj/embed_elt attributes from plugin to image
+objElt.data = "large-pic.jpg";
+objElt.width = 100;
+objElt.height = 100;
+objElt.type = "image/jpg";
+objEltWithIdx.data = "large-pic.jpg";
+objEltWithIdx.width = 100;
+objEltWithIdx.height = 100;
+objEltWithIdx.type = "image/jpg";
+embedElt.src = "large-pic.jpg";
+embedElt.width = 100;
+embedElt.height = 100;
+embedElt.type = "image/jpg";
+
+// As images, obj/embed_elt can take focus as image
+// object image elements require a tabindex to accept focus.
+// embed elements must be reparented before new type is recognized.
+checkFocusable(false, "<object> image", objElt);
+checkFocusable(true, "<object> image with tabindex", objEltWithIdx);
+checkFocusable(false, "<embed> plugin with image attribs before reparenting", embedElt);
+embedElt.parentNode.appendChild(embedElt);
+checkFocusable(true, "<embed> image", embedElt);
+
+// Switch obj/embed_elt attributes from image to plugin
+objElt.type = "application/x-shockwave-flash";
+embedElt.type = "application/x-shockwave-flash";
+
+// embed elements must be reparented before new type is recognized.
+checkFocusable(true, "<embed> image with plugin attribs", embedElt);
+embedElt.parentNode.appendChild(embedElt);
+checkNoneHasFocus("plugin");
+</script>
+</body>
+</html>
+
+
diff --git a/dom/plugins/test/moz.build b/dom/plugins/test/moz.build
new file mode 100644
index 0000000000..c3f85d5c48
--- /dev/null
+++ b/dom/plugins/test/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("gtk", "cocoa", "windows"):
+ MOCHITEST_MANIFESTS += ["mochitest/mochitest.ini"]
+ BROWSER_CHROME_MANIFESTS += ["mochitest/browser.ini"]