/* -*- 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 "GMPServiceChild.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "mozIGeckoMediaPluginService.h" #include "mozIGeckoMediaPluginChromeService.h" #include "nsCOMPtr.h" #include "GMPParent.h" #include "GMPContentParent.h" #include "nsXPCOMPrivate.h" #include "mozilla/SyncRunnable.h" #include "mozilla/StaticMutex.h" #include "runnable_utils.h" #include "base/task.h" #include "nsIObserverService.h" #include "nsComponentManagerUtils.h" #include "mozilla/SystemGroup.h" namespace mozilla { #ifdef LOG #undef LOG #endif #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) #ifdef __CLASS__ #undef __CLASS__ #endif #define __CLASS__ "GMPService" namespace gmp { already_AddRefed GeckoMediaPluginServiceChild::GetSingleton() { MOZ_ASSERT(!XRE_IsParentProcess()); RefPtr service( GeckoMediaPluginService::GetGeckoMediaPluginService()); #ifdef DEBUG if (service) { nsCOMPtr chromeService; CallQueryInterface(service.get(), getter_AddRefs(chromeService)); MOZ_ASSERT(!chromeService); } #endif return service.forget().downcast(); } RefPtr GeckoMediaPluginServiceChild::GetContentParent( GMPCrashHelper* aHelper, const nsACString& aNodeIdString, const nsCString& aAPI, const nsTArray& aTags) { MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); MozPromiseHolder* rawHolder = new MozPromiseHolder(); RefPtr promise = rawHolder->Ensure(__func__); RefPtr thread(GetAbstractGMPThread()); nsCString nodeIdString(aNodeIdString); nsCString api(aAPI); nsTArray tags(aTags); RefPtr helper(aHelper); RefPtr self(this); GetServiceChild()->Then( thread, __func__, [self, nodeIdString, api, tags, helper, rawHolder](GMPServiceChild* child) { UniquePtr> holder( rawHolder); nsresult rv; nsTArray alreadyBridgedTo; child->GetAlreadyBridgedTo(alreadyBridgedTo); base::ProcessId otherProcess; nsCString displayName; uint32_t pluginId = 0; ipc::Endpoint endpoint; nsCString errorDescription = NS_LITERAL_CSTRING(""); bool ok = child->SendLaunchGMP( nodeIdString, api, tags, alreadyBridgedTo, &pluginId, &otherProcess, &displayName, &endpoint, &rv, &errorDescription); if (helper && pluginId) { // Note: Even if the launch failed, we need to connect the crash // helper so that if the launch failed due to the plugin crashing, // we can report the crash via the crash reporter. The crash // handling notification will arrive shortly if the launch failed // due to the plugin crashing. self->ConnectCrashHelper(pluginId, helper); } if (!ok || NS_FAILED(rv)) { MediaResult error( rv, nsPrintfCString( "GeckoMediaPluginServiceChild::GetContentParent " "SendLaunchGMPForNodeId failed with description (%s)", errorDescription.get())); LOGD(("%s", error.Description().get())); holder->Reject(error, __func__); return; } RefPtr parent = child->GetBridgedGMPContentParent( otherProcess, std::move(endpoint)); if (!alreadyBridgedTo.Contains(otherProcess)) { parent->SetDisplayName(displayName); parent->SetPluginId(pluginId); } RefPtr blocker( new GMPContentParent::CloseBlocker(parent)); holder->Resolve(blocker, __func__); }, [rawHolder](MediaResult result) { UniquePtr> holder( rawHolder); holder->Reject(result, __func__); }); return promise; } RefPtr GeckoMediaPluginServiceChild::GetContentParent( GMPCrashHelper* aHelper, const NodeId& aNodeId, const nsCString& aAPI, const nsTArray& aTags) { MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); MozPromiseHolder* rawHolder = new MozPromiseHolder(); RefPtr promise = rawHolder->Ensure(__func__); RefPtr thread(GetAbstractGMPThread()); NodeIdData nodeId(aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName); nsCString api(aAPI); nsTArray tags(aTags); RefPtr helper(aHelper); RefPtr self(this); GetServiceChild()->Then( thread, __func__, [self, nodeId, api, tags, helper, rawHolder](GMPServiceChild* child) { UniquePtr> holder( rawHolder); nsresult rv; nsTArray alreadyBridgedTo; child->GetAlreadyBridgedTo(alreadyBridgedTo); base::ProcessId otherProcess; nsCString displayName; uint32_t pluginId = 0; ipc::Endpoint endpoint; nsCString errorDescription = NS_LITERAL_CSTRING(""); bool ok = child->SendLaunchGMPForNodeId( nodeId, api, tags, alreadyBridgedTo, &pluginId, &otherProcess, &displayName, &endpoint, &rv, &errorDescription); if (helper && pluginId) { // Note: Even if the launch failed, we need to connect the crash // helper so that if the launch failed due to the plugin crashing, // we can report the crash via the crash reporter. The crash // handling notification will arrive shortly if the launch failed // due to the plugin crashing. self->ConnectCrashHelper(pluginId, helper); } if (!ok || NS_FAILED(rv)) { MediaResult error( rv, nsPrintfCString( "GeckoMediaPluginServiceChild::GetContentParent " "SendLaunchGMPForNodeId failed with description (%s)", errorDescription.get())); LOGD(("%s", error.Description().get())); holder->Reject(error, __func__); return; } RefPtr parent = child->GetBridgedGMPContentParent( otherProcess, std::move(endpoint)); if (!alreadyBridgedTo.Contains(otherProcess)) { parent->SetDisplayName(displayName); parent->SetPluginId(pluginId); } RefPtr blocker( new GMPContentParent::CloseBlocker(parent)); holder->Resolve(blocker, __func__); }, [rawHolder](MediaResult result) { UniquePtr> holder( rawHolder); holder->Reject(result, __func__); }); return promise; } typedef mozilla::dom::GMPCapabilityData GMPCapabilityData; typedef mozilla::dom::GMPAPITags GMPAPITags; struct GMPCapabilityAndVersion { explicit GMPCapabilityAndVersion(const GMPCapabilityData& aCapabilities) : mName(aCapabilities.name()), mVersion(aCapabilities.version()) { for (const GMPAPITags& tags : aCapabilities.capabilities()) { GMPCapability cap; cap.mAPIName = tags.api(); for (const nsCString& tag : tags.tags()) { cap.mAPITags.AppendElement(tag); } mCapabilities.AppendElement(std::move(cap)); } } nsCString ToString() const { nsCString s; s.Append(mName); s.AppendLiteral(" version="); s.Append(mVersion); s.AppendLiteral(" tags=["); nsCString tags; for (const GMPCapability& cap : mCapabilities) { if (!tags.IsEmpty()) { tags.AppendLiteral(" "); } tags.Append(cap.mAPIName); for (const nsCString& tag : cap.mAPITags) { tags.AppendLiteral(":"); tags.Append(tag); } } s.Append(tags); s.AppendLiteral("]"); return s; } nsCString mName; nsCString mVersion; nsTArray mCapabilities; }; StaticMutex sGMPCapabilitiesMutex; StaticAutoPtr> sGMPCapabilities; static nsCString GMPCapabilitiesToString() { nsCString s; for (const GMPCapabilityAndVersion& gmp : *sGMPCapabilities) { if (!s.IsEmpty()) { s.AppendLiteral(", "); } s.Append(gmp.ToString()); } return s; } /* static */ void GeckoMediaPluginServiceChild::UpdateGMPCapabilities( nsTArray&& aCapabilities) { { // The mutex should unlock before sending the "gmp-changed" observer service // notification. StaticMutexAutoLock lock(sGMPCapabilitiesMutex); if (!sGMPCapabilities) { sGMPCapabilities = new nsTArray(); ClearOnShutdown(&sGMPCapabilities); } sGMPCapabilities->Clear(); for (const GMPCapabilityData& plugin : aCapabilities) { sGMPCapabilities->AppendElement(GMPCapabilityAndVersion(plugin)); } LOGD(("UpdateGMPCapabilities {%s}", GMPCapabilitiesToString().get())); } // Fire a notification so that any MediaKeySystemAccess // requests waiting on a CDM to download will retry. nsCOMPtr obsService = mozilla::services::GetObserverService(); MOZ_ASSERT(obsService); if (obsService) { obsService->NotifyObservers(nullptr, "gmp-changed", nullptr); } } void GeckoMediaPluginServiceChild::BeginShutdown() { MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); mShuttingDownOnGMPThread = true; } NS_IMETHODIMP GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI, nsTArray* aTags, bool* aHasPlugin) { StaticMutexAutoLock lock(sGMPCapabilitiesMutex); if (!sGMPCapabilities) { *aHasPlugin = false; return NS_OK; } nsCString api(aAPI); for (const GMPCapabilityAndVersion& plugin : *sGMPCapabilities) { if (GMPCapability::Supports(plugin.mCapabilities, api, *aTags)) { *aHasPlugin = true; return NS_OK; } } *aHasPlugin = false; return NS_OK; } NS_IMETHODIMP GeckoMediaPluginServiceChild::GetNodeId( const nsAString& aOrigin, const nsAString& aTopLevelOrigin, const nsAString& aGMPName, UniquePtr&& aCallback) { MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); GetNodeIdCallback* rawCallback = aCallback.release(); RefPtr thread(GetAbstractGMPThread()); nsString origin(aOrigin); nsString topLevelOrigin(aTopLevelOrigin); nsString gmpName(aGMPName); GetServiceChild()->Then( thread, __func__, [rawCallback, origin, topLevelOrigin, gmpName](GMPServiceChild* child) { UniquePtr callback(rawCallback); nsCString outId; if (!child->SendGetGMPNodeId(origin, topLevelOrigin, gmpName, &outId)) { callback->Done(NS_ERROR_FAILURE, EmptyCString()); return; } callback->Done(NS_OK, outId); }, [rawCallback](nsresult rv) { UniquePtr callback(rawCallback); callback->Done(NS_ERROR_FAILURE, EmptyCString()); }); return NS_OK; } NS_IMETHODIMP GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aSomeData) { LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic)); if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) { if (mServiceChild) { mozilla::SyncRunnable::DispatchToThread( mGMPThread, WrapRunnable(mServiceChild.get(), &PGMPServiceChild::Close)); mServiceChild = nullptr; } ShutdownGMPThread(); } else if (!strcmp(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, aTopic)) { mXPCOMWillShutdown = true; } return NS_OK; } RefPtr GeckoMediaPluginServiceChild::GetServiceChild() { MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); if (!mServiceChild) { if (mShuttingDownOnGMPThread) { // We have begun shutdown. Don't allow a new connection to the main // process to be instantiated. This also prevents new plugins being // instantiated. return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); if (!contentChild) { return GetServiceChildPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } MozPromiseHolder* holder = mGetServiceChildPromises.AppendElement(); RefPtr promise = holder->Ensure(__func__); if (mGetServiceChildPromises.Length() == 1) { nsCOMPtr r = WrapRunnable(contentChild, &dom::ContentChild::SendCreateGMPService); SystemGroup::Dispatch(TaskCategory::Other, r.forget()); } return promise; } return GetServiceChildPromise::CreateAndResolve(mServiceChild.get(), __func__); } void GeckoMediaPluginServiceChild::SetServiceChild( UniquePtr&& aServiceChild) { MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); mServiceChild = std::move(aServiceChild); nsTArray> holders; holders.SwapElements(mGetServiceChildPromises); for (MozPromiseHolder& holder : holders) { holder.Resolve(mServiceChild.get(), __func__); } } void GeckoMediaPluginServiceChild::RemoveGMPContentParent( GMPContentParent* aGMPContentParent) { MOZ_ASSERT(mGMPThread->EventTarget()->IsOnCurrentThread()); if (mServiceChild) { mServiceChild->RemoveGMPContentParent(aGMPContentParent); if (mShuttingDownOnGMPThread && !mServiceChild->HaveContentParents()) { mServiceChild->Close(); mServiceChild = nullptr; } } } GMPServiceChild::GMPServiceChild() {} GMPServiceChild::~GMPServiceChild() {} already_AddRefed GMPServiceChild::GetBridgedGMPContentParent( ProcessId aOtherPid, ipc::Endpoint&& endpoint) { RefPtr parent; mContentParents.Get(aOtherPid, getter_AddRefs(parent)); if (parent) { return parent.forget(); } MOZ_ASSERT(aOtherPid == endpoint.OtherPid()); parent = new GMPContentParent(); DebugOnly ok = endpoint.Bind(parent); MOZ_ASSERT(ok); mContentParents.Put(aOtherPid, parent); return parent.forget(); } void GMPServiceChild::RemoveGMPContentParent( GMPContentParent* aGMPContentParent) { for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) { RefPtr& parent = iter.Data(); if (parent == aGMPContentParent) { iter.Remove(); break; } } } void GMPServiceChild::GetAlreadyBridgedTo( nsTArray& aAlreadyBridgedTo) { aAlreadyBridgedTo.SetCapacity(mContentParents.Count()); for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) { const uint64_t& id = iter.Key(); aAlreadyBridgedTo.AppendElement(id); } } class OpenPGMPServiceChild : public mozilla::Runnable { public: OpenPGMPServiceChild(UniquePtr&& aGMPServiceChild, ipc::Endpoint&& aEndpoint) : Runnable("gmp::OpenPGMPServiceChild"), mGMPServiceChild(std::move(aGMPServiceChild)), mEndpoint(std::move(aEndpoint)) {} NS_IMETHOD Run() override { RefPtr gmp = GeckoMediaPluginServiceChild::GetSingleton(); MOZ_ASSERT(!gmp->mServiceChild); if (mEndpoint.Bind(mGMPServiceChild.get())) { gmp->SetServiceChild(std::move(mGMPServiceChild)); } else { gmp->SetServiceChild(nullptr); } return NS_OK; } private: UniquePtr mGMPServiceChild; ipc::Endpoint mEndpoint; }; /* static */ bool GMPServiceChild::Create(Endpoint&& aGMPService) { RefPtr gmp = GeckoMediaPluginServiceChild::GetSingleton(); MOZ_ASSERT(!gmp->mServiceChild); UniquePtr serviceChild(new GMPServiceChild()); nsCOMPtr gmpThread; nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread)); NS_ENSURE_SUCCESS(rv, false); rv = gmpThread->Dispatch( new OpenPGMPServiceChild(std::move(serviceChild), std::move(aGMPService)), NS_DISPATCH_NORMAL); return NS_SUCCEEDED(rv); } ipc::IPCResult GMPServiceChild::RecvBeginShutdown() { RefPtr service = GeckoMediaPluginServiceChild::GetSingleton(); MOZ_ASSERT(service && service->mServiceChild.get() == this); if (service) { service->BeginShutdown(); } return IPC_OK(); } bool GMPServiceChild::HaveContentParents() const { return mContentParents.Count() > 0; } } // namespace gmp } // namespace mozilla