#include "config.h"
#include "modules/encryptedmedia/MediaKeySession.h"
-#include "bindings/v8/ExceptionState.h"
-#include "core/events/Event.h"
+#include "bindings/core/v8/DOMWrapperWorld.h"
+#include "bindings/core/v8/ScriptPromise.h"
+#include "bindings/core/v8/ScriptPromiseResolver.h"
+#include "bindings/core/v8/ScriptState.h"
#include "core/dom/ExceptionCode.h"
+#include "core/events/Event.h"
#include "core/events/GenericEventQueue.h"
#include "core/html/MediaKeyError.h"
#include "modules/encryptedmedia/MediaKeyMessageEvent.h"
#include "modules/encryptedmedia/MediaKeys.h"
+#include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h"
+#include "platform/ContentDecryptionModuleResult.h"
#include "platform/Logging.h"
-#include "platform/drm/ContentDecryptionModule.h"
+#include "platform/Timer.h"
+#include "public/platform/WebContentDecryptionModule.h"
+#include "public/platform/WebContentDecryptionModuleException.h"
+#include "public/platform/WebContentDecryptionModuleSession.h"
+#include "public/platform/WebString.h"
+#include "public/platform/WebURL.h"
+#include "wtf/ArrayBuffer.h"
+#include "wtf/ArrayBufferView.h"
+
+namespace blink {
+
+// A class holding a pending action.
+class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> {
+public:
+ enum Type {
+ Update,
+ Release,
+ Message
+ };
+
+ Type type() const { return m_type; }
+
+ const Persistent<ContentDecryptionModuleResult> result() const
+ {
+ ASSERT(m_type == Update || m_type == Release);
+ return m_result;
+ }
+
+ const RefPtr<ArrayBuffer> data() const
+ {
+ ASSERT(m_type == Update);
+ return m_data;
+ }
+
+ RefPtrWillBeRawPtr<Event> event()
+ {
+ ASSERT(m_type == Message);
+ return m_event;
+ }
+
+ static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<ArrayBuffer> data)
+ {
+ ASSERT(result);
+ ASSERT(data);
+ return new PendingAction(Update, result, data);
+ }
+
+ static PendingAction* CreatePendingRelease(ContentDecryptionModuleResult* result)
+ {
+ ASSERT(result);
+ return new PendingAction(Release, result, PassRefPtr<ArrayBuffer>());
+ }
+
+ static PendingAction* CreatePendingMessage(PassRefPtrWillBeRawPtr<Event> event)
+ {
+ ASSERT(event);
+ return new PendingAction(Message, event);
+ }
+
+ ~PendingAction()
+ {
+ }
+
+ void trace(Visitor* visitor)
+ {
+ visitor->trace(m_result);
+ visitor->trace(m_event);
+ }
+
+private:
+ PendingAction(Type type, ContentDecryptionModuleResult* result, PassRefPtr<ArrayBuffer> data)
+ : m_type(type)
+ , m_result(result)
+ , m_data(data)
+ {
+ }
+
+ PendingAction(Type type, PassRefPtrWillBeRawPtr<Event> event)
+ : m_type(type)
+ , m_event(event)
+ {
+ }
+
+ const Type m_type;
+ const Member<ContentDecryptionModuleResult> m_result;
+ const RefPtr<ArrayBuffer> m_data;
+ const RefPtrWillBeMember<Event> m_event;
+};
+
+// This class allows a MediaKeySession object to be created asynchronously.
+class MediaKeySessionInitializer : public ScriptPromiseResolver {
+ WTF_MAKE_NONCOPYABLE(MediaKeySessionInitializer);
+
+public:
+ static ScriptPromise create(ScriptState*, MediaKeys*, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType);
+ virtual ~MediaKeySessionInitializer();
+
+ void completeWithSession(WebContentDecryptionModuleResult::SessionStatus);
+ void completeWithDOMException(ExceptionCode, const String& errorMessage);
+
+private:
+ MediaKeySessionInitializer(ScriptState*, MediaKeys*, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType);
+ void timerFired(Timer<MediaKeySessionInitializer>*);
+
+ Persistent<MediaKeys> m_mediaKeys;
+ OwnPtr<WebContentDecryptionModuleSession> m_cdmSession;
+
+ // The next 3 values are simply the initialization data saved so that the
+ // asynchronous creation has the data needed.
+ String m_initDataType;
+ RefPtr<ArrayBuffer> m_initData;
+ String m_sessionType;
+
+ Timer<MediaKeySessionInitializer> m_timer;
+};
+
+// Represents the result used when a new WebContentDecryptionModuleSession
+// object has been created. Needed as MediaKeySessionInitializer can't be both
+// a ScriptPromiseResolver and ContentDecryptionModuleResult at the same time.
+class NewMediaKeySessionResult FINAL : public ContentDecryptionModuleResult {
+public:
+ NewMediaKeySessionResult(MediaKeySessionInitializer* initializer)
+ : m_initializer(initializer)
+ {
+ }
+
+ // ContentDecryptionModuleResult implementation.
+ virtual void complete() OVERRIDE
+ {
+ ASSERT_NOT_REACHED();
+ m_initializer->completeWithDOMException(InvalidStateError, "Unexpected completion.");
+ }
+
+ virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
+ {
+ m_initializer->completeWithSession(status);
+ }
+
+ virtual void completeWithError(WebContentDecryptionModuleException code, unsigned long systemCode, const WebString& message) OVERRIDE
+ {
+ m_initializer->completeWithDOMException(WebCdmExceptionToExceptionCode(code), message);
+ }
+
+private:
+ MediaKeySessionInitializer* m_initializer;
+};
+
+ScriptPromise MediaKeySessionInitializer::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType)
+{
+ RefPtr<MediaKeySessionInitializer> initializer = adoptRef(new MediaKeySessionInitializer(scriptState, mediaKeys, initDataType, initData, sessionType));
+ initializer->suspendIfNeeded();
+ initializer->keepAliveWhilePending();
+ return initializer->promise();
+}
+
+MediaKeySessionInitializer::MediaKeySessionInitializer(ScriptState* scriptState, MediaKeys* mediaKeys, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType)
+ : ScriptPromiseResolver(scriptState)
+ , m_mediaKeys(mediaKeys)
+ , m_initDataType(initDataType)
+ , m_initData(initData)
+ , m_sessionType(sessionType)
+ , m_timer(this, &MediaKeySessionInitializer::timerFired)
+{
+ WTF_LOG(Media, "MediaKeySessionInitializer::MediaKeySessionInitializer");
+
+ // Start the timer so that MediaKeySession can be created asynchronously.
+ m_timer.startOneShot(0, FROM_HERE);
+}
+
+MediaKeySessionInitializer::~MediaKeySessionInitializer()
+{
+ WTF_LOG(Media, "MediaKeySessionInitializer::~MediaKeySessionInitializer");
+}
+
+void MediaKeySessionInitializer::timerFired(Timer<MediaKeySessionInitializer>*)
+{
+ WTF_LOG(Media, "MediaKeySessionInitializer::timerFired");
+
+ // Continue MediaKeys::createSession() at step 7.
+ // 7.1 Let request be null. (Request provided by cdm in message event).
+ // 7.2 Let default URL be null. (Also provided by cdm in message event).
+
+ // 7.3 Let cdm be the cdm loaded in create().
+ WebContentDecryptionModule* cdm = m_mediaKeys->contentDecryptionModule();
+
+ // 7.4 Use the cdm to execute the following steps:
+ // 7.4.1 If the init data is not valid for initDataType, reject promise
+ // with a new DOMException whose name is "InvalidAccessError".
+ // 7.4.2 If the init data is not supported by the cdm, reject promise with
+ // a new DOMException whose name is "NotSupportedError".
+ // 7.4.3 Let request be a request (e.g. a license request) generated based
+ // on the init data, which is interpreteted per initDataType, and
+ // sessionType. If sessionType is "temporary", the request is for a
+ // temporary non-persisted license. If sessionType is "persistent",
+ // the request is for a persistable license.
+ // 7.4.4 If the init data indicates a default URL, let default URL be
+ // that URL. The URL may be validated and/or normalized.
+ m_cdmSession = adoptPtr(cdm->createSession());
+ NewMediaKeySessionResult* result = new NewMediaKeySessionResult(this);
+ m_cdmSession->initializeNewSession(m_initDataType, static_cast<unsigned char*>(m_initData->data()), m_initData->byteLength(), m_sessionType, result->result());
+
+ WTF_LOG(Media, "MediaKeySessionInitializer::timerFired done");
+ // Note: As soon as the promise is resolved (or rejected), the
+ // ScriptPromiseResolver object (|this|) is freed. So if
+ // initializeNewSession() is synchronous, access to any members will crash.
+}
+
+void MediaKeySessionInitializer::completeWithSession(WebContentDecryptionModuleResult::SessionStatus status)
+{
+ WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession");
+
+ switch (status) {
+ case WebContentDecryptionModuleResult::NewSession: {
+ // Resume MediaKeys::createSession().
+ // 7.5 Let the session ID be a unique Session ID string. It may be
+ // obtained from cdm (it is).
+ // 7.6 Let session be a new MediaKeySession object, and initialize it.
+ // (Object was created previously, complete the steps for 7.6).
+ RefPtrWillBeRawPtr<MediaKeySession> session = adoptRefCountedGarbageCollectedWillBeNoop(new MediaKeySession(executionContext(), m_mediaKeys, m_cdmSession.release()));
+ session->suspendIfNeeded();
+
+ // 7.7 If any of the preceding steps failed, reject promise with a
+ // new DOMException whose name is the appropriate error name
+ // and that has an appropriate message.
+ // (Implemented by CDM/Chromium calling completeWithError()).
+
+ // 7.8 Add an entry for the value of the sessionId attribute to the
+ // list of active session IDs for this object.
+ // (Implemented in SessionIdAdapter).
+
+ // 7.9 Run the Queue a "message" Event algorithm on the session,
+ // providing request and default URL.
+ // (Done by the CDM).
+
+ // 7.10 Resolve promise with session.
+ resolve(session.release());
+ WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession done w/session");
+ return;
+ }
+
+ case WebContentDecryptionModuleResult::SessionNotFound:
+ // Step 4.7.1 of MediaKeys::loadSession(): If there is no data
+ // stored for the sessionId in the origin, resolve promise with
+ // undefined.
+ resolve(V8UndefinedType());
+ WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession done w/undefined");
+ return;
-namespace WebCore {
+ case WebContentDecryptionModuleResult::SessionAlreadyExists:
+ // If a session already exists, resolve the promise with null.
+ resolve(V8NullType());
+ WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession done w/null");
+ return;
+ }
+ ASSERT_NOT_REACHED();
+}
-DEFINE_GC_INFO(MediaKeySession);
+void MediaKeySessionInitializer::completeWithDOMException(ExceptionCode code, const String& errorMessage)
+{
+ WTF_LOG(Media, "MediaKeySessionInitializer::completeWithDOMException");
+ reject(DOMException::create(code, errorMessage));
+}
-PassRefPtrWillBeRawPtr<MediaKeySession> MediaKeySession::create(ExecutionContext* context, ContentDecryptionModule* cdm, MediaKeys* keys)
+ScriptPromise MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType)
{
- RefPtrWillBeRawPtr<MediaKeySession> session(adoptRefCountedWillBeRefCountedGarbageCollected(new MediaKeySession(context, cdm, keys)));
- session->suspendIfNeeded();
- return session.release();
+ // Since creation is done asynchronously, use MediaKeySessionInitializer
+ // to do it.
+ return MediaKeySessionInitializer::create(scriptState, mediaKeys, initDataType, initData, sessionType);
}
-MediaKeySession::MediaKeySession(ExecutionContext* context, ContentDecryptionModule* cdm, MediaKeys* keys)
+MediaKeySession::MediaKeySession(ExecutionContext* context, MediaKeys* keys, PassOwnPtr<WebContentDecryptionModuleSession> cdmSession)
: ActiveDOMObject(context)
, m_keySystem(keys->keySystem())
, m_asyncEventQueue(GenericEventQueue::create(this))
- , m_session(cdm->createSession(this))
+ , m_session(cdmSession)
, m_keys(keys)
- , m_updateTimer(this, &MediaKeySession::updateTimerFired)
+ , m_isClosed(false)
+ , m_closedPromise(new ClosedPromise(context, this, ClosedPromise::Closed))
+ , m_actionTimer(this, &MediaKeySession::actionTimerFired)
{
- WTF_LOG(Media, "MediaKeySession::MediaKeySession");
+ WTF_LOG(Media, "MediaKeySession(%p)::MediaKeySession", this);
ScriptWrappable::init(this);
- ASSERT(m_session);
+ m_session->setClientInterface(this);
+
+ // Resume MediaKeys::createSession() at step 7.6.
+ // 7.6.1 Set the error attribute to null.
+ ASSERT(!m_error);
+
+ // 7.6.2 Set the sessionId attribute to session ID.
+ ASSERT(!sessionId().isEmpty());
+
+ // 7.6.3 Let expiration be NaN.
+ // 7.6.4 Let closed be a new promise.
+ // 7.6.5 Let the session type be sessionType.
+ // FIXME: Implement the previous 3 values.
}
MediaKeySession::~MediaKeySession()
{
+ WTF_LOG(Media, "MediaKeySession(%p)::~MediaKeySession", this);
m_session.clear();
+#if !ENABLE(OILPAN)
+ // MediaKeySession and m_asyncEventQueue always become unreachable
+ // together. So MediaKeySession and m_asyncEventQueue are destructed in the
+ // same GC. We don't need to call cancelAllEvents explicitly in Oilpan.
m_asyncEventQueue->cancelAllEvents();
-
- // FIXME: Release ref that MediaKeys has by removing it from m_sessions.
- // if (m_keys) m_keys->sessionClosed(this);
- m_keys = 0;
+#endif
}
void MediaKeySession::setError(MediaKeyError* error)
m_error = error;
}
-void MediaKeySession::release(ExceptionState& exceptionState)
+String MediaKeySession::sessionId() const
{
- m_session->release();
+ return m_session->sessionId();
}
-String MediaKeySession::sessionId() const
+ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
{
- return m_session->sessionId();
+ return m_closedPromise->promise(scriptState->world());
}
-void MediaKeySession::initializeNewSession(const String& mimeType, const Uint8Array& initData)
+ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBuffer* response)
{
- m_session->initializeNewSession(mimeType, initData);
+ RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->data(), response->byteLength());
+ return updateInternal(scriptState, responseCopy.release());
}
-void MediaKeySession::update(Uint8Array* response, ExceptionState& exceptionState)
+ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBufferView* response)
{
- WTF_LOG(Media, "MediaKeySession::update");
+ RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->baseAddress(), response->byteLength());
+ return updateInternal(scriptState, responseCopy.release());
+}
+
+ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<ArrayBuffer> response)
+{
+ WTF_LOG(Media, "MediaKeySession(%p)::update", this);
+ ASSERT(!m_isClosed);
// From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-update>:
- // The update(response) method must run the following steps:
- // 1. If the argument is null or an empty array, throw an INVALID_ACCESS_ERR.
- if (!response || !response->length()) {
- exceptionState.throwDOMException(InvalidAccessError, String::format("The response argument provided is %s.", response ? "an empty array" : "invalid"));
- return;
+ // The update(response) method provides messages, including licenses, to the
+ // CDM. It must run the following steps:
+ //
+ // 1. If response is an empty array, return a promise rejected with a new
+ // DOMException whose name is "InvalidAccessError" and that has the
+ // message "The response parameter is empty."
+ if (!response->byteLength()) {
+ return ScriptPromise::rejectWithDOMException(
+ scriptState, DOMException::create(InvalidAccessError, "The response parameter is empty."));
}
- // 2. If the session is not in the PENDING state, throw an INVALID_STATE_ERR.
- // FIXME: Implement states in MediaKeySession.
+ // 2. Let message be a copy of the contents of the response parameter.
+ // (Copied in the caller.)
- // 3. Schedule a task to handle the call, providing response.
- m_pendingUpdates.append(response);
+ // 3. Let promise be a new promise.
+ SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
+ ScriptPromise promise = result->promise();
- if (!m_updateTimer.isActive())
- m_updateTimer.startOneShot(0);
+ // 4. Run the following steps asynchronously (documented in
+ // actionTimerFired())
+ m_pendingActions.append(PendingAction::CreatePendingUpdate(result, response));
+ if (!m_actionTimer.isActive())
+ m_actionTimer.startOneShot(0, FROM_HERE);
+
+ // 5. Return promise.
+ return promise;
}
-void MediaKeySession::updateTimerFired(Timer<MediaKeySession>*)
+ScriptPromise MediaKeySession::release(ScriptState* scriptState)
{
- ASSERT(m_pendingUpdates.size());
+ WTF_LOG(Media, "MediaKeySession(%p)::release", this);
+ SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
+ ScriptPromise promise = result->promise();
+
+ // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close>:
+ // The close() method allows an application to indicate that it no longer
+ // needs the session and the CDM should release any resources associated
+ // with this object and close it. The returned promise is resolved when the
+ // request has been processed, and the closed attribute promise is resolved
+ // when the session is closed. It must run the following steps:
+ //
+ // 1. If the Session Close algorithm has been run on this object, return a
+ // promise fulfilled with undefined.
+ if (m_isClosed) {
+ result->complete();
+ return promise;
+ }
- while (!m_pendingUpdates.isEmpty()) {
- RefPtr<Uint8Array> pendingUpdate = m_pendingUpdates.takeFirst();
+ // 2. Let promise be a new promise.
+ // (Created earlier so it was available in step 1.)
- // NOTE: Continued from step 3. of MediaKeySession::update()
- // 3.1. Let cdm be the cdm loaded in the MediaKeys constructor.
- // NOTE: This is m_session.
- // 3.2. Let request be null.
- // 3.3. Use cdm to execute the following steps:
- // 3.3.1 Process response.
- m_session->update(*pendingUpdate);
+ // 3. Run the following steps asynchronously (documented in
+ // actionTimerFired()).
+ m_pendingActions.append(PendingAction::CreatePendingRelease(result));
+ if (!m_actionTimer.isActive())
+ m_actionTimer.startOneShot(0, FROM_HERE);
+
+ // 4. Return promise.
+ return promise;
+}
+
+void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
+{
+ ASSERT(m_pendingActions.size());
+
+ // Resolving promises now run synchronously and may result in additional
+ // actions getting added to the queue. As a result, swap the queue to
+ // a local copy to avoid problems if this happens.
+ HeapDeque<Member<PendingAction> > pendingActions;
+ pendingActions.swap(m_pendingActions);
+
+ while (!pendingActions.isEmpty()) {
+ PendingAction* action = pendingActions.takeFirst();
+
+ switch (action->type()) {
+ case PendingAction::Update:
+ WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Update", this);
+ // NOTE: Continued from step 4 of MediaKeySession::update().
+ // Continue the update call by passing message to the cdm. Once
+ // completed, it will resolve/reject the promise.
+ m_session->update(static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), action->result()->result());
+ break;
+ case PendingAction::Release:
+ WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Release", this);
+ // NOTE: Continued from step 3 of MediaKeySession::release().
+ // 3.1 Let cdm be the cdm loaded in create().
+ // 3.2 Use the cdm to execute the following steps:
+ // 3.2.1 Process the close request. Do not remove stored session data.
+ // 3.2.2 If the previous step caused the session to be closed, run the
+ // Session Close algorithm on this object.
+ // 3.3 Resolve promise with undefined.
+ m_session->release(action->result()->result());
+ break;
+ case PendingAction::Message:
+ WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Message", this);
+ m_asyncEventQueue->enqueueEvent(action->event().release());
+ break;
+ }
}
}
// Queue a task to fire a simple event named keymessage at the new object
-void MediaKeySession::message(const unsigned char* message, size_t messageLength, const KURL& destinationURL)
+void MediaKeySession::message(const unsigned char* message, size_t messageLength, const WebURL& destinationURL)
{
- WTF_LOG(Media, "MediaKeySession::message");
+ WTF_LOG(Media, "MediaKeySession(%p)::message", this);
MediaKeyMessageEventInit init;
init.bubbles = false;
init.cancelable = false;
- init.message = Uint8Array::create(message, messageLength);
- init.destinationURL = destinationURL;
+ init.message = ArrayBuffer::create(static_cast<const void*>(message), messageLength);
+ init.destinationURL = destinationURL.string();
- RefPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init);
+ RefPtrWillBeRawPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init);
event->setTarget(this);
+
+ if (!hasEventListeners()) {
+ // Since this event may be generated immediately after resolving the
+ // CreateSession() promise, it is possible that the JavaScript hasn't
+ // had time to run the .then() action and bind any necessary event
+ // handlers. If there are no event handlers connected, delay enqueuing
+ // this message to provide time for the JavaScript to run. This will
+ // also affect the (rare) case where there is no message handler
+ // attched during normal operation.
+ m_pendingActions.append(PendingAction::CreatePendingMessage(event.release()));
+ if (!m_actionTimer.isActive())
+ m_actionTimer.startOneShot(0, FROM_HERE);
+ return;
+ }
+
m_asyncEventQueue->enqueueEvent(event.release());
}
void MediaKeySession::ready()
{
- WTF_LOG(Media, "MediaKeySession::ready");
+ WTF_LOG(Media, "MediaKeySession(%p)::ready", this);
- RefPtr<Event> event = Event::create(EventTypeNames::ready);
+ RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::ready);
event->setTarget(this);
m_asyncEventQueue->enqueueEvent(event.release());
}
void MediaKeySession::close()
{
- WTF_LOG(Media, "MediaKeySession::close");
+ WTF_LOG(Media, "MediaKeySession(%p)::close", this);
- RefPtr<Event> event = Event::create(EventTypeNames::close);
- event->setTarget(this);
- m_asyncEventQueue->enqueueEvent(event.release());
+ // Once closed, the session can no longer be the target of events from
+ // the CDM so this object can be garbage collected.
+ m_isClosed = true;
+
+ // Resolve the closed promise.
+ m_closedPromise->resolve(V8UndefinedType());
}
// Queue a task to fire a simple event named keyadded at the MediaKeySession object.
void MediaKeySession::error(MediaKeyErrorCode errorCode, unsigned long systemCode)
{
- WTF_LOG(Media, "MediaKeySession::error: errorCode=%d, systemCode=%lu", errorCode, systemCode);
+ WTF_LOG(Media, "MediaKeySession(%p)::error: errorCode=%d, systemCode=%lu", this, errorCode, systemCode);
MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
switch (errorCode) {
- case UnknownError:
+ case MediaKeyErrorCodeUnknown:
mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
break;
- case ClientError:
+ case MediaKeyErrorCodeClient:
mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
break;
}
m_error = MediaKeyError::create(mediaKeyErrorCode, systemCode);
// 3. queue a task to fire a simple event named keyerror at the MediaKeySession object.
- RefPtr<Event> event = Event::create(EventTypeNames::error);
+ RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::error);
event->setTarget(this);
m_asyncEventQueue->enqueueEvent(event.release());
}
+void MediaKeySession::error(WebContentDecryptionModuleException exception, unsigned long systemCode, const WebString& errorMessage)
+{
+ WTF_LOG(Media, "MediaKeySession::error: exception=%d, systemCode=%lu", exception, systemCode);
+
+ // FIXME: EME-WD MediaKeyError now derives from DOMException. Figure out how
+ // to implement this without breaking prefixed EME, which has a totally
+ // different definition. The spec may also change to be just a DOMException.
+ // For now, simply generate an existing MediaKeyError.
+ MediaKeyErrorCode errorCode;
+ switch (exception) {
+ case WebContentDecryptionModuleExceptionClientError:
+ errorCode = MediaKeyErrorCodeClient;
+ break;
+ default:
+ // All other exceptions get converted into Unknown.
+ errorCode = MediaKeyErrorCodeUnknown;
+ break;
+ }
+ error(errorCode, systemCode);
+}
+
const AtomicString& MediaKeySession::interfaceName() const
{
return EventTargetNames::MediaKeySession;
bool MediaKeySession::hasPendingActivity() const
{
+ // Remain around if there are pending events or MediaKeys is still around
+ // and we're not closed.
+ WTF_LOG(Media, "MediaKeySession(%p)::hasPendingActivity %s%s%s%s", this,
+ ActiveDOMObject::hasPendingActivity() ? " ActiveDOMObject::hasPendingActivity()" : "",
+ !m_pendingActions.isEmpty() ? " !m_pendingActions.isEmpty()" : "",
+ m_asyncEventQueue->hasPendingEvents() ? " m_asyncEventQueue->hasPendingEvents()" : "",
+ (m_keys && !m_isClosed) ? " m_keys && !m_isClosed" : "");
+
return ActiveDOMObject::hasPendingActivity()
- || !m_pendingUpdates.isEmpty()
- || m_asyncEventQueue->hasPendingEvents();
+ || !m_pendingActions.isEmpty()
+ || m_asyncEventQueue->hasPendingEvents()
+ || (m_keys && !m_isClosed);
}
void MediaKeySession::stop()
{
- if (m_updateTimer.isActive())
- m_updateTimer.stop();
- m_pendingUpdates.clear();
+ // Stop the CDM from firing any more events for this session.
+ m_session.clear();
+ m_isClosed = true;
+
+ if (m_actionTimer.isActive())
+ m_actionTimer.stop();
+ m_pendingActions.clear();
m_asyncEventQueue->close();
}
+void MediaKeySession::trace(Visitor* visitor)
+{
+ visitor->trace(m_error);
+ visitor->trace(m_asyncEventQueue);
+ visitor->trace(m_pendingActions);
+ visitor->trace(m_keys);
+ visitor->trace(m_closedPromise);
+ EventTargetWithInlineData::trace(visitor);
}
+
+} // namespace blink