[EME] Implement MediaKeySession::close()
authorzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 8 Feb 2017 10:25:23 +0000 (10:25 +0000)
committerzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 8 Feb 2017 10:25:23 +0000 (10:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167869

Reviewed by Xabier Rodriguez-Calvar.

Source/WebCore:

Implement MediaKeySession::close() as outlined in the specification.

The CDMInstance::closeSession() virtual method, when called, should
close the session that's represented by the passed-in session ID on
the CDMInstance implementor object. That's the same session ID that
the CDMInstance object passes to the MediaKeySession class through
the callback that's provided to the updateLicense call.

The CloseSessionCallback, passed to CDMInstance::closeSession(),
should be invoked by the CDMInstance implementor once the session
is closed. When that is invoked, another task is queued for the
MediaKeySession object that runs the `session closed` algorithm
and resolves the promise.

MockCDMInstance::closeSession() is defined to remove the session
from the MockCDMFactory object and invoke the CloseSessionCallback.

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

* Modules/encryptedmedia/CDMInstance.h:
* Modules/encryptedmedia/MediaKeySession.cpp:
(WebCore::MediaKeySession::close):
* testing/MockCDMFactory.cpp:
(WebCore::MockCDMInstance::closeSession):
* testing/MockCDMFactory.h:

LayoutTests:

Add the mock-MediaKeySession-close.html test case which checks proper
behavior of MediaKeySession::close(), specifically that under specific
conditions the promise returned by that method is properly resolved or
rejected. The test is skipped on all platforms for now.

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

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

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

index 0d41bec..2f5a063 100644 (file)
@@ -1,3 +1,20 @@
+2017-02-08  Zan Dobersek  <zdobersek@igalia.com>
+
+        [EME] Implement MediaKeySession::close()
+        https://bugs.webkit.org/show_bug.cgi?id=167869
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Add the mock-MediaKeySession-close.html test case which checks proper
+        behavior of MediaKeySession::close(), specifically that under specific
+        conditions the promise returned by that method is properly resolved or
+        rejected. The test is skipped on all platforms for now.
+
+        * media/encrypted-media/mock-MediaKeySession-close-expected.txt: Added.
+        * media/encrypted-media/mock-MediaKeySession-close.html: Added.
+        * platform/efl/TestExpectations:
+        * platform/mac/TestExpectations:
+
 2017-02-07  Ryosuke Niwa  <rniwa@webkit.org>
 
         WebContent process repeatedly jetsams on BuzzFeed's Another Round page
diff --git a/LayoutTests/media/encrypted-media/mock-MediaKeySession-close-expected.txt b/LayoutTests/media/encrypted-media/mock-MediaKeySession-close-expected.txt
new file mode 100644 (file)
index 0000000..a5c4986
--- /dev/null
@@ -0,0 +1,44 @@
+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
+
+Closing a non-callable MediaKeySession should reject.
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+EXPECTED (typeof mediaKeySession == 'object') OK
+RUN(promise = mediaKeySession.close())
+Promise rejected correctly OK
+
+Closing a failed MediaKeySession should reject.
+RUN(kids = JSON.stringify({ invalid: "invalid" }))
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+RUN(promise = mediaKeySession.generateRequest("keyids", encoder.encode(kids)))
+Promise rejected correctly OK
+RUN(promise = mediaKeySession.close())
+Promise rejected correctly OK
+
+Closing a valid MediaKeySession should resolve.
+RUN(kids = JSON.stringify({ kids: [ "MTIzNDU=" ] }))
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+RUN(promise = mediaKeySession.generateRequest("keyids", encoder.encode(kids)))
+Promise resolved OK
+RUN(promise = mediaKeySession.close())
+Promise resolved OK
+
+Closing a closed MediaKeySession should resolve.
+RUN(kids = JSON.stringify({ kids: [ "MTIzNDU=" ] }))
+RUN(mediaKeySession = mediaKeys.createSession("temporary"))
+RUN(promise = mediaKeySession.generateRequest("keyids", encoder.encode(kids)))
+Promise resolved OK
+RUN(promise = mediaKeySession.close())
+Promise resolved OK
+RUN(promise = mediaKeySession.close())
+Promise resolved OK
+END OF TEST
+
diff --git a/LayoutTests/media/encrypted-media/mock-MediaKeySession-close.html b/LayoutTests/media/encrypted-media/mock-MediaKeySession-close.html
new file mode 100644 (file)
index 0000000..d7f1d98
--- /dev/null
@@ -0,0 +1,107 @@
+<!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;
+    var encoder = new TextEncoder();
+
+    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();
+    }
+
+    tests = [
+        function() {
+            run('promise = mediaKeySystemAccess.createMediaKeys()');
+            shouldResolve(promise).then(gotMediaKeys, failTest);
+        },
+
+        function() {
+            consoleWrite('Closing a non-callable MediaKeySession should reject.');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            testExpected('typeof mediaKeySession', 'object');
+            run('promise = mediaKeySession.close()');
+            shouldReject(promise).then(next, next);
+        },
+
+        function() {
+            consoleWrite('Closing a failed MediaKeySession should reject.')
+            run('kids = JSON.stringify({ invalid: "invalid" })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", encoder.encode(kids))');
+            shouldReject(promise).then(function() {
+                run('promise = mediaKeySession.close()');
+                shouldReject(promise).then(next, next);
+            }, next);
+        },
+
+        function() {
+            consoleWrite('Closing a valid MediaKeySession should resolve.')
+            run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", encoder.encode(kids))');
+            shouldResolve(promise).then(function() {
+                run('promise = mediaKeySession.close()');
+                shouldResolve(promise).then(next, next);
+            }, next);
+        },
+
+        function() {
+            consoleWrite('Closing a closed MediaKeySession should resolve.')
+            run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", encoder.encode(kids))');
+            shouldResolve(promise).then(function() {
+                run('promise = mediaKeySession.close()');
+                shouldResolve(promise).then(function() {
+                    run('promise = mediaKeySession.close()');
+                    shouldResolve(promise).then(next, next);
+                }, next);
+            }, next);
+        },
+    ];
+    </script>
+</head>
+<body onload="doTest()">
+</body>
+</html>
index 26fd3e9..482720e 100644 (file)
@@ -2981,6 +2981,7 @@ Bug(EFL) media/encrypted-media/encrypted-media-constants.html [ Failure ]
 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-close.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 ]
