/* -*- 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/WidevineCDMManifestBinding.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" #endif #include "mozilla/Services.h" #include "mozilla/SSE.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 "runnable_utils.h" #include "VideoUtils.h" #ifdef XP_WIN # 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; using CrashReporter::GetIDFromMinidump; 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(GMPStateNotLoaded), mPluginId(GeckoChildProcessHost::GetUniqueID()), mProcess(nullptr), mDeleteProcessOnlyOnUnload(false), mAbnormalShutdownInProgress(false), mIsBlockingDeletion(false), mCanDecrypt(false), mGMPContentChildCount(0), mChildPid(0), mHoldingSelfRef(false), #if defined(XP_MACOSX) && defined(__aarch64__) 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; #if defined(XP_WIN) || defined(XP_LINUX) mLibs = aOther->mLibs; #endif for (const GMPCapability& cap : aOther->mCapabilities) { mCapabilities.AppendElement(cap); } mAdapter = aOther->mAdapter; #if defined(XP_MACOSX) && defined(__aarch64__) mChildLaunchArch = aOther->mChildLaunchArch; #endif } #if defined(XP_MACOSX) nsresult GMPParent::GetPluginFileArch(nsIFile* aPluginDir, nsAutoString& aLeafName, uint32_t& aArchSet) { // Build up the plugin filename nsAutoString baseName; baseName = Substring(aLeafName, 4, aLeafName.Length() - 1); nsAutoString pluginFileName = u"lib"_ns + baseName + u".dylib"_ns; 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); // Get the full plugin path nsCString 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 return NS_OK; } #endif // 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_MACOSX) uint32_t pluginArch = 0; rv = GetPluginFileArch(aPluginDir, parentLeafName, 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); } uint32_t x86 = base::PROCESS_ARCH_X86_64 | base::PROCESS_ARCH_I386; # if defined(__aarch64__) uint32_t arm64 = base::PROCESS_ARCH_ARM_64; // 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("widevine") != kNotFound; bool isWidevineAllowed = StaticPrefs::media_gmp_widevinecdm_allow_x64_plugin_on_arm64(); bool isH264 = parentLeafName.Find("openh264") != kNotFound; bool isH264Allowed = StaticPrefs::media_gmp_gmpopenh264_allow_x64_plugin_on_arm64(); // Only allow x64 child GMP processes for Widevine and OpenH264 if (!isWidevine && !isH264) { return GenericPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__); } // And only if prefs permit it. if ((isWidevine && !isWidevineAllowed) || (isH264 && !isH264Allowed)) { return GenericPromise::CreateAndReject(NS_ERROR_PLUGIN_DISABLED, __func__); } // 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__); } } # 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(__aarch64__) #endif // defined(XP_MACOSX) return ReadGMPMetaData(); } void GMPParent::Crash() { if (mState != GMPStateNotLoaded) { Unused << SendCrashPluginNow(); } } nsresult GMPParent::LoadProcess() { MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!"); MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); MOZ_ASSERT(mState == GMPStateNotLoaded); 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 #if defined(XP_MACOSX) && defined(__aarch64__) 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 = base::GetProcId(mProcess->GetChildProcessHandle()); GMP_PARENT_LOG_DEBUG("%s: Launched new child process", __FUNCTION__); bool opened = Open(mProcess->TakeChannel(), base::GetProcId(mProcess->GetChildProcessHandle())); 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 // Intr call to block initialization on plugin load. if (!CallStartPlugin(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 = 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; } mozilla::ipc::IPCResult GMPParent::RecvPGMPContentChildDestroyed() { --mGMPContentChildCount; if (!IsUsed()) { CloseIfUnused(); } return IPC_OK(); } void GMPParent::CloseIfUnused() { MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread()); GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__); 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(); } // 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 %d", __FUNCTION__, mState); if (aDieWhenUnloaded) { mDeleteProcessOnlyOnUnload = true; // don't allow this to go back... } if (mState == GMPStateLoaded) { mState = GMPStateUnloading; } if (mState != GMPStateNotLoaded && 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 == GMPStateNotLoaded || mState == GMPStateClosing) { 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 == GMPStateNotLoaded); } 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 != GMPStateClosing) { // Don't Close() twice! // Probably remove when bug 1043671 is resolved mState = GMPStateClosing; 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 != GMPStateNotLoaded) { 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 = GMPStateNotLoaded; nsCOMPtr r = new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId)); mMainThread->Dispatch(r.forget()); if (mHoldingSelfRef) { Release(); mHoldingSelfRef = false; } } 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 ? gmpThread->SerialEventTarget() : nullptr; } /* static */ bool GMPCapability::Supports(const nsTArray& aCapabilities, const nsCString& 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 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.EqualsLiteral(EME_KEY_SYSTEM_CLEARKEY)) { if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) { if (!WMFDecoderModule::HasH264()) { 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); } 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 = GMPStateClosing; 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 == GMPStateClosing); 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_WARN_IF(NS_FAILED(p->Init()))) { return IPC_FAIL_NO_REASON(this); } 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); } 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 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 nsCString kEMEKeySystem; // 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. if (mDisplayName.EqualsASCII("clearkey")) { kEMEKeySystem.AssignLiteral(EME_KEY_SYSTEM_CLEARKEY); #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 } else if (mDisplayName.EqualsASCII("WidevineCdm")) { kEMEKeySystem.AssignLiteral(EME_KEY_SYSTEM_WIDEVINE); #if XP_WIN // psapi.dll added for GetMappedFileNameW, which could possibly be avoided // in future versions, see bug 1383611 for details. mLibs = "dxva2.dll, psapi.dll"_ns; #endif } else if (mDisplayName.EqualsASCII("fake")) { kEMEKeySystem.AssignLiteral("fake"); #if XP_WIN mLibs = "dxva2.dll"_ns; #endif } else { GMP_PARENT_LOG_DEBUG("%s: Unrecognized key system: %s, failing.", __FUNCTION__, mDisplayName.get()); return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } GMPCapability video; 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://cs.chromium.org/chromium/src/chrome/common/media/cdm_manifest.cc?l=73&rcl=393e60bfc2299449db7ef374c0ef1c324716e562 // // 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")) { 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, failing.", __FUNCTION__, chromiumCodec.get()); return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } video.mAPITags.AppendElement(codec); } video.mAPITags.AppendElement(kEMEKeySystem); 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; } 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__