/* -*- 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" 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& aNodeId, const nsCString& aAPI, const nsTArray& aTags) { MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); MozPromiseHolder* rawHolder = new MozPromiseHolder(); RefPtr promise = rawHolder->Ensure(__func__); RefPtr thread(GetAbstractGMPThread()); nsCString nodeId(aNodeId); 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; bool ok = child->SendLaunchGMP(nodeId, api, tags, alreadyBridgedTo, &pluginId, &otherProcess, &displayName, &endpoint, &rv); 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)) { LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP failed rv=%" PRIu32, static_cast(rv))); holder->Reject(rv, __func__); return; } RefPtr parent = child->GetBridgedGMPContentParent(otherProcess, Move(endpoint)); if (!alreadyBridgedTo.Contains(otherProcess)) { parent->SetDisplayName(displayName); parent->SetPluginId(pluginId); } RefPtr blocker(new GMPContentParent::CloseBlocker(parent)); holder->Resolve(blocker, __func__); }, [rawHolder](nsresult rv) { UniquePtr> holder(rawHolder); holder->Reject(rv, __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(Move(cap)); } } nsCString ToString() const { nsCString s; s.Append(mName); s.Append(" version="); s.Append(mVersion); s.Append(" tags=["); nsCString tags; for (const GMPCapability& cap : mCapabilities) { if (!tags.IsEmpty()) { tags.Append(" "); } tags.Append(cap.mAPIName); for (const nsCString& tag : cap.mAPITags) { tags.Append(":"); tags.Append(tag); } } s.Append(tags); s.Append("]"); 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.Append(", "); } 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); } } 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(NS_GetCurrentThread() == mGMPThread); 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(); } return NS_OK; } RefPtr GeckoMediaPluginServiceChild::GetServiceChild() { MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); if (!mServiceChild) { 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) { NS_DispatchToMainThread(WrapRunnable(contentChild, &dom::ContentChild::SendCreateGMPService)); } return promise; } return GetServiceChildPromise::CreateAndResolve(mServiceChild.get(), __func__); } void GeckoMediaPluginServiceChild::SetServiceChild(UniquePtr&& aServiceChild) { MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); mServiceChild = Move(aServiceChild); nsTArray> holders; holders.SwapElements(mGetServiceChildPromises); for (MozPromiseHolder& holder : holders) { holder.Resolve(mServiceChild.get(), __func__); } } void GeckoMediaPluginServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent) { MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); if (mServiceChild) { mServiceChild->RemoveGMPContentParent(aGMPContentParent); } } 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()); nsCOMPtr mainThread = do_GetMainThread(); MOZ_ASSERT(mainThread); 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) : mGMPServiceChild(Move(aGMPServiceChild)), mEndpoint(Move(aEndpoint)) { } NS_IMETHOD Run() override { RefPtr gmp = GeckoMediaPluginServiceChild::GetSingleton(); MOZ_ASSERT(!gmp->mServiceChild); if (mEndpoint.Bind(mGMPServiceChild.get())) { gmp->SetServiceChild(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(Move(serviceChild), Move(aGMPService)), NS_DISPATCH_NORMAL); return NS_SUCCEEDED(rv); } } // namespace gmp } // namespace mozilla