[EME] Implement MediaKeySession::update()
authorzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Feb 2017 06:28:03 +0000 (06:28 +0000)
committerzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Feb 2017 06:28:03 +0000 (06:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167636

Reviewed by Xabier Rodriguez-Calvar.

Source/WebCore:

Implement the MediaKeySession::update() method by following the steps as
they are described in the specification.

In order to sanitize the passed-in response data, CDM::sanitizeResponse()
is added. It passes the SharedBuffer object by reference to the CDMPrivate
interface implementor, which returns a SharedBuffer object containing
sanitized response data.

CDMInstance::updateLicense() virtual method is added to perform the license
update for some specific CDMInstance object. After the update the CDMInstance
invokes the callback that's passed to updateLicense(), providing information
about session being closed, changed keys or expiration value, any message
that has to be enqueued, and whether the update was successful.

After that callback is invoked, MediaKeySession::update() goes on to handle
all the provided information in a future task, finally resolving the promise
(or rejecting it beforehand in case of any failure during response handling
or license update).

Three algorithms that can be invoked from MediaKeySession::update() (key
status update, expiration update and session closure) will be implemented
separately. Placeholder methods are provided until then.

MockCDM::sanitizeResponse() and MockCDMInstance::updateLicense() are
implemented for testing purposes. For now only the response sanitization
and sanitized response format are checked there. Key status update,
expiration update and session closure should be tested once the
implementations for those algorithms are added.

Test: media/encrypted-media/mock-MediaKeySession-update.html

* Modules/encryptedmedia/CDM.cpp:
(WebCore::CDM::sanitizeResponse):
* Modules/encryptedmedia/CDM.h:
* Modules/encryptedmedia/CDMInstance.h:
* Modules/encryptedmedia/CDMPrivate.h:
* Modules/encryptedmedia/MediaKeySession.cpp:
(WebCore::MediaKeySession::update):
(WebCore::MediaKeySession::updateKeyStatuses):
(WebCore::MediaKeySession::updateExpiration):
(WebCore::MediaKeySession::sessionClosed):
* Modules/encryptedmedia/MediaKeySession.h:
* testing/MockCDMFactory.cpp:
(WebCore::MockCDM::sanitizeResponse):
(WebCore::MockCDMInstance::updateLicense):
* testing/MockCDMFactory.h:

LayoutTests:

Add the mock-MediaKeySession-update.html test, cotaining a few cases that check
the basic operations of MediaKeySession::update(), focusing on proper promise
resolution and rejection based on the state of the object and the passed-in
response argument. Skip the test on all platforms for now.

* media/encrypted-media/mock-MediaKeySession-update-expected.txt: Added.
* media/encrypted-media/mock-MediaKeySession-update.html: Added.
* platform/efl/TestExpectations:
* platform/mac/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@211550 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
LayoutTests/ChangeLog
LayoutTests/media/encrypted-media/mock-MediaKeySession-update-expected.txt [new file with mode: 0644]
LayoutTests/media/encrypted-media/mock-MediaKeySession-update.html [new file with mode: 0644]
LayoutTests/platform/efl/TestExpectations
LayoutTests/platform/mac/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/encryptedmedia/CDM.cpp
Source/WebCore/Modules/encryptedmedia/CDM.h
Source/WebCore/Modules/encryptedmedia/CDMInstance.h
Source/WebCore/Modules/encryptedmedia/CDMPrivate.h
Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp
Source/WebCore/Modules/encryptedmedia/MediaKeySession.h
Source/WebCore/testing/MockCDMFactory.cpp
Source/WebCore/testing/MockCDMFactory.h

index 7f4177b..376fe2c 100644 (file)
@@ -1,3 +1,20 @@
+2017-02-01  Zan Dobersek  <zdobersek@igalia.com>
+
+        [EME] Implement MediaKeySession::update()
+        https://bugs.webkit.org/show_bug.cgi?id=167636
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Add the mock-MediaKeySession-update.html test, cotaining a few cases that check
+        the basic operations of MediaKeySession::update(), focusing on proper promise
+        resolution and rejection based on the state of the object and the passed-in
+        response argument. Skip the test on all platforms for now.
+
+        * media/encrypted-media/mock-MediaKeySession-update-expected.txt: Added.
+        * media/encrypted-media/mock-MediaKeySession-update.html: Added.
+        * platform/efl/TestExpectations:
+        * platform/mac/TestExpectations:
+
 2017-02-01  Ryan Haddad  <ryanhaddad@apple.com>
 
         Rebaseline compositing/geometry/fixed-in-composited.html for ios-simulator.
diff --git a/LayoutTests/media/encrypted-media/mock-MediaKeySession-update-expected.txt b/LayoutTests/media/encrypted-media/mock-MediaKeySession-update-expected.txt
new file mode 100644 (file)
index 0000000..a8fa157
--- /dev/null
@@ -0,0 +1,50 @@
+RUN(internals.initializeMockMediaSource())
+RUN(mock = internals.registerMockCDM())
+RUN(mock.supportedDataTypes = ["keyids"])
+RUN(capabilities.initDataTypes = ["keyids"])
+RUN(capabilities.videoCapabilities = [{ contentType: 'video/mock; codecs="mock"' }] )
+RUN(promise = navigator.requestMediaKeySystemAccess("org.webkit.mock", [capabilities]))
+Promise resolved OK
+
+RUN(promise = mediaKeySystemAccess.createMediaKeys())
+Promise resolved OK
+
+Using a non-callable MediaKeySession should reject.
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+EXPECTED (typeof mediaKeySession == 'object') OK
+RUN(promise = mediaKeySession.update(stringToUInt8Array("invalid-state")))
+Promise rejected correctly OK
+
+Using a zero-length response should reject.
+RUN(kids = JSON.stringify({ kids: [ "MTIzNDU=" ] }))
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+RUN(promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids)))
+Promise resolved OK
+RUN(promise = mediaKeySession.update(new Uint8Array(0)))
+Promise rejected correctly OK
+
+Using a non-sanitizable response should reject.
+RUN(kids = JSON.stringify({ kids: [ "MTIzNDU=" ] }))
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+RUN(promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids)))
+Promise resolved OK
+RUN(promise = mediaKeySession.update(stringToUInt8Array("invalid-response")))
+Promise rejected correctly OK
+
+Using a sanitizable response should resolve.
+RUN(kids = JSON.stringify({ kids: [ "MTIzNDU=" ] }))
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+RUN(promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids)))
+Promise resolved OK
+RUN(promise = mediaKeySession.update(stringToUInt8Array("valid-response")))
+Promise resolved OK
+
+Using a sanitizable response with invalid format should resolve.
+RUN(kids = JSON.stringify({ kids: [ "MTIzNDU=" ] }))
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+RUN(promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids)))
+Promise resolved OK
+RUN(promise = mediaKeySession.update(stringToUInt8Array("valid-response invalid-format")))
+Promise rejected correctly OK
+END OF TEST
+
diff --git a/LayoutTests/media/encrypted-media/mock-MediaKeySession-update.html b/LayoutTests/media/encrypted-media/mock-MediaKeySession-update.html
new file mode 100644 (file)
index 0000000..23d5c8d
--- /dev/null
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src=../video-test.js></script>
+    <script type="text/javascript">
+    var mock;
+    var promise;
+    var mediaKeySystemAccess;
+    var mediaKeys;
+    var mediaKeySession;
+    var capabilities = {};
+    var kids;
+
+    function doTest()
+    {
+        if (!window.internals) {
+            failTest("Internals is required for this test.")
+            return;
+        }
+
+        run('internals.initializeMockMediaSource()');
+        run('mock = internals.registerMockCDM()');
+        run('mock.supportedDataTypes = ["keyids"]');
+        run('capabilities.initDataTypes = ["keyids"]');
+        run(`capabilities.videoCapabilities = [{ contentType: 'video/mock; codecs="mock"' }] `);
+        run('promise = navigator.requestMediaKeySystemAccess("org.webkit.mock", [capabilities])');
+        shouldResolve(promise).then(gotMediaKeySystemAccess, failTest);
+    }
+
+    function next() {
+        if (!tests.length) {
+            mock.unregister();
+            endTest()
+            return;
+        }
+
+        var nextTest = tests.shift();
+        consoleWrite('');
+        nextTest();
+    }
+
+    function gotMediaKeySystemAccess(result) {
+        mediaKeySystemAccess = result;
+        next();
+    }
+
+    function gotMediaKeys(result) {
+        mediaKeys = result;
+        next();
+    }
+
+    function stringToUInt8Array(str)
+    {
+       var array = new Uint8Array(str.length);
+       for (var i=0; i<str.length; i++)
+            array[i] = str.charCodeAt(i);
+       return array;
+    }
+
+    tests = [
+        function() {
+            run('promise = mediaKeySystemAccess.createMediaKeys()');
+            shouldResolve(promise).then(gotMediaKeys, failTest);
+        },
+
+        function() {
+            consoleWrite('Using a non-callable MediaKeySession should reject.');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            testExpected('typeof mediaKeySession', 'object');
+            run('promise = mediaKeySession.update(stringToUInt8Array("invalid-state"))');
+            shouldReject(promise).then(next, next);
+        },
+
+        function() {
+            consoleWrite('Using a zero-length response should reject.');
+            run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids))');
+            shouldResolve(promise).then(function() {
+                run('promise = mediaKeySession.update(new Uint8Array(0))');
+                shouldReject(promise).then(next, next);
+            }, next);
+        },
+
+        function() {
+            consoleWrite('Using a non-sanitizable response should reject.');
+            run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids))');
+            shouldResolve(promise).then(function() {
+                run('promise = mediaKeySession.update(stringToUInt8Array("invalid-response"))');
+                shouldReject(promise).then(next, next);
+            }, next);
+        },
+
+        function() {
+            consoleWrite('Using a sanitizable response should resolve.');
+            run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids))');
+            shouldResolve(promise).then(function() {
+                run('promise = mediaKeySession.update(stringToUInt8Array("valid-response"))');
+                shouldResolve(promise).then(next, next);
+            }, next);
+        },
+
+        function() {
+            consoleWrite('Using a sanitizable response with invalid format should resolve.');
+            run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", stringToUInt8Array(kids))');
+            shouldResolve(promise).then(function() {
+                run('promise = mediaKeySession.update(stringToUInt8Array("valid-response invalid-format"))');
+                shouldReject(promise).then(next, next);
+            }, next);
+        },
+    ];
+    </script>
+</head>
+<body onload="doTest()">
+</body>
+</html>
index 7e71c27..26fd3e9 100644 (file)
@@ -2982,6 +2982,7 @@ Bug(EFL) media/encrypted-media/encrypted-media-is-type-supported.html [ Failure
 Bug(EFL) media/encrypted-media/encrypted-media-not-loaded.html [ Failure ]
 Bug(EFL) media/encrypted-media/encrypted-media-syntax.html [ Failure ]
 Bug(EFL) media/encrypted-media/mock-MediaKeySession-generateRequest.html [ Failure ]
+Bug(EFL) media/encrypted-media/mock-MediaKeySession-update.html [ Failure ]
 Bug(EFL) media/encrypted-media/mock-MediaKeySystemAccess.html [ Failure ]
 Bug(EFL) media/encrypted-media/mock-MediaKeys-createSession.html [ Failure ]
 Bug(EFL) media/encrypted-media/mock-MediaKeys-setServerCertificate.html [ Failure ]
index d2c5aad..d7dd333 100644 (file)
@@ -1496,6 +1496,7 @@ media/encrypted-media/mock-MediaKeySystemAccess.html [ Skip ]
 media/encrypted-media/mock-MediaKeys-setServerCertificate.html [ Skip ]
 media/encrypted-media/mock-MediaKeys-createSession.html [ Skip ]
 media/encrypted-media/mock-MediaKeySession-generateRequest.html [ Skip ]
+media/encrypted-media/mock-MediaKeySession-update.html [ Skip ]
 
 webkit.org/b/166025 http/tests/fetch/fetching-same-resource-with-diffferent-options.html [ Pass Failure ]
 
index df80d8f..a040b0b 100644 (file)
@@ -1,3 +1,57 @@
+2017-02-01  Zan Dobersek  <zdobersek@igalia.com>
+
+        [EME] Implement MediaKeySession::update()
+        https://bugs.webkit.org/show_bug.cgi?id=167636
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Implement the MediaKeySession::update() method by following the steps as
+        they are described in the specification.
+
+        In order to sanitize the passed-in response data, CDM::sanitizeResponse()
+        is added. It passes the SharedBuffer object by reference to the CDMPrivate
+        interface implementor, which returns a SharedBuffer object containing
+        sanitized response data.
+
+        CDMInstance::updateLicense() virtual method is added to perform the license
+        update for some specific CDMInstance object. After the update the CDMInstance
+        invokes the callback that's passed to updateLicense(), providing information
+        about session being closed, changed keys or expiration value, any message
+        that has to be enqueued, and whether the update was successful.
+
+        After that callback is invoked, MediaKeySession::update() goes on to handle
+        all the provided information in a future task, finally resolving the promise
+        (or rejecting it beforehand in case of any failure during response handling
+        or license update).
+
+        Three algorithms that can be invoked from MediaKeySession::update() (key
+        status update, expiration update and session closure) will be implemented
+        separately. Placeholder methods are provided until then.
+
+        MockCDM::sanitizeResponse() and MockCDMInstance::updateLicense() are
+        implemented for testing purposes. For now only the response sanitization
+        and sanitized response format are checked there. Key status update,
+        expiration update and session closure should be tested once the
+        implementations for those algorithms are added.
+
+        Test: media/encrypted-media/mock-MediaKeySession-update.html
+
+        * Modules/encryptedmedia/CDM.cpp:
+        (WebCore::CDM::sanitizeResponse):
+        * Modules/encryptedmedia/CDM.h:
+        * Modules/encryptedmedia/CDMInstance.h:
+        * Modules/encryptedmedia/CDMPrivate.h:
+        * Modules/encryptedmedia/MediaKeySession.cpp:
+        (WebCore::MediaKeySession::update):
+        (WebCore::MediaKeySession::updateKeyStatuses):
+        (WebCore::MediaKeySession::updateExpiration):
+        (WebCore::MediaKeySession::sessionClosed):
+        * Modules/encryptedmedia/MediaKeySession.h:
+        * testing/MockCDMFactory.cpp:
+        (WebCore::MockCDM::sanitizeResponse):
+        (WebCore::MockCDMInstance::updateLicense):
+        * testing/MockCDMFactory.h:
+
 2017-02-01  Eric Carlson  <eric.carlson@apple.com>
 
         [Mac] Update CARingBuffer class
index 5646dfb..846f12a 100644 (file)
@@ -644,6 +644,13 @@ bool CDM::supportsInitData(const AtomicString& initDataType, const SharedBuffer&
     return m_private && m_private->supportsInitData(initDataType, initData);
 }
 
+RefPtr<SharedBuffer> CDM::sanitizeResponse(const SharedBuffer& response)
+{
+    if (!m_private)
+        return nullptr;
+    return m_private->sanitizeResponse(response);
+}
+
 }
 
 #endif
