From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/media/gmp/GMPParent.cpp | 1209 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1209 insertions(+) create mode 100644 dom/media/gmp/GMPParent.cpp (limited to 'dom/media/gmp/GMPParent.cpp') diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp new file mode 100644 index 0000000000..6c1acc3a20 --- /dev/null +++ b/dom/media/gmp/GMPParent.cpp @@ -0,0 +1,1209 @@ +/* -*- 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 "CDMStorageIdProvider.h" +#include "ChromiumCDMAdapter.h" +#include "GMPContentParent.h" +#include "GMPLog.h" +#include "GMPTimerParent.h" +#include "MediaResult.h" +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/dom/KeySystemNames.h" +#include "mozilla/dom/WidevineCDMManifestBinding.h" +#include "mozilla/FOGIPC.h" +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxInfo.h" +# include "base/shared_memory.h" +#endif +#include "mozilla/Services.h" +#include "mozilla/SSE.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "nsComponentManagerUtils.h" +#include "nsIRunnable.h" +#include "nsIObserverService.h" +#include "nsIWritablePropertyBag2.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" +#include "ProfilerParent.h" +#include "runnable_utils.h" +#ifdef XP_WIN +# include "mozilla/FileUtilsWin.h" +# include "WMFDecoderModule.h" +#endif +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/java/GeckoProcessManagerWrappers.h" +# include "mozilla/java/GeckoProcessTypeWrappers.h" +#endif // defined(MOZ_WIDGET_ANDROID) +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +# include "base/process_util.h" +#endif // defined(XP_MACOSX) + +using mozilla::ipc::GeckoChildProcessHost; + +using CrashReporter::AnnotationTable; + +namespace mozilla::gmp { + +#define GMP_PARENT_LOG_DEBUG(x, ...) \ + GMP_LOG_DEBUG("GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__) + +#ifdef __CLASS__ +# undef __CLASS__ +#endif +#define __CLASS__ "GMPParent" + +GMPParent::GMPParent() + : mState(GMPState::NotLoaded), + mPluginId(GeckoChildProcessHost::GetUniqueID()), + mProcess(nullptr), + mDeleteProcessOnlyOnUnload(false), + mAbnormalShutdownInProgress(false), + mIsBlockingDeletion(false), + mCanDecrypt(false), + mGMPContentChildCount(0), + mChildPid(0), +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + mChildLaunchArch(base::PROCESS_ARCH_INVALID), +#endif + mMainThread(GetMainThreadSerialEventTarget()) { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMP_PARENT_LOG_DEBUG("GMPParent ctor id=%u", mPluginId); +} + +GMPParent::~GMPParent() { + // This method is not restricted to a specific thread. + GMP_PARENT_LOG_DEBUG("GMPParent dtor id=%u", mPluginId); + MOZ_ASSERT(!mProcess); +} + +void GMPParent::CloneFrom(const GMPParent* aOther) { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + 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; + mPluginType = aOther->mPluginType; +#if defined(XP_WIN) || defined(XP_LINUX) + mLibs = aOther->mLibs; +#endif + for (const GMPCapability& cap : aOther->mCapabilities) { + mCapabilities.AppendElement(cap); + } + mAdapter = aOther->mAdapter; + +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + mChildLaunchArch = aOther->mChildLaunchArch; +#endif +} + +#if defined(XP_WIN) || defined(XP_MACOSX) +nsresult GMPParent::GetPluginFileArch(nsIFile* aPluginDir, + const nsString& aBaseName, + uint32_t& aArchSet) { + // Build up the plugin filename +# if defined(XP_MACOSX) + nsAutoString pluginFileName = u"lib"_ns + aBaseName + u".dylib"_ns; +# elif defined(XP_WIN) + nsAutoString pluginFileName = aBaseName + u".dll"_ns; +# endif + GMP_PARENT_LOG_DEBUG("%s: pluginFileName: %s", __FUNCTION__, + NS_LossyConvertUTF16toASCII(pluginFileName).get()); + + // Create an nsIFile representing the plugin + nsCOMPtr pluginFile; + nsresult rv = aPluginDir->Clone(getter_AddRefs(pluginFile)); + NS_ENSURE_SUCCESS(rv, rv); + pluginFile->AppendRelativePath(pluginFileName); + +# if defined(XP_MACOSX) + // Get the full plugin path + nsAutoCString pluginPath; + rv = pluginFile->GetNativePath(pluginPath); + NS_ENSURE_SUCCESS(rv, rv); + GMP_PARENT_LOG_DEBUG("%s: pluginPath: %s", __FUNCTION__, pluginPath.get()); + + rv = nsMacUtilsImpl::GetArchitecturesForBinary(pluginPath.get(), &aArchSet); + NS_ENSURE_SUCCESS(rv, rv); + +# if defined(__aarch64__) + mPluginFilePath = pluginPath; +# endif +# elif defined(XP_WIN) + // Get the full plugin path + nsAutoString pluginPath; + rv = pluginFile->GetTarget(pluginPath); + NS_ENSURE_SUCCESS(rv, rv); + GMP_PARENT_LOG_DEBUG("%s: pluginPath: %s", __FUNCTION__, + NS_LossyConvertUTF16toASCII(pluginPath).get()); + + aArchSet = GetExecutableArchitecture(pluginPath.get()); + if (aArchSet == base::PROCESS_ARCH_INVALID) { + return NS_ERROR_FAILURE; + } +# endif + + return NS_OK; +} +#endif // defined(XP_WIN) || defined(XP_MACOSX) + +RefPtr GMPParent::Init(GeckoMediaPluginServiceParent* aService, + nsIFile* aPluginDir) { + MOZ_ASSERT(aPluginDir); + MOZ_ASSERT(aService); + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + + mService = aService; + mDirectory = aPluginDir; + + // aPluginDir is // + // where should be gmp-gmpopenh264 + nsCOMPtr parent; + nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return GenericPromise::CreateAndReject(rv, __func__); + } + nsAutoString parentLeafName; + rv = parent->GetLeafName(parentLeafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return GenericPromise::CreateAndReject(rv, __func__); + } + GMP_PARENT_LOG_DEBUG("%s: for %s", __FUNCTION__, + NS_LossyConvertUTF16toASCII(parentLeafName).get()); + + MOZ_ASSERT(parentLeafName.Length() > 4); + mName = Substring(parentLeafName, 4); + +#if defined(XP_WIN) || defined(XP_MACOSX) + uint32_t pluginArch = base::PROCESS_ARCH_INVALID; + rv = GetPluginFileArch(aPluginDir, mName, pluginArch); + if (NS_FAILED(rv)) { + GMP_PARENT_LOG_DEBUG("%s: Plugin arch error: %d", __FUNCTION__, rv); + } else { + GMP_PARENT_LOG_DEBUG("%s: Plugin arch: 0x%x", __FUNCTION__, pluginArch); + } + + const uint32_t x86 = base::PROCESS_ARCH_X86_64 | base::PROCESS_ARCH_I386; +# ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + const uint32_t arm64 = base::PROCESS_ARCH_ARM_64; + + mChildLaunchArch = pluginArch; + // When executing in an ARM64 process, if the library is x86 or x64, + // set |mChildLaunchArch| to x64 and allow the library to be used as long + // as this process is a universal binary. + if (!(pluginArch & arm64) && (pluginArch & x86)) { + bool isWidevine = parentLeafName.Find(u"widevine") != kNotFound; + bool isWidevineAllowed = + StaticPrefs::media_gmp_widevinecdm_allow_x64_plugin_on_arm64(); + bool isH264 = parentLeafName.Find(u"openh264") != kNotFound; + bool isH264Allowed = + StaticPrefs::media_gmp_gmpopenh264_allow_x64_plugin_on_arm64(); + bool isClearkey = parentLeafName.Find(u"clearkey") != kNotFound; + bool isClearkeyAllowed = + StaticPrefs::media_gmp_gmpclearkey_allow_x64_plugin_on_arm64(); + + // Only allow x64 child GMP processes for Widevine, OpenH264 and Clearkey + if (!isWidevine && !isH264 && !isClearkey) { + return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, + __func__); + } + // And only if prefs permit it. + if ((isWidevine && !isWidevineAllowed) || (isH264 && !isH264Allowed) || + (isClearkey && !isClearkeyAllowed)) { + return GenericPromise::CreateAndReject(NS_ERROR_PLUGIN_DISABLED, + __func__); + } + +# ifdef XP_MACOSX + // We have an x64 library. Get the bundle architecture to determine + // if we are a universal binary and hence if we can launch an x64 + // child process to host this plugin. + uint32_t bundleArch = base::PROCESS_ARCH_INVALID; + rv = nsMacUtilsImpl::GetArchitecturesForBundle(&bundleArch); + if (NS_FAILED(rv)) { + // If we fail here, continue as if this is not a univeral binary. + GMP_PARENT_LOG_DEBUG("%s: Bundle arch error: %d", __FUNCTION__, rv); + } else { + GMP_PARENT_LOG_DEBUG("%s: Bundle arch: 0x%x", __FUNCTION__, bundleArch); + } + + bool isUniversalBinary = (bundleArch & base::PROCESS_ARCH_X86_64) && + (bundleArch & base::PROCESS_ARCH_ARM_64); + if (isUniversalBinary) { + mChildLaunchArch = base::PROCESS_ARCH_X86_64; + PreTranslateBins(); + } else { + return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, + __func__); + } +# endif + } +# else + // When executing in a non-ARM process, if the library is not x86 or x64, + // remove it and return an error. This prevents a child process crash due + // to loading an incompatible library and forces a new plugin version to be + // downloaded when the check is next performed. This could occur if a profile + // is moved from an ARM64 system to an x64 system. + if ((pluginArch & x86) == 0) { + GMP_PARENT_LOG_DEBUG("%s: Removing plugin directory", __FUNCTION__); + aPluginDir->Remove(true); + return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__); + } +# endif // defined(ALLOW_GECKO_CHILD_PROCESS_ARCH) +#endif // defined(XP_WIN) || defined(XP_MACOSX) + + return ReadGMPMetaData(); +} + +void GMPParent::Crash() { + if (mState != GMPState::NotLoaded) { + Unused << SendCrashPluginNow(); + } +} + +class NotifyGMPProcessLoadedTask : public Runnable { + public: + explicit NotifyGMPProcessLoadedTask(const ::base::ProcessId aProcessId, + GMPParent* aGMPParent) + : Runnable("NotifyGMPProcessLoadedTask"), + mProcessId(aProcessId), + mGMPParent(aGMPParent) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + bool canProfile = true; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + if (SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia) && + base::SharedMemory::UsingPosixShm()) { + canProfile = false; + } +#endif + + if (canProfile) { + nsCOMPtr gmpEventTarget = + mGMPParent->GMPEventTarget(); + if (!gmpEventTarget) { + return NS_ERROR_FAILURE; + } + + ipc::Endpoint profilerParent( + ProfilerParent::CreateForProcess(mProcessId)); + + gmpEventTarget->Dispatch( + NewRunnableMethod&&>( + "GMPParent::SendInitProfiler", mGMPParent, + &GMPParent::SendInitProfiler, std::move(profilerParent))); + } + + return NS_OK; + } + + ::base::ProcessId mProcessId; + const RefPtr mGMPParent; +}; + +nsresult GMPParent::LoadProcess() { + MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + MOZ_ASSERT(mState == GMPState::NotLoaded); + + nsAutoString path; + if (NS_WARN_IF(NS_FAILED(mDirectory->GetPath(path)))) { + return NS_ERROR_FAILURE; + } + GMP_PARENT_LOG_DEBUG("%s: for %s", __FUNCTION__, + NS_ConvertUTF16toUTF8(path).get()); + + if (!mProcess) { + mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get()); +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + mProcess->SetRequiresWindowServer(mAdapter.EqualsLiteral("chromium")); +#endif + +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + mProcess->SetLaunchArchitecture(mChildLaunchArch); +#endif + + if (!mProcess->Launch(30 * 1000)) { + GMP_PARENT_LOG_DEBUG("%s: Failed to launch new child process", + __FUNCTION__); + mProcess->Delete(); + mProcess = nullptr; + return NS_ERROR_FAILURE; + } + + mChildPid = mProcess->GetChildProcessId(); + GMP_PARENT_LOG_DEBUG("%s: Launched new child process", __FUNCTION__); + + bool opened = mProcess->TakeInitialEndpoint().Bind(this); + if (!opened) { + GMP_PARENT_LOG_DEBUG("%s: Failed to open channel to new child process", + __FUNCTION__); + mProcess->Delete(); + mProcess = nullptr; + return NS_ERROR_FAILURE; + } + GMP_PARENT_LOG_DEBUG("%s: Opened channel to new child process", + __FUNCTION__); + + // ComputeStorageId may return empty string, we leave the error handling to + // CDM. The CDM will reject the promise once we provide a empty string of + // storage id. + bool ok = + SendProvideStorageId(CDMStorageIdProvider::ComputeStorageId(mNodeId)); + if (!ok) { + GMP_PARENT_LOG_DEBUG("%s: Failed to send storage id to child process", + __FUNCTION__); + return NS_ERROR_FAILURE; + } + GMP_PARENT_LOG_DEBUG("%s: Sent storage id to child process", __FUNCTION__); + +#if defined(XP_WIN) || defined(XP_LINUX) + if (!mLibs.IsEmpty()) { + bool ok = SendPreloadLibs(mLibs); + if (!ok) { + GMP_PARENT_LOG_DEBUG("%s: Failed to send preload-libs to child process", + __FUNCTION__); + return NS_ERROR_FAILURE; + } + GMP_PARENT_LOG_DEBUG("%s: Sent preload-libs ('%s') to child process", + __FUNCTION__, mLibs.get()); + } +#endif + + NS_DispatchToMainThread(new NotifyGMPProcessLoadedTask(OtherPid(), this)); + + // Intr call to block initialization on plugin load. + if (!SendStartPlugin(mAdapter)) { + GMP_PARENT_LOG_DEBUG("%s: Failed to send start to child process", + __FUNCTION__); + return NS_ERROR_FAILURE; + } + GMP_PARENT_LOG_DEBUG("%s: Sent StartPlugin to child process", __FUNCTION__); + } + + mState = GMPState::Loaded; + + return NS_OK; +} + +mozilla::ipc::IPCResult GMPParent::RecvPGMPContentChildDestroyed() { + --mGMPContentChildCount; + if (!IsUsed()) { + CloseIfUnused(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult GMPParent::RecvFOGData(ByteBuf&& aBuf) { + GMP_PARENT_LOG_DEBUG("GMPParent RecvFOGData"); + glean::FOGData(std::move(aBuf)); + return IPC_OK(); +} + +void GMPParent::CloseIfUnused() { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__); + + if ((mDeleteProcessOnlyOnUnload || mState == GMPState::Loaded || + mState == GMPState::Unloading) && + !IsUsed()) { + // Ensure all timers are killed. + for (uint32_t i = mTimers.Length(); i > 0; i--) { + mTimers[i - 1]->Shutdown(); + } + + // Shutdown GMPStorage. Given that all protocol actors must be shutdown + // (!Used() is true), all storage operations should be complete. + for (size_t i = mStorage.Length(); i > 0; i--) { + mStorage[i - 1]->Shutdown(); + } + Shutdown(); + } +} + +void GMPParent::CloseActive(bool aDieWhenUnloaded) { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMP_PARENT_LOG_DEBUG("%s: state %u", __FUNCTION__, + uint32_t(GMPState(mState))); + + if (aDieWhenUnloaded) { + mDeleteProcessOnlyOnUnload = true; // don't allow this to go back... + } + if (mState == GMPState::Loaded) { + mState = GMPState::Unloading; + } + if (mState != GMPState::NotLoaded && IsUsed()) { + Unused << SendCloseActive(); + CloseIfUnused(); + } +} + +void GMPParent::MarkForDeletion() { + mDeleteProcessOnlyOnUnload = true; + mIsBlockingDeletion = true; +} + +bool GMPParent::IsMarkedForDeletion() { return mIsBlockingDeletion; } + +void GMPParent::Shutdown() { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__); + + if (mAbnormalShutdownInProgress) { + return; + } + + MOZ_ASSERT(!IsUsed()); + if (mState == GMPState::NotLoaded || mState == GMPState::Closing) { + return; + } + + RefPtr 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 == GMPState::NotLoaded); +} + +class NotifyGMPShutdownTask : public Runnable { + public: + explicit NotifyGMPShutdownTask(const nsAString& aNodeId) + : Runnable("NotifyGMPShutdownTask"), mNodeId(aNodeId) {} + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr 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 self(this); + nsCOMPtr gmpEventTarget = GMPEventTarget(); + + if (!gmpEventTarget) { + // 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. + GMP_PARENT_LOG_DEBUG("%s::%s: GMPEventTarget() returned nullptr.", + __CLASS__, __FUNCTION__); + } else { + gmpEventTarget->Dispatch( + NewRunnableMethod>( + "gmp::GeckoMediaPluginServiceParent::PluginTerminated", mService, + &GeckoMediaPluginServiceParent::PluginTerminated, self), + NS_DISPATCH_NORMAL); + } +} + +void GMPParent::DeleteProcess() { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__); + + if (mState != GMPState::Closing) { + // Don't Close() twice! + // Probably remove when bug 1043671 is resolved + mState = GMPState::Closing; + Close(); + } + mProcess->Delete(NewRunnableMethod("gmp::GMPParent::ChildTerminated", this, + &GMPParent::ChildTerminated)); + GMP_PARENT_LOG_DEBUG("%s: Shut down process", __FUNCTION__); + mProcess = nullptr; + +#if defined(MOZ_WIDGET_ANDROID) + if (mState != GMPState::NotLoaded) { + nsCOMPtr launcherThread(GetIPCLauncher()); + MOZ_ASSERT(launcherThread); + + auto procType = java::GeckoProcessType::GMPLUGIN(); + auto selector = + java::GeckoProcessManager::Selector::New(procType, OtherPid()); + + launcherThread->Dispatch(NS_NewRunnableFunction( + "GMPParent::DeleteProcess", + [selector = + java::GeckoProcessManager::Selector::GlobalRef(selector)]() { + java::GeckoProcessManager::ShutdownProcess(selector); + })); + } +#endif // defined(MOZ_WIDGET_ANDROID) + + mState = GMPState::NotLoaded; + + nsCOMPtr r = + new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)); + mMainThread->Dispatch(r.forget()); +} + +GMPState GMPParent::State() const { return mState; } + +nsCOMPtr GMPParent::GMPEventTarget() { + nsCOMPtr mps = + do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mps); + if (!mps) { + return nullptr; + } + // Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns + // nullptr if the GeckoMediaPluginService has started shutdown. + nsCOMPtr gmpThread; + mps->GetThread(getter_AddRefs(gmpThread)); + return gmpThread; +} + +/* static */ +bool GMPCapability::Supports(const nsTArray& aCapabilities, + const nsACString& aAPI, + const nsTArray& aTags) { + for (const nsCString& tag : aTags) { + if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) { + return false; + } + } + return true; +} + +/* static */ +bool GMPCapability::Supports(const nsTArray& aCapabilities, + const nsACString& 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.EqualsLiteral(kClearKeyKeySystemName)) { + if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) { + if (!WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::H264)) { + continue; + } + } + } +#endif + return true; + } + } + } + return false; +} + +bool GMPParent::EnsureProcessLoaded() { + switch (mState) { + case GMPState::NotLoaded: + return NS_SUCCEEDED(LoadProcess()); + case GMPState::Loaded: + return true; + case GMPState::Unloading: + case GMPState::Closing: + return false; + } + + MOZ_ASSERT_UNREACHABLE("Unhandled GMPState!"); + return false; +} + +void GMPParent::AddCrashAnnotations() { + if (mCrashReporter) { + mCrashReporter->AddAnnotation(CrashReporter::Annotation::GMPPlugin, true); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginFilename, + NS_ConvertUTF16toUTF8(mName)); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginName, + mDisplayName); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginVersion, + mVersion); + } +} + +void GMPParent::GetCrashID(nsString& aResult) { + AddCrashAnnotations(); + GenerateCrashReport(OtherPid(), &aResult); +} + +static void GMPNotifyObservers(const uint32_t aPluginID, + const nsACString& aPluginName, + const nsAString& aPluginDumpID) { + nsCOMPtr obs = mozilla::services::GetObserverService(); + nsCOMPtr propbag = + do_CreateInstance("@mozilla.org/hash-property-bag;1"); + if (obs && propbag) { + propbag->SetPropertyAsUint32(u"pluginID"_ns, aPluginID); + propbag->SetPropertyAsACString(u"pluginName"_ns, aPluginName); + propbag->SetPropertyAsAString(u"pluginDumpID"_ns, aPluginDumpID); + obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr); + } + + RefPtr service = + gmp::GeckoMediaPluginService::GetGeckoMediaPluginService(); + if (service) { + service->RunPluginCrashCallbacks(aPluginID, aPluginName); + } +} + +void GMPParent::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMP_PARENT_LOG_DEBUG("%s: (%d)", __FUNCTION__, (int)aWhy); + + if (AbnormalShutdown == aWhy) { + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "gmplugin"_ns, + 1); + nsString dumpID; + GetCrashID(dumpID); + if (dumpID.IsEmpty()) { + NS_WARNING("GMP crash without crash report"); + dumpID = mName; + dumpID += '-'; + AppendUTF8toUTF16(mVersion, dumpID); + } + + // NotifyObservers is mainthread-only + nsCOMPtr r = + WrapRunnableNM(&GMPNotifyObservers, mPluginId, mDisplayName, dumpID); + mMainThread->Dispatch(r.forget()); + } + + // warn us off trying to close again + mState = GMPState::Closing; + mAbnormalShutdownInProgress = true; + CloseActive(false); + + // Normal Shutdown() will delete the process on unwind. + if (AbnormalShutdown == aWhy) { + RefPtr self(this); + // Must not call Close() again in DeleteProcess(), as we'll recurse + // infinitely if we do. + MOZ_ASSERT(mState == GMPState::Closing); + DeleteProcess(); + // Note: final destruction will be Dispatched to ourself + mService->ReAddOnGMPThread(self); + } +} + +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(aActor); + p->Shutdown(); + mStorage.RemoveElement(p); + return true; +} + +mozilla::ipc::IPCResult GMPParent::RecvPGMPStorageConstructor( + PGMPStorageParent* aActor) { + GMPStorageParent* p = (GMPStorageParent*)aActor; + if (NS_FAILED(p->Init())) { + // TODO: Verify if this is really a good reason to IPC_FAIL. + // There might be shutdown edge cases here. + return IPC_FAIL(this, + "GMPParent::RecvPGMPStorageConstructor: p->Init() failed."); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult GMPParent::RecvPGMPTimerConstructor( + PGMPTimerParent* actor) { + return IPC_OK(); +} + +PGMPTimerParent* GMPParent::AllocPGMPTimerParent() { + nsCOMPtr target = GMPEventTarget(); + GMPTimerParent* p = new GMPTimerParent(target); + mTimers.AppendElement( + p); // Released in DeallocPGMPTimerParent, or on shutdown. + return p; +} + +bool GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor) { + GMPTimerParent* p = static_cast(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 GMPParent::ReadGMPMetaData() { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); + MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!"); + + nsCOMPtr infoFile; + nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return GenericPromise::CreateAndReject(rv, __func__); + } + infoFile->AppendRelativePath(mName + u".info"_ns); + + if (FileExists(infoFile)) { + return ReadGMPInfoFile(infoFile); + } + + // Maybe this is the Widevine adapted plugin? + nsCOMPtr manifestFile; + rv = mDirectory->Clone(getter_AddRefs(manifestFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return GenericPromise::CreateAndReject(rv, __func__); + } + manifestFile->AppendRelativePath(u"manifest.json"_ns); + return ReadChromiumManifestFile(manifestFile); +} + +#if defined(XP_LINUX) +static void ApplyGlibcWorkaround(nsCString& aLibs) { + // These glibc libraries were merged into libc.so.6 as of glibc + // 2.34; they now exist only as stub libraries for compatibility and + // newly linked code won't depend on them, so we need to ensure + // they're loaded for plugins that may have been linked against a + // different version of glibc. (See also bug 1725828.) + if (!aLibs.IsEmpty()) { + aLibs.AppendLiteral(", "); + } + aLibs.AppendLiteral("libdl.so.2, libpthread.so.0, librt.so.1"); +} +#endif + +#if defined(XP_WIN) +static void ApplyOleaut32(nsCString& aLibs) { + // In the libwebrtc update in bug 1766646 an include of comdef.h for using + // _bstr_t was introduced. This resulted in a dependency on comsupp.lib which + // contains a `_variant_t vtMissing` that would get cleared in an exit + // handler. VariantClear is defined in oleaut32.dll, and so we'd try to load + // oleaut32.dll on exit but get denied by the sandbox. + // Note that we had includes of comdef.h before bug 1766646 but it is the use + // of _bstr_t that triggers the vtMissing exit handler. + // See bug 1788592 for details. + if (!aLibs.IsEmpty()) { + aLibs.AppendLiteral(", "); + } + aLibs.AppendLiteral("oleaut32.dll"); +} +#endif + +RefPtr GMPParent::ReadGMPInfoFile(nsIFile* aFile) { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMPInfoFileParser parser; + if (!parser.Init(aFile)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + nsAutoCString apis; + if (!ReadInfoField(parser, "name"_ns, mDisplayName) || + !ReadInfoField(parser, "description"_ns, mDescription) || + !ReadInfoField(parser, "version"_ns, mVersion) || + !ReadInfoField(parser, "apis"_ns, apis)) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + +#if defined(XP_WIN) || defined(XP_LINUX) + // "Libraries" field is optional. + ReadInfoField(parser, "libraries"_ns, mLibs); +#endif + + UpdatePluginType(); + +#ifdef XP_LINUX + // The glibc workaround (see above) isn't needed for clearkey + // because it's built along with the browser. + if (mPluginType != GMPPluginType::Clearkey) { + ApplyGlibcWorkaround(mLibs); + } +#endif + +#ifdef XP_WIN + ApplyOleaut32(mLibs); +#endif + + nsTArray 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 tagTokens; + SplitAt(":", ts, tagTokens); + for (nsCString tag : tagTokens) { + cap.mAPITags.AppendElement(tag); + } + } + } + + mCapabilities.AppendElement(std::move(cap)); + } + + if (mCapabilities.IsEmpty()) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + return GenericPromise::CreateAndResolve(true, __func__); +} + +RefPtr GMPParent::ReadChromiumManifestFile(nsIFile* aFile) { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + 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(mMainThread, this, __func__, + &GMPParent::ParseChromiumManifest, + NS_ConvertUTF8toUTF16(json)); +} + +static bool IsCDMAPISupported( + const mozilla::dom::WidevineCDMManifest& aManifest) { + nsresult ignored; // Note: ToInteger returns 0 on failure. + int32_t moduleVersion = aManifest.mX_cdm_module_versions.ToInteger(&ignored); + int32_t interfaceVersion = + aManifest.mX_cdm_interface_versions.ToInteger(&ignored); + int32_t hostVersion = aManifest.mX_cdm_host_versions.ToInteger(&ignored); + return ChromiumCDMAdapter::Supports(moduleVersion, interfaceVersion, + hostVersion); +} + +RefPtr GMPParent::ParseChromiumManifest( + const nsAString& aJSON) { + GMP_PARENT_LOG_DEBUG("%s: for '%s'", __FUNCTION__, + NS_LossyConvertUTF16toASCII(aJSON).get()); + + MOZ_ASSERT(NS_IsMainThread()); + mozilla::dom::WidevineCDMManifest m; + if (!m.Init(aJSON)) { + GMP_PARENT_LOG_DEBUG("%s: Failed to initialize json parser, failing.", + __FUNCTION__); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + if (!IsCDMAPISupported(m)) { + GMP_PARENT_LOG_DEBUG("%s: CDM API not supported, failing.", __FUNCTION__); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + CopyUTF16toUTF8(m.mName, mDisplayName); + CopyUTF16toUTF8(m.mDescription, mDescription); + CopyUTF16toUTF8(m.mVersion, mVersion); + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) { + nsPrintfCString msg( + "GMPParent::ParseChromiumManifest: Plugin \"%s\" is an EME CDM" + " but this system can't sandbox it; not loading.", + mDisplayName.get()); + printf_stderr("%s\n", msg.get()); + GMP_PARENT_LOG_DEBUG("%s", msg.get()); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } +#endif + + UpdatePluginType(); + + GMPCapability video; + + // We hard code a few of the settings because they can't be stored in the + // widevine manifest without making our API different to widevine's. + switch (mPluginType) { + case GMPPluginType::Clearkey: + video.mAPITags.AppendElement(nsCString{kClearKeyKeySystemName}); + video.mAPITags.AppendElement( + nsCString{kClearKeyWithProtectionQueryKeySystemName}); +#if XP_WIN + mLibs = nsLiteralCString( + "dxva2.dll, evr.dll, freebl3.dll, mfh264dec.dll, mfplat.dll, " + "msmpeg2vdec.dll, nss3.dll, softokn3.dll"); +#elif XP_LINUX + mLibs = "libfreeblpriv3.so, libsoftokn3.so"_ns; +#endif + break; + case GMPPluginType::Widevine: + video.mAPITags.AppendElement(nsCString{kWidevineKeySystemName}); +#if XP_WIN + // psapi.dll added for GetMappedFileNameW, which could possibly be avoided + // in future versions, see bug 1383611 for details. + mLibs = "dxva2.dll, ole32.dll, psapi.dll, winmm.dll"_ns; +#endif + break; + case GMPPluginType::Fake: + // The fake CDM just exposes a key system with id "fake". + video.mAPITags.AppendElement(nsCString{"fake"}); +#if XP_WIN + mLibs = "dxva2.dll, ole32.dll"_ns; +#endif + break; + default: + GMP_PARENT_LOG_DEBUG("%s: Unrecognized key system: %s, failing.", + __FUNCTION__, mDisplayName.get()); + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + +#ifdef XP_LINUX + ApplyGlibcWorkaround(mLibs); +#endif + +#ifdef XP_WIN + ApplyOleaut32(mLibs); +#endif + + nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs); + nsTArray codecs; + SplitAt(",", codecsString, codecs); + + // Parse the codec strings in the manifest and map them to strings used + // internally by Gecko for capability recognition. + // + // Google's code to parse manifests can be used as a reference for strings + // the manifest may contain + // https://source.chromium.org/chromium/chromium/src/+/master:components/cdm/common/cdm_manifest.cc;l=74;drc=775880ced8a989191281e93854c7f2201f25068f + // + // Gecko's internal strings can be found at + // https://searchfox.org/mozilla-central/rev/ea63a0888d406fae720cf24f4727d87569a8cab5/dom/media/eme/MediaKeySystemAccess.cpp#149-155 + for (const nsCString& chromiumCodec : codecs) { + nsCString codec; + if (chromiumCodec.EqualsASCII("vp8")) { + codec = "vp8"_ns; + } else if (chromiumCodec.EqualsASCII("vp9.0") || // Legacy string. + chromiumCodec.EqualsASCII("vp09")) { + codec = "vp9"_ns; + } else if (chromiumCodec.EqualsASCII("avc1")) { + codec = "h264"_ns; + } else if (chromiumCodec.EqualsASCII("av01")) { + codec = "av1"_ns; + } else { + GMP_PARENT_LOG_DEBUG("%s: Unrecognized codec: %s.", __FUNCTION__, + chromiumCodec.get()); + MOZ_ASSERT_UNREACHABLE( + "Unhandled codec string! Need to add it to the parser."); + continue; + } + + video.mAPITags.AppendElement(codec); + } + + video.mAPIName = nsLiteralCString(CHROMIUM_CDM_API); + mAdapter = u"chromium"_ns; + + mCapabilities.AppendElement(std::move(video)); + + GMP_PARENT_LOG_DEBUG("%s: Successfully parsed manifest.", __FUNCTION__); + return GenericPromise::CreateAndResolve(true, __func__); +} + +bool GMPParent::CanBeSharedCrossNodeIds() const { + return 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 mNodeId == aNodeId; +} + +void GMPParent::SetNodeId(const nsACString& aNodeId) { + MOZ_ASSERT(!aNodeId.IsEmpty()); + mNodeId = aNodeId; +} + +void GMPParent::UpdatePluginType() { + if (mDisplayName.EqualsLiteral("WidevineCdm")) { + mPluginType = GMPPluginType::Widevine; + } else if (mDisplayName.EqualsLiteral("gmpopenh264")) { + mPluginType = GMPPluginType::OpenH264; + } else if (mDisplayName.EqualsLiteral("clearkey")) { + mPluginType = GMPPluginType::Clearkey; + } else if (mDisplayName.EqualsLiteral("fake")) { + mPluginType = GMPPluginType::Fake; + } else { + mPluginType = GMPPluginType::Unknown; + } +} + +const nsCString& GMPParent::GetDisplayName() const { return mDisplayName; } + +const nsCString& GMPParent::GetVersion() const { return mVersion; } + +uint32_t GMPParent::GetPluginId() const { return mPluginId; } + +void GMPParent::ResolveGetContentParentPromises() { + nsTArray>> promises = + std::move(mGetContentParentPromises); + MOZ_ASSERT(mGetContentParentPromises.IsEmpty()); + RefPtr blocker( + new GMPContentParent::CloseBlocker(mGMPContentParent)); + for (auto& holder : promises) { + holder->Resolve(blocker, __func__); + } +} + +bool GMPParent::OpenPGMPContent() { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + MOZ_ASSERT(!mGMPContentParent); + + Endpoint parent; + Endpoint child; + if (NS_WARN_IF(NS_FAILED(PGMPContent::CreateEndpoints( + base::GetCurrentProcId(), OtherPid(), &parent, &child)))) { + return false; + } + + mGMPContentParent = new GMPContentParent(this); + + if (!parent.Bind(mGMPContentParent)) { + return false; + } + + if (!SendInitGMPContentChild(std::move(child))) { + return false; + } + + ResolveGetContentParentPromises(); + + return true; +} + +void GMPParent::RejectGetContentParentPromises() { + nsTArray>> promises = + std::move(mGetContentParentPromises); + MOZ_ASSERT(mGetContentParentPromises.IsEmpty()); + for (auto& holder : promises) { + holder->Reject(NS_ERROR_FAILURE, __func__); + } +} + +void GMPParent::GetGMPContentParent( + UniquePtr>&& aPromiseHolder) { + MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); + GMP_PARENT_LOG_DEBUG("%s %p", __FUNCTION__, this); + + if (mGMPContentParent) { + RefPtr blocker( + new GMPContentParent::CloseBlocker(mGMPContentParent)); + aPromiseHolder->Resolve(blocker, __func__); + } else { + mGetContentParentPromises.AppendElement(std::move(aPromiseHolder)); + // If we don't have a GMPContentParent and we try to get one for the first + // time (mGetContentParentPromises.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 (mGetContentParentPromises.Length() == 1) { + if (!EnsureProcessLoaded() || !OpenPGMPContent()) { + RejectGetContentParentPromises(); + return; + } + // 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; + } + } +} + +already_AddRefed GMPParent::ForgetGMPContentParent() { + MOZ_ASSERT(mGetContentParentPromises.IsEmpty()); + return mGMPContentParent.forget(); +} + +bool GMPParent::EnsureProcessLoaded(base::ProcessId* aID) { + if (!EnsureProcessLoaded()) { + return false; + } + *aID = OtherPid(); + return true; +} + +void GMPParent::IncrementGMPContentChildCount() { ++mGMPContentChildCount; } + +nsString GMPParent::GetPluginBaseName() const { return u"gmp-"_ns + mName; } + +#if defined(XP_MACOSX) && defined(__aarch64__) +void GMPParent::PreTranslateBins() { + nsCOMPtr event = mozilla::NewRunnableMethod( + "RosettaTranslation", this, &GMPParent::PreTranslateBinsWorker); + + DebugOnly rv = + NS_DispatchBackgroundTask(event.forget(), NS_DISPATCH_EVENT_MAY_BLOCK); + + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +void GMPParent::PreTranslateBinsWorker() { + int rv = nsMacUtilsImpl::PreTranslateXUL(); + GMP_PARENT_LOG_DEBUG("%s: XUL translation result: %d", __FUNCTION__, rv); + + rv = nsMacUtilsImpl::PreTranslateBinary(mPluginFilePath); + GMP_PARENT_LOG_DEBUG("%s: %s translation result: %d", __FUNCTION__, + mPluginFilePath.get(), rv); +} +#endif + +} // namespace mozilla::gmp + +#undef GMP_PARENT_LOG_DEBUG +#undef __CLASS__ -- cgit v1.2.3