/* -*- 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> GeckoMediaPluginServiceChild::GetSingleton() { MOZ_ASSERT(!XRE_IsParentProcess()); RefPtr<GeckoMediaPluginService> service( GeckoMediaPluginService::GetGeckoMediaPluginService()); #ifdef DEBUG if (service) { nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService; CallQueryInterface(service.get(), getter_AddRefs(chromeService)); MOZ_ASSERT(!chromeService); } #endif return service.forget().downcast<GeckoMediaPluginServiceChild>(); } class GetContentParentFromDone : public GetServiceChildCallback { public: GetContentParentFromDone(GMPCrashHelper* aHelper, const nsACString& aNodeId, const nsCString& aAPI, const nsTArray<nsCString>& aTags, UniquePtr<GetGMPContentParentCallback>&& aCallback) : mHelper(aHelper), mNodeId(aNodeId), mAPI(aAPI), mTags(aTags), mCallback(Move(aCallback)) { } void Done(GMPServiceChild* aGMPServiceChild) override { if (!aGMPServiceChild) { mCallback->Done(nullptr); return; } uint32_t pluginId; nsresult rv; bool ok = aGMPServiceChild->SendSelectGMP(mNodeId, mAPI, mTags, &pluginId, &rv); if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) { mCallback->Done(nullptr); return; } if (mHelper) { RefPtr<GeckoMediaPluginService> gmps(GeckoMediaPluginService::GetGeckoMediaPluginService()); gmps->ConnectCrashHelper(pluginId, mHelper); } nsTArray<base::ProcessId> alreadyBridgedTo; aGMPServiceChild->GetAlreadyBridgedTo(alreadyBridgedTo); base::ProcessId otherProcess; nsCString displayName; ok = aGMPServiceChild->SendLaunchGMP(pluginId, alreadyBridgedTo, &otherProcess, &displayName, &rv); if (!ok || rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) { mCallback->Done(nullptr); return; } RefPtr<GMPContentParent> parent; aGMPServiceChild->GetBridgedGMPContentParent(otherProcess, getter_AddRefs(parent)); if (!alreadyBridgedTo.Contains(otherProcess)) { parent->SetDisplayName(displayName); parent->SetPluginId(pluginId); } mCallback->Done(parent); } private: RefPtr<GMPCrashHelper> mHelper; nsCString mNodeId; nsCString mAPI; const nsTArray<nsCString> mTags; UniquePtr<GetGMPContentParentCallback> mCallback; }; bool GeckoMediaPluginServiceChild::GetContentParentFrom(GMPCrashHelper* aHelper, const nsACString& aNodeId, const nsCString& aAPI, const nsTArray<nsCString>& aTags, UniquePtr<GetGMPContentParentCallback>&& aCallback) { MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); UniquePtr<GetServiceChildCallback> callback( new GetContentParentFromDone(aHelper, aNodeId, aAPI, aTags, Move(aCallback))); GetServiceChild(Move(callback)); return true; } 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<GMPCapability> mCapabilities; }; StaticMutex sGMPCapabilitiesMutex; StaticAutoPtr<nsTArray<GMPCapabilityAndVersion>> 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<GMPCapabilityData>&& aCapabilities) { { // The mutex should unlock before sending the "gmp-changed" observer service notification. StaticMutexAutoLock lock(sGMPCapabilitiesMutex); if (!sGMPCapabilities) { sGMPCapabilities = new nsTArray<GMPCapabilityAndVersion>(); 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<nsIObserverService> obsService = mozilla::services::GetObserverService(); MOZ_ASSERT(obsService); if (obsService) { obsService->NotifyObservers(nullptr, "gmp-changed", nullptr); } } NS_IMETHODIMP GeckoMediaPluginServiceChild::HasPluginForAPI(const nsACString& aAPI, nsTArray<nsCString>* 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; } class GetNodeIdDone : public GetServiceChildCallback { public: GetNodeIdDone(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, const nsAString& aGMPName, bool aInPrivateBrowsing, UniquePtr<GetNodeIdCallback>&& aCallback) : mOrigin(aOrigin), mTopLevelOrigin(aTopLevelOrigin), mGMPName(aGMPName), mInPrivateBrowsing(aInPrivateBrowsing), mCallback(Move(aCallback)) { } void Done(GMPServiceChild* aGMPServiceChild) override { if (!aGMPServiceChild) { mCallback->Done(NS_ERROR_FAILURE, EmptyCString()); return; } nsCString outId; if (!aGMPServiceChild->SendGetGMPNodeId(mOrigin, mTopLevelOrigin, mGMPName, mInPrivateBrowsing, &outId)) { mCallback->Done(NS_ERROR_FAILURE, EmptyCString()); return; } mCallback->Done(NS_OK, outId); } private: nsString mOrigin; nsString mTopLevelOrigin; nsString mGMPName; bool mInPrivateBrowsing; UniquePtr<GetNodeIdCallback> mCallback; }; NS_IMETHODIMP GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, const nsAString& aGMPName, bool aInPrivateBrowsing, UniquePtr<GetNodeIdCallback>&& aCallback) { UniquePtr<GetServiceChildCallback> callback( new GetNodeIdDone(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, Move(aCallback))); GetServiceChild(Move(callback)); 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; } void GeckoMediaPluginServiceChild::GetServiceChild(UniquePtr<GetServiceChildCallback>&& aCallback) { MOZ_ASSERT(!NS_IsMainThread()); if (!mServiceChild) { dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); if (!contentChild) { return; } mGetServiceChildCallbacks.AppendElement(Move(aCallback)); if (mGetServiceChildCallbacks.Length() == 1) { NS_DispatchToMainThread(WrapRunnable(contentChild, &dom::ContentChild::SendCreateGMPService)); } return; } aCallback->Done(mServiceChild.get()); } void GeckoMediaPluginServiceChild::SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild) { mServiceChild = Move(aServiceChild); nsTArray<UniquePtr<GetServiceChildCallback>> getServiceChildCallbacks; getServiceChildCallbacks.SwapElements(mGetServiceChildCallbacks); for (uint32_t i = 0, length = getServiceChildCallbacks.Length(); i < length; ++i) { getServiceChildCallbacks[i]->Done(mServiceChild.get()); } } void GeckoMediaPluginServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent) { if (mServiceChild) { mServiceChild->RemoveGMPContentParent(aGMPContentParent); } } GMPServiceChild::GMPServiceChild() { } GMPServiceChild::~GMPServiceChild() { } PGMPContentParent* GMPServiceChild::AllocPGMPContentParent(Transport* aTransport, ProcessId aOtherPid) { MOZ_ASSERT(!mContentParents.GetWeak(aOtherPid)); nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); MOZ_ASSERT(mainThread); RefPtr<GMPContentParent> parent = new GMPContentParent(); DebugOnly<bool> ok = parent->Open(aTransport, aOtherPid, XRE_GetIOMessageLoop(), mozilla::ipc::ParentSide); MOZ_ASSERT(ok); mContentParents.Put(aOtherPid, parent); return parent; } void GMPServiceChild::GetBridgedGMPContentParent(ProcessId aOtherPid, GMPContentParent** aGMPContentParent) { mContentParents.Get(aOtherPid, aGMPContentParent); } void GMPServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent) { for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) { RefPtr<GMPContentParent>& parent = iter.Data(); if (parent == aGMPContentParent) { iter.Remove(); break; } } } void GMPServiceChild::GetAlreadyBridgedTo(nsTArray<base::ProcessId>& 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<GMPServiceChild>&& aGMPServiceChild, mozilla::ipc::Transport* aTransport, base::ProcessId aOtherPid) : mGMPServiceChild(Move(aGMPServiceChild)), mTransport(aTransport), mOtherPid(aOtherPid) { } NS_IMETHOD Run() override { RefPtr<GeckoMediaPluginServiceChild> gmp = GeckoMediaPluginServiceChild::GetSingleton(); MOZ_ASSERT(!gmp->mServiceChild); if (mGMPServiceChild->Open(mTransport, mOtherPid, XRE_GetIOMessageLoop(), ipc::ChildSide)) { gmp->SetServiceChild(Move(mGMPServiceChild)); } else { gmp->SetServiceChild(nullptr); } return NS_OK; } private: UniquePtr<GMPServiceChild> mGMPServiceChild; mozilla::ipc::Transport* mTransport; base::ProcessId mOtherPid; }; /* static */ PGMPServiceChild* GMPServiceChild::Create(Transport* aTransport, ProcessId aOtherPid) { RefPtr<GeckoMediaPluginServiceChild> gmp = GeckoMediaPluginServiceChild::GetSingleton(); MOZ_ASSERT(!gmp->mServiceChild); UniquePtr<GMPServiceChild> serviceChild(new GMPServiceChild()); nsCOMPtr<nsIThread> gmpThread; nsresult rv = gmp->GetThread(getter_AddRefs(gmpThread)); NS_ENSURE_SUCCESS(rv, nullptr); GMPServiceChild* result = serviceChild.get(); rv = gmpThread->Dispatch(new OpenPGMPServiceChild(Move(serviceChild), aTransport, aOtherPid), NS_DISPATCH_NORMAL); if (NS_FAILED(rv)) { return nullptr; } return result; } } // namespace gmp } // namespace mozilla