index 7c15934..ec31c0e 100644 (file)
@@ -80,6 +80,8 @@ public:
     RefPtr<SharedBuffer> sanitizeInitData(const AtomicString& initDataType, const SharedBuffer&);
     bool supportsInitData(const AtomicString& initDataType, const SharedBuffer&);
 
+    RefPtr<SharedBuffer> sanitizeResponse(const SharedBuffer&);
+
 private:
     CDM(Document&, const String& keySystem);
 
index fd48df1..197c5e1 100644 (file)
 
 #if ENABLE(ENCRYPTED_MEDIA)
 
+#include <utility>
 #include <wtf/Forward.h>
+#include <wtf/Optional.h>
 #include <wtf/RefCounted.h>
+#include <wtf/Vector.h>
 
 namespace WebCore {
 
@@ -57,6 +60,28 @@ public:
 
     using LicenseCallback = Function<void(Ref<SharedBuffer>&& message, const String& sessionId, bool needsIndividualization, SuccessValue succeeded)>;
     virtual void requestLicense(LicenseType, const AtomicString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback) = 0;
+
+    enum class KeyStatus {
+        Usable,
+        Expired,
+        Released,
+        OutputRestricted,
+        OutputDownscaled,
+        StatusPending,
+        InternalError,
+    };
+
+    enum class MessageType {
+        LicenseRequest,
+        LicenseRenewal,
+        LicenseRelease,
+        IndividualizationRequest,
+    };
+
+    using KeyStatusVector = Vector<std::pair<Ref<SharedBuffer>, KeyStatus>>;
+    using Message = std::pair<MessageType, Ref<SharedBuffer>>;
+    using LicenseUpdateCallback = Function<void(bool sessionWasClosed, std::optional<KeyStatusVector>&& changedKeys, std::optional<double>&& changedExpiration, std::optional<Message>&& message, SuccessValue succeeded)>;
+    virtual void updateLicense(LicenseType, const SharedBuffer& response, LicenseUpdateCallback) = 0;
 };
 
 }
