mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
0901ad8256
When we shutdown the browser while the GMPService is active we can end up leaking a GMPParent, GeckoMediaPluginServiceParent, and a Runnable. I tracked this down to the runnable dispatched to the GMP thread in GMPParent::ChildTerminated(). The dispatch of this runnable is failing as we are dispatching the runnable to a reference of the GMP thread which we have previously acquired, but that thread is now shutdown. So the dispatch fails, and if you look in nsThread::DispatchInternal() you'll see that we deliberately leak the runnable if dispatch fails! The runnable leaking means that the references it holds to the GMPParent and the GMP service parent leak. The solution in this patch is to not cache a reference to the GMP thread on the GMPParent; instead we re-request the GMP thread from the GMPService when we want it. This means that in the case where the browser is shutting down, GMPParent::GMPThread() will return null, and we'll not leak the runnable. We'll then follow the (hacky) shutdown path added in bug 1163239. We also need to change GMPParent::GMPThread() and GMPContentParent::GMPThread() to return a reference to the GMP thread with a refcount held on it, in order to ensure we don't race with the GMP service shutting down the GMP thread while we're trying to dispatch to in on shutdown. MozReview-Commit-ID: CXv9VZqTRzY --HG-- extra : rebase_source : e507e48ee633cad8911287fb7296bbb1679a7bcb
993 lines
27 KiB
C++
993 lines
27 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 "GMPParent.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsIRunnable.h"
|
|
#include "nsIWritablePropertyBag2.h"
|
|
#include "mozIGeckoMediaPluginService.h"
|
|
#include "mozilla/AbstractThread.h"
|
|
#include "mozilla/ipc/GeckoChildProcessHost.h"
|
|
#include "mozilla/SSE.h"
|
|
#include "mozilla/SyncRunnable.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsIObserverService.h"
|
|
#include "GMPTimerParent.h"
|
|
#include "runnable_utils.h"
|
|
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
|
|
#include "mozilla/SandboxInfo.h"
|
|
#endif
|
|
#include "GMPContentParent.h"
|
|
#include "MediaPrefs.h"
|
|
#include "VideoUtils.h"
|
|
|
|
using mozilla::ipc::GeckoChildProcessHost;
|
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
#include "nsPrintfCString.h"
|
|
#include "mozilla/ipc/CrashReporterHost.h"
|
|
using CrashReporter::AnnotationTable;
|
|
using CrashReporter::GetIDFromMinidump;
|
|
#endif
|
|
|
|
#include "mozilla/Telemetry.h"
|
|
|
|
#ifdef XP_WIN
|
|
#include "WMFDecoderModule.h"
|
|
#endif
|
|
|
|
#include "mozilla/dom/WidevineCDMManifestBinding.h"
|
|
#include "widevine-adapter/WidevineAdapter.h"
|
|
#include "ChromiumCDMAdapter.h"
|
|
|
|
namespace mozilla {
|
|
|
|
#undef LOG
|
|
#undef LOGD
|
|
|
|
extern LogModule* GetGMPLog();
|
|
#define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
|
|
#define LOGD(x, ...) LOG(mozilla::LogLevel::Debug, "GMPParent[%p|childPid=%d] " x, this, mChildPid, ##__VA_ARGS__)
|
|
|
|
#ifdef __CLASS__
|
|
#undef __CLASS__
|
|
#endif
|
|
#define __CLASS__ "GMPParent"
|
|
|
|
namespace gmp {
|
|
|
|
GMPParent::GMPParent(AbstractThread* aMainThread)
|
|
: mState(GMPStateNotLoaded)
|
|
, mProcess(nullptr)
|
|
, mDeleteProcessOnlyOnUnload(false)
|
|
, mAbnormalShutdownInProgress(false)
|
|
, mIsBlockingDeletion(false)
|
|
, mCanDecrypt(false)
|
|
, mGMPContentChildCount(0)
|
|
, mChildPid(0)
|
|
, mHoldingSelfRef(false)
|
|
, mMainThread(aMainThread)
|
|
{
|
|
mPluginId = GeckoChildProcessHost::GetUniqueID();
|
|
LOGD("GMPParent ctor id=%u", mPluginId);
|
|
}
|
|
|
|
GMPParent::~GMPParent()
|
|
{
|
|
LOGD("GMPParent dtor id=%u", mPluginId);
|
|
MOZ_ASSERT(!mProcess);
|
|
}
|
|
|
|
nsresult
|
|
GMPParent::CloneFrom(const GMPParent* aOther)
|
|
{
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory");
|
|
|
|
mService = aOther->mService;
|
|
mDirectory = aOther->mDirectory;
|
|
mName = aOther->mName;
|
|
mVersion = aOther->mVersion;
|
|
mDescription = aOther->mDescription;
|
|
mDisplayName = aOther->mDisplayName;
|
|
#ifdef XP_WIN
|
|
mLibs = aOther->mLibs;
|
|
#endif
|
|
for (const GMPCapability& cap : aOther->mCapabilities) {
|
|
mCapabilities.AppendElement(cap);
|
|
}
|
|
mAdapter = aOther->mAdapter;
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<GenericPromise>
|
|
GMPParent::Init(GeckoMediaPluginServiceParent* aService, nsIFile* aPluginDir)
|
|
{
|
|
MOZ_ASSERT(aPluginDir);
|
|
MOZ_ASSERT(aService);
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
|
|
mService = aService;
|
|
mDirectory = aPluginDir;
|
|
|
|
// aPluginDir is <profile-dir>/<gmp-plugin-id>/<version>
|
|
// where <gmp-plugin-id> should be gmp-gmpopenh264
|
|
nsCOMPtr<nsIFile> parent;
|
|
nsresult rv = aPluginDir->GetParent(getter_AddRefs(parent));
|
|
if (NS_FAILED(rv)) {
|
|
return GenericPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
nsAutoString parentLeafName;
|
|
rv = parent->GetLeafName(parentLeafName);
|
|
if (NS_FAILED(rv)) {
|
|
return GenericPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
LOGD("%s: for %s", __FUNCTION__, NS_LossyConvertUTF16toASCII(parentLeafName).get());
|
|
|
|
MOZ_ASSERT(parentLeafName.Length() > 4);
|
|
mName = Substring(parentLeafName, 4);
|
|
|
|
return ReadGMPMetaData();
|
|
}
|
|
|
|
void
|
|
GMPParent::Crash()
|
|
{
|
|
if (mState != GMPStateNotLoaded) {
|
|
Unused << SendCrashPluginNow();
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
GMPParent::LoadProcess()
|
|
{
|
|
MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
MOZ_ASSERT(mState == GMPStateNotLoaded);
|
|
|
|
nsAutoString path;
|
|
if (NS_FAILED(mDirectory->GetPath(path))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
LOGD("%s: for %s", __FUNCTION__, NS_ConvertUTF16toUTF8(path).get());
|
|
|
|
if (!mProcess) {
|
|
mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
|
|
if (!mProcess->Launch(30 * 1000)) {
|
|
LOGD("%s: Failed to launch new child process", __FUNCTION__);
|
|
mProcess->Delete();
|
|
mProcess = nullptr;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mChildPid = base::GetProcId(mProcess->GetChildProcessHandle());
|
|
LOGD("%s: Launched new child process", __FUNCTION__);
|
|
|
|
bool opened = Open(mProcess->GetChannel(),
|
|
base::GetProcId(mProcess->GetChildProcessHandle()));
|
|
if (!opened) {
|
|
LOGD("%s: Failed to open channel to new child process", __FUNCTION__);
|
|
mProcess->Delete();
|
|
mProcess = nullptr;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
LOGD("%s: Opened channel to new child process", __FUNCTION__);
|
|
|
|
#ifdef XP_WIN
|
|
if (!mLibs.IsEmpty()) {
|
|
bool ok = SendPreloadLibs(mLibs);
|
|
if (!ok) {
|
|
LOGD("%s: Failed to send preload-libs to child process", __FUNCTION__);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
LOGD("%s: Sent preload-libs ('%s') to child process", __FUNCTION__, mLibs.get());
|
|
}
|
|
#endif
|
|
|
|
// Intr call to block initialization on plugin load.
|
|
if (!CallStartPlugin(mAdapter)) {
|
|
LOGD("%s: Failed to send start to child process", __FUNCTION__);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
LOGD("%s: Sent StartPlugin to child process", __FUNCTION__);
|
|
}
|
|
|
|
mState = GMPStateLoaded;
|
|
|
|
// Hold a self ref while the child process is alive. This ensures that
|
|
// during shutdown the GMPParent stays alive long enough to
|
|
// terminate the child process.
|
|
MOZ_ASSERT(!mHoldingSelfRef);
|
|
mHoldingSelfRef = true;
|
|
AddRef();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
GMPParent::RecvPGMPContentChildDestroyed()
|
|
{
|
|
--mGMPContentChildCount;
|
|
if (!IsUsed()) {
|
|
CloseIfUnused();
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
void
|
|
GMPParent::CloseIfUnused()
|
|
{
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
LOGD("%s", __FUNCTION__);
|
|
|
|
if ((mDeleteProcessOnlyOnUnload ||
|
|
mState == GMPStateLoaded ||
|
|
mState == GMPStateUnloading) &&
|
|
!IsUsed()) {
|
|
// Ensure all timers are killed.
|
|
for (uint32_t i = mTimers.Length(); i > 0; i--) {
|
|
mTimers[i - 1]->Shutdown();
|
|
}
|
|
|
|
// Shutdown GMPStorage. Given that all protocol actors must be shutdown
|
|
// (!Used() is true), all storage operations should be complete.
|
|
for (size_t i = mStorage.Length(); i > 0; i--) {
|
|
mStorage[i - 1]->Shutdown();
|
|
}
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
void
|
|
GMPParent::CloseActive(bool aDieWhenUnloaded)
|
|
{
|
|
LOGD("%s: state %d", __FUNCTION__, mState);
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
|
|
if (aDieWhenUnloaded) {
|
|
mDeleteProcessOnlyOnUnload = true; // don't allow this to go back...
|
|
}
|
|
if (mState == GMPStateLoaded) {
|
|
mState = GMPStateUnloading;
|
|
}
|
|
if (mState != GMPStateNotLoaded && IsUsed()) {
|
|
Unused << SendCloseActive();
|
|
CloseIfUnused();
|
|
}
|
|
}
|
|
|
|
void
|
|
GMPParent::MarkForDeletion()
|
|
{
|
|
mDeleteProcessOnlyOnUnload = true;
|
|
mIsBlockingDeletion = true;
|
|
}
|
|
|
|
bool
|
|
GMPParent::IsMarkedForDeletion()
|
|
{
|
|
return mIsBlockingDeletion;
|
|
}
|
|
|
|
void
|
|
GMPParent::Shutdown()
|
|
{
|
|
LOGD("%s", __FUNCTION__);
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
|
|
if (mAbnormalShutdownInProgress) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!IsUsed());
|
|
if (mState == GMPStateNotLoaded || mState == GMPStateClosing) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<GMPParent> self(this);
|
|
DeleteProcess();
|
|
|
|
// XXX Get rid of mDeleteProcessOnlyOnUnload and this code when
|
|
// Bug 1043671 is fixed
|
|
if (!mDeleteProcessOnlyOnUnload) {
|
|
// Destroy ourselves and rise from the fire to save memory
|
|
mService->ReAddOnGMPThread(self);
|
|
} // else we've been asked to die and stay dead
|
|
MOZ_ASSERT(mState == GMPStateNotLoaded);
|
|
}
|
|
|
|
class NotifyGMPShutdownTask : public Runnable {
|
|
public:
|
|
explicit NotifyGMPShutdownTask(const nsAString& aNodeId)
|
|
: Runnable("NotifyGMPShutdownTask")
|
|
, mNodeId(aNodeId)
|
|
{
|
|
}
|
|
NS_IMETHOD Run() override {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
|
|
MOZ_ASSERT(obsService);
|
|
if (obsService) {
|
|
obsService->NotifyObservers(nullptr, "gmp-shutdown", mNodeId.get());
|
|
}
|
|
return NS_OK;
|
|
}
|
|
nsString mNodeId;
|
|
};
|
|
|
|
void
|
|
GMPParent::ChildTerminated()
|
|
{
|
|
RefPtr<GMPParent> self(this);
|
|
nsCOMPtr<nsIThread> gmpThread = GMPThread();
|
|
|
|
if (!gmpThread) {
|
|
// Bug 1163239 - this can happen on shutdown.
|
|
// PluginTerminated removes the GMP from the GMPService.
|
|
// On shutdown we can have this case where it is already been
|
|
// removed so there is no harm in not trying to remove it again.
|
|
LOGD("%s::%s: GMPThread() returned nullptr.", __CLASS__, __FUNCTION__);
|
|
} else {
|
|
gmpThread->Dispatch(NewRunnableMethod<RefPtr<GMPParent>>(
|
|
mService,
|
|
&GeckoMediaPluginServiceParent::PluginTerminated,
|
|
self),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
}
|
|
|
|
void
|
|
GMPParent::DeleteProcess()
|
|
{
|
|
LOGD("%s", __FUNCTION__);
|
|
|
|
if (mState != GMPStateClosing) {
|
|
// Don't Close() twice!
|
|
// Probably remove when bug 1043671 is resolved
|
|
mState = GMPStateClosing;
|
|
Close();
|
|
}
|
|
mProcess->Delete(NewRunnableMethod(this, &GMPParent::ChildTerminated));
|
|
LOGD("%s: Shut down process", __FUNCTION__);
|
|
mProcess = nullptr;
|
|
mState = GMPStateNotLoaded;
|
|
|
|
nsCOMPtr<nsIRunnable> r
|
|
= new NotifyGMPShutdownTask(NS_ConvertUTF8toUTF16(mNodeId));
|
|
mMainThread->Dispatch(r.forget());
|
|
|
|
if (mHoldingSelfRef) {
|
|
Release();
|
|
mHoldingSelfRef = false;
|
|
}
|
|
}
|
|
|
|
GMPState
|
|
GMPParent::State() const
|
|
{
|
|
return mState;
|
|
}
|
|
|
|
nsCOMPtr<nsIThread>
|
|
GMPParent::GMPThread()
|
|
{
|
|
nsCOMPtr<mozIGeckoMediaPluginService> mps =
|
|
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
|
|
MOZ_ASSERT(mps);
|
|
if (!mps) {
|
|
return nullptr;
|
|
}
|
|
// Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns
|
|
// nullptr if the GeckoMediaPluginService has started shutdown.
|
|
nsCOMPtr<nsIThread> gmpThread;
|
|
mps->GetThread(getter_AddRefs(gmpThread));
|
|
return gmpThread;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
|
|
const nsCString& aAPI,
|
|
const nsTArray<nsCString>& aTags)
|
|
{
|
|
for (const nsCString& tag : aTags) {
|
|
if (!GMPCapability::Supports(aCapabilities, aAPI, tag)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
|
|
const nsCString& aAPI,
|
|
const nsCString& aTag)
|
|
{
|
|
for (const GMPCapability& capabilities : aCapabilities) {
|
|
if (!capabilities.mAPIName.Equals(aAPI)) {
|
|
continue;
|
|
}
|
|
for (const nsCString& tag : capabilities.mAPITags) {
|
|
if (tag.Equals(aTag)) {
|
|
#ifdef XP_WIN
|
|
// Clearkey on Windows advertises that it can decode in its GMP info
|
|
// file, but uses Windows Media Foundation to decode. That's not present
|
|
// on Windows XP, and on some Vista, Windows N, and KN variants without
|
|
// certain services packs.
|
|
if (tag.Equals(kEMEKeySystemClearkey)) {
|
|
if (capabilities.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) {
|
|
if (!WMFDecoderModule::HasH264()) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
GMPParent::EnsureProcessLoaded()
|
|
{
|
|
if (mState == GMPStateLoaded) {
|
|
return true;
|
|
}
|
|
if (mState == GMPStateClosing ||
|
|
mState == GMPStateUnloading) {
|
|
return false;
|
|
}
|
|
|
|
nsresult rv = LoadProcess();
|
|
|
|
return NS_SUCCEEDED(rv);
|
|
}
|
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
void
|
|
GMPParent::WriteExtraDataForMinidump()
|
|
{
|
|
mCrashReporter->AddNote(NS_LITERAL_CSTRING("GMPPlugin"), NS_LITERAL_CSTRING("1"));
|
|
mCrashReporter->AddNote(NS_LITERAL_CSTRING("PluginFilename"), NS_ConvertUTF16toUTF8(mName));
|
|
mCrashReporter->AddNote(NS_LITERAL_CSTRING("PluginName"), mDisplayName);
|
|
mCrashReporter->AddNote(NS_LITERAL_CSTRING("PluginVersion"), mVersion);
|
|
}
|
|
|
|
bool
|
|
GMPParent::GetCrashID(nsString& aResult)
|
|
{
|
|
if (!mCrashReporter) {
|
|
return false;
|
|
}
|
|
|
|
WriteExtraDataForMinidump();
|
|
if (!mCrashReporter->GenerateCrashReport(OtherPid())) {
|
|
return false;
|
|
}
|
|
|
|
aResult = mCrashReporter->MinidumpID();
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
GMPNotifyObservers(const uint32_t aPluginID, const nsACString& aPluginName, const nsAString& aPluginDumpID)
|
|
{
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
nsCOMPtr<nsIWritablePropertyBag2> propbag =
|
|
do_CreateInstance("@mozilla.org/hash-property-bag;1");
|
|
if (obs && propbag) {
|
|
propbag->SetPropertyAsUint32(NS_LITERAL_STRING("pluginID"), aPluginID);
|
|
propbag->SetPropertyAsACString(NS_LITERAL_STRING("pluginName"), aPluginName);
|
|
propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), aPluginDumpID);
|
|
obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr);
|
|
}
|
|
|
|
RefPtr<gmp::GeckoMediaPluginService> service =
|
|
gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
|
|
if (service) {
|
|
service->RunPluginCrashCallbacks(aPluginID, aPluginName);
|
|
}
|
|
}
|
|
#endif
|
|
void
|
|
GMPParent::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
LOGD("%s: (%d)", __FUNCTION__, (int)aWhy);
|
|
#ifdef MOZ_CRASHREPORTER
|
|
if (AbnormalShutdown == aWhy) {
|
|
Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT,
|
|
NS_LITERAL_CSTRING("gmplugin"), 1);
|
|
nsString dumpID;
|
|
if (!GetCrashID(dumpID)) {
|
|
NS_WARNING("GMP crash without crash report");
|
|
dumpID = mName;
|
|
dumpID += '-';
|
|
AppendUTF8toUTF16(mVersion, dumpID);
|
|
}
|
|
|
|
// NotifyObservers is mainthread-only
|
|
nsCOMPtr<nsIRunnable> r = WrapRunnableNM(
|
|
&GMPNotifyObservers, mPluginId, mDisplayName, dumpID);
|
|
mMainThread->Dispatch(r.forget());
|
|
}
|
|
#endif
|
|
// warn us off trying to close again
|
|
mState = GMPStateClosing;
|
|
mAbnormalShutdownInProgress = true;
|
|
CloseActive(false);
|
|
|
|
// Normal Shutdown() will delete the process on unwind.
|
|
if (AbnormalShutdown == aWhy) {
|
|
RefPtr<GMPParent> self(this);
|
|
// Must not call Close() again in DeleteProcess(), as we'll recurse
|
|
// infinitely if we do.
|
|
MOZ_ASSERT(mState == GMPStateClosing);
|
|
DeleteProcess();
|
|
// Note: final destruction will be Dispatched to ourself
|
|
mService->ReAddOnGMPThread(self);
|
|
}
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
GMPParent::RecvInitCrashReporter(Shmem&& aShmem, const NativeThreadId& aThreadId)
|
|
{
|
|
#ifdef MOZ_CRASHREPORTER
|
|
mCrashReporter = MakeUnique<ipc::CrashReporterHost>(
|
|
GeckoProcessType_GMPlugin,
|
|
aShmem,
|
|
aThreadId);
|
|
#endif
|
|
return IPC_OK();
|
|
}
|
|
|
|
PGMPStorageParent*
|
|
GMPParent::AllocPGMPStorageParent()
|
|
{
|
|
GMPStorageParent* p = new GMPStorageParent(mNodeId, this);
|
|
mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent.
|
|
return p;
|
|
}
|
|
|
|
bool
|
|
GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor)
|
|
{
|
|
GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
|
|
p->Shutdown();
|
|
mStorage.RemoveElement(p);
|
|
return true;
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor)
|
|
{
|
|
GMPStorageParent* p = (GMPStorageParent*)aActor;
|
|
if (NS_WARN_IF(NS_FAILED(p->Init()))) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
|
|
{
|
|
return IPC_OK();
|
|
}
|
|
|
|
PGMPTimerParent*
|
|
GMPParent::AllocPGMPTimerParent()
|
|
{
|
|
nsCOMPtr<nsIThread> thread = GMPThread();
|
|
GMPTimerParent* p = new GMPTimerParent(thread);
|
|
mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
|
|
return p;
|
|
}
|
|
|
|
bool
|
|
GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor)
|
|
{
|
|
GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
|
|
p->Shutdown();
|
|
mTimers.RemoveElement(p);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ReadInfoField(GMPInfoFileParser& aParser, const nsCString& aKey, nsACString& aOutValue)
|
|
{
|
|
if (!aParser.Contains(aKey) || aParser.Get(aKey).IsEmpty()) {
|
|
return false;
|
|
}
|
|
aOutValue = aParser.Get(aKey);
|
|
return true;
|
|
}
|
|
|
|
RefPtr<GenericPromise>
|
|
GMPParent::ReadGMPMetaData()
|
|
{
|
|
MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
|
|
MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!");
|
|
|
|
nsCOMPtr<nsIFile> infoFile;
|
|
nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile));
|
|
if (NS_FAILED(rv)) {
|
|
return GenericPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info"));
|
|
|
|
if (FileExists(infoFile)) {
|
|
return ReadGMPInfoFile(infoFile);
|
|
}
|
|
|
|
// Maybe this is the Widevine adapted plugin?
|
|
nsCOMPtr<nsIFile> manifestFile;
|
|
rv = mDirectory->Clone(getter_AddRefs(manifestFile));
|
|
if (NS_FAILED(rv)) {
|
|
return GenericPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
manifestFile->AppendRelativePath(NS_LITERAL_STRING("manifest.json"));
|
|
return ReadChromiumManifestFile(manifestFile);
|
|
}
|
|
|
|
RefPtr<GenericPromise>
|
|
GMPParent::ReadGMPInfoFile(nsIFile* aFile)
|
|
{
|
|
GMPInfoFileParser parser;
|
|
if (!parser.Init(aFile)) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
nsAutoCString apis;
|
|
if (!ReadInfoField(parser, NS_LITERAL_CSTRING("name"), mDisplayName) ||
|
|
!ReadInfoField(parser, NS_LITERAL_CSTRING("description"), mDescription) ||
|
|
!ReadInfoField(parser, NS_LITERAL_CSTRING("version"), mVersion) ||
|
|
!ReadInfoField(parser, NS_LITERAL_CSTRING("apis"), apis)) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
// "Libraries" field is optional.
|
|
ReadInfoField(parser, NS_LITERAL_CSTRING("libraries"), mLibs);
|
|
#endif
|
|
|
|
nsTArray<nsCString> apiTokens;
|
|
SplitAt(", ", apis, apiTokens);
|
|
for (nsCString api : apiTokens) {
|
|
int32_t tagsStart = api.FindChar('[');
|
|
if (tagsStart == 0) {
|
|
// Not allowed to be the first character.
|
|
// API name must be at least one character.
|
|
continue;
|
|
}
|
|
|
|
GMPCapability cap;
|
|
if (tagsStart == -1) {
|
|
// No tags.
|
|
cap.mAPIName.Assign(api);
|
|
} else {
|
|
auto tagsEnd = api.FindChar(']');
|
|
if (tagsEnd == -1 || tagsEnd < tagsStart) {
|
|
// Invalid syntax, skip whole capability.
|
|
continue;
|
|
}
|
|
|
|
cap.mAPIName.Assign(Substring(api, 0, tagsStart));
|
|
|
|
if ((tagsEnd - tagsStart) > 1) {
|
|
const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1));
|
|
nsTArray<nsCString> tagTokens;
|
|
SplitAt(":", ts, tagTokens);
|
|
for (nsCString tag : tagTokens) {
|
|
cap.mAPITags.AppendElement(tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cap.mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
|
|
mCanDecrypt = true;
|
|
|
|
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
|
|
if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
|
|
nsPrintfCString msg(
|
|
"GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
|
|
" but this system can't sandbox it; not loading.",
|
|
mDisplayName.get());
|
|
printf_stderr("%s\n", msg.get());
|
|
LOGD("%s", msg.get());
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
mCapabilities.AppendElement(Move(cap));
|
|
}
|
|
|
|
if (mCapabilities.IsEmpty()) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
return GenericPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
RefPtr<GenericPromise>
|
|
GMPParent::ReadChromiumManifestFile(nsIFile* aFile)
|
|
{
|
|
nsAutoCString json;
|
|
if (!ReadIntoString(aFile, json, 5 * 1024)) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
// DOM JSON parsing needs to run on the main thread.
|
|
return InvokeAsync<nsString&&>(
|
|
mMainThread, this, __func__,
|
|
&GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
|
|
}
|
|
|
|
static bool
|
|
IsCDMAPISupported(const mozilla::dom::WidevineCDMManifest& aManifest)
|
|
{
|
|
nsresult ignored; // Note: ToInteger returns 0 on failure.
|
|
int32_t moduleVersion = aManifest.mX_cdm_module_versions.ToInteger(&ignored);
|
|
int32_t interfaceVersion =
|
|
aManifest.mX_cdm_interface_versions.ToInteger(&ignored);
|
|
int32_t hostVersion = aManifest.mX_cdm_host_versions.ToInteger(&ignored);
|
|
if (MediaPrefs::EMEChromiumAPIEnabled()) {
|
|
return ChromiumCDMAdapter::Supports(
|
|
moduleVersion, interfaceVersion, hostVersion);
|
|
}
|
|
return WidevineAdapter::Supports(
|
|
moduleVersion, interfaceVersion, hostVersion);
|
|
}
|
|
|
|
RefPtr<GenericPromise>
|
|
GMPParent::ParseChromiumManifest(const nsAString& aJSON)
|
|
{
|
|
LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get());
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mozilla::dom::WidevineCDMManifest m;
|
|
if (!m.Init(aJSON)) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
if (!IsCDMAPISupported(m)) {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
mDisplayName = NS_ConvertUTF16toUTF8(m.mName);
|
|
mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
|
|
mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
|
|
|
|
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
|
|
if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
|
|
nsPrintfCString msg(
|
|
"GMPParent::ParseChromiumManifest: Plugin \"%s\" is an EME CDM"
|
|
" but this system can't sandbox it; not loading.",
|
|
mDisplayName.get());
|
|
printf_stderr("%s\n", msg.get());
|
|
LOGD("%s", msg.get());
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
#endif
|
|
|
|
nsCString kEMEKeySystem;
|
|
|
|
// We hard code a few of the settings because they can't be stored in the
|
|
// widevine manifest without making our API different to widevine's.
|
|
if (mDisplayName.EqualsASCII("clearkey")) {
|
|
kEMEKeySystem = kEMEKeySystemClearkey;
|
|
#if XP_WIN
|
|
mLibs = NS_LITERAL_CSTRING("dxva2.dll, msmpeg2vdec.dll, evr.dll, mfh264dec.dll, mfplat.dll");
|
|
#endif
|
|
} else if (mDisplayName.EqualsASCII("WidevineCdm")) {
|
|
kEMEKeySystem = kEMEKeySystemWidevine;
|
|
#if XP_WIN
|
|
mLibs = NS_LITERAL_CSTRING("dxva2.dll");
|
|
#endif
|
|
} else {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
GMPCapability video;
|
|
|
|
nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
|
|
nsTArray<nsCString> codecs;
|
|
SplitAt(",", codecsString, codecs);
|
|
|
|
for (const nsCString& chromiumCodec : codecs) {
|
|
nsCString codec;
|
|
if (chromiumCodec.EqualsASCII("vp8")) {
|
|
codec = NS_LITERAL_CSTRING("vp8");
|
|
} else if (chromiumCodec.EqualsASCII("vp9.0")) {
|
|
codec = NS_LITERAL_CSTRING("vp9");
|
|
} else if (chromiumCodec.EqualsASCII("avc1")) {
|
|
codec = NS_LITERAL_CSTRING("h264");
|
|
} else {
|
|
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
video.mAPITags.AppendElement(codec);
|
|
}
|
|
|
|
video.mAPITags.AppendElement(kEMEKeySystem);
|
|
|
|
if (MediaPrefs::EMEChromiumAPIEnabled()) {
|
|
video.mAPIName = NS_LITERAL_CSTRING(CHROMIUM_CDM_API);
|
|
mAdapter = NS_LITERAL_STRING("chromium");
|
|
} else {
|
|
video.mAPIName = NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
|
|
mAdapter = NS_LITERAL_STRING("widevine");
|
|
|
|
GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
|
|
decrypt.mAPITags.AppendElement(kEMEKeySystem);
|
|
mCapabilities.AppendElement(Move(decrypt));
|
|
}
|
|
mCapabilities.AppendElement(Move(video));
|
|
|
|
return GenericPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
bool
|
|
GMPParent::CanBeSharedCrossNodeIds() const
|
|
{
|
|
return mNodeId.IsEmpty() &&
|
|
// XXX bug 1159300 hack -- maybe remove after openh264 1.4
|
|
// We don't want to use CDM decoders for non-encrypted playback
|
|
// just yet; especially not for WebRTC. Don't allow CDMs to be used
|
|
// without a node ID.
|
|
!mCanDecrypt;
|
|
}
|
|
|
|
bool
|
|
GMPParent::CanBeUsedFrom(const nsACString& aNodeId) const
|
|
{
|
|
return mNodeId == aNodeId;
|
|
}
|
|
|
|
void
|
|
GMPParent::SetNodeId(const nsACString& aNodeId)
|
|
{
|
|
MOZ_ASSERT(!aNodeId.IsEmpty());
|
|
mNodeId = aNodeId;
|
|
}
|
|
|
|
const nsCString&
|
|
GMPParent::GetDisplayName() const
|
|
{
|
|
return mDisplayName;
|
|
}
|
|
|
|
const nsCString&
|
|
GMPParent::GetVersion() const
|
|
{
|
|
return mVersion;
|
|
}
|
|
|
|
uint32_t
|
|
GMPParent::GetPluginId() const
|
|
{
|
|
return mPluginId;
|
|
}
|
|
|
|
void
|
|
GMPParent::ResolveGetContentParentPromises()
|
|
{
|
|
nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises;
|
|
promises.SwapElements(mGetContentParentPromises);
|
|
MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
|
|
RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(mGMPContentParent));
|
|
for (auto& holder : promises) {
|
|
holder->Resolve(blocker, __func__);
|
|
}
|
|
}
|
|
|
|
bool
|
|
GMPParent::OpenPGMPContent()
|
|
{
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
MOZ_ASSERT(!mGMPContentParent);
|
|
|
|
Endpoint<PGMPContentParent> parent;
|
|
Endpoint<PGMPContentChild> child;
|
|
if (NS_FAILED(PGMPContent::CreateEndpoints(base::GetCurrentProcId(),
|
|
OtherPid(), &parent, &child))) {
|
|
return false;
|
|
}
|
|
|
|
mGMPContentParent = new GMPContentParent(this);
|
|
|
|
if (!parent.Bind(mGMPContentParent)) {
|
|
return false;
|
|
}
|
|
|
|
if (!SendInitGMPContentChild(Move(child))) {
|
|
return false;
|
|
}
|
|
|
|
ResolveGetContentParentPromises();
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
GMPParent::RejectGetContentParentPromises()
|
|
{
|
|
nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> promises;
|
|
promises.SwapElements(mGetContentParentPromises);
|
|
MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
|
|
for (auto& holder : promises) {
|
|
holder->Reject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
}
|
|
|
|
void
|
|
GMPParent::GetGMPContentParent(UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>&& aPromiseHolder)
|
|
{
|
|
LOGD("%s %p", __FUNCTION__, this);
|
|
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
|
|
|
|
if (mGMPContentParent) {
|
|
RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(mGMPContentParent));
|
|
aPromiseHolder->Resolve(blocker, __func__);
|
|
} else {
|
|
mGetContentParentPromises.AppendElement(Move(aPromiseHolder));
|
|
// If we don't have a GMPContentParent and we try to get one for the first
|
|
// time (mGetContentParentPromises.Length() == 1) then call PGMPContent::Open. If more
|
|
// calls to GetGMPContentParent happen before mGMPContentParent has been
|
|
// set then we should just store them, so that they get called when we set
|
|
// mGMPContentParent as a result of the PGMPContent::Open call.
|
|
if (mGetContentParentPromises.Length() == 1) {
|
|
if (!EnsureProcessLoaded() || !OpenPGMPContent()) {
|
|
RejectGetContentParentPromises();
|
|
return;
|
|
}
|
|
// We want to increment this as soon as possible, to avoid that we'd try
|
|
// to shut down the GMP process while we're still trying to get a
|
|
// PGMPContentParent actor.
|
|
++mGMPContentChildCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
already_AddRefed<GMPContentParent>
|
|
GMPParent::ForgetGMPContentParent()
|
|
{
|
|
MOZ_ASSERT(mGetContentParentPromises.IsEmpty());
|
|
return Move(mGMPContentParent.forget());
|
|
}
|
|
|
|
bool
|
|
GMPParent::EnsureProcessLoaded(base::ProcessId* aID)
|
|
{
|
|
if (!EnsureProcessLoaded()) {
|
|
return false;
|
|
}
|
|
*aID = OtherPid();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
GMPParent::IncrementGMPContentChildCount()
|
|
{
|
|
++mGMPContentChildCount;
|
|
}
|
|
|
|
nsString
|
|
GMPParent::GetPluginBaseName() const
|
|
{
|
|
return NS_LITERAL_STRING("gmp-") + mName;
|
|
}
|
|
|
|
} // namespace gmp
|
|
} // namespace mozilla
|
|
|
|
#undef LOG
|
|
#undef LOGD
|