index 6f59842..ae3156e 100644 (file)
@@ -1492,6 +1492,7 @@ media/encrypted-media/mock-navigator-requestMediaKeySystemAccess.html [ Skip ]
 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-close.html [ Skip ]
 media/encrypted-media/mock-MediaKeySession-generateRequest.html [ Skip ]
 media/encrypted-media/mock-MediaKeySession-update.html [ Skip ]
 
index 2999a43..e700107 100644 (file)
@@ -1,5 +1,38 @@
 2017-02-08  Zan Dobersek  <zdobersek@igalia.com>
 
+        [EME] Implement MediaKeySession::close()
+        https://bugs.webkit.org/show_bug.cgi?id=167869
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Implement MediaKeySession::close() as outlined in the specification.
+
+        The CDMInstance::closeSession() virtual method, when called, should
+        close the session that's represented by the passed-in session ID on
+        the CDMInstance implementor object. That's the same session ID that
+        the CDMInstance object passes to the MediaKeySession class through
+        the callback that's provided to the updateLicense call.
+
+        The CloseSessionCallback, passed to CDMInstance::closeSession(),
+        should be invoked by the CDMInstance implementor once the session
+        is closed. When that is invoked, another task is queued for the
+        MediaKeySession object that runs the `session closed` algorithm
+        and resolves the promise.
+
+        MockCDMInstance::closeSession() is defined to remove the session
+        from the MockCDMFactory object and invoke the CloseSessionCallback.
+
+        Test: media/encrypted-media/mock-MediaKeySession-close.html
+
+        * Modules/encryptedmedia/CDMInstance.h:
+        * Modules/encryptedmedia/MediaKeySession.cpp:
+        (WebCore::MediaKeySession::close):
+        * testing/MockCDMFactory.cpp:
+        (WebCore::MockCDMInstance::closeSession):
+        * testing/MockCDMFactory.h:
+
+2017-02-08  Zan Dobersek  <zdobersek@igalia.com>
+
         [EME] Alias CDMInstance enums to the specification-defined enums
         https://bugs.webkit.org/show_bug.cgi?id=167896
 