index 793c540..72a39c8 100644 (file)
@@ -54,6 +54,7 @@ public:
     virtual bool supportsServerCertificates() const = 0;
     virtual bool supportsSessions() const = 0;
     virtual bool supportsInitData(const AtomicString&, const SharedBuffer&) const = 0;
+    virtual RefPtr<SharedBuffer> sanitizeResponse(const SharedBuffer&) const = 0;
 };
 
 }
index 9b160c5..872b9e1 100644 (file)
@@ -245,9 +245,143 @@ void MediaKeySession::load(const String&, Ref<DeferredPromise>&&)
     notImplemented();
 }
 
-void MediaKeySession::update(const BufferSource&, Ref<DeferredPromise>&&)
+void MediaKeySession::update(const BufferSource& response, Ref<DeferredPromise>&& promise)
 {
-    notImplemented();
+    // https://w3c.github.io/encrypted-media/#dom-mediakeysession-update
+    // W3C Editor's Draft 09 November 2016
+
+    // When this method is invoked, the user agent must run the following steps:
+    // 1. If this object is closed, return a promise rejected with an InvalidStateError.
+    // 2. If this object's callable value is false, return a promise rejected with an InvalidStateError.
+    if (m_closed || !m_callable) {
+        promise->reject(INVALID_STATE_ERR);
+        return;
+    }
+
+    // 3. If response is an empty array, return a promise rejected with a newly created TypeError.
+    if (!response.length()) {
+        promise->reject(TypeError);
+        return;
+    }
+
+    // 4. Let response copy be a copy of the contents of the response parameter.
+    // 5. Let promise be a new promise.
+    // 6. Run the following steps in parallel:
+    m_taskQueue.enqueueTask([this, response = SharedBuffer::create(response.data(), response.length()), promise = WTFMove(promise)] () mutable {
+        // 6.1. Let sanitized response be a validated and/or sanitized version of response copy.
+        RefPtr<SharedBuffer> sanitizedResponse = m_implementation->sanitizeResponse(response);
+
+        // 6.2. If the preceding step failed, or if sanitized response is empty, reject promise with a newly created TypeError.
+        if (!sanitizedResponse || sanitizedResponse->isEmpty()) {
+            promise->reject(TypeError);
+            return;
+        }
+
+        CDMInstance::LicenseType licenseType;
+        switch (m_sessionType) {
+        case MediaKeySessionType::Temporary:
+            licenseType = CDMInstance::LicenseType::Temporary;
+            break;
+        case MediaKeySessionType::PersistentLicense:
+            licenseType = CDMInstance::LicenseType::Persistable;
+            break;
+        case MediaKeySessionType::PersistentUsageRecord:
+            licenseType = CDMInstance::LicenseType::UsageRecord;
+            break;
+        };
+
+        // 6.3. Let message be null.
+        // 6.4. Let message type be null.
+        // 6.5. Let session closed be false.
+        // 6.6. Let cdm be the CDM instance represented by this object's cdm instance value.
+        // 6.7. Use the cdm to execute the following steps:
+        m_instance->updateLicense(licenseType, *sanitizedResponse, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] (bool sessionWasClosed, std::optional<CDMInstance::KeyStatusVector>&& changedKeys, std::optional<double>&& changedExpiration, std::optional<CDMInstance::Message>&& message, CDMInstance::SuccessValue succeeded) mutable {
+            if (!weakThis)
+                return;
+
+            // 6.7.1. If the format of sanitized response is invalid in any way, reject promise with a newly created TypeError.
+            // 6.7.2. Process sanitized response, following the stipulation for the first matching condition from the following list:
+            //   ↳ If sanitized response contains a license or key(s)
+            //     Process sanitized response, following the stipulation for the first matching condition from the following list:
+            //     ↳ If sessionType is "temporary" and sanitized response does not specify that session data, including any license, key(s), or similar session data it contains, should be stored
+            //       Process sanitized response, not storing any session data.
+            //     ↳ If sessionType is "persistent-license" and sanitized response contains a persistable license
+            //       Process sanitized response, storing the license/key(s) and related session data contained in sanitized response. Such data must be stored such that only the origin of this object's Document can access it.
+            //     ↳ If sessionType is "persistent-usage-record" and sanitized response contains a non-persistable license
+            //       Run the following steps:
+            //         6.7.2.3.1. Process sanitized response, not storing any session data.
+            //         6.7.2.3.2. If processing sanitized response results in the addition of keys to the set of known keys, add the key IDs of these keys to this object's record of key usage.
+            //     ↳ Otherwise
+            //       Reject promise with a newly created TypeError.
+            //   ↳ If sanitized response contains a record of license destruction acknowledgement and sessionType is "persistent-license"
+            //     Run the following steps:
+            //       6.7.2.1. Close the key session and clear all stored session data associated with this object, including the sessionId and record of license destruction.
+            //       6.7.2.2. Set session closed to true.
+            //   ↳ Otherwise
+            //     Process sanitized response, not storing any session data.
+            // NOTE: Steps 6.7.1. and 6.7.2. should be implemented in CDMInstance.
+
+            if (succeeded == CDMInstance::SuccessValue::Failed) {
+                promise->reject(TypeError);
+                return;
+            }
+
+            // 6.7.3. If a message needs to be sent to the server, execute the following steps:
+            //   6.7.3.1. Let message be that message.
+            //   6.7.3.2. Let message type be the appropriate MediaKeyMessageType for the message.
+            // 6.8. Queue a task to run the following steps:
+            m_taskQueue.enqueueTask([this, sessionWasClosed, changedKeys = WTFMove(changedKeys), changedExpiration = WTFMove(changedExpiration), message = WTFMove(message), promise = WTFMove(promise)] () mutable {
+                // 6.8.1.
+                if (sessionWasClosed) {
+                    // ↳ If session closed is true:
+                    //   Run the Session Closed algorithm on this object.
+                    sessionClosed();
+                } else {
+                    // ↳ Otherwise:
+                    //   Run the following steps:
+                    //     6.8.1.1. If the set of keys known to the CDM for this object changed or the status of any key(s) changed, run the Update Key Statuses
+                    //              algorithm on the session, providing each known key's key ID along with the appropriate MediaKeyStatus. Should additional
+                    //              processing be necessary to determine with certainty the status of a key, use "status-pending". Once the additional processing
+                    //              for one or more keys has completed, run the Update Key Statuses algorithm again with the actual status(es).
+                    if (changedKeys)
+                        updateKeyStatuses(WTFMove(*changedKeys));
+
+                    //     6.8.1.2. If the expiration time for the session changed, run the Update Expiration algorithm on the session, providing the new expiration time.
+                    if (changedExpiration)
+                        updateExpiration(*changedExpiration);
+
+                    //     6.8.1.3. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
+                    // FIXME: At this point the implementations of preceding steps can't fail.
+
+                    //     6.8.1.4. If message is not null, run the Queue a "message" Event algorithm on the session, providing message type and message.
+                    if (message) {
+                        MediaKeyMessageType messageType;
+                        switch (message->first) {
+                        case CDMInstance::MessageType::LicenseRequest:
+                            messageType = MediaKeyMessageType::LicenseRequest;
+                            break;
+                        case CDMInstance::MessageType::LicenseRenewal:
+                            messageType = MediaKeyMessageType::LicenseRenewal;
+                            break;
+                        case CDMInstance::MessageType::LicenseRelease:
+                            messageType = MediaKeyMessageType::LicenseRelease;
+                            break;
+                        case CDMInstance::MessageType::IndividualizationRequest:
+                            messageType = MediaKeyMessageType::IndividualizationRequest;
+                            break;
+                        }
+
+                        enqueueMessage(messageType, WTFMove(message->second));
+                    }
+                }
+
+                // 6.8.2. Resolve promise.
+                promise->resolve();
+            });
+        });
+    });
+
+    // 7. Return promise.
 }
 
 void MediaKeySession::close(Ref<DeferredPromise>&&)
