Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / modules / encryptedmedia / MediaKeySession.cpp
index f48367f..54417ce 100644 (file)
@@ -30,6 +30,8 @@
 #include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptPromiseResolver.h"
 #include "bindings/core/v8/ScriptState.h"
+#include "core/dom/DOMArrayBuffer.h"
+#include "core/dom/DOMArrayBufferView.h"
 #include "core/dom/ExceptionCode.h"
 #include "core/events/Event.h"
 #include "core/events/GenericEventQueue.h"
 #include "public/platform/WebContentDecryptionModuleSession.h"
 #include "public/platform/WebString.h"
 #include "public/platform/WebURL.h"
-#include "wtf/ArrayBuffer.h"
-#include "wtf/ArrayBufferView.h"
+#include "wtf/ASCIICType.h"
+#include <cmath>
+#include <limits>
+
+namespace {
+
+// The list of possible values for |sessionType|.
+const char* kTemporary = "temporary";
+const char* kPersistent = "persistent";
+
+// Minimum and maximum length for session ids.
+enum {
+    MinSessionIdLength = 1,
+    MaxSessionIdLength = 512
+};
+
+} // namespace
 
 namespace blink {
 
@@ -69,13 +86,33 @@ static bool isKeySystemSupportedWithInitDataType(const String& keySystem, const
     return MIMETypeRegistry::isSupportedEncryptedMediaMIMEType(keySystem, type.type(), type.parameter("codecs"));
 }
 
+// Checks that |sessionId| looks correct and returns whether all checks pass.
+static bool isValidSessionId(const String& sessionId)
+{
+    if ((sessionId.length() < MinSessionIdLength) || (sessionId.length() > MaxSessionIdLength))
+        return false;
+
+    if (!sessionId.containsOnlyASCII())
+        return false;
+
+    // Check that the sessionId only contains alphanumeric characters.
+    for (unsigned i = 0; i < sessionId.length(); ++i) {
+        if (!isASCIIAlphanumeric(sessionId[i]))
+            return false;
+    }
+
+    return true;
+}
+
 // A class holding a pending action.
 class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> {
 public:
     enum Type {
         GenerateRequest,
+        Load,
         Update,
-        Release
+        Close,
+        Remove
     };
 
     Type type() const { return m_type; }
@@ -85,7 +122,7 @@ public:
         return m_result;
     }
 
-    const RefPtr<ArrayBuffer> data() const
+    const PassRefPtr<DOMArrayBuffer> data() const
     {
         ASSERT(m_type == GenerateRequest || m_type == Update);
         return m_data;
@@ -94,27 +131,45 @@ public:
     const String& initDataType() const
     {
         ASSERT(m_type == GenerateRequest);
-        return m_initDataType;
+        return m_stringData;
     }
 
-    static PendingAction* CreatePendingGenerateRequest(ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<ArrayBuffer> initData)
+    const String& sessionId() const
+    {
+        ASSERT(m_type == Load);
+        return m_stringData;
+    }
+
+    static PendingAction* CreatePendingGenerateRequest(ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<DOMArrayBuffer> initData)
     {
         ASSERT(result);
         ASSERT(initData);
         return new PendingAction(GenerateRequest, result, initDataType, initData);
     }
 
-    static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<ArrayBuffer> data)
+    static PendingAction* CreatePendingLoadRequest(ContentDecryptionModuleResult* result, const String& sessionId)
+    {
+        ASSERT(result);
+        return new PendingAction(Load, result, sessionId, PassRefPtr<DOMArrayBuffer>());
+    }
+
+    static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<DOMArrayBuffer> data)
     {
         ASSERT(result);
         ASSERT(data);
         return new PendingAction(Update, result, String(), data);
     }
 
-    static PendingAction* CreatePendingRelease(ContentDecryptionModuleResult* result)
+    static PendingAction* CreatePendingClose(ContentDecryptionModuleResult* result)
     {
         ASSERT(result);
-        return new PendingAction(Release, result, String(), PassRefPtr<ArrayBuffer>());
+        return new PendingAction(Close, result, String(), PassRefPtr<DOMArrayBuffer>());
+    }
+
+    static PendingAction* CreatePendingRemove(ContentDecryptionModuleResult* result)
+    {
+        ASSERT(result);
+        return new PendingAction(Remove, result, String(), PassRefPtr<DOMArrayBuffer>());
     }
 
     ~PendingAction()
