gecko-dev/dom/plugins/base/nsPluginHost.cpp
Gijs Kruitbosch ca508d989b Bug 1371888 - cache plugin information in pluginreg.dat to avoid sync startup load, r=florian,mossop
This changes the pluginreg.dat format to include the blocklist state.

There is now only the saved blocklist state in a plugin tag instance, rather than
looking it up from in there using the blocklist service, so it was renamed from
mCachedBlocklistState to mBlocklistState. We pass the 'right' state to the plugin
instance when the plugintag is constructed. If we don't have state, we mark it as
unblocked.

mCachedBlocklistStateChanged was never read so it's being removed.

Bug 1439519 adds a 'blocklist-loaded' notification that is fired once the blocklist is loaded.
The plugin host implementation will listen to this in the parent process and update the
blocklist state of all the plugins, and broadcast changes to the child process, just like when
we update the blocklist from the server. We now also avoid re-sending plugin content to the
content processes if the plugin state hasn't changed as a result of the blocklist having been
loaded.

Finally, because new plugins should still get an up-to-date blocklist state, and
telemetry should get up-to-date data about which plugins are and aren't enabled
once we have that data, we ensure that once we've loaded the blocklist async,
we schedule an idle task to parse it and consider it loaded.

All this means that plugin blocklist information could be mistaken between the points where
a new plugin is installed and we first run Firefox with the new plugin, and the point where
we load the blocklist. Given the trade-offs, that size of window (tiny) seems OK, also given
that there's already a much larger window in blocklist updates (which only happen once every 24h).

MozReview-Commit-ID: 1gsojRkUzTw

--HG--
extra : rebase_source : 4709916b4674ada54f8a495fd2d16fcef8c58d20
2018-02-20 16:53:48 +00:00

3999 lines
119 KiB
C++

