summaryrefslogtreecommitdiffstats
path: root/dom/media/gmp/GMPService.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/gmp/GMPService.cpp536
1 files changed, 536 insertions, 0 deletions
diff --git a/dom/media/gmp/GMPService.cpp b/dom/media/gmp/GMPService.cpp
new file mode 100644
index 0000000000..24633a2686
--- /dev/null
+++ b/dom/media/gmp/GMPService.cpp
@@ -0,0 +1,536 @@
+/* -*- 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"
+#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;
+}
+
+#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();
+ service->Init();
+ 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();
+ service->Init();
+ 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();
+ MOZ_RELEASE_ASSERT(svc);
+
+ nsCOMPtr<nsIAsyncShutdownClient> barrier;
+ nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
+
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_RELEASE_ASSERT(barrier);
+ return barrier;
+}
+
+nsresult GeckoMediaPluginService::GMPDispatch(nsIRunnable* event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> r(event);
+ return GMPDispatch(r.forget());
+}
+
+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__