@@ -127,18 +182,18 @@ public:
     }
 
 private:
-    PendingAction(Type type, ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<ArrayBuffer> data)
+    PendingAction(Type type, ContentDecryptionModuleResult* result, const String& stringData, PassRefPtr<DOMArrayBuffer> data)
         : m_type(type)
         , m_result(result)
-        , m_initDataType(initDataType)
+        , m_stringData(stringData)
         , m_data(data)
     {
     }
 
     const Type m_type;
     const Member<ContentDecryptionModuleResult> m_result;
-    const String m_initDataType;
-    const RefPtr<ArrayBuffer> m_data;
+    const String m_stringData;
+    const RefPtr<DOMArrayBuffer> m_data;
 };
 
 // This class wraps the promise resolver used when initializing a new session
@@ -161,13 +216,13 @@ public:
     }
 
     // ContentDecryptionModuleResult implementation.
-    virtual void complete() OVERRIDE
+    virtual void complete() override
     {
         ASSERT_NOT_REACHED();
         completeWithDOMException(InvalidStateError, "Unexpected completion.");
     }
 
-    virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
+    virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) override
     {
         if (status != WebContentDecryptionModuleResult::NewSession) {
             ASSERT_NOT_REACHED();
@@ -179,7 +234,82 @@ public:
         m_resolver.clear();
     }
 
-    virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) OVERRIDE
+    virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) override
+    {
+        completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage);
+    }
+
+    // It is only valid to call this before completion.
+    ScriptPromise promise() { return m_resolver->promise(); }
+
+    void trace(Visitor* visitor)
+    {
+        visitor->trace(m_session);
+        ContentDecryptionModuleResult::trace(visitor);
+    }
+
+private:
+    // Reject the promise with a DOMException.
+    void completeWithDOMException(ExceptionCode code, const String& errorMessage)
+    {
+        m_resolver->reject(DOMException::create(code, errorMessage));
+        m_resolver.clear();
+    }
+
+    RefPtr<ScriptPromiseResolver> m_resolver;
+    Member<MediaKeySession> m_session;
+};
+
+// This class wraps the promise resolver used when loading a session
+// and is passed to Chromium to fullfill the promise. This implementation of
+// completeWithSession() will resolve the promise with true/false, while
+// completeWithError() will reject the promise with an exception. complete()
+// is not expected to be called, and will reject the promise.
+class LoadSessionResult : public ContentDecryptionModuleResult {
+public:
+    LoadSessionResult(ScriptState* scriptState, MediaKeySession* session)
+        : m_resolver(ScriptPromiseResolver::create(scriptState))
+        , m_session(session)
+    {
+        WTF_LOG(Media, "LoadSessionResult(%p)", this);
+    }
+
+    virtual ~LoadSessionResult()
+    {
+        WTF_LOG(Media, "~LoadSessionResult(%p)", this);
+    }
+
+    // ContentDecryptionModuleResult implementation.
+    virtual void complete() override
+    {
+        ASSERT_NOT_REACHED();
+        completeWithDOMException(InvalidStateError, "Unexpected completion.");
+    }
+
+    virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) override
+    {
+        bool result = false;
+        switch (status) {
+        case WebContentDecryptionModuleResult::NewSession:
+            result = true;
+            break;
+
+        case WebContentDecryptionModuleResult::SessionNotFound:
+            result = false;
+            break;
+
+        case WebContentDecryptionModuleResult::SessionAlreadyExists:
+            ASSERT_NOT_REACHED();
+            completeWithDOMException(InvalidStateError, "Unexpected completion.");
+            return;
+        }
+
+        m_session->finishLoad();
+        m_resolver->resolve(result);
+        m_resolver.clear();
+    }
+
+    virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) override
     {
         completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage);
     }
