[EME] Implement MediaKeySession::sessionClosed()
authorzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 10 Feb 2017 10:31:57 +0000 (10:31 +0000)
committerzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 10 Feb 2017 10:31:57 +0000 (10:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=168039

Reviewed by Xabier Rodriguez-Calvar.

Source/WebCore:

Implement the 'session closed' algorithm for MediaKeySession by
following the specified steps. After this algorithm is run, the
session should be considered closed, which we track via the m_closed
member variable on the class. This is set to true before the promise
that's accessible through the 'closed' attribute is resolved.

Because the algorithm requires the CDM instance to store any record
of key usage when the session's type is 'persistent-usage-record', the
storeRecordOfKeyUsage() virtual method is added to the CDMInstance
interface. MockCDMInstance implementation is left unimplemented for now.

JSMediaKeySession::closed() accessor now has a custom implementation
that creates a deferred promise for that object if there's none yet, and
shares it with the wrapped class through the registerClosedPromise()
method, storing a reference to the promise in the m_closedPromise
member variable, or resolving the promise immediately if the session was
already closed.

Test cases added to media/encrypted-media/mock-MediaKeySession-close.html.

* Modules/encryptedmedia/CDMInstance.h:
* Modules/encryptedmedia/MediaKeySession.cpp:
(WebCore::MediaKeySession::registerClosedPromise):
(WebCore::MediaKeySession::sessionClosed):
* Modules/encryptedmedia/MediaKeySession.h:
* bindings/js/JSMediaKeySessionCustom.cpp:
(WebCore::JSMediaKeySession::closed):
* testing/MockCDMFactory.cpp:
(WebCore::MockCDMInstance::storeRecordOfKeyUsage):
* testing/MockCDMFactory.h:

LayoutTests:

Enhance the mock-MediaKeySession-close.html test by adding test cases
covering the dispatch of the promise that's accessible through the
'closed' attribute and covering the session closure status, making
sure that various operations properly resolve or reject after the
session object was closed.

* media/encrypted-media/mock-MediaKeySession-close-expected.txt:
* media/encrypted-media/mock-MediaKeySession-close.html:

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

LayoutTests/ChangeLog
LayoutTests/media/encrypted-media/mock-MediaKeySession-close-expected.txt
LayoutTests/media/encrypted-media/mock-MediaKeySession-close.html
Source/WebCore/ChangeLog
Source/WebCore/Modules/encryptedmedia/CDMInstance.h
Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp
Source/WebCore/Modules/encryptedmedia/MediaKeySession.h
Source/WebCore/bindings/js/JSMediaKeySessionCustom.cpp
Source/WebCore/testing/MockCDMFactory.cpp
Source/WebCore/testing/MockCDMFactory.h

index 828a45d..4a7628d 100644 (file)
@@ -1,5 +1,21 @@
 2017-02-10  Zan Dobersek  <zdobersek@igalia.com>
 
+        [EME] Implement MediaKeySession::sessionClosed()
+        https://bugs.webkit.org/show_bug.cgi?id=168039
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Enhance the mock-MediaKeySession-close.html test by adding test cases
+        covering the dispatch of the promise that's accessible through the
+        'closed' attribute and covering the session closure status, making
+        sure that various operations properly resolve or reject after the
+        session object was closed.
+
+        * media/encrypted-media/mock-MediaKeySession-close-expected.txt:
+        * media/encrypted-media/mock-MediaKeySession-close.html:
+
+2017-02-10  Zan Dobersek  <zdobersek@igalia.com>
+
         [EME] Implement MediaKeySession::updateKeyStatuses(), MediaKeyStatusMap
         https://bugs.webkit.org/show_bug.cgi?id=167888
 
index a5c4986..ae38954 100644 (file)
@@ -31,6 +31,26 @@ Promise resolved OK
 RUN(promise = mediaKeySession.close())
 Promise resolved OK
 
+Closing a valid MediaKeySession should resolve the "closed" promise.
+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
+Promise resolved OK
+"closed" promise correctly resolved.
+
+Already closed MediaKeySession should resolve the "closed" promise immediately.
+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
+Promise resolved OK
+"closed" promise correctly resolved.
+
 Closing a closed MediaKeySession should resolve.
 RUN(kids = JSON.stringify({ kids: [ "MTIzNDU=" ] }))
 RUN(mediaKeySession = mediaKeys.createSession("temporary"))
@@ -40,5 +60,21 @@ RUN(promise = mediaKeySession.close())
 Promise resolved OK
 RUN(promise = mediaKeySession.close())
 Promise resolved OK
+
+Operating on a closed MediaKeySession should resolve for close(), reject otherwise.
+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.generateRequest("keyids", encoder.encode(kids)))
+Promise rejected correctly OK
+RUN(promise = mediaKeySession.update(encoder.encode("some-data")))
+Promise rejected correctly OK
+RUN(promise = mediaKeySession.close())
+Promise resolved OK
+RUN(promise = mediaKeySession.remove())
+Promise rejected correctly OK
 END OF TEST
 