/* -*- 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 "nscore.h"
#include "nsPluginHost.h"
#include <cstdlib>
#include <stdio.h>
#include "prio.h"
#include "nsExceptionHandler.h"
#include "nsNPAPIPlugin.h"
#include "nsNPAPIPluginStreamListener.h"
#include "nsNPAPIPluginInstance.h"
#include "nsPluginInstanceOwner.h"
#include "nsObjectLoadingContent.h"
#include "nsIHTTPHeaderListener.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIObserverService.h"
#include "nsIHttpProtocolHandler.h"
#include "nsIHttpChannel.h"
#include "nsIUploadChannel.h"
#include "nsIByteRangeRequest.h"
#include "nsIStreamListener.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIURL.h"
#include "nsTArray.h"
#include "nsReadableUtils.h"
#include "nsIStreamConverterService.h"
#include "nsIFile.h"
#if defined(XP_MACOSX)
#include "nsILocalFileMac.h"
#endif
#include "nsISeekableStream.h"
#include "nsNetUtil.h"
#include "nsIFileStreams.h"
#include "nsISimpleEnumerator.h"
#include "nsIStringStream.h"
#include "nsIProgressEventSink.h"
#include "nsIDocument.h"
#include "nsPluginLogging.h"
#include "nsIScriptChannel.h"
#include "nsIBlocklistService.h"
#include "nsVersionComparator.h"
#include "nsIObjectLoadingContent.h"
#include "nsIWritablePropertyBag2.h"
#include "nsICategoryManager.h"
#include "nsPluginStreamListenerPeer.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/FakePluginTagInitBinding.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/plugins/PluginBridge.h"
#include "mozilla/plugins/PluginTypes.h"
#include "mozilla/Preferences.h"
#include "mozilla/ipc/URIUtils.h"
#include "nsEnumeratorUtils.h"
#include "nsXPCOM.h"
#include "nsXPCOMCID.h"
#include "nsISupportsPrimitives.h"
#include "nsXULAppAPI.h"
#include "nsIXULRuntime.h"
// for the dialog
#include "nsIWindowWatcher.h"
#include "nsIDOMElement.h"
#include "nsIDOMWindow.h"
#include "nsNetCID.h"
#include "mozilla/Sprintf.h"
#include "nsThreadUtils.h"
#include "nsIInputStreamTee.h"
#include "nsQueryObject.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsPluginDirServiceProvider.h"
#include "nsUnicharUtils.h"
#include "nsPluginManifestLineReader.h"
#include "nsIWeakReferenceUtils.h"
#include "nsIPresShell.h"
#include "nsPluginNativeWindow.h"
#include "nsIContentPolicy.h"
#include "nsContentPolicyUtils.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Telemetry.h"
#include "nsIImageLoadingContent.h"
#include "mozilla/Preferences.h"
#include "nsVersionComparator.h"
#include "NullPrincipal.h"
#if defined(XP_WIN)
#include "nsIWindowMediator.h"
#include "nsIBaseWindow.h"
#include "windows.h"
#include "winbase.h"
#endif
#include "npapi.h"
using namespace mozilla;
using mozilla::TimeStamp;
using mozilla::plugins::FakePluginTag;
using mozilla::plugins::PluginTag;
using mozilla::dom::FakePluginTagInit;
using mozilla::dom::FakePluginMimeEntry;
// 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 *kPrefWhitelist = "plugin.allowed_types";
static const char *kPrefLoadInParentPrefix = "plugin.load_in_parent_process.";
static const char *kPrefDisableFullPage = "plugin.disable_full_page_plugin_for_types";
// How long we wait before unloading an idle plugin process.
// Defaults to 30 seconds.
static const char *kPrefUnloadPluginTimeoutSecs = "dom.ipc.plugins.unloadTimeoutSecs";
static const uint32_t kDefaultPluginUnloadingTimeout = 30;
static const char *kPluginRegistryVersion = "0.19";
static const char kDirectoryServiceContractID[] = "@mozilla.org/file/directory_service;1";
#define kPluginRegistryFilename NS_LITERAL_CSTRING("pluginreg.dat")
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;
/* to cope with short read */
/* we should probably put this into a global library now that this is the second
time we need this. */
static
int32_t
busy_beaver_PR_Read(PRFileDesc *fd, void * start, int32_t len)
{
int n;
int32_t remaining = len;
while (remaining > 0)
{
n = PR_Read(fd, start, remaining);
if (n < 0)
{
/* may want to repeat if errno == EINTR */
if( (len - remaining) == 0 ) // no octet is ever read
return -1;
break;
}
remaining -= n;
char *cp = (char *) start;
cp += n;
start = cp;
}
return len - remaining;
}
NS_IMPL_ISUPPORTS0(nsInvalidPluginTag)
nsInvalidPluginTag::nsInvalidPluginTag(const char* aFullPath, int64_t aLastModifiedTime)
: mFullPath(aFullPath),
mLastModifiedTime(aLastModifiedTime),
mSeen(false)
{}
nsInvalidPluginTag::~nsInvalidPluginTag() = default;
// 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);
}
// flat file reg funcs
static
bool ReadSectionHeader(nsPluginManifestLineReader& reader, const char *token)
{
do {
if (*reader.LinePtr() == '[') {
char* p = reader.LinePtr() + (reader.LineLength() - 1);
if (*p != ']')
break;
*p = 0;
char* values[1];
if (1 != reader.ParseLine(values, 1))
break;
// ignore the leading '['
if (PL_strcmp(values[0]+1, token)) {
break; // it's wrong token
}
return true;
}
} while (reader.NextLine());
return false;
}
static bool UnloadPluginsASAP()
{
return (Preferences::GetUint(kPrefUnloadPluginTimeoutSecs, kDefaultPluginUnloadingTimeout) == 0);
}
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);
mPluginsDisabled = Preferences::GetBool("plugin.disable", false);
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, "blocklist-updated", false);
obsService->AddObserver(this, "blocklist-loaded", 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
// 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 Begin\n"));
// If we're calling this from a content process, forward the reload request to
// the parent process. If plugins actually changed, it will notify us
// asynchronously later.
if (XRE_IsContentProcess())
{
Unused << mozilla::dom::ContentChild::GetSingleton()->SendMaybeReloadPlugins();
// In content processes, always signal that plugins have not changed. We
// will never know if they changed here unless we make slow synchronous
// calls. This information will hopefully only be wrong once, as if there
// has been a plugin update, we expect to have gotten notification from the
// parent process and everything should be updated by the next time this is
// called. See Bug 1337058 for more info.
return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED;
}
// this will create the initial plugin list out of cache
// if it was not created yet
if (!mPluginsLoaded)
return LoadPlugins();
// we are re-scanning plugins. New plugins may have been added, also some
// plugins may have been removed, so we should probably shut everything down
// but don't touch running (active and not stopped) plugins
// check if plugins changed, no need to do anything else
// if no changes to plugins have been made
// false instructs not to touch the plugin list, just to
// look for possible changes
bool pluginschanged = true;
FindPlugins(false, &pluginschanged);
// if no changed detected, return an appropriate error code
if (!pluginschanged)
return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED;
return ActuallyReloadPlugins();
}
nsresult
nsPluginHost::ActuallyReloadPlugins()
{
nsresult rv = NS_OK;
// 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;
}
// 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.
SendPluginsToContent();
}
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);
NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
// Lets remove any of the temporary files that we created.
if (sPluginTempDir) {
sPluginTempDir->Remove(true);
NS_RELEASE(sPluginTempDir);
}
#ifdef XP_WIN
if (mPrivateDirServiceProvider) {
nsCOMPtr<nsIDirectoryService> dirService =
do_GetService(kDirectoryServiceContractID);
if (dirService)
dirService->UnregisterProvider(mPrivateDirServiceProvider);
mPrivateDirServiceProvider = nullptr;
}
#endif /* XP_WIN */
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;
}
}
// We have some options for unloading plugins if they have no instances.
//
// Unloading plugins immediately can be bad - some plugins retain state
// between instances even when there are none. This is largely limited to
// going from one page to another, so state is retained without an instance
// for only a very short period of time. In order to allow this to work
// we don't unload plugins immediately by default. This is supported
// via a hidden user pref though.
//
// Another reason not to unload immediately is that loading is expensive,
// and it is better to leave popular plugins loaded.
//
// Our default behavior is to try to unload a plugin after a pref-controlled
// delay once its last instance is destroyed. This seems like a reasonable
// compromise that allows us to reclaim memory while allowing short state
// retention and avoid perf hits for loading popular plugins.
if (!hasInstance) {
if (UnloadPluginsASAP()) {
aPluginTag->TryUnloadPlugin(false);
} else {
if (aPluginTag->mUnloadTimer) {
aPluginTag->mUnloadTimer->Cancel();
} else {
aPluginTag->mUnloadTimer = NS_NewTimer();
}
uint32_t unloadTimeout = Preferences::GetUint(kPrefUnloadPluginTimeoutSecs,
kDefaultPluginUnloadingTimeout);
aPluginTag->mUnloadTimer->InitWithCallback(this,
1000 * unloadTimeout,
nsITimer::TYPE_ONE_SHOT);
}
}
}
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()) {
NS_NOTREACHED("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;
rv = instanceOwner->GetInstance(getter_AddRefs(instance));
if (NS_FAILED(rv)) {
instanceOwner->Destroy();
return rv;
}
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<nsIDocument> document;
aOwner->GetDocument(getter_AddRefs(document));
nsCOMPtr<nsIDocument> 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(uint32_t* aPluginCount, nsIPluginTag*** aResults)
{
LoadPlugins();
uint32_t count = 0;
uint32_t fakeCount = mFakePlugins.Length();
RefPtr<nsPluginTag> plugin = mPlugins;
while (plugin != nullptr) {
count++;
plugin = plugin->mNext;
}
*aResults = static_cast<nsIPluginTag**>
(moz_xmalloc((fakeCount + count) * sizeof(**aResults)));
if (!*aResults)
return NS_ERROR_OUT_OF_MEMORY;
*aPluginCount = count + fakeCount;
plugin = mPlugins;
for (uint32_t i = 0; i < count; i++) {
(*aResults)[i] = plugin;
NS_ADDREF((*aResults)[i]);
plugin = plugin->mNext;
}
for (uint32_t i = 0; i < fakeCount; i++) {
(*aResults)[i + count] = static_cast<nsIInternalPluginTag*>(mFakePlugins[i]);
NS_ADDREF((*aResults)[i + count]);
}
return NS_OK;
}
nsPluginTag*
nsPluginHost::FindPreferredPlugin(const InfallibleTArray<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();
InfallibleTArray<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();
InfallibleTArray<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 InfallibleTArray<nsCString>& sites,
InfallibleTArray<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::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(InfallibleTArray<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;
InfallibleTArray<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), keepWaiting(true)
{
}
NS_IMETHOD SitesWithData(InfallibleTArray<nsCString>& sites) override {
retVal = HandleGetSites(sites);
keepWaiting = false;
return NS_OK;
}
nsresult HandleGetSites(InfallibleTArray<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.
InfallibleTArray<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>(do_QueryInterface(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;
}
// Don't have to worry about fake plugins here, since this is only used during
// the plugin directory scan, which doesn't pick up fake plugins.
nsPluginTag*
nsPluginHost::FirstPluginWithPath(const nsCString& path)
{
for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) {
if (tag->mFullPath.Equals(path)) {
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;
}
namespace {
int64_t GetPluginLastModifiedTime(const nsCOMPtr<nsIFile>& localfile)
{
PRTime fileModTime = 0;
#if defined(XP_MACOSX)
// On OS X the date of a bundle's "contents" (i.e. of its Info.plist file)
// is a much better guide to when it was last modified than the date of
// its package directory. See bug 313700.
nsCOMPtr<nsILocalFileMac> localFileMac = do_QueryInterface(localfile);
if (localFileMac) {
localFileMac->GetBundleContentsLastModifiedTime(&fileModTime);
} else {
localfile->GetLastModifiedTime(&fileModTime);
}
#else
localfile->GetLastModifiedTime(&fileModTime);
#endif
return fileModTime;
}
bool
GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile,
const nsCOMArray<nsIFile>& extensionDirs)
{
for (uint32_t i = 0; i < extensionDirs.Length(); ++i) {
bool contains;
if (NS_FAILED(extensionDirs[i]->Contains(pluginFile, &contains)) || !contains) {
continue;
}
return true;
}
return false;
}
void
GetExtensionDirectories(nsCOMArray<nsIFile>& dirs)
{
nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
if (!dirService) {
return;
}
nsCOMPtr<nsISimpleEnumerator> list;
nsresult rv = dirService->Get(XRE_EXTENSIONS_DIR_LIST,
NS_GET_IID(nsISimpleEnumerator),
getter_AddRefs(list));
if (NS_FAILED(rv)) {
return;
}
bool more;
while (NS_SUCCEEDED(list->HasMoreElements(&more)) && more) {
nsCOMPtr<nsISupports> next;
if (NS_FAILED(list->GetNext(getter_AddRefs(next)))) {
break;
}
nsCOMPtr<nsIFile> file = do_QueryInterface(next);
if (file) {
file->Normalize();
dirs.AppendElement(file.forget());
}
}
}
struct CompareFilesByTime
{
bool
LessThan(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const
{
return GetPluginLastModifiedTime(a) < GetPluginLastModifiedTime(b);
}
bool
Equals(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const
{
return GetPluginLastModifiedTime(a) == GetPluginLastModifiedTime(b);
}
};
} // namespace
static
bool
ShouldAddPlugin(const nsPluginInfo& info, bool flashOnly)
{
if (!info.fName || (strcmp(info.fName, "Shockwave Flash") != 0 && flashOnly)) {
return false;
}
for (uint32_t i = 0; i < info.fVariantCount; ++i) {
if (info.fMimeTypeArray[i] &&
(!strcmp(info.fMimeTypeArray[i], "application/x-shockwave-flash") ||
!strcmp(info.fMimeTypeArray[i], "application/x-shockwave-flash-test"))) {
return true;
}
if (flashOnly) {
continue;
}
if (info.fMimeTypeArray[i] &&
(!strcmp(info.fMimeTypeArray[i], "application/x-test") ||
!strcmp(info.fMimeTypeArray[i], "application/x-Second-Test"))) {
return true;
}
}
#ifdef PLUGIN_LOGGING
PLUGIN_LOG(PLUGIN_LOG_NORMAL,
("ShouldAddPlugin : Ignoring non-flash plugin library %s\n", aPluginTag->FileName().get()));
#endif // PLUGIN_LOGGING
return false;
}
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);
nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir,
bool aCreatePluginList,
bool *aPluginsChanged)
{
MOZ_ASSERT(XRE_IsParentProcess());
NS_ENSURE_ARG_POINTER(aPluginsChanged);
nsresult rv;
*aPluginsChanged = false;
#ifdef PLUGIN_LOGGING
nsAutoCString dirPath;
pluginsDir->GetNativePath(dirPath);
PLUGIN_LOG(PLUGIN_LOG_BASIC,
("nsPluginHost::ScanPluginsDirectory dir=%s\n", dirPath.get()));
#endif
bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true);
nsCOMPtr<nsISimpleEnumerator> iter;
rv = pluginsDir->GetDirectoryEntries(getter_AddRefs(iter));
if (NS_FAILED(rv))
return rv;
AutoTArray<nsCOMPtr<nsIFile>, 6> pluginFiles;
bool hasMore;
while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> supports;
rv = iter->GetNext(getter_AddRefs(supports));
if (NS_FAILED(rv))
continue;
nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
if (NS_FAILED(rv))
continue;
// Sun's JRE 1.3.1 plugin must have symbolic links resolved or else it'll crash.
// See bug 197855.
dirEntry->Normalize();
if (nsPluginsDir::IsPluginFile(dirEntry)) {
pluginFiles.AppendElement(dirEntry);
}
}
pluginFiles.Sort(CompareFilesByTime());
nsCOMArray<nsIFile> extensionDirs;
GetExtensionDirectories(extensionDirs);
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
bool isBlocklistLoaded = false;
if (blocklist && NS_FAILED(blocklist->GetIsLoaded(&isBlocklistLoaded))) {
isBlocklistLoaded = false;
}
for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) {
nsCOMPtr<nsIFile>& localfile = pluginFiles[i];
nsString utf16FilePath;
rv = localfile->GetPath(utf16FilePath);
if (NS_FAILED(rv))
continue;
const int64_t fileModTime = GetPluginLastModifiedTime(localfile);
const bool fromExtension = GetPluginIsFromExtension(localfile, extensionDirs);
// Look for it in our cache
NS_ConvertUTF16toUTF8 filePath(utf16FilePath);
RefPtr<nsPluginTag> pluginTag;
RemoveCachedPluginsInfo(filePath.get(), getter_AddRefs(pluginTag));
bool seenBefore = false;
if (pluginTag) {
seenBefore = true;
// If plugin changed, delete cachedPluginTag and don't use cache
if (fileModTime != pluginTag->mLastModifiedTime) {
// Plugins has changed. Don't use cached plugin info.
pluginTag = nullptr;
// plugin file changed, flag this fact
*aPluginsChanged = true;
}
// If we're not creating a list and we already know something changed then
// we're done.
if (!aCreatePluginList) {
if (*aPluginsChanged) {
return NS_OK;
}
continue;
}
}
bool isKnownInvalidPlugin = false;
for (RefPtr<nsInvalidPluginTag> invalidPlugins = mInvalidPlugins;
invalidPlugins; invalidPlugins = invalidPlugins->mNext) {
// If already marked as invalid, ignore it
if (invalidPlugins->mFullPath.Equals(filePath.get()) &&
invalidPlugins->mLastModifiedTime == fileModTime) {
if (aCreatePluginList) {
invalidPlugins->mSeen = true;
}
isKnownInvalidPlugin = true;
break;
}
}
if (isKnownInvalidPlugin) {
continue;
}
// if it is not found in cache info list or has been changed, create a new one
if (!pluginTag) {
nsPluginFile pluginFile(localfile);
// create a tag describing this plugin.
PRLibrary *library = nullptr;
nsPluginInfo info;
memset(&info, 0, sizeof(info));
nsresult res;
// Opening a block for the telemetry AutoTimer
{
Telemetry::AutoTimer<Telemetry::PLUGIN_LOAD_METADATA> telemetry;
res = pluginFile.GetPluginInfo(info, &library);
}
// if we don't have mime type don't proceed, this is not a plugin
if (NS_FAILED(res) || !info.fMimeTypeArray ||
(!ShouldAddPlugin(info, flashOnly))) {
RefPtr<nsInvalidPluginTag> invalidTag = new nsInvalidPluginTag(filePath.get(),
fileModTime);
pluginFile.FreePluginInfo(info);
if (aCreatePluginList) {
invalidTag->mSeen = true;
}
invalidTag->mNext = mInvalidPlugins;
if (mInvalidPlugins) {
mInvalidPlugins->mPrev = invalidTag;
}
mInvalidPlugins = invalidTag;
// Mark aPluginsChanged so pluginreg is rewritten
*aPluginsChanged = true;
continue;
}
uint32_t state = nsIBlocklistService::STATE_NOT_BLOCKED;
pluginTag = new nsPluginTag(&info, fileModTime, fromExtension, state);
pluginTag->mLibrary = library;
// If the blocklist is loaded, get the blocklist state now.
// If it isn't loaded yet, we'll update it once it loads.
if (isBlocklistLoaded &&
NS_SUCCEEDED(blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
EmptyString(), &state))) {
pluginTag->SetBlocklistState(state);
}
pluginFile.FreePluginInfo(info);
// If the blocklist says it is risky and we have never seen this
// plugin before, then disable it.
if (state == nsIBlocklistService::STATE_SOFTBLOCKED && !seenBefore) {
pluginTag->SetEnabledState(nsIPluginTag::STATE_DISABLED);
}
// Plugin unloading is tag-based. If we created a new tag and loaded
// the library in the process then we want to attempt to unload it here.
// Only do this if the pref is set for aggressive unloading.
if (UnloadPluginsASAP()) {
pluginTag->TryUnloadPlugin(false);
}
}
// do it if we still want it
if (!seenBefore) {
// We have a valid new plugin so report that plugins have changed.
*aPluginsChanged = true;
}
// Don't add the same plugin again if it hasn't changed
if (nsPluginTag* duplicate = FirstPluginWithPath(pluginTag->mFullPath)) {
if (pluginTag->mLastModifiedTime == duplicate->mLastModifiedTime) {
continue;
}
}
// If we're not creating a plugin list, simply looking for changes,
// then we're done.
if (!aCreatePluginList) {
return NS_OK;
}
AddPluginTag(pluginTag);
}
return NS_OK;
}
nsresult nsPluginHost::ScanPluginsDirectoryList(nsISimpleEnumerator *dirEnum,
bool aCreatePluginList,
bool *aPluginsChanged)
{
MOZ_ASSERT(XRE_IsParentProcess());
bool hasMore;
while (NS_SUCCEEDED(dirEnum->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> supports;
nsresult rv = dirEnum->GetNext(getter_AddRefs(supports));
if (NS_FAILED(rv))
continue;
nsCOMPtr<nsIFile> nextDir(do_QueryInterface(supports, &rv));
if (NS_FAILED(rv))
continue;
// don't pass aPluginsChanged directly to prevent it from been reset
bool pluginschanged = false;
ScanPluginsDirectory(nextDir, aCreatePluginList, &pluginschanged);
if (pluginschanged)
*aPluginsChanged = true;
// if changes are detected and we are not creating the list, do not proceed
if (!aCreatePluginList && *aPluginsChanged)
break;
}
return NS_OK;
}
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;
}
#ifdef XP_WIN
static void
WatchRegKey(uint32_t aRoot, nsCOMPtr<nsIWindowsRegKey>& aKey)
{
if (aKey) {
return;
}
aKey = do_CreateInstance("@mozilla.org/windows-registry-key;1");
if (!aKey) {
return;
}
nsresult rv = aKey->Open(aRoot,
NS_LITERAL_STRING("Software\\MozillaPlugins"),
nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::ACCESS_NOTIFY);
if (NS_FAILED(rv)) {
aKey = nullptr;
return;
}
aKey->StartWatching(true);
}
#endif
nsresult nsPluginHost::LoadPlugins()
{
// This should only be run in the parent process. On plugin list change, we'll
// update observers in the content process as part of SetPluginsInContent
if (XRE_IsContentProcess()) {
return NS_OK;
}
// do not do anything if it is already done
// use ReloadPlugins() to enforce loading
if (mPluginsLoaded)
return NS_OK;
if (mPluginsDisabled)
return NS_OK;
#ifdef XP_WIN
WatchRegKey(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, mRegKeyHKLM);
WatchRegKey(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, mRegKeyHKCU);
#endif
bool pluginschanged;
nsresult rv = FindPlugins(true, &pluginschanged);
if (NS_FAILED(rv))
return rv;
// only if plugins have changed will we notify plugin-change observers
if (pluginschanged) {
if (XRE_IsParentProcess()) {
IncrementChromeEpoch();
}
nsCOMPtr<nsIObserverService> obsService =
mozilla::services::GetObserverService();
if (obsService)
obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr);
}
return NS_OK;
}
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, trigger a
// reload now to clear out all old entries.
ActuallyReloadPlugins();
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(),
nsTArray<nsCString>(tag.mimeTypes()),
nsTArray<nsCString>(tag.mimeDescriptions()),
nsTArray<nsCString>(tag.extensions()),
tag.isFlashPlugin(),
tag.supportsAsyncRender(),
tag.lastModifiedTime(),
tag.isFromExtension(),
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;
}
// if aCreatePluginList is false we will just scan for plugins
// and see if any changes have been made to the plugins.
// This is needed in ReloadPlugins to prevent possible recursive reloads
nsresult nsPluginHost::FindPlugins(bool aCreatePluginList, bool * aPluginsChanged)
{
Telemetry::AutoTimer<Telemetry::FIND_PLUGINS> telemetry;
NS_ENSURE_ARG_POINTER(aPluginsChanged);
*aPluginsChanged = false;
// If plugins are found or change, the content process will be notified by the
// parent process. Bail out early if this is called from the content process.
if (XRE_IsContentProcess()) {
return NS_OK;
}
nsresult rv;
// Read cached plugins info. If the profile isn't yet available then don't
// scan for plugins
if (ReadPluginInfo() == NS_ERROR_NOT_AVAILABLE)
return NS_OK;
#ifdef XP_WIN
// Failure here is not a show-stopper so just warn.
rv = EnsurePrivateDirServiceProvider();
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to register dir service provider.");
#endif /* XP_WIN */
nsCOMPtr<nsIProperties> dirService(do_GetService(kDirectoryServiceContractID, &rv));
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsISimpleEnumerator> dirList;
// Scan plugins directories;
// don't pass aPluginsChanged directly, to prevent its
// possible reset in subsequent ScanPluginsDirectory calls
bool pluginschanged = false;
// Scan the app-defined list of plugin dirs.
rv = dirService->Get(NS_APP_PLUGINS_DIR_LIST, NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dirList));
if (NS_SUCCEEDED(rv)) {
ScanPluginsDirectoryList(dirList, aCreatePluginList, &pluginschanged);
if (pluginschanged)
*aPluginsChanged = true;
// if we are just looking for possible changes,
// no need to proceed if changes are detected
if (!aCreatePluginList && *aPluginsChanged) {
NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
return NS_OK;
}
}
mPluginsLoaded = true; // at this point 'some' plugins have been loaded,
// the rest is optional
#ifdef XP_WIN
bool bScanPLIDs = Preferences::GetBool("plugin.scan.plid.all", false);
// Now lets scan any PLID directories
if (bScanPLIDs && mPrivateDirServiceProvider) {
rv = mPrivateDirServiceProvider->GetPLIDDirectories(getter_AddRefs(dirList));
if (NS_SUCCEEDED(rv)) {
ScanPluginsDirectoryList(dirList, aCreatePluginList, &pluginschanged);
if (pluginschanged)
*aPluginsChanged = true;
// if we are just looking for possible changes,
// no need to proceed if changes are detected
if (!aCreatePluginList && *aPluginsChanged) {
NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
return NS_OK;
}
}
}
#endif
// We should also consider plugins to have changed if any plugins have been removed.
// We'll know if any were removed if they weren't taken out of the cached plugins list
// during our scan, thus we can assume something was removed if the cached plugins list
// contains anything.
if (!*aPluginsChanged && mCachedPlugins) {
*aPluginsChanged = true;
}
// Remove unseen invalid plugins
RefPtr<nsInvalidPluginTag> invalidPlugins = mInvalidPlugins;
while (invalidPlugins) {
if (!invalidPlugins->mSeen) {
RefPtr<nsInvalidPluginTag> invalidPlugin = invalidPlugins;
if (invalidPlugin->mPrev) {
invalidPlugin->mPrev->mNext = invalidPlugin->mNext;
}
else {
mInvalidPlugins = invalidPlugin->mNext;
}
if (invalidPlugin->mNext) {
invalidPlugin->mNext->mPrev = invalidPlugin->mPrev;
}
invalidPlugins = invalidPlugin->mNext;
invalidPlugin->mPrev = nullptr;
invalidPlugin->mNext = nullptr;
}
else {
invalidPlugins->mSeen = false;
invalidPlugins = invalidPlugins->mNext;
}
}
// if we are not creating the list, there is no need to proceed
if (!aCreatePluginList) {
NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
return NS_OK;
}
// if we are creating the list, it is already done;
// update the plugins info cache if changes are detected
if (*aPluginsChanged)
WritePluginInfo();
// No more need for cached plugins. Clear it up.
NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
return NS_OK;
}
nsresult
nsPluginHost::SendPluginsToContent()
{
MOZ_ASSERT(XRE_IsParentProcess());
nsTArray<PluginTag> pluginTags;
nsTArray<FakePluginTag> fakePluginTags;
// Load plugins so that the epoch is correct.
nsresult rv = LoadPlugins();
if (NS_FAILED(rv)) {
return rv;
}
uint32_t newPluginEpoch = ChromeEpoch();
nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins;
GetPlugins(plugins, true);
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);
fakePluginTags.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;
}
pluginTags.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->IsFromExtension(),
tag->mSandboxLevel,
blocklistState));
}
nsTArray<dom::ContentParent*> parents;
dom::ContentParent::GetAll(parents);
for (auto p : parents)
{
Unused << p->SendSetPluginList(newPluginEpoch, pluginTags, fakePluginTags);
}
return NS_OK;
}
void
nsPluginHost::UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag)
{
NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
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());
ReadPluginInfo();
WritePluginInfo();
IncrementChromeEpoch();
UpdateInMemoryPluginInfo(aPluginTag);
}
/* static */ bool
nsPluginHost::IsTypeWhitelisted(const char *aMimeType)
{
nsAutoCString whitelist;
Preferences::GetCString(kPrefWhitelist, whitelist);
if (whitelist.IsEmpty()) {
return true;
}
nsDependentCString wrap(aMimeType);
return IsTypeInList(wrap, whitelist);
}
/* static */ bool
nsPluginHost::ShouldLoadTypeInParent(const nsACString& aMimeType)
{
nsCString prefName(kPrefLoadInParentPrefix);
prefName += aMimeType;
return Preferences::GetBool(prefName.get(), false);
}
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;
}
const char *contractId =
"@mozilla.org/content/plugin/document-loader-factory;1";
if (aType == ePluginRegister) {
catMan->AddCategoryEntry("Gecko-Content-Viewers",
aMimeType.get(),
contractId,
false, /* persist: broken by bug 193031 */
mOverrideInternalTypes,
nullptr);
} 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.get(),
getter_Copies(value));
if (NS_SUCCEEDED(rv) && strcmp(value.get(), contractId) == 0) {
catMan->DeleteCategoryEntry("Gecko-Content-Viewers",
aMimeType.get(),
true);
}
}
}
nsresult
nsPluginHost::WritePluginInfo()
{
MOZ_ASSERT(XRE_IsParentProcess());
nsresult rv = NS_OK;
nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,&rv));
if (NS_FAILED(rv))
return rv;
directoryService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
getter_AddRefs(mPluginRegFile));
if (!mPluginRegFile)
return NS_ERROR_FAILURE;
PRFileDesc* fd = nullptr;
nsCOMPtr<nsIFile> pluginReg;
rv = mPluginRegFile->Clone(getter_AddRefs(pluginReg));
if (NS_FAILED(rv))
return rv;
nsAutoCString filename(kPluginRegistryFilename);
filename.AppendLiteral(".tmp");
rv = pluginReg->AppendNative(filename);
if (NS_FAILED(rv))
return rv;
rv = pluginReg->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
if (!runtime) {
return NS_ERROR_FAILURE;
}
nsAutoCString arch;
rv = runtime->GetXPCOMABI(arch);
if (NS_FAILED(rv)) {
return rv;
}
bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true);
PR_fprintf(fd, "Generated File. Do not edit.\n");
PR_fprintf(fd, "\n[HEADER]\nVersion%c%s%c%c%c\nArch%c%s%c%c\n",
PLUGIN_REGISTRY_FIELD_DELIMITER,
kPluginRegistryVersion,
flashOnly ? 't' : 'f',
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER,
PLUGIN_REGISTRY_FIELD_DELIMITER,
arch.get(),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
// Store all plugins in the mPlugins list - all plugins currently in use.
PR_fprintf(fd, "\n[PLUGINS]\n");
for (nsPluginTag *tag = mPlugins; tag; tag = tag->mNext) {
// store each plugin info into the registry
// filename & fullpath are on separate line
// because they can contain field delimiter char
PR_fprintf(fd, "%s%c%c\n%s%c%c\n%s%c%c\n",
(tag->FileName().get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER,
(tag->mFullPath.get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER,
(tag->Version().get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
// lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension|blocklistState
PR_fprintf(fd, "%lld%c%d%c%lu%c%d%c%d%c%c\n",
tag->mLastModifiedTime,
PLUGIN_REGISTRY_FIELD_DELIMITER,
false, // did store whether or not to unload in-process plugins
PLUGIN_REGISTRY_FIELD_DELIMITER,
0, // legacy field for flags
PLUGIN_REGISTRY_FIELD_DELIMITER,
tag->IsFromExtension(),
PLUGIN_REGISTRY_FIELD_DELIMITER,
tag->BlocklistState(),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
//description, name & mtypecount are on separate line
PR_fprintf(fd, "%s%c%c\n%s%c%c\n%d\n",
(tag->Description().get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER,
(tag->Name().get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER,
tag->MimeTypes().Length());
// Add in each mimetype this plugin supports
for (uint32_t i = 0; i < tag->MimeTypes().Length(); i++) {
PR_fprintf(fd, "%d%c%s%c%s%c%s%c%c\n",
i,PLUGIN_REGISTRY_FIELD_DELIMITER,
(tag->MimeTypes()[i].get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
(tag->MimeDescriptions()[i].get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
(tag->Extensions()[i].get()),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
}
}
PR_fprintf(fd, "\n[INVALID]\n");
RefPtr<nsInvalidPluginTag> invalidPlugins = mInvalidPlugins;
while (invalidPlugins) {
// fullPath
PR_fprintf(fd, "%s%c%c\n",
(!invalidPlugins->mFullPath.IsEmpty() ? invalidPlugins->mFullPath.get() : ""),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
// lastModifiedTimeStamp
PR_fprintf(fd, "%lld%c%c\n",
invalidPlugins->mLastModifiedTime,
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
invalidPlugins = invalidPlugins->mNext;
}
PRStatus prrc;
prrc = PR_Close(fd);
if (prrc != PR_SUCCESS) {
// we should obtain a refined value based on prrc;
rv = NS_ERROR_FAILURE;
MOZ_ASSERT(false, "PR_Close() failed.");
return rv;
}
nsCOMPtr<nsIFile> parent;
rv = pluginReg->GetParent(getter_AddRefs(parent));
NS_ENSURE_SUCCESS(rv, rv);
rv = pluginReg->MoveToNative(parent, kPluginRegistryFilename);
return rv;
}
nsresult
nsPluginHost::ReadPluginInfo()
{
MOZ_ASSERT(XRE_IsParentProcess());
const long PLUGIN_REG_MIMETYPES_ARRAY_SIZE = 12;
const long PLUGIN_REG_MAX_MIMETYPES = 1000;
nsresult rv;
nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,&rv));
if (NS_FAILED(rv))
return rv;
directoryService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
getter_AddRefs(mPluginRegFile));
if (!mPluginRegFile) {
// There is no profile yet, this will tell us if there is going to be one
// in the future.
directoryService->Get(NS_APP_PROFILE_DIR_STARTUP, NS_GET_IID(nsIFile),
getter_AddRefs(mPluginRegFile));
if (!mPluginRegFile)
return NS_ERROR_FAILURE;
return NS_ERROR_NOT_AVAILABLE;
}
PRFileDesc* fd = nullptr;
nsCOMPtr<nsIFile> pluginReg;
rv = mPluginRegFile->Clone(getter_AddRefs(pluginReg));
if (NS_FAILED(rv))
return rv;
rv = pluginReg->AppendNative(kPluginRegistryFilename);
if (NS_FAILED(rv))
return rv;
int64_t fileSize;
rv = pluginReg->GetFileSize(&fileSize);
if (NS_FAILED(rv))
return rv;
if (fileSize > INT32_MAX) {
return NS_ERROR_FAILURE;
}
int32_t flen = int32_t(fileSize);
if (flen == 0) {
NS_WARNING("Plugins Registry Empty!");
return NS_OK; // ERROR CONDITION
}
nsPluginManifestLineReader reader;
char* registry = reader.Init(flen);
if (!registry)
return NS_ERROR_OUT_OF_MEMORY;
rv = pluginReg->OpenNSPRFileDesc(PR_RDONLY, 0444, &fd);
if (NS_FAILED(rv))
return rv;
// set rv to return an error on goto out
rv = NS_ERROR_FAILURE;
// We know how many octes we are supposed to read.
// So let use the busy_beaver_PR_Read version.
int32_t bread = busy_beaver_PR_Read(fd, registry, flen);
PRStatus prrc;
prrc = PR_Close(fd);
if (prrc != PR_SUCCESS) {
// Strange error: this is one of those "Should not happen" error.
// we may want to report something more refined than NS_ERROR_FAILURE.
MOZ_ASSERT(false, "PR_Close() failed.");
return rv;
}
// short read error, so to speak.
if (flen > bread)
return rv;
if (!ReadSectionHeader(reader, "HEADER"))
return rv;;
if (!reader.NextLine())
return rv;
char* values[6];
// VersionLiteral, kPluginRegistryVersion
if (2 != reader.ParseLine(values, 2))
return rv;
// VersionLiteral
if (PL_strcmp(values[0], "Version"))
return rv;
// If we're reading an old registry, ignore it
// If we flipped the flash-only pref, ignore it
bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true);
nsAutoCString expectedVersion(kPluginRegistryVersion);
expectedVersion.Append(flashOnly ? 't' : 'f');
if (!expectedVersion.Equals(values[1])) {
return rv;
}
char* archValues[6];
if (!reader.NextLine()) {
return rv;
}
// ArchLiteral, Architecture
if (2 != reader.ParseLine(archValues, 2)) {
return rv;
}
// ArchLiteral
if (PL_strcmp(archValues[0], "Arch")) {
return rv;
}
nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
if (!runtime) {
return rv;
}
nsAutoCString arch;
if (NS_FAILED(runtime->GetXPCOMABI(arch))) {
return rv;
}
// If this is a registry from a different architecture then don't attempt to read it
if (PL_strcmp(archValues[1], arch.get())) {
return rv;
}
if (!ReadSectionHeader(reader, "PLUGINS"))
return rv;
while (reader.NextLine()) {
if (*reader.LinePtr() == '[') {
break;
}
const char* filename = reader.LinePtr();
if (!reader.NextLine())
return rv;
const char* fullpath = reader.LinePtr();
if (!reader.NextLine())
return rv;
const char *version;
version = reader.LinePtr();
if (!reader.NextLine())
return rv;
// lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension|blocklistState
if (5 != reader.ParseLine(values, 5))
return rv;
int64_t lastmod = nsCRT::atoll(values[0]);
bool fromExtension = atoi(values[3]);
uint16_t blocklistState = atoi(values[4]);
if (!reader.NextLine())
return rv;
char *description = reader.LinePtr();
if (!reader.NextLine())
return rv;
const char *name = reader.LinePtr();
if (!reader.NextLine())
return rv;
long mimetypecount = std::strtol(reader.LinePtr(), nullptr, 10);
if (mimetypecount == LONG_MAX || mimetypecount == LONG_MIN ||
mimetypecount >= PLUGIN_REG_MAX_MIMETYPES || mimetypecount < 0) {
return NS_ERROR_FAILURE;
}
char *stackalloced[PLUGIN_REG_MIMETYPES_ARRAY_SIZE * 3];
char **mimetypes;
char **mimedescriptions;
char **extensions;
char **heapalloced = 0;
if (mimetypecount > PLUGIN_REG_MIMETYPES_ARRAY_SIZE - 1) {
heapalloced = new char *[mimetypecount * 3];
mimetypes = heapalloced;
} else {
mimetypes = stackalloced;
}
mimedescriptions = mimetypes + mimetypecount;
extensions = mimedescriptions + mimetypecount;
int mtr = 0; //mimetype read
for (; mtr < mimetypecount; mtr++) {
if (!reader.NextLine())
break;
//line number|mimetype|description|extension
if (4 != reader.ParseLine(values, 4))
break;
int line = atoi(values[0]);
if (line != mtr)
break;
mimetypes[mtr] = values[1];
mimedescriptions[mtr] = values[2];
extensions[mtr] = values[3];
}
if (mtr != mimetypecount) {
delete [] heapalloced;
return rv;
}
RefPtr<nsPluginTag> tag = new nsPluginTag(name,
description,
filename,
fullpath,
version,
(const char* const*)mimetypes,
(const char* const*)mimedescriptions,
(const char* const*)extensions,
mimetypecount, lastmod, fromExtension, blocklistState, true);
delete [] heapalloced;
// Import flags from registry into prefs for old registry versions
MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC,
("LoadCachedPluginsInfo : Loading Cached plugininfo for %s\n", tag->FileName().get()));
tag->mNext = mCachedPlugins;
mCachedPlugins = tag;
}
if (!ReadSectionHeader(reader, "INVALID")) {
return rv;
}
while (reader.NextLine()) {
const char *fullpath = reader.LinePtr();
if (!reader.NextLine()) {
return rv;
}
const char *lastModifiedTimeStamp = reader.LinePtr();
int64_t lastmod = nsCRT::atoll(lastModifiedTimeStamp);
RefPtr<nsInvalidPluginTag> invalidTag = new nsInvalidPluginTag(fullpath, lastmod);
invalidTag->mNext = mInvalidPlugins;
if (mInvalidPlugins) {
mInvalidPlugins->mPrev = invalidTag;
}
mInvalidPlugins = invalidTag;
}
return NS_OK;
}
void
nsPluginHost::RemoveCachedPluginsInfo(const char *filePath, nsPluginTag **result)
{
RefPtr<nsPluginTag> prev;
RefPtr<nsPluginTag> tag = mCachedPlugins;
while (tag)
{
if (tag->mFullPath.Equals(filePath)) {
// Found it. Remove it from our list
if (prev)
prev->mNext = tag->mNext;
else
mCachedPlugins = tag->mNext;
tag->mNext = nullptr;
*result = tag;
NS_ADDREF(*result);
break;
}
prev = tag;
tag = tag->mNext;
}
}
#ifdef XP_WIN
nsresult
nsPluginHost::EnsurePrivateDirServiceProvider()
{
if (!mPrivateDirServiceProvider) {
nsresult rv;
mPrivateDirServiceProvider = new nsPluginDirServiceProvider();
nsCOMPtr<nsIDirectoryService> dirService(do_GetService(kDirectoryServiceContractID, &rv));
if (NS_FAILED(rv))
return rv;
rv = dirService->RegisterProvider(mPrivateDirServiceProvider);
if (NS_FAILED(rv))
return rv;
}
return NS_OK;
}
#endif /* XP_WIN */
nsresult nsPluginHost::NewPluginURLStream(const nsString& aURL,
nsNPAPIPluginInstance *aInstance,
nsNPAPIPluginStreamListener* aListener,
nsIInputStream *aPostStream,
const char *aHeadersData,
uint32_t aHeadersDataLen)
{
nsCOMPtr<nsIURI> url;
nsAutoString absUrl;
nsresult rv;
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) {
nsCOMPtr<nsIURI> baseURI = owner->GetBaseURI();
rv = NS_MakeAbsoluteURI(absUrl, aURL, baseURI);
}
if (absUrl.IsEmpty())
absUrl.Assign(aURL);
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);
nsCOMPtr<nsIDOMElement> element;
nsCOMPtr<nsIDocument> doc;
if (owner) {
owner->GetDOMElement(getter_AddRefs(element));
owner->GetDocument(getter_AddRefs(doc));
}
nsCOMPtr<nsINode> requestingNode(do_QueryInterface(element));
NS_ENSURE_TRUE(requestingNode, 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,
requestingNode,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
nsIContentPolicy::TYPE_OBJECT_SUBREQUEST,
nullptr, // aPerformanceStorage
nullptr, // aLoadGroup
listenerPeer,
nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI |
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;
net::ReferrerPolicy referrerPolicy = net::RP_Unset;
nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(element);
if (olc)
olc->GetSrcURI(getter_AddRefs(referer));
if (!referer) {
if (!doc) {
return NS_ERROR_FAILURE;
}
referer = doc->GetDocumentURI();
referrerPolicy = doc->GetReferrerPolicy();
}
rv = httpChannel->SetReferrerWithPolicy(referer, referrerPolicy);
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, EmptyCString(), -1);
}
if (aHeadersData) {
rv = AddHeadersToChannel(aHeadersData, aHeadersDataLen, httpChannel);
NS_ENSURE_SUCCESS(rv,rv);
}
}
rv = channel->AsyncOpen2(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("blocklist-updated", aTopic) || !strcmp("blocklist-loaded", aTopic))) {
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
if (!blocklist) {
return NS_OK;
}
nsPluginTag* plugin = mPlugins;
bool blocklistAlteredPlugins = false;
while (plugin) {
uint32_t blocklistState = nsIBlocklistService::STATE_NOT_BLOCKED;
nsresult rv = blocklist->GetPluginBlocklistState(plugin, EmptyString(),
EmptyString(), &blocklistState);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t oldBlocklistState;
plugin->GetBlocklistState(&oldBlocklistState);
plugin->SetBlocklistState(blocklistState);
blocklistAlteredPlugins |= (oldBlocklistState != blocklistState);
plugin = plugin->mNext;
}
if (blocklistAlteredPlugins) {
// Write the changed list to disk:
WritePluginInfo();
// We update blocklists asynchronously by just sending a new plugin list to
// content.
// We'll need to repack our tags and send them to content again.
IncrementChromeEpoch();
SendPluginsToContent();
}
}
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;
if (!(*outPostData = p = (char*)moz_xmalloc(newBufferLen)))
return NS_ERROR_OUT_OF_MEMORY;
// 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;
if (!(*outPostData = p = (char*)moz_xmalloc(newBufferLen)))
return NS_ERROR_OUT_OF_MEMORY;
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->GetXULWindowEnumerator(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& pluginDumpID,
const nsAString& browserDumpID)
{
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(NS_LITERAL_STRING("runID"), runID);
nsCString pluginName;
crashedPluginTag->GetName(pluginName);
propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginName"),
NS_ConvertUTF8toUTF16(pluginName));
propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"),
pluginDumpID);
propbag->SetPropertyAsAString(NS_LITERAL_STRING("browserDumpID"),
browserDumpID);
propbag->SetPropertyAsBool(NS_LITERAL_STRING("submittedCrashReport"),
submittedCrashReport);
obsService->NotifyObservers(propbag, "plugin-crashed", nullptr);
// see if an observer submitted a crash report.
propbag->GetPropertyAsBool(NS_LITERAL_STRING("submittedCrashReport"),
&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
nsCOMPtr<nsIDOMElement> domElement;
instance->GetDOMElement(getter_AddRefs(domElement));
nsCOMPtr<nsIObjectLoadingContent> objectContent(do_QueryInterface(domElement));
if (objectContent) {
objectContent->PluginCrashed(crashedPluginTag, pluginDumpID, browserDumpID,
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);
nsCOMPtr<nsIDOMElement> 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") ||
aMIMEType.LowerCaseEqualsLiteral("application/x-second-test") ||
aMIMEType.LowerCaseEqualsLiteral("application/x-third-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;
}