@@ -207,17 +337,24 @@ private:
 
 MediaKeySession* MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
 {
-    RefPtrWillBeRawPtr<MediaKeySession> session = adoptRefCountedGarbageCollectedWillBeNoop(new MediaKeySession(scriptState, mediaKeys, sessionType));
+    ASSERT(sessionType == kTemporary || sessionType == kPersistent);
+    RefPtrWillBeRawPtr<MediaKeySession> session = new MediaKeySession(scriptState, mediaKeys, sessionType);
     session->suspendIfNeeded();
     return session.get();
 }
 
+bool MediaKeySession::isValidSessionType(const String& sessionType)
+{
+    return (sessionType == kTemporary || sessionType == kPersistent);
+}
+
 MediaKeySession::MediaKeySession(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
     : ActiveDOMObject(scriptState->executionContext())
     , m_keySystem(mediaKeys->keySystem())
     , m_asyncEventQueue(GenericEventQueue::create(this))
     , m_mediaKeys(mediaKeys)
     , m_sessionType(sessionType)
+    , m_expiration(std::numeric_limits<double>::quiet_NaN())
     , m_isUninitialized(true)
     , m_isCallable(false)
     , m_isClosed(false)
@@ -238,13 +375,13 @@ MediaKeySession::MediaKeySession(ScriptState* scriptState, MediaKeys* mediaKeys,
     ASSERT(sessionId().isEmpty());
 
     // 2.2 Let the expiration attribute be NaN.
-    // FIXME: Add expiration property.
+    ASSERT(std::isnan(m_expiration));
 
     // 2.3 Let the closed attribute be a new promise.
     ASSERT(!closed(scriptState).isUndefinedOrNull());
 
     // 2.4 Let the session type be sessionType.
-    ASSERT(sessionType == m_sessionType);
+    ASSERT(isValidSessionType(sessionType));
 
     // 2.5 Let uninitialized be true.
     ASSERT(m_isUninitialized);
@@ -280,19 +417,19 @@ ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
     return m_closedPromise->promise(scriptState->world());
 }
 
-ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, ArrayBuffer* initData)
+ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, DOMArrayBuffer* initData)
 {
-    RefPtr<ArrayBuffer> initDataCopy = ArrayBuffer::create(initData->data(), initData->byteLength());
+    RefPtr<DOMArrayBuffer> initDataCopy = DOMArrayBuffer::create(initData->data(), initData->byteLength());
     return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
 }
 
-ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, ArrayBufferView* initData)
+ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, DOMArrayBufferView* initData)
 {
-    RefPtr<ArrayBuffer> initDataCopy = ArrayBuffer::create(initData->baseAddress(), initData->byteLength());
+    RefPtr<DOMArrayBuffer> initDataCopy = DOMArrayBuffer::create(initData->baseAddress(), initData->byteLength());
     return generateRequestInternal(scriptState, initDataType, initDataCopy.release());
 }
 
