Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / modules / encryptedmedia / MediaKeySession.cpp
index b7d37d6..7c2f1ae 100644 (file)
 #include "config.h"
 #include "modules/encryptedmedia/MediaKeySession.h"
 
-#include "bindings/v8/ExceptionState.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/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>());
+    }
 
-namespace WebCore {
+    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)
+    {
+    }
 
-PassOwnPtr<MediaKeySession::PendingAction> MediaKeySession::PendingAction::CreatePendingUpdate(PassRefPtr<Uint8Array> data)
+    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)
 {
-    ASSERT(data);
-    return adoptPtr(new PendingAction(Update, data));
+    RefPtr<MediaKeySessionInitializer> initializer = adoptRef(new MediaKeySessionInitializer(scriptState, mediaKeys, initDataType, initData, sessionType));
+    initializer->suspendIfNeeded();
+    initializer->keepAliveWhilePending();
+    return initializer->promise();
 }
 
-PassOwnPtr<MediaKeySession::PendingAction> MediaKeySession::PendingAction::CreatePendingRelease()
+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)
 {
-    return adoptPtr(new PendingAction(Release, PassRefPtr<Uint8Array>()));
+    WTF_LOG(Media, "MediaKeySessionInitializer::MediaKeySessionInitializer");
+
+    // Start the timer so that MediaKeySession can be created asynchronously.
+    m_timer.startOneShot(0, FROM_HERE);
 }
 
-MediaKeySession::PendingAction::PendingAction(Type type, PassRefPtr<Uint8Array> data)
-    : type(type)
-    , data(data)
+MediaKeySessionInitializer::~MediaKeySessionInitializer()
 {
+    WTF_LOG(Media, "MediaKeySessionInitializer::~MediaKeySessionInitializer");
 }
 
-MediaKeySession::PendingAction::~PendingAction()
+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.
 }
 
-MediaKeySession* MediaKeySession::create(ExecutionContext* context, blink::WebContentDecryptionModule* cdm, MediaKeys* keys)
+void MediaKeySessionInitializer::completeWithSession(WebContentDecryptionModuleResult::SessionStatus status)
 {
-    MediaKeySession* session = adoptRefCountedGarbageCollectedWillBeNoop(new MediaKeySession(context, cdm, keys));
-    session->suspendIfNeeded();
-    return session;
+    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;
+
+    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();
 }
 
-MediaKeySession::MediaKeySession(ExecutionContext* context, blink::WebContentDecryptionModule* cdm, MediaKeys* keys)
+void MediaKeySessionInitializer::completeWithDOMException(ExceptionCode code, const String& errorMessage)
+{
+    WTF_LOG(Media, "MediaKeySessionInitializer::completeWithDOMException");
+    reject(DOMException::create(code, errorMessage));
+}
+
+ScriptPromise MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType)
+{
+    // Since creation is done asynchronously, use MediaKeySessionInitializer
+    // to do it.
+    return MediaKeySessionInitializer::create(scriptState, mediaKeys, initDataType, initData, sessionType);
+}
+
+MediaKeySession::MediaKeySession(ExecutionContext* context, MediaKeys* keys, PassOwnPtr<WebContentDecryptionModuleSession> cdmSession)
     : ActiveDOMObject(context)
     , m_keySystem(keys->keySystem())
     , m_asyncEventQueue(GenericEventQueue::create(this))
-    , m_session(adoptPtr(cdm->createSession(this)))
+    , m_session(cdmSession)
     , m_keys(keys)
     , 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
@@ -102,101 +358,164 @@ String MediaKeySession::sessionId() const
     return m_session->sessionId();
 }
 