index d7f1d98..14d256a 100644 (file)
         },
 
         function() {
+            consoleWrite('Closing a valid MediaKeySession should resolve the "closed" promise.')
+            run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
+            run('mediaKeySession = mediaKeys.createSession("temporary")');
+            run('promise = mediaKeySession.generateRequest("keyids", encoder.encode(kids))');
+            shouldResolve(promise).then(function() {
+                shouldResolve(mediaKeySession.closed).then(function() {
+                    consoleWrite('"closed" promise correctly resolved.');
+                    next();
+                }, next);
+
+                run('promise = mediaKeySession.close()');
+                shouldResolve(promise);
+            }, next);
+        },
+
+        function() {
+            consoleWrite('Already closed MediaKeySession should resolve the "closed" promise immediately.');
+            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() {
+                    shouldResolve(mediaKeySession.closed).then(function () {
+                        consoleWrite('"closed" promise correctly resolved.');
+                        next();
+                    }, next);
+                }, next);
+            }, next);
+        },
+
+        function() {
             consoleWrite('Closing a closed MediaKeySession should resolve.')
             run('kids = JSON.stringify({ kids: [ "MTIzNDU=" ] })');
             run('mediaKeySession = mediaKeys.createSession("temporary")');
                 }, next);
             }, next);
         },
+
+        function() {
+            consoleWrite('Operating on a closed MediaKeySession should resolve for close(), reject otherwise.')
+            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.generateRequest("keyids", encoder.encode(kids))');
+                    shouldReject(promise).then(function() {
+                        run('promise = mediaKeySession.update(encoder.encode("some-data"))');
+                        shouldReject(promise).then(function() {
+                            run('promise = mediaKeySession.close()');
+                            shouldResolve(promise).then(function() {
+                                run('promise = mediaKeySession.remove()');
+                                shouldReject(promise).then(next, next);
+                            }, next);
+                        }, next);
+                    }, next);
+                }, next);
+            }, next);
+        },
     ];
     </script>
 </head>
index 00abd30..a90fc72 100644 (file)
@@ -1,5 +1,43 @@
 2017-02-10  Zan Dobersek  <zdobersek@igalia.com>
 
+        [EME] Implement MediaKeySession::sessionClosed()
+        https://bugs.webkit.org/show_bug.cgi?id=168039
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Implement the 'session closed' algorithm for MediaKeySession by
+        following the specified steps. After this algorithm is run, the
+        session should be considered closed, which we track via the m_closed
+        member variable on the class. This is set to true before the promise
+        that's accessible through the 'closed' attribute is resolved.
+
+        Because the algorithm requires the CDM instance to store any record
+        of key usage when the session's type is 'persistent-usage-record', the
+        storeRecordOfKeyUsage() virtual method is added to the CDMInstance
+        interface. MockCDMInstance implementation is left unimplemented for now.
+
+        JSMediaKeySession::closed() accessor now has a custom implementation
+        that creates a deferred promise for that object if there's none yet, and
+        shares it with the wrapped class through the registerClosedPromise()
+        method, storing a reference to the promise in the m_closedPromise
+        member variable, or resolving the promise immediately if the session was
+        already closed.
+
+        Test cases added to media/encrypted-media/mock-MediaKeySession-close.html.
+
+        * Modules/encryptedmedia/CDMInstance.h:
+        * Modules/encryptedmedia/MediaKeySession.cpp:
+        (WebCore::MediaKeySession::registerClosedPromise):
+        (WebCore::MediaKeySession::sessionClosed):
+        * Modules/encryptedmedia/MediaKeySession.h:
+        * bindings/js/JSMediaKeySessionCustom.cpp:
+        (WebCore::JSMediaKeySession::closed):
+        * testing/MockCDMFactory.cpp:
+        (WebCore::MockCDMInstance::storeRecordOfKeyUsage):
+        * testing/MockCDMFactory.h:
+
+2017-02-10  Zan Dobersek  <zdobersek@igalia.com>
+
         [EME] Implement MediaKeySession::updateKeyStatuses(), MediaKeyStatusMap
         https://bugs.webkit.org/show_bug.cgi?id=167888
 
index 8e621b2..22f542c 100644 (file)
@@ -72,6 +72,8 @@ public:
 
     using RemoveSessionDataCallback = Function<void(KeyStatusVector&&, std::optional<Ref<SharedBuffer>>&&, SuccessValue)>;
     virtual void removeSessionData(const String& sessionId, LicenseType, RemoveSessionDataCallback) = 0;