-ScriptPromise MediaKeySession::generateRequestInternal(ScriptState* scriptState, const String& initDataType, PassRefPtr<ArrayBuffer> initData)
+ScriptPromise MediaKeySession::generateRequestInternal(ScriptState* scriptState, const String& initDataType, PassRefPtr<DOMArrayBuffer> initData)
 {
     WTF_LOG(Media, "MediaKeySession(%p)::generateRequest %s", this, initDataType.ascii().data());
 
@@ -356,19 +493,75 @@ ScriptPromise MediaKeySession::generateRequestInternal(ScriptState* scriptState,
     return promise;
 }
 
-ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBuffer* response)
+ScriptPromise MediaKeySession::load(ScriptState* scriptState, const String& sessionId)
 {
-    RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->data(), response->byteLength());
+    WTF_LOG(Media, "MediaKeySession(%p)::load %s", this, sessionId.ascii().data());
+
+    // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-load:
+    // The load(sessionId) method loads the data stored for the sessionId into
+    // the session represented by the object. It must run the following steps:
+
+    // 1. If this object's uninitialized value is false, return a promise
+    //    rejected with a new DOMException whose name is "InvalidStateError".
+    if (!m_isUninitialized) {
+        return ScriptPromise::rejectWithDOMException(
+            scriptState, DOMException::create(InvalidStateError, "The session is already initialized."));
+    }
+
+    // 2. Let this object's uninitialized be false.
+    m_isUninitialized = false;
+
+    // 3. If sessionId is an empty string, return a promise rejected with a
+    //    new DOMException whose name is "InvalidAccessError".
+    if (sessionId.isEmpty()) {
+        return ScriptPromise::rejectWithDOMException(
+            scriptState, DOMException::create(InvalidAccessError, "The sessionId parameter is empty."));
+    }
+
+    // 4. If this object's session type is not "persistent", return a promise
+    //    rejected with a new DOMException whose name is "InvalidAccessError".
+    if (m_sessionType != kPersistent) {
+        return ScriptPromise::rejectWithDOMException(
+            scriptState, DOMException::create(InvalidAccessError, "The session type is not 'persistent'."));
+    }
+
+    // 5. Let media keys be the MediaKeys object that created this object.
+    //    (Done in constructor.)
+    ASSERT(m_mediaKeys);
+
+    // 6. If the content decryption module corresponding to media keys's
+    //    keySystem attribute does not support loading previous sessions,
+    //    return a promise rejected with a new DOMException whose name is
+    //    "NotSupportedError".
+    //    (Done by CDM.)
+
+    // 7. Let promise be a new promise.
+    LoadSessionResult* result = new LoadSessionResult(scriptState, this);
+    ScriptPromise promise = result->promise();
+
+    // 8. Run the following steps asynchronously (documented in
+    //    actionTimerFired())
+    m_pendingActions.append(PendingAction::CreatePendingLoadRequest(result, sessionId));
+    ASSERT(!m_actionTimer.isActive());
+    m_actionTimer.startOneShot(0, FROM_HERE);
+
+    // 9. Return promise.
+    return promise;
+}
+
+ScriptPromise MediaKeySession::update(ScriptState* scriptState, DOMArrayBuffer* response)
+{
+    RefPtr<DOMArrayBuffer> responseCopy = DOMArrayBuffer::create(response->data(), response->byteLength());
     return updateInternal(scriptState, responseCopy.release());
 }
 
-ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBufferView* response)
+ScriptPromise MediaKeySession::update(ScriptState* scriptState, DOMArrayBufferView* response)
 {
-    RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->baseAddress(), response->byteLength());
+    RefPtr<DOMArrayBuffer> responseCopy = DOMArrayBuffer::create(response->baseAddress(), response->byteLength());
     return updateInternal(scriptState, responseCopy.release());
 }
 
-ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<ArrayBuffer> response)
+ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<DOMArrayBuffer> response)
 {
     WTF_LOG(Media, "MediaKeySession(%p)::update", this);
     ASSERT(!m_isClosed);
@@ -402,36 +595,84 @@ ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefP
     return promise;
 }
 
