| Index: Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
|
| diff --git a/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp b/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
|
| index 1a3969e9b1750239c11716657ba29156140c10f2..1378d09347b1d540e47a12ede471195f73f2a385 100644
|
| --- a/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
|
| +++ b/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
|
| @@ -6,12 +6,18 @@
|
| #include "modules/encryptedmedia/HTMLMediaElementEncryptedMedia.h"
|
|
|
| #include "bindings/core/v8/ExceptionState.h"
|
| +#include "bindings/core/v8/ScriptPromise.h"
|
| +#include "bindings/core/v8/ScriptPromiseResolver.h"
|
| +#include "bindings/core/v8/ScriptState.h"
|
| +#include "core/dom/DOMException.h"
|
| #include "core/dom/ExceptionCode.h"
|
| #include "core/html/HTMLMediaElement.h"
|
| #include "core/html/MediaKeyError.h"
|
| #include "core/html/MediaKeyEvent.h"
|
| #include "modules/encryptedmedia/MediaKeyNeededEvent.h"
|
| #include "modules/encryptedmedia/MediaKeys.h"
|
| +#include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h"
|
| +#include "platform/ContentDecryptionModuleResult.h"
|
| #include "platform/Logging.h"
|
| #include "platform/RuntimeEnabledFeatures.h"
|
| #include "wtf/Uint8Array.h"
|
| @@ -38,6 +44,191 @@ static void throwExceptionIfMediaKeyExceptionOccurred(const String& keySystem, c
|
| return;
|
| }
|
|
|
| +// This class allows MediaKeys to be set asynchronously.
|
| +class SetMediaKeysWorker : public ScriptPromiseResolver {
|
| + WTF_MAKE_NONCOPYABLE(SetMediaKeysWorker);
|
| +
|
| +public:
|
| + static ScriptPromise create(ScriptState*, HTMLMediaElement&, MediaKeys*);
|
| + virtual ~SetMediaKeysWorker();
|
| +
|
| + void completeWithDOMException(ExceptionCode, const String& errorMessage);
|
| + void timerFired(Timer<SetMediaKeysWorker>*);
|
| + void proceedToNextStage();
|
| +
|
| +private:
|
| + enum Stage {
|
| + Starting,
|
| + Clearing,
|
| + Setting
|
| + };
|
| + SetMediaKeysWorker(ScriptState*, HTMLMediaElement&, MediaKeys*);
|
| +
|
| + RawPtrWillBeMember<HTMLMediaElement> m_element;
|
| + Persistent<MediaKeys> m_mediaKeys;
|
| +
|
| + Stage m_stage;
|
| + Timer<SetMediaKeysWorker> m_timer;
|
| +};
|
| +
|
| +// Represents the result used when setContentDecryptionModule() is called.
|
| +// Needed as SetMediaKeysWorker may need to call setContentDecryptionModule()
|
| +// multiple times, and not resolve the promise until the very end. Any errors
|
| +// that happen will reject the promise immediately.
|
| +//
|
| +// This class can be used without specifying a worker to call. Useful when
|
| +// calling setContentDecryptionModule() while tearing down a player.
|
| +class SetContentDecryptionModuleResult FINAL : public ContentDecryptionModuleResult {
|
| +public:
|
| + explicit SetContentDecryptionModuleResult(SetMediaKeysWorker* worker)
|
| + : m_worker(worker)
|
| + {
|
| + }
|
| +
|
| + // ContentDecryptionModuleResult implementation.
|
| + virtual void complete() OVERRIDE
|
| + {
|
| + if (m_worker)
|
| + m_worker->proceedToNextStage();
|
| + }
|
| +
|
| + virtual void completeWithSession(blink::WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
|
| + {
|
| + ASSERT_NOT_REACHED();
|
| + if (m_worker)
|
| + m_worker->completeWithDOMException(InvalidStateError, "Unexpected completion.");
|
| + }
|
| +
|
| + virtual void completeWithError(blink::WebContentDecryptionModuleException code, unsigned long systemCode, const blink::WebString& message) OVERRIDE
|
| + {
|
| + if (m_worker)
|
| + m_worker->completeWithDOMException(WebCdmExceptionToExceptionCode(code), message);
|
| + }
|
| +
|
| +private:
|
| + SetMediaKeysWorker* m_worker;
|
| +};
|
| +
|
| +ScriptPromise SetMediaKeysWorker::create(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
|
| +{
|
| + RefPtr<SetMediaKeysWorker> worker = adoptRef(new SetMediaKeysWorker(scriptState, element, mediaKeys));
|
| + worker->suspendIfNeeded();
|
| + worker->keepAliveWhilePending();
|
| + return worker->promise();
|
| +}
|
| +
|
| +SetMediaKeysWorker::SetMediaKeysWorker(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
|
| + : ScriptPromiseResolver(scriptState)
|
| + , m_element(element)
|
| + , m_mediaKeys(mediaKeys)
|
| + , m_stage(Starting)
|
| + , m_timer(this, &SetMediaKeysWorker::timerFired)
|
| +{
|
| + WTF_LOG(Media, "SetMediaKeysWorker::SetMediaKeysWorker");
|
| +
|
| + // 3. Complete setting this asynchronously.
|
| + m_timer.startOneShot(0, FROM_HERE);
|
| +}
|
| +
|
| +SetMediaKeysWorker::~SetMediaKeysWorker()
|
| +{
|
| +}
|
| +
|
| +void SetMediaKeysWorker::completeWithDOMException(ExceptionCode code, const String& errorMessage)
|
| +{
|
| + reject(DOMException::create(code, errorMessage));
|
| +}
|
| +
|
| +void SetMediaKeysWorker::timerFired(Timer<SetMediaKeysWorker>*)
|
| +{
|
| + ASSERT(m_stage == Starting);
|
| + proceedToNextStage();
|
| +}
|
| +
|
| +void SetMediaKeysWorker::proceedToNextStage()
|
| +{
|
| + WTF_LOG(Media, "SetMediaKeysWorker::proceedToNextStage(%d)", m_stage);
|
| + HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
|
| +
|
| + switch (m_stage) {
|
| + case Starting:
|
| + // 3.1 If mediaKeys is not null, it is already in use by another media
|
| + // element, and the user agent is unable to use it with this element,
|
| + // reject promise with a new DOMException whose name is
|
| + // "QuotaExceededError". (Currently no restrictions on using the same
|
| + // MediaKeys with multiple media elements.)
|
| + // 3.2 If the mediaKeys attribute is not null, run the following steps:
|
| + m_stage = Clearing;
|
| + if (thisElement.m_mediaKeys) {
|
| + // 3.2.1 If the user agent or CDM do not support removing the
|
| + // association, return a promise rejected with a new DOMException
|
| + // whose name is "NotSupportedError".
|
| + // 3.2.2 If the association cannot currently be removed (i.e. during
|
| + // playback), return a promise rejected with a new DOMException
|
| + // whose name is "InvalidStateError".
|
| + if (m_element->isPlaying()) {
|
| + completeWithDOMException(InvalidStateError, "The existing MediaKeys object cannot be removed at this time.");
|
| + return;
|
| + }
|
| +
|
| + // 3.2.3 Stop using the CDM instance represented by the mediaKeys
|
| + // attribute to decrypt media data and remove the association
|
| + // with the media element.
|
| + // 3.2.4 If the preceding step failed, reject promise with a new
|
| + // DOMException whose name is the appropriate error name and
|
| + // that has an appropriate message.
|
| + if (m_element->webMediaPlayer()) {
|
| + ContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(this);
|
| + m_element->webMediaPlayer()->setContentDecryptionModule(nullptr, result->result());
|
| +
|
| + // Don't do anything more until SetContentDecryptionModuleResult
|
| + // finishes (and calls back to proceedToNextStage()).
|
| + return;
|
| + }
|
| + }
|
| +
|
| + // MediaKeys not currently set or no player connected. Continue to next
|
| + // stage.
|
| +
|
| + case Clearing:
|
| + // Successfully cleared MediaKeys, so drop reference to previous value.
|
| + thisElement.m_mediaKeys.clear();
|
| +
|
| + // 3.3 If mediaKeys is not null, run the following steps:
|
| + m_stage = Setting;
|
| + if (m_mediaKeys) {
|
| + // 3.3.1 Associate the CDM instance represented by mediaKeys with the
|
| + // media element for decrypting media data.
|
| + // 3.3.2 If the preceding step failed, run the following steps:
|
| + // 3.3.2.1 Set the mediaKeys attribute to null.
|
| + // 3.3.2.2 Reject promise with a new DOMException whose name is the
|
| + // appropriate error name and that has an appropriate message.
|
| + // 3.3.3 Run the Attempt to Resume Playback If Necessary algorithm on
|
| + // the media element. The user agent may choose to skip this
|
| + // step if it knows resuming will fail (i.e. mediaKeys has no
|
| + // sessions).
|
| + if (m_element->webMediaPlayer()) {
|
| + ContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(this);
|
| + m_element->webMediaPlayer()->setContentDecryptionModule(m_mediaKeys->contentDecryptionModule(), result->result());
|
| +
|
| + // Don't do anything more until SetContentDecryptionModuleResult
|
| + // finishes (and calls back to proceedToNextStage()).
|
| + return;
|
| + }
|
| + }
|
| + // MediaKeys doesn't need to be set on the player, so continue on to
|
| + // next stage.
|
| +
|
| + case Setting:
|
| + // 3.4 Set the mediaKeys attribute to mediaKeys.
|
| + thisElement.m_mediaKeys = m_mediaKeys;
|
| +
|
| + // 3.5 Resolve promise with undefined.
|
| + resolve();
|
| + return;
|
| + }
|
| +}
|
| +
|
| HTMLMediaElementEncryptedMedia::HTMLMediaElementEncryptedMedia()
|
| : m_emeMode(EmeModeNotSelected)
|
| {
|
| @@ -60,12 +251,11 @@ HTMLMediaElementEncryptedMedia& HTMLMediaElementEncryptedMedia::from(HTMLMediaEl
|
| return *supplement;
|
| }
|
|
|
| -bool HTMLMediaElementEncryptedMedia::setEmeMode(EmeMode emeMode, ExceptionState& exceptionState)
|
| +bool HTMLMediaElementEncryptedMedia::setEmeMode(EmeMode emeMode)
|
| {
|
| - if (m_emeMode != EmeModeNotSelected && m_emeMode != emeMode) {
|
| - exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
|
| + if (m_emeMode != EmeModeNotSelected && m_emeMode != emeMode)
|
| return false;
|
| - }
|
| +
|
| m_emeMode = emeMode;
|
| return true;
|
| }
|
| @@ -81,28 +271,21 @@ MediaKeys* HTMLMediaElementEncryptedMedia::mediaKeys(HTMLMediaElement& element)
|
| return thisElement.m_mediaKeys.get();
|
| }
|
|
|
| -void HTMLMediaElementEncryptedMedia::setMediaKeysInternal(HTMLMediaElement& element, MediaKeys* mediaKeys)
|
| -{
|
| - if (m_mediaKeys == mediaKeys)
|
| - return;
|
| -
|
| - ASSERT(m_emeMode == EmeModeUnprefixed);
|
| - m_mediaKeys = mediaKeys;
|
| -
|
| - // If a player is connected, tell it that the CDM has changed.
|
| - if (element.webMediaPlayer())
|
| - element.webMediaPlayer()->setContentDecryptionModule(contentDecryptionModule());
|
| -}
|
| -
|
| -void HTMLMediaElementEncryptedMedia::setMediaKeys(HTMLMediaElement& element, MediaKeys* mediaKeys, ExceptionState& exceptionState)
|
| +ScriptPromise HTMLMediaElementEncryptedMedia::setMediaKeys(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
|
| {
|
| - WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::setMediaKeys");
|
| HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
|
| + WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::setMediaKeys current(%p), new(%p)", thisElement.m_mediaKeys.get(), mediaKeys);
|
|
|
| - if (!thisElement.setEmeMode(EmeModeUnprefixed, exceptionState))
|
| - return;
|
| + if (!thisElement.setEmeMode(EmeModeUnprefixed))
|
| + return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed."));
|
| +
|
| + // 1. If mediaKeys and the mediaKeys attribute are the same object, return
|
| + // a promise resolved with undefined.
|
| + if (thisElement.m_mediaKeys == mediaKeys)
|
| + return ScriptPromise::cast(scriptState, V8ValueTraits<V8UndefinedType>::toV8Value(V8UndefinedType(), scriptState->context()->Global(), scriptState->isolate()));
|
|
|
| - thisElement.setMediaKeysInternal(element, mediaKeys);
|
| + // 2. Let promise be a new promise. Remaining steps done in worker.
|
| + return SetMediaKeysWorker::create(scriptState, element, mediaKeys);
|
| }
|
|
|
| // Create a MediaKeyNeededEvent for WD EME.
|
| @@ -139,8 +322,10 @@ void HTMLMediaElementEncryptedMedia::generateKeyRequest(blink::WebMediaPlayer* w
|
| {
|
| WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitGenerateKeyRequest");
|
|
|
| - if (!setEmeMode(EmeModePrefixed, exceptionState))
|
| + if (!setEmeMode(EmeModePrefixed)) {
|
| + exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
|
| return;
|
| + }
|
|
|
| if (keySystem.isEmpty()) {
|
| exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
|
| @@ -177,8 +362,10 @@ void HTMLMediaElementEncryptedMedia::addKey(blink::WebMediaPlayer* webMediaPlaye
|
| {
|
| WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitAddKey");
|
|
|
| - if (!setEmeMode(EmeModePrefixed, exceptionState))
|
| + if (!setEmeMode(EmeModePrefixed)) {
|
| + exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
|
| return;
|
| + }
|
|
|
| if (keySystem.isEmpty()) {
|
| exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
|
| @@ -225,8 +412,10 @@ void HTMLMediaElementEncryptedMedia::cancelKeyRequest(blink::WebMediaPlayer* web
|
| {
|
| WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitCancelKeyRequest");
|
|
|
| - if (!setEmeMode(EmeModePrefixed, exceptionState))
|
| + if (!setEmeMode(EmeModePrefixed)) {
|
| + exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
|
| return;
|
| + }
|
|
|
| if (keySystem.isEmpty()) {
|
| exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
|
| @@ -341,7 +530,17 @@ void HTMLMediaElementEncryptedMedia::playerDestroyed(HTMLMediaElement& element)
|
| #endif
|
|
|
| HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
|
| - thisElement.setMediaKeysInternal(element, 0);
|
| + if (!thisElement.m_mediaKeys)
|
| + return;
|
| +
|
| + ASSERT(thisElement.m_emeMode == EmeModeUnprefixed);
|
| + thisElement.m_mediaKeys.clear();
|
| +
|
| + // If a player is connected, tell it that the CDM has changed.
|
| + if (element.webMediaPlayer()) {
|
| + SetContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(0);
|
| + element.webMediaPlayer()->setContentDecryptionModule(thisElement.contentDecryptionModule(), result->result());
|
| + }
|
| }
|
|
|
| blink::WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionModule(HTMLMediaElement& element)
|
|
|