+
+    virtual void storeRecordOfKeyUsage(const String& sessionId) = 0;
 };
 
 }
index e17165d..0e1dd37 100644 (file)
@@ -469,6 +469,16 @@ void MediaKeySession::remove(Ref<DeferredPromise>&& promise)
     // 5. Return promise.
 }
 
+void MediaKeySession::registerClosedPromise(ClosedPromise&& promise)
+{
+    ASSERT(!m_closedPromise);
+    if (m_closed) {
+        promise.resolve();
+        return;
+    }
+    m_closedPromise = WTFMove(promise);
+}
+
 void MediaKeySession::enqueueMessage(MediaKeyMessageType messageType, const SharedBuffer& message)
 {
     // 6.4.1 Queue a "message" Event
@@ -536,7 +546,30 @@ void MediaKeySession::updateExpiration(double)
 
 void MediaKeySession::sessionClosed()
 {
-    notImplemented();
+    // https://w3c.github.io/encrypted-media/#session-closed
+    // W3C Editor's Draft 09 November 2016
+
+    // 1. Let session be the associated MediaKeySession object.
+    // 2. If session's session type is "persistent-usage-record", execute the following steps in parallel:
+    if (m_sessionType == MediaKeySessionType::PersistentUsageRecord) {
+        // 2.1. Let cdm be the CDM instance represented by session's cdm instance value.
+        // 2.2. Use cdm to store session's record of key usage, if it exists.
+        m_instance->storeRecordOfKeyUsage(m_sessionId);
+    }
+
+    // 3. Run the Update Key Statuses algorithm on the session, providing an empty sequence.
+    updateKeyStatuses({ });
+
+    // 4. Run the Update Expiration algorithm on the session, providing NaN.
+    updateExpiration(std::numeric_limits<double>::quiet_NaN());
+
+    // Let's consider the session closed before any promise on the 'closed' attribute is resolved.
+    m_closed = true;
+
+    // 5. Let promise be the closed attribute of the session.
+    // 6. Resolve promise.
+    if (m_closedPromise)
+        m_closedPromise->resolve();
 }
 
 bool MediaKeySession::hasPendingActivity() const
index aaf6e11..0ce875d 100644 (file)
@@ -70,6 +70,9 @@ public:
     void close(Ref<DeferredPromise>&&);
     void remove(Ref<DeferredPromise>&&);
 
+    using ClosedPromise = DOMPromise<void>;
+    void registerClosedPromise(ClosedPromise&&);
+
     const Vector<std::pair<Ref<SharedBuffer>, MediaKeyStatus>>& statuses() const { return m_statuses; }
 
 private:
@@ -93,6 +96,7 @@ private:
 
     String m_sessionId;
     double m_expiration;
+    std::optional<ClosedPromise> m_closedPromise;
     Ref<MediaKeyStatusMap> m_keyStatuses;
     bool m_closed { false };
     bool m_uninitialized { true };
index a37153c..4227ccd 100644 (file)
 
 #include "JSMediaKeySession.h"
 
-#include "NotImplemented.h"
-
 namespace WebCore {
 
-JSC::JSValue JSMediaKeySession::closed(JSC::ExecState&) const
+JSC::JSValue JSMediaKeySession::closed(JSC::ExecState& state) const
 {
-    notImplemented();
-    return JSC::jsUndefined();
+    if (!m_closed) {
+        auto promise = createDeferredPromise(state, domWindow());
+        m_closed.set(state.vm(), this, promise->promise());
+        wrapped().registerClosedPromise(WTFMove(promise));
+    }
+    return m_closed.get();
 }
 
 } // namespace WebCore
index 7929442..9ee7513 100644 (file)
@@ -349,6 +349,11 @@ void MockCDMInstance::removeSessionData(const String& id, LicenseType, RemoveSes
     callback(WTFMove(keyStatusVector), SharedBuffer::create(message.data(), message.length()), SuccessValue::Succeeded);
 }
 
+void MockCDMInstance::storeRecordOfKeyUsage(const String&)
+{
+    // FIXME: This should be implemented along with the support for persistent-usage-record sessions.
+}
+
 }
 
 #endif
index 6d44d0d..a5f2289 100644 (file)
@@ -134,6 +134,7 @@ private:
     void updateLicense(const String&, LicenseType, const SharedBuffer&, LicenseUpdateCallback) final;
     void closeSession(const String&, CloseSessionCallback) final;
     void removeSessionData(const String&, LicenseType, RemoveSessionDataCallback) final;
+    void storeRecordOfKeyUsage(const String&) final;
 
     WeakPtr<MockCDM> m_cdm;
     bool m_distinctiveIdentifiersAllowed { true };