/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: sw=4 ts=4 et : * 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/. */ #ifdef MOZ_WIDGET_QT #include "PluginHelperQt.h" #endif #include "mozilla/plugins/PluginModuleParent.h" #include "base/process_util.h" #include "mozilla/Attributes.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/PCrashReporterParent.h" #include "mozilla/ipc/MessageChannel.h" #include "mozilla/plugins/BrowserStreamParent.h" #include "mozilla/plugins/PluginBridge.h" #include "mozilla/plugins/PluginInstanceParent.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/unused.h" #include "nsAutoPtr.h" #include "nsCRT.h" #include "nsIFile.h" #include "nsIObserverService.h" #include "nsNPAPIPlugin.h" #include "nsPrintfCString.h" #include "prsystem.h" #include "GeckoProfiler.h" #ifdef XP_WIN #include "PluginHangUIParent.h" #include "mozilla/widget/AudioSession.h" #endif #ifdef MOZ_ENABLE_PROFILER_SPS #include "nsIProfiler.h" #include "nsIProfileSaveEvent.h" #endif #ifdef MOZ_WIDGET_GTK #include #elif XP_MACOSX #include "PluginInterposeOSX.h" #include "PluginUtilsOSX.h" #endif using base::KillProcess; using mozilla::PluginLibrary; using mozilla::ipc::MessageChannel; using mozilla::dom::PCrashReporterParent; using mozilla::dom::CrashReporterParent; using namespace mozilla; using namespace mozilla::plugins; using namespace mozilla::plugins::parent; #ifdef MOZ_CRASHREPORTER #include "mozilla/dom/CrashReporterParent.h" using namespace CrashReporter; #endif static const char kChildTimeoutPref[] = "dom.ipc.plugins.timeoutSecs"; static const char kParentTimeoutPref[] = "dom.ipc.plugins.parentTimeoutSecs"; static const char kLaunchTimeoutPref[] = "dom.ipc.plugins.processLaunchTimeoutSecs"; #ifdef XP_WIN static const char kHangUITimeoutPref[] = "dom.ipc.plugins.hangUITimeoutSecs"; static const char kHangUIMinDisplayPref[] = "dom.ipc.plugins.hangUIMinDisplaySecs"; #define CHILD_TIMEOUT_PREF kHangUITimeoutPref #else #define CHILD_TIMEOUT_PREF kChildTimeoutPref #endif template<> struct RunnableMethodTraits { typedef mozilla::plugins::PluginModuleParent Class; static void RetainCallee(Class* obj) { } static void ReleaseCallee(Class* obj) { } }; bool mozilla::plugins::SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent) { nsRefPtr host = nsPluginHost::GetInst(); nsRefPtr plugin; nsresult rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin)); if (NS_FAILED(rv)) { return false; } PluginModuleParent* chromeParent = static_cast(plugin->GetLibrary()); return PPluginModule::Bridge(aContentParent, chromeParent); } // A linked list used to fetch newly created PluginModuleContentParent instances // for LoadModule. Each pending LoadModule call owns an element in this list. // The element's mModule field is filled in when the new // PluginModuleContentParent arrives from chrome. struct MOZ_STACK_CLASS SavedPluginModule { SavedPluginModule() : mModule(nullptr), mNext(sSavedModuleStack) { sSavedModuleStack = this; } ~SavedPluginModule() { MOZ_ASSERT(sSavedModuleStack == this); sSavedModuleStack = mNext; } PluginModuleContentParent* GetModule() { return mModule; } // LoadModule can be on the stack multiple times since the intr message it // sends will dispatch arbitrary incoming messages from the chrome process, // which can include new HTTP data. This makes it somewhat tricky to match // up the object created in PluginModuleContentParent::Create with the // LoadModule call that asked for it. // // Each invocation of LoadModule pushes a new SavedPluginModule object on // the sSavedModuleStack stack, with the most recent invocation at the // front. LoadModule messages are always processed by the chrome process in // order, and PluginModuleContentParent allocation messages will also be // received in order. Therefore, we need to match up the first received // PluginModuleContentParent creation message with the first sent LoadModule // call. This call will be the last one in the list that doesn't already // have a module attached to it. static void SaveModule(PluginModuleContentParent* module) { SavedPluginModule* saved = sSavedModuleStack; SavedPluginModule* prev = nullptr; while (saved && !saved->mModule) { prev = saved; saved = saved->mNext; } MOZ_ASSERT(prev); MOZ_ASSERT(!prev->mModule); prev->mModule = module; } private: PluginModuleContentParent* mModule; SavedPluginModule* mNext; static SavedPluginModule* sSavedModuleStack; }; SavedPluginModule* SavedPluginModule::sSavedModuleStack; /* static */ PluginLibrary* PluginModuleContentParent::LoadModule(uint32_t aPluginId) { SavedPluginModule saved; MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content); /* * We send a LoadPlugin message to the chrome process using an intr * message. Before it sends its response, it sends a message to create a * PluginModuleContentParent instance. That message is handled by * PluginModuleContentParent::Create. See SavedPluginModule for details * about how we match up the result of PluginModuleContentParent::Create * with the LoadModule call that requested it. */ dom::ContentChild* cp = dom::ContentChild::GetSingleton(); if (!cp->CallLoadPlugin(aPluginId)) { return nullptr; } PluginModuleContentParent* parent = saved.GetModule(); MOZ_ASSERT(parent); return parent; } /* static */ PluginModuleContentParent* PluginModuleContentParent::Create(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) { nsAutoPtr parent(new PluginModuleContentParent()); ProcessHandle handle; if (!base::OpenProcessHandle(aOtherProcess, &handle)) { // Bug 1090578 - need to kill |aOtherProcess|, it's boned. return nullptr; } SavedPluginModule::SaveModule(parent); DebugOnly ok = parent->Open(aTransport, handle, XRE_GetIOMessageLoop(), mozilla::ipc::ParentSide); MOZ_ASSERT(ok); // Request Windows message deferral behavior on our channel. This // applies to the top level and all sub plugin protocols since they // all share the same channel. parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); return parent.forget(); } // static PluginLibrary* PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId) { PLUGIN_LOG_DEBUG_FUNCTION; int32_t prefSecs = Preferences::GetInt(kLaunchTimeoutPref, 0); // Block on the child process being launched and initialized. nsAutoPtr parent(new PluginModuleChromeParent(aFilePath, aPluginId)); bool launched = parent->mSubprocess->Launch(prefSecs * 1000); if (!launched) { // We never reached open parent->mShutdown = true; return nullptr; } parent->Open(parent->mSubprocess->GetChannel(), parent->mSubprocess->GetChildProcessHandle()); // Request Windows message deferral behavior on our channel. This // applies to the top level and all sub plugin protocols since they // all share the same channel. parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); TimeoutChanged(CHILD_TIMEOUT_PREF, parent); #ifdef MOZ_CRASHREPORTER // If this fails, we're having IPC troubles, and we're doomed anyways. if (!CrashReporterParent::CreateCrashReporter(parent.get())) { parent->Close(); return nullptr; } #ifdef XP_WIN mozilla::MutexAutoLock lock(parent->mCrashReporterMutex); parent->mCrashReporter = parent->CrashReporter(); #endif #endif return parent.forget(); } PluginModuleParent::PluginModuleParent(bool aIsChrome) : mIsChrome(aIsChrome) , mShutdown(false) , mClearSiteDataSupported(false) , mGetSitesWithDataSupported(false) , mNPNIface(nullptr) , mPlugin(nullptr) , mTaskFactory(MOZ_THIS_IN_INITIALIZER_LIST()) { } PluginModuleParent::~PluginModuleParent() { if (!OkToCleanup()) { NS_RUNTIMEABORT("unsafe destruction"); } if (!mShutdown) { NS_WARNING("Plugin host deleted the module without shutting down."); NPError err; NP_Shutdown(&err); } } PluginModuleContentParent::PluginModuleContentParent() : PluginModuleParent(false) { } PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId) : PluginModuleParent(true) , mSubprocess(new PluginProcessParent(aFilePath)) , mPluginId(aPluginId) , mChromeTaskFactory(MOZ_THIS_IN_INITIALIZER_LIST()) , mHangAnnotationFlags(0) #ifdef XP_WIN , mPluginCpuUsageOnHang() , mHangUIParent(nullptr) , mHangUIEnabled(true) , mIsTimerReset(true) #ifdef MOZ_CRASHREPORTER , mCrashReporterMutex("PluginModuleParent::mCrashReporterMutex") , mCrashReporter(nullptr) #endif #endif #ifdef MOZ_CRASHREPORTER_INJECTOR , mFlashProcess1(0) , mFlashProcess2(0) #endif { NS_ASSERTION(mSubprocess, "Out of memory!"); Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this); Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this); #ifdef XP_WIN Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this); Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); #endif RegisterSettingsCallbacks(); #ifdef MOZ_ENABLE_PROFILER_SPS InitPluginProfiling(); #endif mozilla::HangMonitor::RegisterAnnotator(*this); } PluginModuleChromeParent::~PluginModuleChromeParent() { if (!OkToCleanup()) { NS_RUNTIMEABORT("unsafe destruction"); } #ifdef MOZ_ENABLE_PROFILER_SPS ShutdownPluginProfiling(); #endif if (!mShutdown) { NS_WARNING("Plugin host deleted the module without shutting down."); NPError err; NP_Shutdown(&err); } NS_ASSERTION(mShutdown, "NP_Shutdown didn't"); if (mSubprocess) { mSubprocess->Delete(); mSubprocess = nullptr; } #ifdef MOZ_CRASHREPORTER_INJECTOR if (mFlashProcess1) UnregisterInjectorCallback(mFlashProcess1); if (mFlashProcess2) UnregisterInjectorCallback(mFlashProcess2); #endif UnregisterSettingsCallbacks(); Preferences::UnregisterCallback(TimeoutChanged, kChildTimeoutPref, this); Preferences::UnregisterCallback(TimeoutChanged, kParentTimeoutPref, this); #ifdef XP_WIN Preferences::UnregisterCallback(TimeoutChanged, kHangUITimeoutPref, this); Preferences::UnregisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); if (mHangUIParent) { delete mHangUIParent; mHangUIParent = nullptr; } #endif mozilla::HangMonitor::UnregisterAnnotator(*this); } #ifdef MOZ_CRASHREPORTER void PluginModuleChromeParent::WriteExtraDataForMinidump(AnnotationTable& notes) { #ifdef XP_WIN // mCrashReporterMutex is already held by the caller mCrashReporterMutex.AssertCurrentThreadOwns(); #endif typedef nsDependentCString CS; // Get the plugin filename, try to get just the file leafname const std::string& pluginFile = mSubprocess->GetPluginFilePath(); size_t filePos = pluginFile.rfind(FILE_PATH_SEPARATOR); if (filePos == std::string::npos) filePos = 0; else filePos++; notes.Put(NS_LITERAL_CSTRING("PluginFilename"), CS(pluginFile.substr(filePos).c_str())); notes.Put(NS_LITERAL_CSTRING("PluginName"), mPluginName); notes.Put(NS_LITERAL_CSTRING("PluginVersion"), mPluginVersion); CrashReporterParent* crashReporter = CrashReporter(); if (crashReporter) { #ifdef XP_WIN if (mPluginCpuUsageOnHang.Length() > 0) { notes.Put(NS_LITERAL_CSTRING("NumberOfProcessors"), nsPrintfCString("%d", PR_GetNumberOfProcessors())); nsCString cpuUsageStr; cpuUsageStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[0] * 100) / 100); notes.Put(NS_LITERAL_CSTRING("PluginCpuUsage"), cpuUsageStr); #ifdef MOZ_CRASHREPORTER_INJECTOR for (uint32_t i=1; i 0) ? (1000 * aChildTimeout) : MessageChannel::kNoTimeout; SetReplyTimeoutMs(timeoutMs); } void PluginModuleChromeParent::TimeoutChanged(const char* aPref, void* aModule) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); #ifndef XP_WIN if (!strcmp(aPref, kChildTimeoutPref)) { // The timeout value used by the parent for children int32_t timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0); static_cast(aModule)->SetChildTimeout(timeoutSecs); #else if (!strcmp(aPref, kChildTimeoutPref) || !strcmp(aPref, kHangUIMinDisplayPref) || !strcmp(aPref, kHangUITimeoutPref)) { static_cast(aModule)->EvaluateHangUIState(true); #endif // XP_WIN } else if (!strcmp(aPref, kParentTimeoutPref)) { // The timeout value used by the child for its parent int32_t timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0); unused << static_cast(aModule)->SendSetParentHangTimeout(timeoutSecs); } } void PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI) { if (mShutdown) { return; } if (!OkToCleanup()) { // there's still plugin code on the C++ stack, try again MessageLoop::current()->PostDelayedTask( FROM_HERE, mChromeTaskFactory.NewRunnableMethod( &PluginModuleChromeParent::CleanupFromTimeout, aFromHangUI), 10); return; } /* If the plugin container was terminated by the Plugin Hang UI, then either the I/O thread detects a channel error, or the main thread must set the error (whomever gets there first). OTOH, if we terminate and return false from ShouldContinueFromReplyTimeout, then the channel state has already been set to ChannelTimeout and we should call the regular Close function. */ if (aFromHangUI) { GetIPCChannel()->CloseWithError(); } else { Close(); } } #ifdef XP_WIN namespace { uint64_t FileTimeToUTC(const FILETIME& ftime) { ULARGE_INTEGER li; li.LowPart = ftime.dwLowDateTime; li.HighPart = ftime.dwHighDateTime; return li.QuadPart; } struct CpuUsageSamples { uint64_t sampleTimes[2]; uint64_t cpuTimes[2]; }; bool GetProcessCpuUsage(const InfallibleTArray& processHandles, InfallibleTArray& cpuUsage) { InfallibleTArray samples(processHandles.Length()); FILETIME creationTime, exitTime, kernelTime, userTime, currentTime; BOOL res; for (uint32_t i = 0; i < processHandles.Length(); ++i) { ::GetSystemTimeAsFileTime(¤tTime); res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, &kernelTime, &userTime); if (!res) { NS_WARNING("failed to get process times"); return false; } CpuUsageSamples s; s.sampleTimes[0] = FileTimeToUTC(currentTime); s.cpuTimes[0] = FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); samples.AppendElement(s); } // we already hung for a while, a little bit longer won't matter ::Sleep(50); const int32_t numberOfProcessors = PR_GetNumberOfProcessors(); for (uint32_t i = 0; i < processHandles.Length(); ++i) { ::GetSystemTimeAsFileTime(¤tTime); res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, &kernelTime, &userTime); if (!res) { NS_WARNING("failed to get process times"); return false; } samples[i].sampleTimes[1] = FileTimeToUTC(currentTime); samples[i].cpuTimes[1] = FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); const uint64_t deltaSampleTime = samples[i].sampleTimes[1] - samples[i].sampleTimes[0]; const uint64_t deltaCpuTime = samples[i].cpuTimes[1] - samples[i].cpuTimes[0]; const float usage = 100.f * (float(deltaCpuTime) / deltaSampleTime) / numberOfProcessors; cpuUsage.AppendElement(usage); } return true; } } // anonymous namespace #endif // #ifdef XP_WIN void PluginModuleChromeParent::EnteredCxxStack() { mHangAnnotationFlags |= kInPluginCall; } void PluginModuleChromeParent::ExitedCxxStack() { mHangAnnotationFlags = 0; #ifdef XP_WIN FinishHangUI(); #endif } /** * This function is always called by the HangMonitor thread. */ void PluginModuleChromeParent::AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations) { uint32_t flags = mHangAnnotationFlags; if (flags) { /* We don't actually annotate anything specifically for kInPluginCall; we use it to determine whether to annotate other things. It will be pretty obvious from the ChromeHang stack that we're in a plugin call when the hang occurred. */ if (flags & kHangUIShown) { aAnnotations.AddAnnotation(NS_LITERAL_STRING("HangUIShown"), true); } if (flags & kHangUIContinued) { aAnnotations.AddAnnotation(NS_LITERAL_STRING("HangUIContinued"), true); } if (flags & kHangUIDontShow) { aAnnotations.AddAnnotation(NS_LITERAL_STRING("HangUIDontShow"), true); } aAnnotations.AddAnnotation(NS_LITERAL_STRING("pluginName"), mPluginName); aAnnotations.AddAnnotation(NS_LITERAL_STRING("pluginVersion"), mPluginVersion); } } #ifdef MOZ_CRASHREPORTER_INJECTOR static bool CreateFlashMinidump(DWORD processId, ThreadId childThread, nsIFile* parentMinidump, const nsACString& name) { if (processId == 0) { return false; } base::ProcessHandle handle; if (!base::OpenPrivilegedProcessHandle(processId, &handle)) { return false; } bool res = CreateAdditionalChildMinidump(handle, 0, parentMinidump, name); base::CloseProcessHandle(handle); return res; } #endif bool PluginModuleChromeParent::ShouldContinueFromReplyTimeout() { #ifdef XP_WIN if (LaunchHangUI()) { return true; } // If LaunchHangUI returned false then we should proceed with the // original plugin hang behaviour and kill the plugin container. FinishHangUI(); #endif // XP_WIN TerminateChildProcess(MessageLoop::current()); return false; } void PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop) { #ifdef MOZ_CRASHREPORTER #ifdef XP_WIN mozilla::MutexAutoLock lock(mCrashReporterMutex); CrashReporterParent* crashReporter = mCrashReporter; if (!crashReporter) { // If mCrashReporter is null then the hang has ended, the plugin module // is shutting down. There's nothing to do here. return; } #else CrashReporterParent* crashReporter = CrashReporter(); #endif crashReporter->AnnotateCrashReport(NS_LITERAL_CSTRING("PluginHang"), NS_LITERAL_CSTRING("1")); #ifdef XP_WIN if (mHangUIParent) { unsigned int hangUIDuration = mHangUIParent->LastShowDurationMs(); if (hangUIDuration) { nsPrintfCString strHangUIDuration("%u", hangUIDuration); crashReporter->AnnotateCrashReport( NS_LITERAL_CSTRING("PluginHangUIDuration"), strHangUIDuration); } } #endif // XP_WIN if (crashReporter->GeneratePairedMinidump(this)) { mPluginDumpID = crashReporter->ChildDumpID(); PLUGIN_LOG_DEBUG( ("generated paired browser/plugin minidumps: %s)", NS_ConvertUTF16toUTF8(mPluginDumpID).get())); nsAutoCString additionalDumps("browser"); #ifdef MOZ_CRASHREPORTER_INJECTOR nsCOMPtr pluginDumpFile; if (GetMinidumpForID(mPluginDumpID, getter_AddRefs(pluginDumpFile)) && pluginDumpFile) { nsCOMPtr childDumpFile; if (CreateFlashMinidump(mFlashProcess1, 0, pluginDumpFile, NS_LITERAL_CSTRING("flash1"))) { additionalDumps.AppendLiteral(",flash1"); } if (CreateFlashMinidump(mFlashProcess2, 0, pluginDumpFile, NS_LITERAL_CSTRING("flash2"))) { additionalDumps.AppendLiteral(",flash2"); } } #endif crashReporter->AnnotateCrashReport( NS_LITERAL_CSTRING("additional_minidumps"), additionalDumps); } else { NS_WARNING("failed to capture paired minidumps from hang"); } #endif #ifdef XP_WIN // collect cpu usage for plugin processes InfallibleTArray processHandles; processHandles.AppendElement(OtherProcess()); #ifdef MOZ_CRASHREPORTER_INJECTOR { base::ProcessHandle handle; if (mFlashProcess1 && base::OpenProcessHandle(mFlashProcess1, &handle)) { processHandles.AppendElement(handle); } if (mFlashProcess2 && base::OpenProcessHandle(mFlashProcess2, &handle)) { processHandles.AppendElement(handle); } } #endif if (!GetProcessCpuUsage(processHandles, mPluginCpuUsageOnHang)) { mPluginCpuUsageOnHang.Clear(); } #endif // this must run before the error notification from the channel, // or not at all bool isFromHangUI = aMsgLoop != MessageLoop::current(); aMsgLoop->PostTask( FROM_HERE, mChromeTaskFactory.NewRunnableMethod( &PluginModuleChromeParent::CleanupFromTimeout, isFromHangUI)); if (!KillProcess(OtherProcess(), 1, false)) NS_WARNING("failed to kill subprocess!"); } bool PluginModuleParent::GetPluginDetails(nsACString& aPluginName, nsACString& aPluginVersion) { nsRefPtr host = nsPluginHost::GetInst(); if (!host) { return false; } nsPluginTag* pluginTag = host->TagForPlugin(mPlugin); if (!pluginTag) { return false; } aPluginName = pluginTag->mName; aPluginVersion = pluginTag->mVersion; return true; } #ifdef XP_WIN void PluginModuleChromeParent::EvaluateHangUIState(const bool aReset) { int32_t minDispSecs = Preferences::GetInt(kHangUIMinDisplayPref, 10); int32_t autoStopSecs = Preferences::GetInt(kChildTimeoutPref, 0); int32_t timeoutSecs = 0; if (autoStopSecs > 0 && autoStopSecs < minDispSecs) { /* If we're going to automatically terminate the plugin within a time frame shorter than minDispSecs, there's no point in showing the hang UI; it would just flash briefly on the screen. */ mHangUIEnabled = false; } else { timeoutSecs = Preferences::GetInt(kHangUITimeoutPref, 0); mHangUIEnabled = timeoutSecs > 0; } if (mHangUIEnabled) { if (aReset) { mIsTimerReset = true; SetChildTimeout(timeoutSecs); return; } else if (mIsTimerReset) { /* The Hang UI is being shown, so now we're setting the timeout to kChildTimeoutPref while we wait for a user response. ShouldContinueFromReplyTimeout will fire after (reply timeout / 2) seconds, which is not what we want. Doubling the timeout value here so that we get the right result. */ autoStopSecs *= 2; } } mIsTimerReset = false; SetChildTimeout(autoStopSecs); } bool PluginModuleChromeParent::LaunchHangUI() { if (!mHangUIEnabled) { return false; } if (mHangUIParent) { if (mHangUIParent->IsShowing()) { // We've already shown the UI but the timeout has expired again. return false; } if (mHangUIParent->DontShowAgain()) { mHangAnnotationFlags |= kHangUIDontShow; bool wasLastHangStopped = mHangUIParent->WasLastHangStopped(); if (!wasLastHangStopped) { mHangAnnotationFlags |= kHangUIContinued; } return !wasLastHangStopped; } delete mHangUIParent; mHangUIParent = nullptr; } mHangUIParent = new PluginHangUIParent(this, Preferences::GetInt(kHangUITimeoutPref, 0), Preferences::GetInt(kChildTimeoutPref, 0)); bool retval = mHangUIParent->Init(NS_ConvertUTF8toUTF16(mPluginName)); if (retval) { mHangAnnotationFlags |= kHangUIShown; /* Once the UI is shown we switch the timeout over to use kChildTimeoutPref, allowing us to terminate a hung plugin after kChildTimeoutPref seconds if the user doesn't respond to the hang UI. */ EvaluateHangUIState(false); } return retval; } void PluginModuleChromeParent::FinishHangUI() { if (mHangUIEnabled && mHangUIParent) { bool needsCancel = mHangUIParent->IsShowing(); // If we're still showing, send a Cancel notification if (needsCancel) { mHangUIParent->Cancel(); } /* If we cancelled the UI or if the user issued a response, we need to reset the child process timeout. */ if (needsCancel || (!mIsTimerReset && mHangUIParent->WasShown())) { /* We changed the timeout to kChildTimeoutPref when the plugin hang UI was displayed. Now that we're finishing the UI, we need to switch it back to kHangUITimeoutPref. */ EvaluateHangUIState(true); } } } void PluginModuleChromeParent::OnHangUIContinue() { mHangAnnotationFlags |= kHangUIContinued; } #endif // XP_WIN #ifdef MOZ_CRASHREPORTER CrashReporterParent* PluginModuleChromeParent::CrashReporter() { return static_cast(ManagedPCrashReporterParent()[0]); } #ifdef MOZ_CRASHREPORTER_INJECTOR static void RemoveMinidump(nsIFile* minidump) { if (!minidump) return; minidump->Remove(false); nsCOMPtr extraFile; if (GetExtraFileForMinidump(minidump, getter_AddRefs(extraFile))) { extraFile->Remove(true); } } #endif // MOZ_CRASHREPORTER_INJECTOR void PluginModuleChromeParent::ProcessFirstMinidump() { #ifdef XP_WIN mozilla::MutexAutoLock lock(mCrashReporterMutex); #endif CrashReporterParent* crashReporter = CrashReporter(); if (!crashReporter) return; AnnotationTable notes(4); WriteExtraDataForMinidump(notes); if (!mPluginDumpID.IsEmpty()) { crashReporter->GenerateChildData(¬es); return; } uint32_t sequence = UINT32_MAX; nsCOMPtr dumpFile; nsAutoCString flashProcessType; TakeMinidump(getter_AddRefs(dumpFile), &sequence); #ifdef MOZ_CRASHREPORTER_INJECTOR nsCOMPtr childDumpFile; uint32_t childSequence; if (mFlashProcess1 && TakeMinidumpForChild(mFlashProcess1, getter_AddRefs(childDumpFile), &childSequence)) { if (childSequence < sequence) { RemoveMinidump(dumpFile); dumpFile = childDumpFile; sequence = childSequence; flashProcessType.AssignLiteral("Broker"); } else { RemoveMinidump(childDumpFile); } } if (mFlashProcess2 && TakeMinidumpForChild(mFlashProcess2, getter_AddRefs(childDumpFile), &childSequence)) { if (childSequence < sequence) { RemoveMinidump(dumpFile); dumpFile = childDumpFile; sequence = childSequence; flashProcessType.AssignLiteral("Sandbox"); } else { RemoveMinidump(childDumpFile); } } #endif if (!dumpFile) { NS_WARNING("[PluginModuleParent::ActorDestroy] abnormal shutdown without minidump!"); return; } PLUGIN_LOG_DEBUG(("got child minidump: %s", NS_ConvertUTF16toUTF8(mPluginDumpID).get())); GetIDFromMinidump(dumpFile, mPluginDumpID); if (!flashProcessType.IsEmpty()) { notes.Put(NS_LITERAL_CSTRING("FlashProcessDump"), flashProcessType); } crashReporter->GenerateCrashReportForMinidump(dumpFile, ¬es); } #endif void PluginModuleParent::ActorDestroy(ActorDestroyReason why) { switch (why) { case AbnormalShutdown: { mShutdown = true; // Defer the PluginCrashed method so that we don't re-enter // and potentially modify the actor child list while enumerating it. if (mPlugin) MessageLoop::current()->PostTask( FROM_HERE, mTaskFactory.NewRunnableMethod( &PluginModuleParent::NotifyPluginCrashed)); break; } case NormalShutdown: mShutdown = true; break; default: NS_RUNTIMEABORT("Unexpected shutdown reason for toplevel actor."); } } void PluginModuleChromeParent::ActorDestroy(ActorDestroyReason why) { if (why == AbnormalShutdown) { #ifdef MOZ_CRASHREPORTER ProcessFirstMinidump(); #endif } // We can't broadcast settings changes anymore. UnregisterSettingsCallbacks(); PluginModuleParent::ActorDestroy(why); } void PluginModuleParent::NotifyPluginCrashed() { if (!OkToCleanup()) { // there's still plugin code on the C++ stack. try again MessageLoop::current()->PostDelayedTask( FROM_HERE, mTaskFactory.NewRunnableMethod( &PluginModuleParent::NotifyPluginCrashed), 10); return; } if (mPlugin) mPlugin->PluginCrashed(mPluginDumpID, mBrowserDumpID); } PPluginInstanceParent* PluginModuleParent::AllocPPluginInstanceParent(const nsCString& aMimeType, const uint16_t& aMode, const InfallibleTArray& aNames, const InfallibleTArray& aValues, NPError* rv) { NS_ERROR("Not reachable!"); return nullptr; } bool PluginModuleParent::DeallocPPluginInstanceParent(PPluginInstanceParent* aActor) { PLUGIN_LOG_DEBUG_METHOD; delete aActor; return true; } void PluginModuleParent::SetPluginFuncs(NPPluginFuncs* aFuncs) { aFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; aFuncs->javaClass = nullptr; // Gecko should always call these functions through a PluginLibrary object. aFuncs->newp = nullptr; aFuncs->clearsitedata = nullptr; aFuncs->getsiteswithdata = nullptr; aFuncs->destroy = NPP_Destroy; aFuncs->setwindow = NPP_SetWindow; aFuncs->newstream = NPP_NewStream; aFuncs->destroystream = NPP_DestroyStream; aFuncs->asfile = NPP_StreamAsFile; aFuncs->writeready = NPP_WriteReady; aFuncs->write = NPP_Write; aFuncs->print = NPP_Print; aFuncs->event = NPP_HandleEvent; aFuncs->urlnotify = NPP_URLNotify; aFuncs->getvalue = NPP_GetValue; aFuncs->setvalue = NPP_SetValue; aFuncs->gotfocus = nullptr; aFuncs->lostfocus = nullptr; aFuncs->urlredirectnotify = nullptr; // Provide 'NPP_URLRedirectNotify', 'NPP_ClearSiteData', and // 'NPP_GetSitesWithData' functionality if it is supported by the plugin. bool urlRedirectSupported = false; unused << CallOptionalFunctionsSupported(&urlRedirectSupported, &mClearSiteDataSupported, &mGetSitesWithDataSupported); if (urlRedirectSupported) { aFuncs->urlredirectnotify = NPP_URLRedirectNotify; } } NPError PluginModuleParent::NPP_Destroy(NPP instance, NPSavedData** /*saved*/) { // FIXME/cjones: // (1) send a "destroy" message to the child // (2) the child shuts down its instance // (3) remove both parent and child IDs from map // (4) free parent PLUGIN_LOG_DEBUG_FUNCTION; PluginInstanceParent* parentInstance = static_cast(instance->pdata); if (!parentInstance) return NPERR_NO_ERROR; NPError retval = parentInstance->Destroy(); instance->pdata = nullptr; unused << PluginInstanceParent::Call__delete__(parentInstance); return retval; } NPError PluginModuleParent::NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype) { PROFILER_LABEL("PluginModuleParent", "NPP_NewStream", js::ProfileEntry::Category::OTHER); PluginInstanceParent* i = InstCast(instance); if (!i) return NPERR_GENERIC_ERROR; return i->NPP_NewStream(type, stream, seekable, stype); } NPError PluginModuleParent::NPP_SetWindow(NPP instance, NPWindow* window) { PluginInstanceParent* i = InstCast(instance); if (!i) return NPERR_GENERIC_ERROR; return i->NPP_SetWindow(window); } NPError PluginModuleParent::NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { PluginInstanceParent* i = InstCast(instance); if (!i) return NPERR_GENERIC_ERROR; return i->NPP_DestroyStream(stream, reason); } int32_t PluginModuleParent::NPP_WriteReady(NPP instance, NPStream* stream) { BrowserStreamParent* s = StreamCast(instance, stream); if (!s) return -1; return s->WriteReady(); } int32_t PluginModuleParent::NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer) { BrowserStreamParent* s = StreamCast(instance, stream); if (!s) return -1; return s->Write(offset, len, buffer); } void PluginModuleParent::NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname) { BrowserStreamParent* s = StreamCast(instance, stream); if (!s) return; s->StreamAsFile(fname); } void PluginModuleParent::NPP_Print(NPP instance, NPPrint* platformPrint) { PluginInstanceParent* i = InstCast(instance); if (i) i->NPP_Print(platformPrint); } int16_t PluginModuleParent::NPP_HandleEvent(NPP instance, void* event) { PluginInstanceParent* i = InstCast(instance); if (!i) return false; return i->NPP_HandleEvent(event); } void PluginModuleParent::NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData) { PluginInstanceParent* i = InstCast(instance); if (!i) return; i->NPP_URLNotify(url, reason, notifyData); } NPError PluginModuleParent::NPP_GetValue(NPP instance, NPPVariable variable, void *ret_value) { PluginInstanceParent* i = InstCast(instance); if (!i) return NPERR_GENERIC_ERROR; return i->NPP_GetValue(variable, ret_value); } NPError PluginModuleParent::NPP_SetValue(NPP instance, NPNVariable variable, void *value) { PluginInstanceParent* i = InstCast(instance); if (!i) return NPERR_GENERIC_ERROR; return i->NPP_SetValue(variable, value); } bool PluginModuleParent::RecvBackUpXResources(const FileDescriptor& aXSocketFd) { #ifndef MOZ_X11 NS_RUNTIMEABORT("This message only makes sense on X11 platforms"); #else NS_ABORT_IF_FALSE(0 > mPluginXSocketFdDup.get(), "Already backed up X resources??"); mPluginXSocketFdDup.forget(); if (aXSocketFd.IsValid()) { mPluginXSocketFdDup.reset(aXSocketFd.PlatformHandle()); } #endif return true; } void PluginModuleParent::NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, void* notifyData) { PluginInstanceParent* i = InstCast(instance); if (!i) return; i->NPP_URLRedirectNotify(url, status, notifyData); } PluginInstanceParent* PluginModuleParent::InstCast(NPP instance) { PluginInstanceParent* ip = static_cast(instance->pdata); // If the plugin crashed and the PluginInstanceParent was deleted, // instance->pdata will be nullptr. if (!ip) return nullptr; if (instance != ip->mNPP) { NS_RUNTIMEABORT("Corrupted plugin data."); } return ip; } BrowserStreamParent* PluginModuleParent::StreamCast(NPP instance, NPStream* s) { PluginInstanceParent* ip = InstCast(instance); if (!ip) return nullptr; BrowserStreamParent* sp = static_cast(static_cast(s->pdata)); if (sp->mNPP != ip || s != sp->mStream) { NS_RUNTIMEABORT("Corrupted plugin stream data."); } return sp; } bool PluginModuleParent::HasRequiredFunctions() { return true; } nsresult PluginModuleParent::AsyncSetWindow(NPP instance, NPWindow* window) { PluginInstanceParent* i = InstCast(instance); if (!i) return NS_ERROR_FAILURE; return i->AsyncSetWindow(window); } nsresult PluginModuleParent::GetImageContainer(NPP instance, mozilla::layers::ImageContainer** aContainer) { PluginInstanceParent* i = InstCast(instance); return !i ? NS_ERROR_FAILURE : i->GetImageContainer(aContainer); } nsresult PluginModuleParent::GetImageSize(NPP instance, nsIntSize* aSize) { PluginInstanceParent* i = InstCast(instance); return !i ? NS_ERROR_FAILURE : i->GetImageSize(aSize); } nsresult PluginModuleParent::SetBackgroundUnknown(NPP instance) { PluginInstanceParent* i = InstCast(instance); if (!i) return NS_ERROR_FAILURE; return i->SetBackgroundUnknown(); } nsresult PluginModuleParent::BeginUpdateBackground(NPP instance, const nsIntRect& aRect, gfxContext** aCtx) { PluginInstanceParent* i = InstCast(instance); if (!i) return NS_ERROR_FAILURE; return i->BeginUpdateBackground(aRect, aCtx); } nsresult PluginModuleParent::EndUpdateBackground(NPP instance, gfxContext* aCtx, const nsIntRect& aRect) { PluginInstanceParent* i = InstCast(instance); if (!i) return NS_ERROR_FAILURE; return i->EndUpdateBackground(aCtx, aRect); } class OfflineObserver MOZ_FINAL : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER explicit OfflineObserver(PluginModuleChromeParent* pmp) : mPmp(pmp) {} private: ~OfflineObserver() {} PluginModuleChromeParent* mPmp; }; NS_IMPL_ISUPPORTS(OfflineObserver, nsIObserver) NS_IMETHODIMP OfflineObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { MOZ_ASSERT(!strcmp(aTopic, "ipc:network:set-offline")); mPmp->CachedSettingChanged(); return NS_OK; } static const char* kSettingsPrefs[] = {"javascript.enabled", "dom.ipc.plugins.nativeCursorSupport"}; void PluginModuleChromeParent::RegisterSettingsCallbacks() { for (size_t i = 0; i < ArrayLength(kSettingsPrefs); i++) { Preferences::RegisterCallback(CachedSettingChanged, kSettingsPrefs[i], this); } nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { mOfflineObserver = new OfflineObserver(this); observerService->AddObserver(mOfflineObserver, "ipc:network:set-offline", false); } } void PluginModuleChromeParent::UnregisterSettingsCallbacks() { for (size_t i = 0; i < ArrayLength(kSettingsPrefs); i++) { Preferences::UnregisterCallback(CachedSettingChanged, kSettingsPrefs[i], this); } nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->RemoveObserver(mOfflineObserver, "ipc:network:set-offline"); mOfflineObserver = nullptr; } } bool PluginModuleParent::GetSetting(NPNVariable aVariable) { NPBool boolVal = false; mozilla::plugins::parent::_getvalue(nullptr, aVariable, &boolVal); return boolVal; } void PluginModuleParent::GetSettings(PluginSettings* aSettings) { aSettings->javascriptEnabled() = GetSetting(NPNVjavascriptEnabledBool); aSettings->asdEnabled() = GetSetting(NPNVasdEnabledBool); aSettings->isOffline() = GetSetting(NPNVisOfflineBool); aSettings->supportsXembed() = GetSetting(NPNVSupportsXEmbedBool); aSettings->supportsWindowless() = GetSetting(NPNVSupportsWindowless); aSettings->userAgent() = NullableString(mNPNIface->uagent(nullptr)); #if defined(XP_MACOSX) aSettings->nativeCursorsSupported() = Preferences::GetBool("dom.ipc.plugins.nativeCursorSupport", false); #else // Need to initialize this to satisfy IPDL. aSettings->nativeCursorsSupported() = false; #endif } void PluginModuleChromeParent::CachedSettingChanged() { PluginSettings settings; GetSettings(&settings); unused << SendSettingChanged(settings); } /* static */ void PluginModuleChromeParent::CachedSettingChanged(const char* aPref, void* aModule) { PluginModuleChromeParent *module = static_cast(aModule); module->CachedSettingChanged(); } #if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) nsresult PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) { PLUGIN_LOG_DEBUG_METHOD; mNPNIface = bFuncs; if (mShutdown) { *error = NPERR_GENERIC_ERROR; return NS_ERROR_FAILURE; } *error = NPERR_NO_ERROR; if (IsChrome()) { PluginSettings settings; GetSettings(&settings); if (!CallNP_Initialize(settings, error)) { Close(); return NS_ERROR_FAILURE; } else if (*error != NPERR_NO_ERROR) { Close(); return NS_OK; } } SetPluginFuncs(pFuncs); return NS_OK; } #else nsresult PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) { PLUGIN_LOG_DEBUG_METHOD; mNPNIface = bFuncs; if (mShutdown) { *error = NPERR_GENERIC_ERROR; return NS_ERROR_FAILURE; } *error = NPERR_NO_ERROR; return NS_OK; } nsresult PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) { nsresult rv = PluginModuleParent::NP_Initialize(bFuncs, error); if (NS_FAILED(rv)) return rv; PluginSettings settings; GetSettings(&settings); if (!CallNP_Initialize(settings, error)) { Close(); return NS_ERROR_FAILURE; } if (*error != NPERR_NO_ERROR) { Close(); return NS_OK; } #if defined XP_WIN // Send the info needed to join the chrome process's audio session to the // plugin process nsID id; nsString sessionName; nsString iconPath; if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName, iconPath))) unused << SendSetAudioSessionData(id, sessionName, iconPath); #endif #ifdef MOZ_CRASHREPORTER_INJECTOR InitializeInjector(); #endif return NS_OK; } #endif nsresult PluginModuleParent::NP_Shutdown(NPError* error) { PLUGIN_LOG_DEBUG_METHOD; if (mShutdown) { *error = NPERR_GENERIC_ERROR; return NS_ERROR_FAILURE; } bool ok = true; if (IsChrome()) { ok = CallNP_Shutdown(error); } // if NP_Shutdown() is nested within another interrupt call, this will // break things. but lord help us if we're doing that anyway; the // plugin dso will have been unloaded on the other side by the // CallNP_Shutdown() message Close(); return ok ? NS_OK : NS_ERROR_FAILURE; } nsresult PluginModuleParent::NP_GetMIMEDescription(const char** mimeDesc) { PLUGIN_LOG_DEBUG_METHOD; *mimeDesc = "application/x-foobar"; return NS_OK; } nsresult PluginModuleParent::NP_GetValue(void *future, NPPVariable aVariable, void *aValue, NPError* error) { PR_LOG(GetPluginLog(), PR_LOG_WARNING, ("%s Not implemented, requested variable %i", __FUNCTION__, (int) aVariable)); //TODO: implement this correctly *error = NPERR_GENERIC_ERROR; return NS_OK; } #if defined(XP_WIN) || defined(XP_MACOSX) nsresult PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) { NS_ASSERTION(pFuncs, "Null pointer!"); // We need to have the plugin process update its function table here by // actually calling NP_GetEntryPoints. The parent's function table will // reflect nullptr entries in the child's table once SetPluginFuncs is // called. if (IsChrome()) { if (!CallNP_GetEntryPoints(error)) { return NS_ERROR_FAILURE; } else if (*error != NPERR_NO_ERROR) { return NS_OK; } } *error = NPERR_NO_ERROR; SetPluginFuncs(pFuncs); return NS_OK; } #endif nsresult PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved, NPError* error) { PLUGIN_LOG_DEBUG_METHOD; if (mShutdown) { *error = NPERR_GENERIC_ERROR; return NS_ERROR_FAILURE; } if (mPluginName.IsEmpty()) { GetPluginDetails(mPluginName, mPluginVersion); } // create the instance on the other side InfallibleTArray names; InfallibleTArray values; for (int i = 0; i < argc; ++i) { names.AppendElement(NullableString(argn[i])); values.AppendElement(NullableString(argv[i])); } PluginInstanceParent* parentInstance = new PluginInstanceParent(this, instance, nsDependentCString(pluginType), mNPNIface); if (!parentInstance->Init()) { delete parentInstance; return NS_ERROR_FAILURE; } instance->pdata = parentInstance; if (!CallPPluginInstanceConstructor(parentInstance, nsDependentCString(pluginType), mode, names, values, error)) { // |parentInstance| is automatically deleted. instance->pdata = nullptr; // if IPC is down, we'll get an immediate "failed" return, but // without *error being set. So make sure that the error // condition is signaled to nsNPAPIPluginInstance if (NPERR_NO_ERROR == *error) *error = NPERR_GENERIC_ERROR; return NS_ERROR_FAILURE; } if (*error != NPERR_NO_ERROR) { NPP_Destroy(instance, 0); return NS_ERROR_FAILURE; } UpdatePluginTimeout(); return NS_OK; } void PluginModuleChromeParent::UpdatePluginTimeout() { TimeoutChanged(kParentTimeoutPref, this); } nsresult PluginModuleParent::NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge) { if (!mClearSiteDataSupported) return NS_ERROR_NOT_AVAILABLE; NPError result; if (!CallNPP_ClearSiteData(NullableString(site), flags, maxAge, &result)) return NS_ERROR_FAILURE; switch (result) { case NPERR_NO_ERROR: return NS_OK; case NPERR_TIME_RANGE_NOT_SUPPORTED: return NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED; case NPERR_MALFORMED_SITE: return NS_ERROR_INVALID_ARG; default: return NS_ERROR_FAILURE; } } nsresult PluginModuleParent::NPP_GetSitesWithData(InfallibleTArray& result) { if (!mGetSitesWithDataSupported) return NS_ERROR_NOT_AVAILABLE; if (!CallNPP_GetSitesWithData(&result)) return NS_ERROR_FAILURE; return NS_OK; } #if defined(XP_MACOSX) nsresult PluginModuleParent::IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) { PluginInstanceParent* i = InstCast(instance); if (!i) return NS_ERROR_FAILURE; return i->IsRemoteDrawingCoreAnimation(aDrawing); } nsresult PluginModuleParent::ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) { PluginInstanceParent* i = InstCast(instance); if (!i) return NS_ERROR_FAILURE; return i->ContentsScaleFactorChanged(aContentsScaleFactor); } #endif // #if defined(XP_MACOSX) #if defined(MOZ_WIDGET_QT) bool PluginModuleParent::AnswerProcessSomeEvents() { PLUGIN_LOG_DEBUG(("Spinning mini nested loop ...")); PluginHelperQt::AnswerProcessSomeEvents(); PLUGIN_LOG_DEBUG(("... quitting mini nested loop")); return true; } #elif defined(XP_MACOSX) bool PluginModuleParent::AnswerProcessSomeEvents() { mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop(); return true; } #elif !defined(MOZ_WIDGET_GTK) bool PluginModuleParent::AnswerProcessSomeEvents() { NS_RUNTIMEABORT("unreached"); return false; } #else static const int kMaxChancesToProcessEvents = 20; bool PluginModuleParent::AnswerProcessSomeEvents() { PLUGIN_LOG_DEBUG(("Spinning mini nested loop ...")); int i = 0; for (; i < kMaxChancesToProcessEvents; ++i) if (!g_main_context_iteration(nullptr, FALSE)) break; PLUGIN_LOG_DEBUG(("... quitting mini nested loop; processed %i tasks", i)); return true; } #endif bool PluginModuleParent::RecvProcessNativeEventsInInterruptCall() { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); #if defined(OS_WIN) ProcessNativeEventsInInterruptCall(); return true; #else NS_NOTREACHED( "PluginModuleParent::RecvProcessNativeEventsInInterruptCall not implemented!"); return false; #endif } void PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall() { #if defined(OS_WIN) unused << SendProcessNativeEventsInInterruptCall(); return; #endif NS_NOTREACHED( "PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall not implemented!"); } bool PluginModuleParent::RecvPluginShowWindow(const uint32_t& aWindowId, const bool& aModal, const int32_t& aX, const int32_t& aY, const size_t& aWidth, const size_t& aHeight) { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); #if defined(XP_MACOSX) CGRect windowBound = ::CGRectMake(aX, aY, aWidth, aHeight); mac_plugin_interposing::parent::OnPluginShowWindow(aWindowId, windowBound, aModal); return true; #else NS_NOTREACHED( "PluginInstanceParent::RecvPluginShowWindow not implemented!"); return false; #endif } bool PluginModuleParent::RecvPluginHideWindow(const uint32_t& aWindowId) { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); #if defined(XP_MACOSX) mac_plugin_interposing::parent::OnPluginHideWindow(aWindowId, OtherSidePID()); return true; #else NS_NOTREACHED( "PluginInstanceParent::RecvPluginHideWindow not implemented!"); return false; #endif } PCrashReporterParent* PluginModuleParent::AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id, uint32_t* processType) { MOZ_CRASH("unreachable"); } bool PluginModuleParent::DeallocPCrashReporterParent(PCrashReporterParent* actor) { MOZ_CRASH("unreachable"); } PCrashReporterParent* PluginModuleChromeParent::AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id, uint32_t* processType) { #ifdef MOZ_CRASHREPORTER return new CrashReporterParent(); #else return nullptr; #endif } bool PluginModuleChromeParent::DeallocPCrashReporterParent(PCrashReporterParent* actor) { #ifdef MOZ_CRASHREPORTER #ifdef XP_WIN mozilla::MutexAutoLock lock(mCrashReporterMutex); if (actor == static_cast(mCrashReporter)) { mCrashReporter = nullptr; } #endif #endif delete actor; return true; } bool PluginModuleParent::RecvSetCursor(const NSCursorInfo& aCursorInfo) { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); #if defined(XP_MACOSX) mac_plugin_interposing::parent::OnSetCursor(aCursorInfo); return true; #else NS_NOTREACHED( "PluginInstanceParent::RecvSetCursor not implemented!"); return false; #endif } bool PluginModuleParent::RecvShowCursor(const bool& aShow) { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); #if defined(XP_MACOSX) mac_plugin_interposing::parent::OnShowCursor(aShow); return true; #else NS_NOTREACHED( "PluginInstanceParent::RecvShowCursor not implemented!"); return false; #endif } bool PluginModuleParent::RecvPushCursor(const NSCursorInfo& aCursorInfo) { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); #if defined(XP_MACOSX) mac_plugin_interposing::parent::OnPushCursor(aCursorInfo); return true; #else NS_NOTREACHED( "PluginInstanceParent::RecvPushCursor not implemented!"); return false; #endif } bool PluginModuleParent::RecvPopCursor() { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); #if defined(XP_MACOSX) mac_plugin_interposing::parent::OnPopCursor(); return true; #else NS_NOTREACHED( "PluginInstanceParent::RecvPopCursor not implemented!"); return false; #endif } bool PluginModuleParent::RecvNPN_SetException(const nsCString& aMessage) { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); // This function ignores its first argument. mozilla::plugins::parent::_setexception(nullptr, NullableStringGet(aMessage)); return true; } bool PluginModuleParent::RecvNPN_ReloadPlugins(const bool& aReloadPages) { PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); mozilla::plugins::parent::_reloadplugins(aReloadPages); return true; } bool PluginModuleChromeParent::RecvNotifyContentModuleDestroyed() { nsRefPtr host = nsPluginHost::GetInst(); if (host) { host->NotifyContentModuleDestroyed(mPluginId); } return true; } #ifdef MOZ_CRASHREPORTER_INJECTOR // We only add the crash reporter to subprocess which have the filename // FlashPlayerPlugin* #define FLASH_PROCESS_PREFIX "FLASHPLAYERPLUGIN" static DWORD GetFlashChildOfPID(DWORD pid, HANDLE snapshot) { PROCESSENTRY32 entry = { sizeof(entry) }; for (BOOL ok = Process32First(snapshot, &entry); ok; ok = Process32Next(snapshot, &entry)) { if (entry.th32ParentProcessID == pid) { nsString name(entry.szExeFile); ToUpperCase(name); if (StringBeginsWith(name, NS_LITERAL_STRING(FLASH_PROCESS_PREFIX))) { return entry.th32ProcessID; } } } return 0; } // We only look for child processes of the Flash plugin, NPSWF* #define FLASH_PLUGIN_PREFIX "NPSWF" void PluginModuleChromeParent::InitializeInjector() { if (!Preferences::GetBool("dom.ipc.plugins.flash.subprocess.crashreporter.enabled", false)) return; nsCString path(Process()->GetPluginFilePath().c_str()); ToUpperCase(path); int32_t lastSlash = path.RFindCharInSet("\\/"); if (kNotFound == lastSlash) return; if (!StringBeginsWith(Substring(path, lastSlash + 1), NS_LITERAL_CSTRING(FLASH_PLUGIN_PREFIX))) return; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (INVALID_HANDLE_VALUE == snapshot) return; DWORD pluginProcessPID = GetProcessId(Process()->GetChildProcessHandle()); mFlashProcess1 = GetFlashChildOfPID(pluginProcessPID, snapshot); if (mFlashProcess1) { InjectCrashReporterIntoProcess(mFlashProcess1, this); mFlashProcess2 = GetFlashChildOfPID(mFlashProcess1, snapshot); if (mFlashProcess2) { InjectCrashReporterIntoProcess(mFlashProcess2, this); } } } void PluginModuleChromeParent::OnCrash(DWORD processID) { if (!mShutdown) { GetIPCChannel()->CloseWithError(); KillProcess(OtherProcess(), 1, false); } } #endif // MOZ_CRASHREPORTER_INJECTOR #ifdef MOZ_ENABLE_PROFILER_SPS class PluginProfilerObserver MOZ_FINAL : public nsIObserver, public nsSupportsWeakReference { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER explicit PluginProfilerObserver(PluginModuleParent* pmp) : mPmp(pmp) {} private: ~PluginProfilerObserver() {} PluginModuleParent* mPmp; }; NS_IMPL_ISUPPORTS(PluginProfilerObserver, nsIObserver, nsISupportsWeakReference) NS_IMETHODIMP PluginProfilerObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { if (!strcmp(aTopic, "profiler-started")) { nsCOMPtr params(do_QueryInterface(aSubject)); uint32_t entries; double interval; params->GetEntries(&entries); params->GetInterval(&interval); const nsTArray& features = params->GetFeatures(); const nsTArray& threadFilterNames = params->GetThreadFilterNames(); unused << mPmp->SendStartProfiler(entries, interval, features, threadFilterNames); } else if (!strcmp(aTopic, "profiler-stopped")) { unused << mPmp->SendStopProfiler(); } else if (!strcmp(aTopic, "profiler-subprocess")) { nsCOMPtr pse = do_QueryInterface(aSubject); if (pse) { nsCString result; bool success = mPmp->CallGetProfile(&result); if (success && !result.IsEmpty()) { pse->AddSubProfile(result.get()); } } } return NS_OK; } void PluginModuleChromeParent::InitPluginProfiling() { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { mProfilerObserver = new PluginProfilerObserver(this); observerService->AddObserver(mProfilerObserver, "profiler-started", false); observerService->AddObserver(mProfilerObserver, "profiler-stopped", false); observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false); } } void PluginModuleChromeParent::ShutdownPluginProfiling() { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->RemoveObserver(mProfilerObserver, "profiler-started"); observerService->RemoveObserver(mProfilerObserver, "profiler-stopped"); observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess"); } } #endif