diff options
Diffstat (limited to 'dom/media/gmp/GMPService.cpp')
-rw-r--r-- | dom/media/gmp/GMPService.cpp | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/dom/media/gmp/GMPService.cpp b/dom/media/gmp/GMPService.cpp new file mode 100644 index 0000000000..1d2444a4aa --- /dev/null +++ b/dom/media/gmp/GMPService.cpp @@ -0,0 +1,570 @@ +/* -*- 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 "GMPService.h" + +#include "ChromiumCDMParent.h" +#include "GMPLog.h" +#include "GMPParent.h" +#include "GMPProcessParent.h" +#include "GMPServiceChild.h" +#include "GMPServiceParent.h" +#include "GMPVideoDecoderParent.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/PluginCrashedEvent.h" +#include "nsThreadUtils.h" +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxInfo.h" +#endif +#include "VideoUtils.h" +#include "mozilla/Services.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Unused.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsComponentManagerUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsHashKeys.h" +#include "nsIObserverService.h" +#include "nsIXULAppInfo.h" +#include "nsNativeCharsetUtils.h" +#include "nsXPCOMPrivate.h" +#include "prio.h" +#include "runnable_utils.h" + +namespace mozilla { + +LogModule* GetGMPLog() { + static LazyLogModule sLog("GMP"); + return sLog; +} + +LogModule* GetGMPLibraryLog() { + static LazyLogModule sLog("GMPLibrary"); + return sLog; +} + +GMPLogLevel GetGMPLibraryLogLevel() { + switch (GetGMPLibraryLog()->Level()) { + case LogLevel::Disabled: + return kGMPLogQuiet; + case LogLevel::Error: + return kGMPLogError; + case LogLevel::Warning: + return kGMPLogWarning; + case LogLevel::Info: + return kGMPLogInfo; + case LogLevel::Debug: + return kGMPLogDebug; + case LogLevel::Verbose: + return kGMPLogDetail; + } + return kGMPLogInvalid; +} + +#ifdef __CLASS__ +# undef __CLASS__ +#endif +#define __CLASS__ "GMPService" + +namespace gmp { + +static StaticRefPtr<GeckoMediaPluginService> sSingletonService; + +class GMPServiceCreateHelper final : public mozilla::Runnable { + RefPtr<GeckoMediaPluginService> mService; + + public: + static already_AddRefed<GeckoMediaPluginService> GetOrCreate() { + RefPtr<GeckoMediaPluginService> service; + + if (NS_IsMainThread()) { + service = GetOrCreateOnMainThread(); + } else { + RefPtr<GMPServiceCreateHelper> createHelper = + new GMPServiceCreateHelper(); + + mozilla::SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), + createHelper, true); + + service = std::move(createHelper->mService); + } + + return service.forget(); + } + + private: + GMPServiceCreateHelper() : Runnable("GMPServiceCreateHelper") {} + + ~GMPServiceCreateHelper() { MOZ_ASSERT(!mService); } + + static already_AddRefed<GeckoMediaPluginService> GetOrCreateOnMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sSingletonService) { + if (XRE_IsParentProcess()) { + RefPtr<GeckoMediaPluginServiceParent> service = + new GeckoMediaPluginServiceParent(); + if (NS_WARN_IF(NS_FAILED(service->Init()))) { + return nullptr; + } + sSingletonService = service; +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // GMPProcessParent should only be instantiated in the parent + // so initialization only needs to be done in the parent. + GMPProcessParent::InitStaticMainThread(); +#endif + } else { + RefPtr<GeckoMediaPluginServiceChild> service = + new GeckoMediaPluginServiceChild(); + if (NS_WARN_IF(NS_FAILED(service->Init()))) { + return nullptr; + } + sSingletonService = service; + } + ClearOnShutdown(&sSingletonService); + } + + RefPtr<GeckoMediaPluginService> service = sSingletonService.get(); + return service.forget(); + } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + mService = GetOrCreateOnMainThread(); + return NS_OK; + } +}; + +already_AddRefed<GeckoMediaPluginService> +GeckoMediaPluginService::GetGeckoMediaPluginService() { + return GMPServiceCreateHelper::GetOrCreate(); +} + +NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, + nsIObserver) + +GeckoMediaPluginService::GeckoMediaPluginService() + : mMutex("GeckoMediaPluginService::mMutex"), + mMainThread(GetMainThreadSerialEventTarget()), + mGMPThreadShutdown(false), + mShuttingDownOnGMPThread(false), + mXPCOMWillShutdown(false) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + if (appInfo) { + nsAutoCString version; + nsAutoCString buildID; + if (NS_SUCCEEDED(appInfo->GetVersion(version)) && + NS_SUCCEEDED(appInfo->GetAppBuildID(buildID))) { + GMP_LOG_DEBUG( + "GeckoMediaPluginService created; Gecko version=%s buildID=%s", + version.get(), buildID.get()); + } + } +} + +GeckoMediaPluginService::~GeckoMediaPluginService() = default; + +NS_IMETHODIMP +GeckoMediaPluginService::RunPluginCrashCallbacks( + uint32_t aPluginId, const nsACString& aPluginName) { + MOZ_ASSERT(NS_IsMainThread()); + GMP_LOG_DEBUG("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId); + + mozilla::UniquePtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers; + { + MutexAutoLock lock(mMutex); + mPluginCrashHelpers.Remove(aPluginId, &helpers); + } + if (!helpers) { + GMP_LOG_DEBUG("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__, + __FUNCTION__, aPluginId); + return NS_OK; + } + + for (const auto& helper : *helpers) { + nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget(); + if (NS_WARN_IF(!window)) { + continue; + } + RefPtr<dom::Document> document(window->GetExtantDoc()); + if (NS_WARN_IF(!document)) { + continue; + } + + dom::PluginCrashedEventInit init; + init.mPluginID = aPluginId; + init.mBubbles = true; + init.mCancelable = true; + init.mGmpPlugin = true; + CopyUTF8toUTF16(aPluginName, init.mPluginName); + init.mSubmittedCrashReport = false; + RefPtr<dom::PluginCrashedEvent> event = + dom::PluginCrashedEvent::Constructor(document, u"PluginCrashed"_ns, + init); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr); + } + + return NS_OK; +} + +nsresult GeckoMediaPluginService::Init() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + MOZ_ASSERT(obsService); + MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver( + this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)); + + // Kick off scanning for plugins + nsCOMPtr<nsIThread> thread; + return GetThread(getter_AddRefs(thread)); +} + +RefPtr<GetCDMParentPromise> GeckoMediaPluginService::GetCDM( + const NodeIdParts& aNodeIdParts, const nsACString& aKeySystem, + GMPCrashHelper* aHelper) { + AssertOnGMPThread(); + + if (mShuttingDownOnGMPThread || aKeySystem.IsEmpty()) { + nsPrintfCString reason( + "%s::%s failed, aKeySystem.IsEmpty() = %d, mShuttingDownOnGMPThread = " + "%d.", + __CLASS__, __FUNCTION__, aKeySystem.IsEmpty(), + mShuttingDownOnGMPThread); + return GetCDMParentPromise::CreateAndReject( + MediaResult(NS_ERROR_FAILURE, reason.get()), __func__); + } + + typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder; + PromiseHolder* rawHolder(new PromiseHolder()); + RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__); + nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); + RefPtr<GMPCrashHelper> helper(aHelper); + nsTArray<nsCString> tags{nsCString{aKeySystem}}; + GetContentParent(aHelper, NodeIdVariant{aNodeIdParts}, + nsLiteralCString(CHROMIUM_CDM_API), tags) + ->Then( + thread, __func__, + [rawHolder, helper, keySystem = nsCString{aKeySystem}]( + RefPtr<GMPContentParent::CloseBlocker> wrapper) { + RefPtr<GMPContentParent> parent = wrapper->mParent; + MOZ_ASSERT( + parent, + "Wrapper should wrap a valid parent if we're in this path."); + UniquePtr<PromiseHolder> holder(rawHolder); + RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM(keySystem); + if (!cdm) { + nsPrintfCString reason( + "%s::%s failed since GetChromiumCDM returns nullptr.", + __CLASS__, __FUNCTION__); + holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), + __func__); + return; + } + if (helper) { + cdm->SetCrashHelper(helper); + } + holder->Resolve(cdm, __func__); + }, + [rawHolder](MediaResult result) { + nsPrintfCString reason( + "%s::%s failed since GetContentParent rejects the promise with " + "reason %s.", + __CLASS__, __FUNCTION__, result.Description().get()); + UniquePtr<PromiseHolder> holder(rawHolder); + holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), + __func__); + }); + + return promise; +} + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +RefPtr<GetGMPContentParentPromise> +GeckoMediaPluginService::GetContentParentForTest() { + AssertOnGMPThread(); + + nsTArray<nsCString> tags; + tags.AppendElement("fake"_ns); + + const nsString origin1 = u"http://example1.com"_ns; + const nsString origin2 = u"http://example2.org"_ns; + const nsString gmpName = u"gmp-fake"_ns; + + NodeIdParts nodeIdParts = NodeIdParts{origin1, origin2, gmpName}; + + if (mShuttingDownOnGMPThread) { + nsPrintfCString reason("%s::%s failed, mShuttingDownOnGMPThread = %d.", + __CLASS__, __FUNCTION__, mShuttingDownOnGMPThread); + return GetGMPContentParentPromise::CreateAndReject( + MediaResult(NS_ERROR_FAILURE, reason.get()), __func__); + } + + using PromiseHolder = MozPromiseHolder<GetGMPContentParentPromise>; + PromiseHolder* rawHolder(new PromiseHolder()); + RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__); + nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); + GetContentParent(nullptr, NodeIdVariant{nodeIdParts}, + nsLiteralCString(CHROMIUM_CDM_API), tags) + ->Then( + thread, __func__, + [rawHolder](const RefPtr<GMPContentParent::CloseBlocker>& wrapper) { + RefPtr<GMPContentParent> parent = wrapper->mParent; + MOZ_ASSERT( + parent, + "Wrapper should wrap a valid parent if we're in this path."); + UniquePtr<PromiseHolder> holder(rawHolder); + if (!parent) { + nsPrintfCString reason("%s::%s failed since no GMPContentParent.", + __CLASS__, __FUNCTION__); + holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), + __func__); + return; + } + holder->Resolve(wrapper, __func__); + }, + [rawHolder](const MediaResult& result) { + nsPrintfCString reason( + "%s::%s failed since GetContentParent rejects the promise with " + "reason %s.", + __CLASS__, __FUNCTION__, result.Description().get()); + UniquePtr<PromiseHolder> holder(rawHolder); + holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), + __func__); + }); + + return promise; +} +#endif + +void GeckoMediaPluginService::ShutdownGMPThread() { + GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__); + nsCOMPtr<nsIThread> gmpThread; + { + MutexAutoLock lock(mMutex); + mGMPThreadShutdown = true; + mGMPThread.swap(gmpThread); + } + + if (gmpThread) { + gmpThread->Shutdown(); + } +} + +/* static */ +nsCOMPtr<nsIAsyncShutdownClient> GeckoMediaPluginService::GetShutdownBarrier() { + nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService(); + if (NS_WARN_IF(!svc)) { + MOZ_ASSERT_UNREACHABLE("No async shutdown service!"); + return nullptr; + } + + nsCOMPtr<nsIAsyncShutdownClient> barrier; + nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier)); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_ASSERT_UNREACHABLE("Could not create shutdown barrier!"); + return nullptr; + } + + MOZ_RELEASE_ASSERT(barrier); + return barrier; +} + +nsresult GeckoMediaPluginService::GMPDispatch(nsIRunnable* event, + uint32_t flags) { + nsCOMPtr<nsIRunnable> r(event); + return GMPDispatch(r.forget(), flags); +} + +nsresult GeckoMediaPluginService::GMPDispatch( + already_AddRefed<nsIRunnable> event, uint32_t flags) { + nsCOMPtr<nsIRunnable> r(event); + nsCOMPtr<nsIThread> thread; + nsresult rv = GetThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return thread->Dispatch(r, flags); +} + +// always call with getter_AddRefs, because it does +NS_IMETHODIMP +GeckoMediaPluginService::GetThread(nsIThread** aThread) { + MOZ_ASSERT(aThread); + + // This can be called from any thread. + MutexAutoLock lock(mMutex); + + return GetThreadLocked(aThread); +} + +// always call with getter_AddRefs, because it does +nsresult GeckoMediaPluginService::GetThreadLocked(nsIThread** aThread) { + MOZ_ASSERT(aThread); + + mMutex.AssertCurrentThreadOwns(); + + if (!mGMPThread) { + // Don't allow the thread to be created after shutdown has started. + if (mGMPThreadShutdown) { + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Tell the thread to initialize plugins + InitializePlugins(mGMPThread); + } + + nsCOMPtr<nsIThread> copy = mGMPThread; + copy.forget(aThread); + + return NS_OK; +} + +already_AddRefed<nsISerialEventTarget> GeckoMediaPluginService::GetGMPThread() { + nsCOMPtr<nsISerialEventTarget> thread; + { + MutexAutoLock lock(mMutex); + thread = mGMPThread; + } + return thread.forget(); +} + +NS_IMETHODIMP +GeckoMediaPluginService::GetGMPVideoDecoder( + GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) { + AssertOnGMPThread(); + NS_ENSURE_ARG(aTags && aTags->Length() > 0); + NS_ENSURE_ARG(aCallback); + + if (mShuttingDownOnGMPThread) { + return NS_ERROR_FAILURE; + } + + GetGMPVideoDecoderCallback* rawCallback = aCallback.release(); + nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); + RefPtr<GMPCrashHelper> helper(aHelper); + GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)}, + nsLiteralCString(GMP_API_VIDEO_DECODER), *aTags) + ->Then( + thread, __func__, + [rawCallback, + helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) { + RefPtr<GMPContentParent> parent = wrapper->mParent; + UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback); + GMPVideoDecoderParent* actor = nullptr; + GMPVideoHostImpl* host = nullptr; + if (parent && NS_SUCCEEDED(parent->GetGMPVideoDecoder(&actor))) { + host = &(actor->Host()); + actor->SetCrashHelper(helper); + } + callback->Done(actor, host); + }, + [rawCallback] { + UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback); + callback->Done(nullptr, nullptr); + }); + + return NS_OK; +} + +NS_IMETHODIMP +GeckoMediaPluginService::GetGMPVideoEncoder( + GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags, + const nsACString& aNodeId, + UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) { + AssertOnGMPThread(); + NS_ENSURE_ARG(aTags && aTags->Length() > 0); + NS_ENSURE_ARG(aCallback); + + if (mShuttingDownOnGMPThread) { + return NS_ERROR_FAILURE; + } + + GetGMPVideoEncoderCallback* rawCallback = aCallback.release(); + nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); + RefPtr<GMPCrashHelper> helper(aHelper); + GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)}, + nsLiteralCString(GMP_API_VIDEO_ENCODER), *aTags) + ->Then( + thread, __func__, + [rawCallback, + helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) { + RefPtr<GMPContentParent> parent = wrapper->mParent; + UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback); + GMPVideoEncoderParent* actor = nullptr; + GMPVideoHostImpl* host = nullptr; + if (parent && NS_SUCCEEDED(parent->GetGMPVideoEncoder(&actor))) { + host = &(actor->Host()); + actor->SetCrashHelper(helper); + } + callback->Done(actor, host); + }, + [rawCallback] { + UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback); + callback->Done(nullptr, nullptr); + }); + + return NS_OK; +} + +void GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId, + GMPCrashHelper* aHelper) { + if (!aHelper) { + return; + } + + MutexAutoLock lock(mMutex); + mPluginCrashHelpers.WithEntryHandle(aPluginId, [&](auto&& entry) { + if (!entry) { + entry.Insert(MakeUnique<nsTArray<RefPtr<GMPCrashHelper>>>()); + } else if (entry.Data()->Contains(aHelper)) { + return; + } + entry.Data()->AppendElement(aHelper); + }); +} + +void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper) { + if (!aHelper) { + return; + } + MutexAutoLock lock(mMutex); + for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) { + nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.UserData(); + if (!helpers->Contains(aHelper)) { + continue; + } + helpers->RemoveElement(aHelper); + MOZ_ASSERT(!helpers->Contains(aHelper)); // Ensure there aren't duplicates. + if (helpers->IsEmpty()) { + iter.Remove(); + } + } +} + +} // namespace gmp +} // namespace mozilla + +#undef __CLASS__ |