gecko-dev/dom/media/gmp/GMPServiceChild.cpp
Gerald Squelart 9231dfc6ed Bug 1356516 - Close channel before destroying GMPServiceChild - r=billm
mServiceChild is a UniquePtr, so nulling it will destroy the GMPServiceChild,
which will destroy the associated message channel. So we need to close the
channel first before it gets destroyed. (Just as it was correctly done in
Observe() above.)

MozReview-Commit-ID: INuHN2Is7bC

--HG--
extra : rebase_source : 2a927bb06dd8fb4f1114dc0b64025cbdddc7c133
2017-04-19 15:48:32 +12:00

556 lines
18 KiB
C++

/* -*- 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>();
}
RefPtr<GetGMPContentParentPromise>
GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
const nsACString& aNodeIdString,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
MozPromiseHolder<GetGMPContentParentPromise>* rawHolder = new MozPromiseHolder<GetGMPContentParentPromise>();
RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
nsCString nodeIdString(aNodeIdString);
nsCString api(aAPI);
nsTArray<nsCString> tags(aTags);
RefPtr<GMPCrashHelper> helper(aHelper);
RefPtr<GeckoMediaPluginServiceChild> self(this);
GetServiceChild()->Then(
thread,
__func__,
[self, nodeIdString, api, tags, helper, rawHolder](GMPServiceChild* child) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
nsresult rv;
nsTArray<base::ProcessId> alreadyBridgedTo;
child->GetAlreadyBridgedTo(alreadyBridgedTo);
base::ProcessId otherProcess;
nsCString displayName;
uint32_t pluginId = 0;
ipc::Endpoint<PGMPContentParent> endpoint;
bool ok = child->SendLaunchGMP(nodeIdString,
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=0x%x",
static_cast<uint32_t>(rv)));
holder->Reject(rv, __func__);
return;
}
RefPtr<GMPContentParent> parent =
child->GetBridgedGMPContentParent(otherProcess, Move(endpoint));
if (!alreadyBridgedTo.Contains(otherProcess)) {
parent->SetDisplayName(displayName);
parent->SetPluginId(pluginId);
}
RefPtr<GMPContentParent::CloseBlocker> blocker(
new GMPContentParent::CloseBlocker(parent));
holder->Resolve(blocker, __func__);
},
[rawHolder](nsresult rv) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
holder->Reject(rv, __func__);
});
return promise;
}
RefPtr<GetGMPContentParentPromise>
GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
const NodeId& aNodeId,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
MozPromiseHolder<GetGMPContentParentPromise>* rawHolder =
new MozPromiseHolder<GetGMPContentParentPromise>();
RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
NodeIdData nodeId(aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName);
nsCString api(aAPI);
nsTArray<nsCString> tags(aTags);
RefPtr<GMPCrashHelper> helper(aHelper);
RefPtr<GeckoMediaPluginServiceChild> self(this);
GetServiceChild()->Then(thread, __func__,
[self, nodeId, api, tags, helper, rawHolder](GMPServiceChild* child) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
nsresult rv;
nsTArray<base::ProcessId> alreadyBridgedTo;
child->GetAlreadyBridgedTo(alreadyBridgedTo);
base::ProcessId otherProcess;
nsCString displayName;
uint32_t pluginId = 0;
ipc::Endpoint<PGMPContentParent> endpoint;
bool ok = child->SendLaunchGMPForNodeId(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<uint32_t>(rv)));
holder->Reject(rv, __func__);
return;
}
RefPtr<GMPContentParent> parent = child->GetBridgedGMPContentParent(otherProcess,
Move(endpoint));
if (!alreadyBridgedTo.Contains(otherProcess)) {
parent->SetDisplayName(displayName);
parent->SetPluginId(pluginId);
}
RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(parent));
holder->Resolve(blocker, __func__);
},
[rawHolder](nsresult rv) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> 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<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);
}
}
void
GeckoMediaPluginServiceChild::BeginShutdown()
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
mShuttingDownOnGMPThread = true;
}
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;
}
NS_IMETHODIMP
GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin,
const nsAString& aTopLevelOrigin,
const nsAString& aGMPName,
UniquePtr<GetNodeIdCallback>&& aCallback)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
GetNodeIdCallback* rawCallback = aCallback.release();
RefPtr<AbstractThread> thread(GetAbstractGMPThread());
nsString origin(aOrigin);
nsString topLevelOrigin(aTopLevelOrigin);
nsString gmpName(aGMPName);
GetServiceChild()->Then(thread, __func__,
[rawCallback, origin, topLevelOrigin, gmpName](GMPServiceChild* child) {
UniquePtr<GetNodeIdCallback> 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<GetNodeIdCallback> 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::GetServiceChildPromise>
GeckoMediaPluginServiceChild::GetServiceChild()
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
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<GetServiceChildPromise>* holder = mGetServiceChildPromises.AppendElement();
RefPtr<GetServiceChildPromise> promise = holder->Ensure(__func__);
if (mGetServiceChildPromises.Length() == 1) {
nsCOMPtr<nsIRunnable> r = WrapRunnable(
contentChild, &dom::ContentChild::SendCreateGMPService);
SystemGroup::Dispatch("SendCreateGMPService", TaskCategory::Other, r.forget());
}
return promise;
}
return GetServiceChildPromise::CreateAndResolve(mServiceChild.get(), __func__);
}
void
GeckoMediaPluginServiceChild::SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
mServiceChild = Move(aServiceChild);
nsTArray<MozPromiseHolder<GetServiceChildPromise>> holders;
holders.SwapElements(mGetServiceChildPromises);
for (MozPromiseHolder<GetServiceChildPromise>& holder : holders) {
holder.Resolve(mServiceChild.get(), __func__);
}
}
void
GeckoMediaPluginServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent)
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
if (mServiceChild) {
mServiceChild->RemoveGMPContentParent(aGMPContentParent);
if (mShuttingDownOnGMPThread && !mServiceChild->HaveContentParents()) {
mServiceChild->Close();
mServiceChild = nullptr;
}
}
}
GMPServiceChild::GMPServiceChild()
{
}
GMPServiceChild::~GMPServiceChild()
{
}
already_AddRefed<GMPContentParent>
GMPServiceChild::GetBridgedGMPContentParent(ProcessId aOtherPid,
ipc::Endpoint<PGMPContentParent>&& endpoint)
{
RefPtr<GMPContentParent> parent;
mContentParents.Get(aOtherPid, getter_AddRefs(parent));
if (parent) {
return parent.forget();
}
MOZ_ASSERT(aOtherPid == endpoint.OtherPid());
parent = new GMPContentParent();
DebugOnly<bool> 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<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,
ipc::Endpoint<PGMPServiceChild>&& aEndpoint)
: mGMPServiceChild(Move(aGMPServiceChild)),
mEndpoint(Move(aEndpoint))
{
}
NS_IMETHOD Run() override
{
RefPtr<GeckoMediaPluginServiceChild> 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<GMPServiceChild> mGMPServiceChild;
ipc::Endpoint<PGMPServiceChild> mEndpoint;
};
/* static */
bool
GMPServiceChild::Create(Endpoint<PGMPServiceChild>&& aGMPService)
{
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, false);
rv = gmpThread->Dispatch(new OpenPGMPServiceChild(Move(serviceChild),
Move(aGMPService)),
NS_DISPATCH_NORMAL);
return NS_SUCCEEDED(rv);
}
ipc::IPCResult
GMPServiceChild::RecvBeginShutdown()
{
RefPtr<GeckoMediaPluginServiceChild> 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