index 78f48a6..c941671 100644 (file)
@@ -66,6 +66,9 @@ public:
     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;
+
+    using CloseSessionCallback = Function<void()>;
+    virtual void closeSession(const String& sessionId, CloseSessionCallback) = 0;
 };
 
 }
index 7bac30e..f14bc5c 100644 (file)
@@ -359,9 +359,45 @@ void MediaKeySession::update(const BufferSource& response, Ref<DeferredPromise>&
     // 7. Return promise.
 }
 
-void MediaKeySession::close(Ref<DeferredPromise>&&)
+void MediaKeySession::close(Ref<DeferredPromise>&& promise)
 {
-    notImplemented();
+    // https://w3c.github.io/encrypted-media/#dom-mediakeysession-close
+    // W3C Editor's Draft 09 November 2016
+
+    // 1. Let session be the associated MediaKeySession object.
+    // 2. If session is closed, return a resolved promise.
+    if (m_closed) {
+        promise->resolve();
+        return;
+    }
+
+    // 3. If session's callable value is false, return a promise rejected with an InvalidStateError.
+    if (!m_callable) {
+        promise->reject(INVALID_STATE_ERR);
+        return;
+    }
+
+    // 4. Let promise be a new promise.
+    // 5. Run the following steps in parallel:
+    m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
+        // 5.1. Let cdm be the CDM instance represented by session's cdm instance value.
+        // 5.2. Use cdm to close the key session associated with session.
+        m_instance->closeSession(m_sessionId, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] () mutable {
+            if (!weakThis)
+                return;
+
+            // 5.3. Queue a task to run the following steps:
+            m_taskQueue.enqueueTask([this, promise = WTFMove(promise)] () mutable {
+                // 5.3.1. Run the Session Closed algorithm on the session.
+                sessionClosed();
+
+                // 5.3.2. Resolve promise.
+                promise->resolve();
+            });
+        });
+    });
+
+    // 6. Return promise.
 }
 
 void MediaKeySession::remove(Ref<DeferredPromise>&&)
index 475f3e3..0ff2610 100644 (file)
@@ -289,6 +289,18 @@ void MockCDMInstance::updateLicense(LicenseType, const SharedBuffer& response, L
     callback(false, std::nullopt, std::nullopt, std::nullopt, SuccessValue::Succeeded);
 }
 
+void MockCDMInstance::closeSession(const String& sessionID, CloseSessionCallback callback)
+{
+    MockCDMFactory* factory = m_cdm ? m_cdm->factory() : nullptr;
+    if (!factory) {
+        callback();
+        return;
+    }
+
+    factory->removeSessionWithID(sessionID);
+    callback();
+}
+
 }
 
 #endif
index 09b53fe..28c3d5f 100644 (file)
@@ -130,6 +130,7 @@ private:
     SuccessValue setServerCertificate(Ref<SharedBuffer>&&) final;
     void requestLicense(LicenseType, const AtomicString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback) final;
     void updateLicense(LicenseType, const SharedBuffer&, LicenseUpdateCallback) final;
+    void closeSession(const String&, CloseSessionCallback) final;
 
     WeakPtr<MockCDM> m_cdm;
     bool m_distinctiveIdentifiersAllowed { true };