@@ -275,6 +409,21 @@ void MediaKeySession::enqueueMessage(MediaKeyMessageType messageType, const Shar
     m_eventQueue.enqueueEvent(WTFMove(messageEvent));
 }
 
+void MediaKeySession::updateKeyStatuses(CDMInstance::KeyStatusVector&&)
+{
+    notImplemented();
+}
+
+void MediaKeySession::updateExpiration(double)
+{
+    notImplemented();
+}
+
+void MediaKeySession::sessionClosed()
+{
+    notImplemented();
+}
+
 bool MediaKeySession::hasPendingActivity() const
 {
     notImplemented();
index eb36bee..8f40f37 100644 (file)
@@ -31,6 +31,7 @@
 #if ENABLE(ENCRYPTED_MEDIA)
 
 #include "ActiveDOMObject.h"
+#include "CDMInstance.h"
 #include "EventTarget.h"
 #include "GenericEventQueue.h"
 #include "GenericTaskQueue.h"
@@ -46,7 +47,6 @@ namespace WebCore {
 
 class BufferSource;
 class CDM;
-class CDMInstance;
 class MediaKeyStatusMap;
 class MediaKeys;
 class SharedBuffer;
@@ -72,6 +72,9 @@ public:
 private:
     MediaKeySession(ScriptExecutionContext&, MediaKeySessionType, bool useDistinctiveIdentifier, Ref<CDM>&&, Ref<CDMInstance>&&);
     void enqueueMessage(MediaKeyMessageType, const SharedBuffer&);
+    void updateKeyStatuses(CDMInstance::KeyStatusVector&&);
+    void updateExpiration(double);
+    void sessionClosed();
 
     // EventTarget
     EventTargetInterface eventTargetInterface() const override { return MediaKeySessionEventTargetInterfaceType; }
index 596583c..451bc1f 100644 (file)
@@ -177,6 +177,20 @@ bool MockCDM::supportsInitData(const AtomicString& initDataType, const SharedBuf
     return true;
 }
 
+RefPtr<SharedBuffer> MockCDM::sanitizeResponse(const SharedBuffer& response) const
+{
+    if (!charactersAreAllASCII(reinterpret_cast<const LChar*>(response.data()), response.size()))
+        return nullptr;
+
+    Vector<String> responseArray;
+    String(response.data(), response.size()).split(ASCIILiteral(" "), responseArray);
+
+    if (!responseArray.contains(String(ASCIILiteral("valid-response"))))
+        return nullptr;
+
+    return response.copy();
+}
+
 MockCDMInstance::MockCDMInstance(WeakPtr<MockCDM> cdm)
     : m_cdm(cdm)
 {
@@ -256,6 +270,28 @@ void MockCDMInstance::requestLicense(LicenseType licenseType, const AtomicString
     callback(SharedBuffer::create(license.data(), license.length()), sessionID, false, SuccessValue::Succeeded);
 }
 
+void MockCDMInstance::updateLicense(LicenseType, const SharedBuffer& response, LicenseUpdateCallback callback)
+{
+    MockCDMFactory* factory = m_cdm ? m_cdm->factory() : nullptr;
+    if (!factory) {
+        callback(false, std::nullopt, std::nullopt, std::nullopt, SuccessValue::Failed);
+        return;
+    }
+
+    Vector<String> responseVector;
+    String(response.data(), response.size()).split(ASCIILiteral(" "), responseVector);
+
+    if (responseVector.contains(String(ASCIILiteral("invalid-format")))) {
+        callback(false, std::nullopt, std::nullopt, std::nullopt, SuccessValue::Failed);
+        return;
+    }
+
+    // FIXME: Session closure, key status, expiration and message handling should be implemented
+    // once the relevant algorithms are supported.
+
+    callback(false, std::nullopt, std::nullopt, std::nullopt, SuccessValue::Succeeded);
+}
+
 }
 
 #endif
index f76180c..09b53fe 100644 (file)
@@ -113,6 +113,7 @@ private:
     bool supportsServerCertificates() const final;
     bool supportsSessions() const final;
     bool supportsInitData(const AtomicString&, const SharedBuffer&) const final;
+    RefPtr<SharedBuffer> sanitizeResponse(const SharedBuffer&) const final;
 
     WeakPtr<MockCDMFactory> m_factory;
     WeakPtrFactory<MockCDM> m_weakPtrFactory;
@@ -128,6 +129,7 @@ private:
     SuccessValue setPersistentStateAllowed(bool) final;
     SuccessValue setServerCertificate(Ref<SharedBuffer>&&) final;
     void requestLicense(LicenseType, const AtomicString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback) final;
+    void updateLicense(LicenseType, const SharedBuffer&, LicenseUpdateCallback) final;
 
     WeakPtr<MockCDM> m_cdm;
     bool m_distinctiveIdentifiersAllowed { true };