-ScriptPromise MediaKeySession::release(ScriptState* scriptState)
+ScriptPromise MediaKeySession::close(ScriptState* scriptState)
 {
-    WTF_LOG(Media, "MediaKeySession(%p)::release", this);
-    SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
-    ScriptPromise promise = result->promise();
+    WTF_LOG(Media, "MediaKeySession(%p)::close", this);
 
-    // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close>:
+    // 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.
+    // 1. If this object's callable value is false, return a promise rejected
+    //    with a new DOMException whose name is "InvalidStateError".
+    if (!m_isCallable) {
+        return ScriptPromise::rejectWithDOMException(
+            scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
+    }
+
+    // 2. If the Session Close algorithm has been run on this object,
+    //    return a resolved promise.
+    if (m_isClosed)
+        return ScriptPromise::cast(scriptState, ScriptValue());
+
+    // 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::CreatePendingClose(result));
+    if (!m_actionTimer.isActive())
+        m_actionTimer.startOneShot(0, FROM_HERE);
+
+    // 5. Return promise.
+    return promise;
+}
+
+ScriptPromise MediaKeySession::remove(ScriptState* scriptState)
+{
+    WTF_LOG(Media, "MediaKeySession(%p)::remove", this);
+
+    // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-remove:
+    // The remove() method allows an application to remove stored session data
+    // associated with this object. It must run the following steps:
+
+    // 1. If this object's callable value is false, return a promise rejected
+    //    with a new DOMException whose name is "InvalidStateError".
+    if (!m_isCallable) {
+        return ScriptPromise::rejectWithDOMException(
+            scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
+    }
+
+    // 2. If this object's session type is not "persistent", return a promise
+    //    rejected with a new DOMException whose name is "InvalidAccessError".
+    if (m_sessionType != kPersistent) {
+        return ScriptPromise::rejectWithDOMException(
+            scriptState, DOMException::create(InvalidAccessError, "The session type is not 'persistent'."));
+    }
+
+    // 3. If the Session Close algorithm has been run on this object, return a
+    //    promise rejected with a new DOMException whose name is
+    //    "InvalidStateError".
     if (m_isClosed) {
-        result->complete();
-        return promise;
+        return ScriptPromise::rejectWithDOMException(
+            scriptState, DOMException::create(InvalidStateError, "The session is already closed."));
     }
 
-    // 2. Let promise be a new promise.
-    // (Created earlier so it was available in step 1.)
+    // 4. Let promise be a new promise.
+    SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
+    ScriptPromise promise = result->promise();
 
-    // 3. Run the following steps asynchronously (documented in
+    // 5. Run the following steps asynchronously (documented in
     //    actionTimerFired()).
-    m_pendingActions.append(PendingAction::CreatePendingRelease(result));
+    m_pendingActions.append(PendingAction::CreatePendingRemove(result));
     if (!m_actionTimer.isActive())
         m_actionTimer.startOneShot(0, FROM_HERE);
 
-    // 4. Return promise.
+    // 6. Return promise.
     return promise;
 }
 
@@ -471,6 +712,63 @@ void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
             // when |result| is resolved.
             break;
 
+        case PendingAction::Load:
+            // NOTE: Continue step 8 of MediaKeySession::load().
+
+            // 8.1 Let sanitized session ID be a validated and/or sanitized
+            //     version of sessionId. The user agent should thoroughly
+            //     validate the sessionId value before passing it to the CDM.
+            //     At a minimum, this should include checking that the length
+            //     and value (e.g. alphanumeric) are reasonable.
+            // 8.2 If the previous step failed, reject promise with a new
+            //     DOMException whose name is "InvalidAccessError".
+            if (!isValidSessionId(action->sessionId())) {
+                action->result()->completeWithError(WebContentDecryptionModuleExceptionInvalidAccessError, 0, "Invalid sessionId");
+                return;
+            }
+
+            // 8.3 Let expiration time be NaN.
+            //     (Done in the constructor.)
+            ASSERT(std::isnan(m_expiration));
+
+            // 8.4 Let message be null.
+            // 8.5 Let message type be null.
+            //     (Will be provided by the CDM if needed.)
+
+            // 8.6 Let origin be the origin of this object's Document.
+            //     (Obtained previously when CDM created.)
+
+            // 8.7 Let cdm be the CDM loaded during the initialization of media
+            //     keys.
+            // 8.8 Use the cdm to execute the following steps:
+            // 8.8.1 If there is no data stored for the sanitized session ID in
+            //       the origin, resolve promise with false.
+            // 8.8.2 Let session data be the data stored for the sanitized
+            //       session ID in the origin. This must not include data from
+            //       other origin(s) or that is not associated with an origin.
+            // 8.8.3 If there is an unclosed "persistent" session in any
+            //       Document representing the session data, reject promise
+            //       with a new DOMException whose name is "QuotaExceededError".
+            // 8.8.4 In other words, do not create a session if a non-closed
+            //       persistent session already exists for this sanitized
+            //       session ID in any browsing context.
+            // 8.8.5 Load the session data.
+            // 8.8.6 If the session data indicates an expiration time for the
+            //       session, let expiration time be the expiration time
+            //       in milliseconds since 01 January 1970 UTC.
+            // 8.8.6 If the CDM needs to send a message:
+            // 8.8.6.1 Let message be a message generated by the CDM based on
+            //         the session data.
+            // 8.8.6.2 Let message type be the appropriate MediaKeyMessageType
+            //         for the message.
+            // 8.9 If any of the preceding steps failed, reject promise with a
+            //     new DOMException whose name is the appropriate error name.
+            m_session->load(action->sessionId(), action->result()->result());
+
+            // Remainder of steps executed in finishLoad(), called
+            // when |result| is resolved.
+            break;
+
         case PendingAction::Update:
             WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Update", this);
             // NOTE: Continued from step 4 of MediaKeySession::update().
@@ -479,16 +777,43 @@ void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
             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());
+        case PendingAction::Close:
+            WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Close", this);
+            // NOTE: Continued from step 4 of MediaKeySession::close().
+            // 4.1 Let cdm be the CDM loaded during the initialization of the
+            //     MediaKeys object that created this object.
+            //     (Already captured when creating m_session).
+            // 4.2 Use the cdm to execute the following steps:
+            // 4.2.1 Process the close request. Do not remove stored session
+            //       data.
+            // 4.2.3 If the previous step caused the session to be closed,
+            //       run the Session Close algorithm on this object.
+            // 4.3 Resolve promise.
+            m_session->close(action->result()->result());
+            break;
+
+        case PendingAction::Remove:
+            WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Remove", this);
+            // NOTE: Continued from step 5 of MediaKeySession::remove().
+            // 5.1 Let cdm be the CDM loaded during the initialization of the
+            //     MediaKeys object that created this object.
+            //     (Already captured when creating m_session).
+            // 5.2 Use the cdm to execute the following steps:
+            // 5.2.1 Process the remove request. This may involve exchanging
+            //       message(s) with the application. Unless this step fails,
+            //       the CDM must have cleared all stored session data
+            //       associated with this object, including the sessionId,
+            //       before proceeding to the next step. (A subsequent call
+            //       to load() with sessionId would fail because there is no
+            //       data stored for the sessionId.)
+            // 5.3 Run the following steps asynchronously once the above step
+            //     has completed:
+            // 5.3.1 If any of the preceding steps failed, reject promise
+            //       with a new DOMException whose name is the appropriate
+            //       error name.
+            // 5.3.2 Run the Session Close algorithm on this object.
+            // 5.3.3 Resolve promise.
+            m_session->remove(action->result()->result());
             break;
         }
     }
