diff options
Diffstat (limited to 'dom/media/gmp/GMPParent.cpp')
-rw-r--r-- | dom/media/gmp/GMPParent.cpp | 1167 |
1 files changed, 1167 insertions, 0 deletions
diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp new file mode 100644 index 000000000..75468ea9a --- /dev/null +++ b/dom/media/gmp/GMPParent.cpp @@ -0,0 +1,1167 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPParent.h" +#include "mozilla/Logging.h" +#include "nsComponentManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsIRunnable.h" +#include "nsIWritablePropertyBag2.h" +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/SSE.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Unused.h" +#include "nsIObserverService.h" +#include "GMPTimerParent.h" +#include "runnable_utils.h" +#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX) +#include "mozilla/SandboxInfo.h" +#endif +#include "GMPContentParent.h" +#include "MediaPrefs.h" +#include "VideoUtils.h" + +#include "mozilla/dom/CrashReporterParent.h" +using mozilla::dom::CrashReporterParent; +using mozilla::ipc::GeckoChildProcessHost; + +#ifdef MOZ_CRASHREPORTER +#include "nsPrintfCString.h" +using CrashReporter::AnnotationTable; +using CrashReporter::GetIDFromMinidump; +#endif + +#include "mozilla/Telemetry.h" + +#ifdef XP_WIN +#include "WMFDecoderModule.h" +#endif + +#include "mozilla/dom/WidevineCDMManifestBinding.h" +#include "widevine-adapter/WidevineAdapter.h" + +namespace mozilla { + +#undef LOG +#undef LOGD + +extern LogModule* GetGMPLog(); +#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__)) +#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__) + +#ifdef __CLASS__ +#undef __CLASS__ +#endif +#define __CLASS__ "GMPParent" + +namespace gmp { + +GMPParent::GMPParent() + : mState(GMPStateNotLoaded) + , mProcess(nullptr) + , mDeleteProcessOnlyOnUnload(false) + , mAbnormalShutdownInProgress(false) + , mIsBlockingDeletion(false) + , mCanDecrypt(false) + , mGMPContentChildCount(0) + , mAsyncShutdownRequired(false) + , mAsyncShutdownInProgress(false) + , mChildPid(0) + , mHoldingSelfRef(false) +{ + mPluginId = GeckoChildProcessHost::GetUniqueID(); + LOGD("GMPParent ctor id=%u", mPluginId); +} + +GMPParent::~GMPParent() +{ + LOGD("GMPParent dtor id=%u", mPluginId); + MOZ_ASSERT(!mProcess); +} + +nsresult +GMPParent::CloneFrom(const GMPParent* aOther) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory"); + + mService = aOther->mService; + mDirectory = aOther->mDirectory; + mName = aOther->mName; + mVersion = aOther->mVersion; + mDescription = aOther->mDescription; + mDisplayName = aOther->mDisplayName; +#ifdef XP_WIN + mLibs = aOther->mLibs; +#endif + for (const GMPCapability& cap : aOther->mCapabilities) { + mCapabilities.AppendElement(cap); + } + mAdapter = aOther->mAdapter; + return NS_OK; +} + +RefPtr<GenericPromise> +GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir) +{ + MOZ_ASSERT(aPluginDir); + MOZ_ASSERT(aService); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + mService = aService; + mDirectory = aPluginDir; + + // aPluginDir is <profile-dir>/<gmp-plugin-id>/<version> + // where <gmp-plugin-id> should be gmp-gmpopenh264 + nsCOMPtr<nsIFile> parent; + nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + nsAutoString parentLeafName; + rv = parent->GetLeafName(parentLeafName); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + LOGD("%s: for %s", __FUNCTION__, NS_LossyConvertUTF16toASCII(parentLeafName).get()); + + MOZ_ASSERT(parentLeafName.Length() > 4); + mName = Substring(parentLeafName, 4); + + return ReadGMPMetaData(); +} + +void +GMPParent::Crash() +{ + if (mState != GMPStateNotLoaded) { + Unused << SendCrashPluginNow(); + } +} + +nsresult +GMPParent::LoadProcess() +{ + MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + MOZ_ASSERT(mState == GMPStateNotLoaded); + + nsAutoString path; + if (NS_FAILED(mDirectory->GetPath(path))) { + return NS_ERROR_FAILURE; + } + LOGD("%s: for %s", __FUNCTION__, NS_ConvertUTF16toUTF8(path).get()); + + if (!mProcess) { + mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get()); + if (!mProcess->Launch(30 * 1000)) { + LOGD("%s: Failed to launch new child process", __FUNCTION__); + mProcess->Delete(); + mProcess = nullptr; + return NS_ERROR_FAILURE; + } + + mChildPid = base::GetProcId(mProcess->GetChildProcessHandle()); + LOGD("%s: Launched new child process", __FUNCTION__); + + bool opened = Open(mProcess->GetChannel(), + base::GetProcId(mProcess->GetChildProcessHandle())); + if (!opened) { + LOGD("%s: Failed to open channel to new child process", __FUNCTION__); + mProcess->Delete(); + mProcess = nullptr; + return NS_ERROR_FAILURE; + } + LOGD("%s: Opened channel to new child process", __FUNCTION__); + + bool ok = SendSetNodeId(mNodeId); + if (!ok) { + LOGD("%s: Failed to send node id to child process", __FUNCTION__); + return NS_ERROR_FAILURE; + } + LOGD("%s: Sent node id to child process", __FUNCTION__); + +#ifdef XP_WIN + if (!mLibs.IsEmpty()) { + bool ok = SendPreloadLibs(mLibs); + if (!ok) { + LOGD("%s: Failed to send preload-libs to child process", __FUNCTION__); + return NS_ERROR_FAILURE; + } + LOGD("%s: Sent preload-libs ('%s') to child process", __FUNCTION__, mLibs.get()); + } +#endif + + // Intr call to block initialization on plugin load. + ok = CallStartPlugin(mAdapter); + if (!ok) { + LOGD("%s: Failed to send start to child process", __FUNCTION__); + return NS_ERROR_FAILURE; + } + LOGD("%s: Sent StartPlugin to child process", __FUNCTION__); + } + + mState = GMPStateLoaded; + + // Hold a self ref while the child process is alive. This ensures that + // during shutdown the GMPParent stays alive long enough to + // terminate the child process. + MOZ_ASSERT(!mHoldingSelfRef); + mHoldingSelfRef = true; + AddRef(); + + return NS_OK; +} + +// static +void +GMPParent::AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure) +{ + NS_WARNING("Timed out waiting for GMP async shutdown!"); + GMPParent* parent = reinterpret_cast<GMPParent*>(aClosure); + MOZ_ASSERT(parent->mService); +#if defined(MOZ_CRASHREPORTER) + parent->mService->SetAsyncShutdownPluginState(parent, 'G', + NS_LITERAL_CSTRING("Timed out waiting for async shutdown")); +#endif + parent->mService->AsyncShutdownComplete(parent); +} + +nsresult +GMPParent::EnsureAsyncShutdownTimeoutSet() +{ + MOZ_ASSERT(mAsyncShutdownRequired); + if (mAsyncShutdownTimeout) { + return NS_OK; + } + + nsresult rv; + mAsyncShutdownTimeout = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Set timer to abort waiting for plugin to shutdown if it takes + // too long. + rv = mAsyncShutdownTimeout->SetTarget(mGMPThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int32_t timeout = MediaPrefs::GMPAsyncShutdownTimeout(); + RefPtr<GeckoMediaPluginServiceParent> service = + GeckoMediaPluginServiceParent::GetSingleton(); + if (service) { + timeout = service->AsyncShutdownTimeoutMs(); + } + rv = mAsyncShutdownTimeout->InitWithFuncCallback( + &AbortWaitingForGMPAsyncShutdown, this, timeout, + nsITimer::TYPE_ONE_SHOT); + Unused << NS_WARN_IF(NS_FAILED(rv)); + return rv; +} + +bool +GMPParent::RecvPGMPContentChildDestroyed() +{ + --mGMPContentChildCount; + if (!IsUsed()) { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'E', + NS_LITERAL_CSTRING("Last content child destroyed")); + } +#endif + CloseIfUnused(); + } +#if defined(MOZ_CRASHREPORTER) + else { + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'F', + nsPrintfCString("Content child destroyed, remaining: %u", mGMPContentChildCount)); + } + } +#endif + return true; +} + +void +GMPParent::CloseIfUnused() +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + LOGD("%s: mAsyncShutdownRequired=%d", __FUNCTION__, mAsyncShutdownRequired); + + if ((mDeleteProcessOnlyOnUnload || + mState == GMPStateLoaded || + mState == GMPStateUnloading) && + !IsUsed()) { + // Ensure all timers are killed. + for (uint32_t i = mTimers.Length(); i > 0; i--) { + mTimers[i - 1]->Shutdown(); + } + + if (mAsyncShutdownRequired) { + if (!mAsyncShutdownInProgress) { + LOGD("%s: sending async shutdown notification", __FUNCTION__); +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'H', + NS_LITERAL_CSTRING("Sent BeginAsyncShutdown")); + } +#endif + mAsyncShutdownInProgress = true; + if (!SendBeginAsyncShutdown()) { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'I', + NS_LITERAL_CSTRING("Could not send BeginAsyncShutdown - Aborting async shutdown")); + } +#endif + AbortAsyncShutdown(); + } else if (NS_FAILED(EnsureAsyncShutdownTimeoutSet())) { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'J', + NS_LITERAL_CSTRING("Could not start timer after sending BeginAsyncShutdown - Aborting async shutdown")); + } +#endif + AbortAsyncShutdown(); + } + } + } else { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'K', + NS_LITERAL_CSTRING("No (more) async-shutdown required")); + } +#endif + // No async-shutdown, kill async-shutdown timer started in CloseActive(). + AbortAsyncShutdown(); + // Any async shutdown must be complete. Shutdown GMPStorage. + for (size_t i = mStorage.Length(); i > 0; i--) { + mStorage[i - 1]->Shutdown(); + } + Shutdown(); + } + } +} + +void +GMPParent::AbortAsyncShutdown() +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + LOGD("%s", __FUNCTION__); + + if (mAsyncShutdownTimeout) { + mAsyncShutdownTimeout->Cancel(); + mAsyncShutdownTimeout = nullptr; + } + + if (!mAsyncShutdownRequired || !mAsyncShutdownInProgress) { + return; + } + + RefPtr<GMPParent> kungFuDeathGrip(this); + mService->AsyncShutdownComplete(this); + mAsyncShutdownRequired = false; + mAsyncShutdownInProgress = false; + CloseIfUnused(); +} + +void +GMPParent::CloseActive(bool aDieWhenUnloaded) +{ + LOGD("%s: state %d", __FUNCTION__, mState); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + if (aDieWhenUnloaded) { + mDeleteProcessOnlyOnUnload = true; // don't allow this to go back... + } + if (mState == GMPStateLoaded) { + mState = GMPStateUnloading; + } + if (mState != GMPStateNotLoaded && IsUsed()) { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'A', + nsPrintfCString("Sent CloseActive, content children to close: %u", mGMPContentChildCount)); + } +#endif + if (!SendCloseActive()) { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'B', + NS_LITERAL_CSTRING("Could not send CloseActive - Aborting async shutdown")); + } +#endif + AbortAsyncShutdown(); + } else if (IsUsed()) { + // We're expecting RecvPGMPContentChildDestroyed's -> Start async-shutdown timer now if needed. + if (mAsyncShutdownRequired && NS_FAILED(EnsureAsyncShutdownTimeoutSet())) { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'C', + NS_LITERAL_CSTRING("Could not start timer after sending CloseActive - Aborting async shutdown")); + } +#endif + AbortAsyncShutdown(); + } + } else { + // We're not expecting any RecvPGMPContentChildDestroyed + // -> Call CloseIfUnused() now, to run async shutdown if necessary. + // Note that CloseIfUnused() may have already been called from a prior + // RecvPGMPContentChildDestroyed(), however depending on the state at + // that time, it might not have proceeded with shutdown; And calling it + // again after shutdown is fine because after the first one we'll be in + // GMPStateNotLoaded. +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'D', + NS_LITERAL_CSTRING("Content children already destroyed")); + } +#endif + CloseIfUnused(); + } + } +} + +void +GMPParent::MarkForDeletion() +{ + mDeleteProcessOnlyOnUnload = true; + mIsBlockingDeletion = true; +} + +bool +GMPParent::IsMarkedForDeletion() +{ + return mIsBlockingDeletion; +} + +void +GMPParent::Shutdown() +{ + LOGD("%s", __FUNCTION__); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + MOZ_ASSERT(!mAsyncShutdownTimeout, "Should have canceled shutdown timeout"); + + if (mAbnormalShutdownInProgress) { + return; + } + + MOZ_ASSERT(!IsUsed()); + if (mState == GMPStateNotLoaded || mState == GMPStateClosing) { + return; + } + + RefPtr<GMPParent> self(this); + DeleteProcess(); + + // XXX Get rid of mDeleteProcessOnlyOnUnload and this code when + // Bug 1043671 is fixed + if (!mDeleteProcessOnlyOnUnload) { + // Destroy ourselves and rise from the fire to save memory + mService->ReAddOnGMPThread(self); + } // else we've been asked to die and stay dead + MOZ_ASSERT(mState == GMPStateNotLoaded); +} + +class NotifyGMPShutdownTask : public Runnable { +public: + explicit NotifyGMPShutdownTask(const nsAString& aNodeId) + : mNodeId(aNodeId) + { + } + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + if (obsService) { + obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get()); + } + return NS_OK; + } + nsString mNodeId; +}; + +void +GMPParent::ChildTerminated() +{ + RefPtr<GMPParent> self(this); + nsIThread* gmpThread = GMPThread(); + + if (!gmpThread) { + // Bug 1163239 - this can happen on shutdown. + // PluginTerminated removes the GMP from the GMPService. + // On shutdown we can have this case where it is already been + // removed so there is no harm in not trying to remove it again. + LOGD("%s::%s: GMPThread() returned nullptr.", __CLASS__, __FUNCTION__); + } else { + gmpThread->Dispatch(NewRunnableMethod<RefPtr<GMPParent>>( + mService, + &GeckoMediaPluginServiceParent::PluginTerminated, + self), + NS_DISPATCH_NORMAL); + } +} + +void +GMPParent::DeleteProcess() +{ + LOGD("%s", __FUNCTION__); + + if (mState != GMPStateClosing) { + // Don't Close() twice! + // Probably remove when bug 1043671 is resolved + mState = GMPStateClosing; + Close(); + } + mProcess->Delete(NewRunnableMethod(this, &GMPParent::ChildTerminated)); + LOGD("%s: Shut down process", __FUNCTION__); + mProcess = nullptr; + mState = GMPStateNotLoaded; + + NS_DispatchToMainThread( + new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)), + NS_DISPATCH_NORMAL); + + if (mHoldingSelfRef) { + Release(); + mHoldingSelfRef = false; + } +} + +GMPState +GMPParent::State() const +{ + return mState; +} + +// Not changing to use mService since we'll be removing it +nsIThread* +GMPParent::GMPThread() +{ + if (!mGMPThread) { + nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mps); + if (!mps) { + return nullptr; + } + // Not really safe if we just grab to the mGMPThread, as we don't know + // what thread we're running on and other threads may be trying to + // access this without locks! However, debug only, and primary failure + // mode outside of compiler-helped TSAN is a leak. But better would be + // to use swap() under a lock. + mps->GetThread(getter_AddRefs(mGMPThread)); + MOZ_ASSERT(mGMPThread); + } + + return mGMPThread; +} + +/* static */ +bool +GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities, + const nsCString& aAPI, + const nsTArray<nsCString>& aTags) +{ + for (const nsCString& tag : aTags) { + if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) { + return false; + } + } + return true; +} + +/* static */ +bool +GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities, + const nsCString& aAPI, + const nsCString& aTag) +{ + for (const GMPCapability& capabilities : aCapabilities) { + if (!capabilities.mAPIName.Equals(aAPI)) { + continue; + } + for (const nsCString& tag : capabilities.mAPITags) { + if (tag.Equals(aTag)) { +#ifdef XP_WIN + // Clearkey on Windows advertises that it can decode in its GMP info + // file, but uses Windows Media Foundation to decode. That's not present + // on Windows XP, and on some Vista, Windows N, and KN variants without + // certain services packs. + if (tag.Equals(kEMEKeySystemClearkey)) { + if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) { + if (!WMFDecoderModule::HasH264()) { + continue; + } + } else if (capabilities.mAPIName.EqualsLiteral(GMP_API_AUDIO_DECODER)) { + if (!WMFDecoderModule::HasAAC()) { + continue; + } + } + } +#endif + return true; + } + } + } + return false; +} + +bool +GMPParent::EnsureProcessLoaded() +{ + if (mState == GMPStateLoaded) { + return true; + } + if (mState == GMPStateClosing || + mState == GMPStateUnloading) { + return false; + } + + nsresult rv = LoadProcess(); + + return NS_SUCCEEDED(rv); +} + +#ifdef MOZ_CRASHREPORTER +void +GMPParent::WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes) +{ + notes.Put(NS_LITERAL_CSTRING("GMPPlugin"), NS_LITERAL_CSTRING("1")); + notes.Put(NS_LITERAL_CSTRING("PluginFilename"), + NS_ConvertUTF16toUTF8(mName)); + notes.Put(NS_LITERAL_CSTRING("PluginName"), mDisplayName); + notes.Put(NS_LITERAL_CSTRING("PluginVersion"), mVersion); +} + +void +GMPParent::GetCrashID(nsString& aResult) +{ + CrashReporterParent* cr = + static_cast<CrashReporterParent*>(LoneManagedOrNullAsserts(ManagedPCrashReporterParent())); + if (NS_WARN_IF(!cr)) { + return; + } + + AnnotationTable notes(4); + WriteExtraDataForMinidump(notes); + nsCOMPtr<nsIFile> dumpFile; + TakeMinidump(getter_AddRefs(dumpFile), nullptr); + if (!dumpFile) { + NS_WARNING("GMP crash without crash report"); + aResult = mName; + aResult += '-'; + AppendUTF8toUTF16(mVersion, aResult); + return; + } + GetIDFromMinidump(dumpFile, aResult); + cr->GenerateCrashReportForMinidump(dumpFile, ¬es); +} + +static void +GMPNotifyObservers(const uint32_t aPluginID, const nsACString& aPluginName, const nsAString& aPluginDumpID) +{ + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + nsCOMPtr<nsIWritablePropertyBag2> propbag = + do_CreateInstance("@mozilla.org/hash-property-bag;1"); + if (obs && propbag) { + propbag->SetPropertyAsUint32(NS_LITERAL_STRING("pluginID"), aPluginID); + propbag->SetPropertyAsACString(NS_LITERAL_STRING("pluginName"), aPluginName); + propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), aPluginDumpID); + obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr); + } + + RefPtr<gmp::GeckoMediaPluginService> service = + gmp::GeckoMediaPluginService::GetGeckoMediaPluginService(); + if (service) { + service->RunPluginCrashCallbacks(aPluginID, aPluginName); + } +} +#endif +void +GMPParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOGD("%s: (%d)", __FUNCTION__, (int)aWhy); +#ifdef MOZ_CRASHREPORTER + if (AbnormalShutdown == aWhy) { + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, + NS_LITERAL_CSTRING("gmplugin"), 1); + nsString dumpID; + GetCrashID(dumpID); + + // NotifyObservers is mainthread-only + NS_DispatchToMainThread(WrapRunnableNM(&GMPNotifyObservers, + mPluginId, mDisplayName, dumpID), + NS_DISPATCH_NORMAL); + } +#endif + // warn us off trying to close again + mState = GMPStateClosing; + mAbnormalShutdownInProgress = true; + CloseActive(false); + + // Normal Shutdown() will delete the process on unwind. + if (AbnormalShutdown == aWhy) { + RefPtr<GMPParent> self(this); + if (mAsyncShutdownRequired) { +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'M', + NS_LITERAL_CSTRING("Actor destroyed")); + } +#endif + mService->AsyncShutdownComplete(this); + mAsyncShutdownRequired = false; + } + // Must not call Close() again in DeleteProcess(), as we'll recurse + // infinitely if we do. + MOZ_ASSERT(mState == GMPStateClosing); + DeleteProcess(); + // Note: final destruction will be Dispatched to ourself + mService->ReAddOnGMPThread(self); + } +} + +mozilla::dom::PCrashReporterParent* +GMPParent::AllocPCrashReporterParent(const NativeThreadId& aThread) +{ +#ifndef MOZ_CRASHREPORTER + MOZ_ASSERT(false, "Should only be sent if crash reporting is enabled."); +#endif + CrashReporterParent* cr = new CrashReporterParent(); + cr->SetChildData(aThread, GeckoProcessType_GMPlugin); + return cr; +} + +bool +GMPParent::DeallocPCrashReporterParent(PCrashReporterParent* aCrashReporter) +{ + delete aCrashReporter; + return true; +} + +PGMPStorageParent* +GMPParent::AllocPGMPStorageParent() +{ + GMPStorageParent* p = new GMPStorageParent(mNodeId, this); + mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent. + return p; +} + +bool +GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor) +{ + GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor); + p->Shutdown(); + mStorage.RemoveElement(p); + return true; +} + +bool +GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor) +{ + GMPStorageParent* p = (GMPStorageParent*)aActor; + if (NS_WARN_IF(NS_FAILED(p->Init()))) { + return false; + } + return true; +} + +bool +GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor) +{ + return true; +} + +PGMPTimerParent* +GMPParent::AllocPGMPTimerParent() +{ + GMPTimerParent* p = new GMPTimerParent(GMPThread()); + mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown. + return p; +} + +bool +GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor) +{ + GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor); + p->Shutdown(); + mTimers.RemoveElement(p); + return true; +} + +bool +ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey, nsACString& aOutValue) +{ + if (!aParser.Contains(aKey) || aParser.Get(aKey).IsEmpty()) { + return false; + } + aOutValue = aParser.Get(aKey); + return true; +} + +RefPtr<GenericPromise> +GMPParent::ReadGMPMetaData() +{ + MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); + MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!"); + + nsCOMPtr<nsIFile> infoFile; + nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile)); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info")); + + if (FileExists(infoFile)) { + return ReadGMPInfoFile(infoFile); + } + + // Maybe this is the Widevine adapted plugin? + nsCOMPtr<nsIFile> manifestFile; + rv = mDirectory->Clone(getter_AddRefs(manifestFile)); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + manifestFile->AppendRelativePath(NS_LITERAL_STRING("manifest.json")); + return ReadChromiumManifestFile(manifestFile); +} + +RefPtr<GenericPromise> +GMPParent::ReadGMPInfoFile(nsIFile* aFile) +{ + GMPInfoFileParser parser; + if (!parser.Init(aFile)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + nsAutoCString apis; + if (!ReadInfoField(parser, NS_LITERAL_CSTRING("name"), mDisplayName) || + !ReadInfoField(parser, NS_LITERAL_CSTRING("description"), mDescription) || + !ReadInfoField(parser, NS_LITERAL_CSTRING("version"), mVersion) || + !ReadInfoField(parser, NS_LITERAL_CSTRING("apis"), apis)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + +#ifdef XP_WIN + // "Libraries" field is optional. + ReadInfoField(parser, NS_LITERAL_CSTRING("libraries"), mLibs); +#endif + + nsTArray<nsCString> apiTokens; + SplitAt(", ", apis, apiTokens); + for (nsCString api : apiTokens) { + int32_t tagsStart = api.FindChar('['); + if (tagsStart == 0) { + // Not allowed to be the first character. + // API name must be at least one character. + continue; + } + + GMPCapability cap; + if (tagsStart == -1) { + // No tags. + cap.mAPIName.Assign(api); + } else { + auto tagsEnd = api.FindChar(']'); + if (tagsEnd == -1 || tagsEnd < tagsStart) { + // Invalid syntax, skip whole capability. + continue; + } + + cap.mAPIName.Assign(Substring(api, 0, tagsStart)); + + if ((tagsEnd - tagsStart) > 1) { + const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1)); + nsTArray<nsCString> tagTokens; + SplitAt(":", ts, tagTokens); + for (nsCString tag : tagTokens) { + cap.mAPITags.AppendElement(tag); + } + } + } + + // We support the current GMPDecryptor version, and the previous. + // We Adapt the previous to the current in the GMPContentChild. + if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) { + cap.mAPIName.AssignLiteral(GMP_API_DECRYPTOR); + } + + if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) { + mCanDecrypt = true; + +#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX) + if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) { + printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM" + " but this system can't sandbox it; not loading.\n", + mDisplayName.get()); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } +#endif +#ifdef XP_WIN + // Adobe GMP doesn't work without SSE2. Check the tags to see if + // the decryptor is for the Adobe GMP, and refuse to load it if + // SSE2 isn't supported. + if (cap.mAPITags.Contains(kEMEKeySystemPrimetime) && + !mozilla::supports_sse2()) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } +#endif // XP_WIN + } + + mCapabilities.AppendElement(Move(cap)); + } + + if (mCapabilities.IsEmpty()) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + return GenericPromise::CreateAndResolve(true, __func__); +} + +RefPtr<GenericPromise> +GMPParent::ReadChromiumManifestFile(nsIFile* aFile) +{ + nsAutoCString json; + if (!ReadIntoString(aFile, json, 5 * 1024)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // DOM JSON parsing needs to run on the main thread. + return InvokeAsync(AbstractThread::MainThread(), this, __func__, + &GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json)); +} + +RefPtr<GenericPromise> +GMPParent::ParseChromiumManifest(nsString aJSON) +{ + LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get()); + + MOZ_ASSERT(NS_IsMainThread()); + mozilla::dom::WidevineCDMManifest m; + if (!m.Init(aJSON)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + nsresult ignored; // Note: ToInteger returns 0 on failure. + if (!WidevineAdapter::Supports(m.mX_cdm_module_versions.ToInteger(&ignored), + m.mX_cdm_interface_versions.ToInteger(&ignored), + m.mX_cdm_host_versions.ToInteger(&ignored))) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + mDisplayName = NS_ConvertUTF16toUTF8(m.mName); + mDescription = NS_ConvertUTF16toUTF8(m.mDescription); + mVersion = NS_ConvertUTF16toUTF8(m.mVersion); + + GMPCapability video(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER)); + video.mAPITags.AppendElement(NS_LITERAL_CSTRING("h264")); + video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp8")); + video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp9")); + video.mAPITags.AppendElement(kEMEKeySystemWidevine); + mCapabilities.AppendElement(Move(video)); + + GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)); + decrypt.mAPITags.AppendElement(kEMEKeySystemWidevine); + mCapabilities.AppendElement(Move(decrypt)); + + MOZ_ASSERT(mName.EqualsLiteral("widevinecdm")); + mAdapter = NS_LITERAL_STRING("widevine"); +#ifdef XP_WIN + mLibs = NS_LITERAL_CSTRING("dxva2.dll"); +#endif + + return GenericPromise::CreateAndResolve(true, __func__); +} + +bool +GMPParent::CanBeSharedCrossNodeIds() const +{ + return !mAsyncShutdownInProgress && + mNodeId.IsEmpty() && + // XXX bug 1159300 hack -- maybe remove after openh264 1.4 + // We don't want to use CDM decoders for non-encrypted playback + // just yet; especially not for WebRTC. Don't allow CDMs to be used + // without a node ID. + !mCanDecrypt; +} + +bool +GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const +{ + return !mAsyncShutdownInProgress && mNodeId == aNodeId; +} + +void +GMPParent::SetNodeId(const nsACString& aNodeId) +{ + MOZ_ASSERT(!aNodeId.IsEmpty()); + mNodeId = aNodeId; +} + +const nsCString& +GMPParent::GetDisplayName() const +{ + return mDisplayName; +} + +const nsCString& +GMPParent::GetVersion() const +{ + return mVersion; +} + +uint32_t +GMPParent::GetPluginId() const +{ + return mPluginId; +} + +bool +GMPParent::RecvAsyncShutdownRequired() +{ + LOGD("%s", __FUNCTION__); + if (mAsyncShutdownRequired) { + NS_WARNING("Received AsyncShutdownRequired message more than once!"); + return true; + } + mAsyncShutdownRequired = true; + mService->AsyncShutdownNeeded(this); + return true; +} + +bool +GMPParent::RecvAsyncShutdownComplete() +{ + LOGD("%s", __FUNCTION__); + + MOZ_ASSERT(mAsyncShutdownRequired); +#if defined(MOZ_CRASHREPORTER) + if (mService) { + mService->SetAsyncShutdownPluginState(this, 'L', + NS_LITERAL_CSTRING("Received AsyncShutdownComplete")); + } +#endif + AbortAsyncShutdown(); + return true; +} + +class RunCreateContentParentCallbacks : public Runnable +{ +public: + explicit RunCreateContentParentCallbacks(GMPContentParent* aGMPContentParent) + : mGMPContentParent(aGMPContentParent) + { + } + + void TakeCallbacks(nsTArray<UniquePtr<GetGMPContentParentCallback>>& aCallbacks) + { + mCallbacks.SwapElements(aCallbacks); + } + + NS_IMETHOD + Run() override + { + for (uint32_t i = 0, length = mCallbacks.Length(); i < length; ++i) { + mCallbacks[i]->Done(mGMPContentParent); + } + return NS_OK; + } + +private: + RefPtr<GMPContentParent> mGMPContentParent; + nsTArray<UniquePtr<GetGMPContentParentCallback>> mCallbacks; +}; + +PGMPContentParent* +GMPParent::AllocPGMPContentParent(Transport* aTransport, ProcessId aOtherPid) +{ + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + MOZ_ASSERT(!mGMPContentParent); + + mGMPContentParent = new GMPContentParent(this); + mGMPContentParent->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(), + ipc::ParentSide); + + RefPtr<RunCreateContentParentCallbacks> runCallbacks = + new RunCreateContentParentCallbacks(mGMPContentParent); + runCallbacks->TakeCallbacks(mCallbacks); + NS_DispatchToCurrentThread(runCallbacks); + MOZ_ASSERT(mCallbacks.IsEmpty()); + + return mGMPContentParent; +} + +bool +GMPParent::GetGMPContentParent(UniquePtr<GetGMPContentParentCallback>&& aCallback) +{ + LOGD("%s %p", __FUNCTION__, this); + MOZ_ASSERT(GMPThread() == NS_GetCurrentThread()); + + if (mGMPContentParent) { + aCallback->Done(mGMPContentParent); + } else { + mCallbacks.AppendElement(Move(aCallback)); + // If we don't have a GMPContentParent and we try to get one for the first + // time (mCallbacks.Length() == 1) then call PGMPContent::Open. If more + // calls to GetGMPContentParent happen before mGMPContentParent has been + // set then we should just store them, so that they get called when we set + // mGMPContentParent as a result of the PGMPContent::Open call. + if (mCallbacks.Length() == 1) { + if (!EnsureProcessLoaded() || !PGMPContent::Open(this)) { + return false; + } + // We want to increment this as soon as possible, to avoid that we'd try + // to shut down the GMP process while we're still trying to get a + // PGMPContentParent actor. + ++mGMPContentChildCount; + } + } + return true; +} + +already_AddRefed<GMPContentParent> +GMPParent::ForgetGMPContentParent() +{ + MOZ_ASSERT(mCallbacks.IsEmpty()); + return Move(mGMPContentParent.forget()); +} + +bool +GMPParent::EnsureProcessLoaded(base::ProcessId* aID) +{ + if (!EnsureProcessLoaded()) { + return false; + } + *aID = OtherPid(); + return true; +} + +bool +GMPParent::Bridge(GMPServiceParent* aGMPServiceParent) +{ + if (NS_FAILED(PGMPContent::Bridge(aGMPServiceParent, this))) { + return false; + } + ++mGMPContentChildCount; + return true; +} + +nsString +GMPParent::GetPluginBaseName() const +{ + return NS_LITERAL_STRING("gmp-") + mName; +} + +} // namespace gmp +} // namespace mozilla + +#undef LOG +#undef LOGD |