-void MediaKeySession::initializeNewSession(const String& mimeType, const Uint8Array& initData)
+ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
 {
-    ASSERT(!m_isClosed);
-    m_session->initializeNewSession(mimeType, initData.data(), initData.length());
+    return m_closedPromise->promise(scriptState->world());
+}
+
+ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBuffer* response)
+{
+    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 response is an empty array, throw an INVALID_ACCESS_ERR
-    //    exception and abort these steps.
-    if (!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_pendingActions.append(PendingAction::CreatePendingUpdate(response));
+    // 3. Let promise be a new promise.
+    SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
+    ScriptPromise promise = result->promise();
 
+    // 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::release(ExceptionState& exceptionState)
+ScriptPromise MediaKeySession::release(ScriptState* scriptState)
 {
-    WTF_LOG(Media, "MediaKeySession::release");
-    ASSERT(!m_isClosed);
-
-    // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-release>:
-    // The release() method must run the following steps:
-    // 1. If the state of the MediaKeySession is CLOSED then abort these steps.
-    // 2. If the state of the MediaKeySession is ERROR, throw an INVALID_STATE_ERR
-    //    exception and abort these steps.
-    // FIXME: Implement states in MediaKeySession.
+    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;
+    }
 
-    // 3. Schedule a task to handle the call.
-    m_pendingActions.append(PendingAction::CreatePendingRelease());
+    // 2. Let promise be a new promise.
+    // (Created earlier so it was available in step 1.)
 
+    // 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());
 
-    while (!m_pendingActions.isEmpty()) {
-        OwnPtr<PendingAction> pendingAction(m_pendingActions.takeFirst());
+    // 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);
 
-        switch (pendingAction->type) {
+    while (!pendingActions.isEmpty()) {
+        PendingAction* action = pendingActions.takeFirst();
+
+        switch (action->type()) {
         case PendingAction::Update:
-            // NOTE: Continued from step 3. of MediaKeySession::update()
-            // 3.1. Let cdm be the cdm loaded in the MediaKeys constructor.
-            // 3.2. Let request be null.
-            // 3.3. Use cdm to execute the following steps:
-            // 3.3.1 Process response.
-            m_session->update(pendingAction->data->data(), pendingAction->data->length());
+            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:
-            // NOTE: Continued from step 3. of MediaKeySession::release().
-            // 3.1 Let cdm be the cdm loaded in the MediaKeys constructor.
-            // 3.2 Use cdm to execute the following steps:
-            // 3.2.1 Process the release request.
-            m_session->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 blink::WebURL& 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.message = ArrayBuffer::create(static_cast<const void*>(message), messageLength);
     init.destinationURL = destinationURL.string();
 
     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);
 
     RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::ready);
     event->setTarget(this);
@@ -205,21 +524,20 @@ void MediaKeySession::ready()
 
 void MediaKeySession::close()
 {
-    WTF_LOG(Media, "MediaKeySession::close");
-
-    RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::close);
-    event->setTarget(this);
-    m_asyncEventQueue->enqueueEvent(event.release());
+    WTF_LOG(Media, "MediaKeySession(%p)::close", this);
 
     // 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) {
@@ -243,7 +561,7 @@ void MediaKeySession::error(MediaKeyErrorCode errorCode, unsigned long systemCod
     m_asyncEventQueue->enqueueEvent(event.release());
 }
 
-void MediaKeySession::error(blink::WebContentDecryptionModuleException exception, unsigned long systemCode, const blink::WebString& errorMessage)
+void MediaKeySession::error(WebContentDecryptionModuleException exception, unsigned long systemCode, const WebString& errorMessage)
 {
     WTF_LOG(Media, "MediaKeySession::error: exception=%d, systemCode=%lu", exception, systemCode);
 
@@ -253,7 +571,7 @@ void MediaKeySession::error(blink::WebContentDecryptionModuleException exception
     // For now, simply generate an existing MediaKeyError.
     MediaKeyErrorCode errorCode;
     switch (exception) {
-    case blink::WebContentDecryptionModuleExceptionClientError:
+    case WebContentDecryptionModuleExceptionClientError:
         errorCode = MediaKeyErrorCodeClient;
         break;
     default:
@@ -278,6 +596,12 @@ 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_pendingActions.isEmpty()
         || m_asyncEventQueue->hasPendingEvents()
@@ -298,9 +622,12 @@ void MediaKeySession::stop()
 
 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