@@ -517,6 +842,31 @@ void MediaKeySession::finishGenerateRequest()
     m_isCallable = true;
 }
 
+void MediaKeySession::finishLoad()
+{
+    // 8.10 Set the sessionId attribute to sanitized session ID.
+    ASSERT(!sessionId().isEmpty());
+
+    // 8.11 Let this object's callable be true.
+    m_isCallable = true;
+
+    // 8.12 If the loaded session contains usable keys, run the Usable
+    //      Keys Changed algorithm on the session. The algorithm may
+    //      also be run later should additional processing be necessary
+    //      to determine with certainty whether one or more keys is
+    //      usable.
+    //      (Done by the CDM.)
+
+    // 8.13 Run the Update Expiration algorithm on the session,
+    //      providing expiration time.
+    //      (Done by the CDM.)
+
+    // 8.14 If message is not null, run the Queue a "message" Event
+    //      algorithm on the session, providing message type and
+    //      message.
+    //      (Done by the CDM.)
+}
+
 // 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 WebURL& destinationURL)
 {
@@ -528,7 +878,7 @@ void MediaKeySession::message(const unsigned char* message, size_t messageLength
     MediaKeyMessageEventInit init;
     init.bubbles = false;
     init.cancelable = false;
-    init.message = ArrayBuffer::create(static_cast<const void*>(message), messageLength);
+    init.message = DOMArrayBuffer::create(static_cast<const void*>(message), messageLength);
     init.destinationURL = destinationURL.string();
 
     RefPtrWillBeRawPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init);
@@ -605,6 +955,11 @@ void MediaKeySession::error(WebContentDecryptionModuleException exception, unsig
     error(errorCode, systemCode);
 }
 
+void MediaKeySession::expirationChanged(double updatedExpiryTimeInMS)
+{
+    m_expiration = updatedExpiryTimeInMS;
+}
+
 const AtomicString& MediaKeySession::interfaceName() const
 {
     return EventTargetNames::MediaKeySession;