Bug 1067216 - Make MediaKeys.isTypeSupported() more accurate. r=edwin,jesup

This commit is contained in:
Chris Pearce 2014-10-14 13:52:56 +13:00
parent 5ac2fe1151
commit d0653badc3
7 changed files with 206 additions and 47 deletions

View File

@ -16,9 +16,16 @@
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsIScriptObjectPrincipal.h" #include "nsIScriptObjectPrincipal.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "nsContentTypeParser.h"
#ifdef MOZ_FMP4
#include "MP4Decoder.h"
#endif
#ifdef XP_WIN #ifdef XP_WIN
#include "mozilla/WindowsVersion.h" #include "mozilla/WindowsVersion.h"
#endif #endif
#include "nsContentCID.h"
#include "nsServiceManagerUtils.h"
#include "mozIGeckoMediaPluginService.h"
namespace mozilla { namespace mozilla {
@ -107,15 +114,86 @@ MediaKeys::SetServerCertificate(const ArrayBufferViewOrArrayBuffer& aCert, Error
} }
static bool static bool
IsSupportedKeySystem(const nsAString& aKeySystem) HaveGMPFor(const nsCString& aKeySystem,
const nsCString& aAPI,
const nsCString& aTag = EmptyCString())
{ {
return aKeySystem.EqualsASCII("org.w3.clearkey") || nsCOMPtr<mozIGeckoMediaPluginService> mps =
#ifdef XP_WIN do_GetService("@mozilla.org/gecko-media-plugin-service;1");
(aKeySystem.EqualsASCII("com.adobe.access") && if (NS_WARN_IF(!mps)) {
IsVistaOrLater() && return false;
Preferences::GetBool("media.eme.adobe-access.enabled", false)) || }
nsTArray<nsCString> tags;
tags.AppendElement(aKeySystem);
if (!aTag.IsEmpty()) {
tags.AppendElement(aTag);
}
// Note: EME plugins need a non-null nodeId here, as they must
// not be shared across origins.
bool hasPlugin = false;
if (NS_FAILED(mps->HasPluginForAPI(aAPI,
&tags,
&hasPlugin))) {
return false;
}
return hasPlugin;
}
static bool
IsPlayableMP4Type(const nsAString& aContentType)
{
#ifdef MOZ_FMP4
nsContentTypeParser parser(aContentType);
nsAutoString mimeType;
nsresult rv = parser.GetType(mimeType);
if (NS_FAILED(rv)) {
return false;
}
nsAutoString codecs;
parser.GetParameter("codecs", codecs);
NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeType);
return MP4Decoder::CanHandleMediaType(mimeTypeUTF8, codecs);
#else
return false;
#endif #endif
false; }
bool
MediaKeys::IsTypeSupported(const nsAString& aKeySystem,
const Optional<nsAString>& aInitDataType,
const Optional<nsAString>& aContentType)
{
if (aKeySystem.EqualsLiteral("org.w3.clearkey") &&
(!aInitDataType.WasPassed() || aInitDataType.Value().EqualsLiteral("cenc")) &&
(!aContentType.WasPassed() || IsPlayableMP4Type(aContentType.Value())) &&
HaveGMPFor(NS_LITERAL_CSTRING("org.w3.clearkey"),
NS_LITERAL_CSTRING("eme-decrypt"))) {
return true;
}
#ifdef XP_WIN
// Note: Adobe Access's GMP uses WMF to decode, so anything our MP4Reader
// thinks it can play on Windows, the Access GMP should be able to play.
if (aKeySystem.EqualsLiteral("com.adobe.access") &&
Preferences::GetBool("media.eme.adobe-access.enabled", false) &&
IsVistaOrLater() && // Win Vista and later only.
(!aInitDataType.WasPassed() || aInitDataType.Value().EqualsLiteral("cenc")) &&
(!aContentType.WasPassed() || IsPlayableMP4Type(aContentType.Value())) &&
HaveGMPFor(NS_LITERAL_CSTRING("com.adobe.access"),
NS_LITERAL_CSTRING("eme-decrypt")) &&
HaveGMPFor(NS_LITERAL_CSTRING("com.adobe.access"),
NS_LITERAL_CSTRING("decode-video"),
NS_LITERAL_CSTRING("h264")) &&
HaveGMPFor(NS_LITERAL_CSTRING("com.adobe.access"),
NS_LITERAL_CSTRING("decode-audio"),
NS_LITERAL_CSTRING("aac"))) {
return true;
}
#endif
return false;
} }
/* static */ /* static */
@ -128,8 +206,16 @@ MediaKeys::IsTypeSupported(const GlobalObject& aGlobal,
{ {
// TODO: Should really get spec changed to this is async, so we can wait // TODO: Should really get spec changed to this is async, so we can wait
// for user to consent to running plugin. // for user to consent to running plugin.
return IsSupportedKeySystem(aKeySystem) ? IsTypeSupportedResult::Maybe bool supported = IsTypeSupported(aKeySystem, aInitDataType, aContentType);
: IsTypeSupportedResult::_empty;
EME_LOG("MediaKeys::IsTypeSupported keySystem='%s' initDataType='%s' contentType='%s' supported=%d",
NS_ConvertUTF16toUTF8(aKeySystem).get(),
(aInitDataType.WasPassed() ? NS_ConvertUTF16toUTF8(aInitDataType.Value()).get() : ""),
(aContentType.WasPassed() ? NS_ConvertUTF16toUTF8(aContentType.Value()).get() : ""),
supported);
return supported ? IsTypeSupportedResult::Probably
: IsTypeSupportedResult::_empty;
} }
already_AddRefed<Promise> already_AddRefed<Promise>
@ -242,7 +328,7 @@ MediaKeys::Init(ErrorResult& aRv)
return nullptr; return nullptr;
} }
if (!IsSupportedKeySystem(mKeySystem)) { if (!IsTypeSupported(mKeySystem)) {
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return promise.forget(); return promise.forget();
} }
@ -271,7 +357,7 @@ MediaKeys::Init(ErrorResult& aRv)
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget(); return promise.forget();
} }
mTopLevelPrincipal = top->GetExtantDoc()->NodePrincipal(); mTopLevelPrincipal = top->GetExtantDoc()->NodePrincipal();
if (!mPrincipal || !mTopLevelPrincipal) { if (!mPrincipal || !mTopLevelPrincipal) {

View File

@ -124,6 +124,10 @@ public:
private: private:
static bool IsTypeSupported(const nsAString& aKeySystem,
const Optional<nsAString>& aInitDataType = Optional<nsAString>(),
const Optional<nsAString>& aContentType = Optional<nsAString>());
bool IsInPrivateBrowsing(); bool IsInPrivateBrowsing();
already_AddRefed<Promise> Init(ErrorResult& aRv); already_AddRefed<Promise> Init(ErrorResult& aRv);

View File

@ -143,6 +143,7 @@ GeckoMediaPluginService::GeckoMediaPluginService()
: mMutex("GeckoMediaPluginService::mMutex") : mMutex("GeckoMediaPluginService::mMutex")
, mShuttingDown(false) , mShuttingDown(false)
, mShuttingDownOnGMPThread(false) , mShuttingDownOnGMPThread(false)
, mScannedPluginOnDisk(false)
, mWaitingForPluginsAsyncShutdown(false) , mWaitingForPluginsAsyncShutdown(false)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
@ -590,7 +591,7 @@ GeckoMediaPluginService::UnloadPlugins()
MutexAutoLock lock(mMutex); MutexAutoLock lock(mMutex);
// Note: CloseActive is async; it will actually finish // Note: CloseActive is async; it will actually finish
// shutting down when all the plugins have unloaded. // shutting down when all the plugins have unloaded.
for (uint32_t i = 0; i < mPlugins.Length(); i++) { for (size_t i = 0; i < mPlugins.Length(); i++) {
mPlugins[i]->CloseActive(true); mPlugins[i]->CloseActive(true);
} }
mPlugins.Clear(); mPlugins.Clear();
@ -619,7 +620,7 @@ GeckoMediaPluginService::CrashPlugins()
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread); MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
MutexAutoLock lock(mMutex); MutexAutoLock lock(mMutex);
for (uint32_t i = 0; i < mPlugins.Length(); i++) { for (size_t i = 0; i < mPlugins.Length(); i++) {
mPlugins[i]->Crash(); mPlugins[i]->Crash();
} }
} }
@ -652,6 +653,8 @@ GeckoMediaPluginService::LoadFromEnvironment()
pos = next + 1; pos = next + 1;
} }
} }
mScannedPluginOnDisk = true;
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -693,47 +696,88 @@ GeckoMediaPluginService::RemovePluginDirectory(const nsAString& aDirectory)
return NS_OK; return NS_OK;
} }
class DummyRunnable : public nsRunnable {
public:
NS_IMETHOD Run() { return NS_OK; }
};
NS_IMETHODIMP NS_IMETHODIMP
GeckoMediaPluginService::HasPluginForAPI(const nsACString& aNodeId, GeckoMediaPluginService::HasPluginForAPI(const nsACString& aAPI,
const nsACString& aAPI,
nsTArray<nsCString>* aTags, nsTArray<nsCString>* aTags,
bool* aResult) bool* aResult)
{ {
NS_ENSURE_ARG(aTags && aTags->Length() > 0); NS_ENSURE_ARG(aTags && aTags->Length() > 0);
NS_ENSURE_ARG(aResult); NS_ENSURE_ARG(aResult);
nsCString temp(aAPI); const char* env = nullptr;
GMPParent *parent = SelectPluginForAPI(aNodeId, temp, *aTags, false); if (!mScannedPluginOnDisk && (env = PR_GetEnv("MOZ_GMP_PATH")) && *env) {
*aResult = !!parent; // We have a MOZ_GMP_PATH environment variable which may specify the
// location of plugins to load, and we haven't yet scanned the disk to
// see if there are plugins there. Get the GMP thread, which will
// cause an event to be dispatched to which scans for plugins. We
// dispatch a sync event to the GMP thread here in order to wait until
// after the GMP thread has scanned any paths in MOZ_GMP_PATH.
nsCOMPtr<nsIThread> thread;
nsresult rv = GetThread(getter_AddRefs(thread));
if (NS_FAILED(rv)) {
return rv;
}
thread->Dispatch(new DummyRunnable(), NS_DISPATCH_SYNC);
MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now");
}
{
MutexAutoLock lock(mMutex);
nsCString api(aAPI);
GMPParent* gmp = FindPluginForAPIFrom(0, api, *aTags, nullptr);
*aResult = (gmp != nullptr);
}
return NS_OK; return NS_OK;
} }
GMPParent*
GeckoMediaPluginService::FindPluginForAPIFrom(size_t aSearchStartIndex,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags,
size_t* aOutPluginIndex)
{
mMutex.AssertCurrentThreadOwns();
for (size_t i = aSearchStartIndex; i < mPlugins.Length(); i++) {
GMPParent* gmp = mPlugins[i];
bool supportsAllTags = true;
for (size_t t = 0; t < aTags.Length(); t++) {
const nsCString& tag = aTags.ElementAt(t);
if (!gmp->SupportsAPI(aAPI, tag)) {
supportsAllTags = false;
break;
}
}
if (!supportsAllTags) {
continue;
}
if (aOutPluginIndex) {
*aOutPluginIndex = i;
}
return gmp;
}
return nullptr;
}
GMPParent* GMPParent*
GeckoMediaPluginService::SelectPluginForAPI(const nsACString& aNodeId, GeckoMediaPluginService::SelectPluginForAPI(const nsACString& aNodeId,
const nsCString& aAPI, const nsCString& aAPI,
const nsTArray<nsCString>& aTags, const nsTArray<nsCString>& aTags)
bool aCloneCrossNodeIds)
{ {
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread || !aCloneCrossNodeIds, MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread,
"Can't clone GMP plugins on non-GMP threads."); "Can't clone GMP plugins on non-GMP threads.");
GMPParent* gmpToClone = nullptr; GMPParent* gmpToClone = nullptr;
{ {
MutexAutoLock lock(mMutex); MutexAutoLock lock(mMutex);
for (uint32_t i = 0; i < mPlugins.Length(); i++) { size_t index = 0;
GMPParent* gmp = mPlugins[i]; GMPParent* gmp = nullptr;
bool supportsAllTags = true; while ((gmp = FindPluginForAPIFrom(index, aAPI, aTags, &index))) {
for (uint32_t t = 0; t < aTags.Length(); t++) {
const nsCString& tag = aTags[t];
if (!gmp->SupportsAPI(aAPI, tag)) {
supportsAllTags = false;
break;
}
}
if (!supportsAllTags) {
continue;
}
if (aNodeId.IsEmpty()) { if (aNodeId.IsEmpty()) {
if (gmp->CanBeSharedCrossNodeIds()) { if (gmp->CanBeSharedCrossNodeIds()) {
return gmp; return gmp;
@ -744,15 +788,17 @@ GeckoMediaPluginService::SelectPluginForAPI(const nsACString& aNodeId,
return gmp; return gmp;
} }
// This GMP has the correct type but has the wrong origin; hold on to it // This GMP has the correct type but has the wrong nodeId; hold on to it
// in case we need to clone it. // in case we need to clone it.
gmpToClone = gmp; gmpToClone = gmp;
// Loop around and try the next plugin; it may be usable from aNodeId.
index++;
} }
} }
// Plugin exists, but we can't use it due to cross-origin separation. Create a // Plugin exists, but we can't use it due to cross-origin separation. Create a
// new one. // new one.
if (aCloneCrossNodeIds && gmpToClone) { if (gmpToClone) {
GMPParent* clone = ClonePlugin(gmpToClone); GMPParent* clone = ClonePlugin(gmpToClone);
if (!aNodeId.IsEmpty()) { if (!aNodeId.IsEmpty()) {
clone->SetNodeId(aNodeId); clone->SetNodeId(aNodeId);
@ -847,7 +893,7 @@ GeckoMediaPluginService::RemoveOnGMPThread(const nsAString& aDirectory)
} }
MutexAutoLock lock(mMutex); MutexAutoLock lock(mMutex);
for (uint32_t i = 0; i < mPlugins.Length(); ++i) { for (size_t i = 0; i < mPlugins.Length(); ++i) {
nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory(); nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory();
bool equals; bool equals;
if (NS_SUCCEEDED(directory->Equals(pluginpath, &equals)) && equals) { if (NS_SUCCEEDED(directory->Equals(pluginpath, &equals)) && equals) {

View File

@ -19,6 +19,7 @@
#include "nsITimer.h" #include "nsITimer.h"
#include "nsClassHashtable.h" #include "nsClassHashtable.h"
#include "nsDataHashtable.h" #include "nsDataHashtable.h"
#include "mozilla/Atomics.h"
template <class> struct already_AddRefed; template <class> struct already_AddRefed;
@ -49,8 +50,11 @@ private:
GMPParent* SelectPluginForAPI(const nsACString& aNodeId, GMPParent* SelectPluginForAPI(const nsACString& aNodeId,
const nsCString& aAPI, const nsCString& aAPI,
const nsTArray<nsCString>& aTags, const nsTArray<nsCString>& aTags);
bool aCloneCrossNodeIds = true); GMPParent* FindPluginForAPIFrom(size_t aSearchStartIndex,
const nsCString& aAPI,
const nsTArray<nsCString>& aTags,
size_t* aOutPluginIndex);
void UnloadPlugins(); void UnloadPlugins();
void CrashPlugins(); void CrashPlugins();
@ -94,6 +98,10 @@ private:
bool mShuttingDown; bool mShuttingDown;
bool mShuttingDownOnGMPThread; bool mShuttingDownOnGMPThread;
// True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any
// plugins found there into mPlugins.
Atomic<bool> mScannedPluginOnDisk;
template<typename T> template<typename T>
class MainThreadOnly { class MainThreadOnly {
public: public:

View File

@ -26,7 +26,7 @@ class GMPVideoHost;
[ptr] native GMPDecryptorProxy(GMPDecryptorProxy); [ptr] native GMPDecryptorProxy(GMPDecryptorProxy);
[ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy); [ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy);
[scriptable, uuid(b350d3b6-00c9-4602-bdfe-84c2be8d1e7a)] [scriptable, uuid(657443a4-6b5d-4181-98b0-56997b35bd57)]
interface mozIGeckoMediaPluginService : nsISupports interface mozIGeckoMediaPluginService : nsISupports
{ {
@ -40,9 +40,7 @@ interface mozIGeckoMediaPluginService : nsISupports
* Callable on any thread * Callable on any thread
*/ */
[noscript] [noscript]
boolean hasPluginForAPI([optional] in ACString nodeId, boolean hasPluginForAPI(in ACString api, in TagArray tags);
in ACString api,
in TagArray tags);
/** /**
* Get a video decoder that supports the specified tags. * Get a video decoder that supports the specified tags.

View File

@ -174,6 +174,9 @@ function startTest(test, token)
}, bail("failed to create MediaKeys object")).then(function() { }, bail("failed to create MediaKeys object")).then(function() {
info(token + " set MediaKeys on <video> element ok"); info(token + " set MediaKeys on <video> element ok");
ok(MediaKeys.isTypeSupported(KEYSYSTEM_TYPE, ev.initDataType, test.type),
"MediaKeys should still support keysystem after CDM created...");
var session = v.mediaKeys.createSession(test.sessionType); var session = v.mediaKeys.createSession(test.sessionType);
session.addEventListener("message", UpdateSessionFunc(test)); session.addEventListener("message", UpdateSessionFunc(test));
session.generateRequest(ev.initDataType, ev.initData).then(function() { session.generateRequest(ev.initDataType, ev.initData).then(function() {
@ -202,7 +205,23 @@ function startTest(test, token)
PlayTest(test, v); PlayTest(test, v);
} }
function testIsTypeSupported()
{
var t = MediaKeys.isTypeSupported;
const clearkey = "org.w3.clearkey";
ok(!t("bogus", "bogon", "video/bogus"), "Invalid type.");
ok(t(clearkey), "ClearKey supported.");
ok(!t(clearkey, "bogus"), "ClearKey bogus initDataType not supported.");
ok(t(clearkey, "cenc"), "ClearKey/cenc should be supported.");
ok(!t(clearkey, "cenc", "bogus"), "ClearKey/cenc bogus content type should be supported.");
ok(t(clearkey, "cenc", 'video/mp4'), "ClearKey/cenc video/mp4 supported.");
ok(t(clearkey, "cenc", 'video/mp4; codecs="avc1.4d4015,mp4a.40.2"'), "ClearKey/cenc H.264/AAC supported.");
ok(t(clearkey, "cenc", 'audio/mp4'), "ClearKey/cenc audio/mp4 supported.");
ok(t(clearkey, "cenc", 'audio/mp4; codecs="mp4a.40.2"'), "ClearKey/cenc AAC LC supported.");
}
function beginTest() { function beginTest() {
testIsTypeSupported();
manager.runTests(gEMETests, startTest); manager.runTests(gEMETests, startTest);
} }

View File

@ -203,16 +203,14 @@ int VcmSIPCCBinding::getVideoCodecsGmp()
// H.264 only for now // H.264 only for now
bool has_gmp; bool has_gmp;
nsresult rv; nsresult rv;
rv = gSelf->mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(""), rv = gSelf->mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING("encode-video"),
NS_LITERAL_CSTRING("encode-video"),
&tags, &tags,
&has_gmp); &has_gmp);
if (NS_FAILED(rv) || !has_gmp) { if (NS_FAILED(rv) || !has_gmp) {
return 0; return 0;
} }
rv = gSelf->mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(""), rv = gSelf->mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING("decode-video"),
NS_LITERAL_CSTRING("decode-video"),
&tags, &tags,
&has_gmp); &has_gmp);
if (NS_FAILED(rv) || !has_gmp) { if (NS_FAILED(rv) || !has_gmp) {