<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[212110] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/212110">212110</a></dd>
<dt>Author</dt> <dd>zandobersek@gmail.com</dd>
<dt>Date</dt> <dd>2017-02-10 02:40:29 -0800 (Fri, 10 Feb 2017)</dd>
</dl>

<h3>Log Message</h3>
<pre>[EME] Implement MediaKeySession::load()
https://bugs.webkit.org/show_bug.cgi?id=168041

Reviewed by Xabier Rodriguez-Calvar.

Source/WebCore:

Implement the MediaKeySession::load() method, tracing the steps as they
are defined in the EME specification. The only exception is step 8.3,
which requires additional facility that tracks currently open sessions
and provides information whether for a given session ID there's already
a MediaKeySession that's not yet been closed.

Session ID sanitization is done through the CDM::sanitizeSessionId()
method, which relays the task to the CDMPrivate implementation.

The CDMInstance::loadSession() virtual method is called with the session
type, sanitized ID, the Document's origin (in string form) and the
callback that's invoked upon completion of the task. The callback
checks whether the operation was successful, or examines the reason for
the load failure in case it wasn't, rejecting the promise in the latter
case either immediately or in the following task at the latest.

When the load was successful, the optional known keys, expiration time
and message are handled appropriately, and the promise is resolved.

MockCDM::sanitizeSessionId() implementation only treats
'valid-loaded-session' as a valid session ID.
MockCDMInstance::loadSession() implementation is kept slim for now, only
providing the 'license-renewal' message when invoking the passed-in
callback. Known keys and expiration time will also be tested once the
relevant MediaKeySession algorithms are implemented.

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

* Modules/encryptedmedia/CDM.cpp:
(WebCore::CDM::sanitizeSessionId):
* Modules/encryptedmedia/CDM.h:
* Modules/encryptedmedia/CDMInstance.h:
* Modules/encryptedmedia/CDMPrivate.h:
* Modules/encryptedmedia/MediaKeySession.cpp:
(WebCore::MediaKeySession::load):
* testing/MockCDMFactory.cpp:
(WebCore::MockCDM::sanitizeSessionId):
(WebCore::MockCDMInstance::loadSession):
* testing/MockCDMFactory.h:

LayoutTests:

Add the mock-MediaKeySession-load.html test, testing the basic behavior
of the MediaKeySession::load() implementation. Invalid session IDs and
session types are tested to ensure the returned promise is rejected.
A simplistic test case also tests that for a valid session ID and
session type, the basic MockCDM implementation correctly 'loads' the
specified session and provides the 'license-renewal' message in return.
More tests should be added as the EME implementation advances and the
MockCDM implementations improve to cover additional cases.

* media/encrypted-media/mock-MediaKeySession-load-expected.txt: Added.
* media/encrypted-media/mock-MediaKeySession-load.html: Added.
* platform/efl/TestExpectations:
* platform/mac/TestExpectations:</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsplatformeflTestExpectations">trunk/LayoutTests/platform/efl/TestExpectations</a></li>
<li><a href="#trunkLayoutTestsplatformmacTestExpectations">trunk/LayoutTests/platform/mac/TestExpectations</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaCDMcpp">trunk/Source/WebCore/Modules/encryptedmedia/CDM.cpp</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaCDMh">trunk/Source/WebCore/Modules/encryptedmedia/CDM.h</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaCDMInstanceh">trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaCDMPrivateh">trunk/Source/WebCore/Modules/encryptedmedia/CDMPrivate.h</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaMediaKeySessioncpp">trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp</a></li>
<li><a href="#trunkSourceWebCoretestingMockCDMFactorycpp">trunk/Source/WebCore/testing/MockCDMFactory.cpp</a></li>
<li><a href="#trunkSourceWebCoretestingMockCDMFactoryh">trunk/Source/WebCore/testing/MockCDMFactory.h</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsmediaencryptedmediamockMediaKeySessionloadexpectedtxt">trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediaencryptedmediamockMediaKeySessionloadhtml">trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/LayoutTests/ChangeLog        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -1,5 +1,26 @@
</span><span class="cx"> 2017-02-10  Zan Dobersek  &lt;zdobersek@igalia.com&gt;
</span><span class="cx"> 
</span><ins>+        [EME] Implement MediaKeySession::load()
+        https://bugs.webkit.org/show_bug.cgi?id=168041
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Add the mock-MediaKeySession-load.html test, testing the basic behavior
+        of the MediaKeySession::load() implementation. Invalid session IDs and
+        session types are tested to ensure the returned promise is rejected.
+        A simplistic test case also tests that for a valid session ID and
+        session type, the basic MockCDM implementation correctly 'loads' the
+        specified session and provides the 'license-renewal' message in return.
+        More tests should be added as the EME implementation advances and the
+        MockCDM implementations improve to cover additional cases.
+
+        * media/encrypted-media/mock-MediaKeySession-load-expected.txt: Added.
+        * media/encrypted-media/mock-MediaKeySession-load.html: Added.
+        * platform/efl/TestExpectations:
+        * platform/mac/TestExpectations:
+
+2017-02-10  Zan Dobersek  &lt;zdobersek@igalia.com&gt;
+
</ins><span class="cx">         [EME] Implement MediaKeySession::sessionClosed()
</span><span class="cx">         https://bugs.webkit.org/show_bug.cgi?id=168039
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkLayoutTestsmediaencryptedmediamockMediaKeySessionloadexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load-expected.txt (0 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load-expected.txt                                (rev 0)
+++ trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load-expected.txt        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -0,0 +1,54 @@
</span><ins>+RUN(internals.initializeMockMediaSource())
+RUN(mock = internals.registerMockCDM())
+RUN(mock.supportedDataTypes = [&quot;keyids&quot;])
+RUN(capabilities.initDataTypes = [&quot;keyids&quot;])
+RUN(capabilities.videoCapabilities = [{ contentType: 'video/mock; codecs=&quot;mock&quot;' }] )
+RUN(capabilities.sessionTypes = [ &quot;temporary&quot;, &quot;persistent-license&quot; ])
+RUN(promise = navigator.requestMediaKeySystemAccess(&quot;org.webkit.mock&quot;, [capabilities]))
+Promise resolved OK
+
+RUN(promise = mediaKeySystemAccess.createMediaKeys())
+Promise resolved OK
+
+Loading MediaKeySession with a persistent license and empty ID should reject.
+RUN(mediaKeySession = mediaKeys.createSession(&quot;persistent-license&quot;))
+EXPECTED (typeof mediaKeySession == 'object') OK
+RUN(promise = mediaKeySession.load(&quot;&quot;))
+Promise rejected correctly OK
+
+Loading MediaKeySession with a temporary license should reject.
+RUN(mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;))
+EXPECTED (typeof mediaKeySession == 'object') OK
+RUN(promise = mediaKeySession.load(&quot;valid-loaded-session&quot;))
+Promise rejected correctly OK
+
+Loading MediaKeySession with a non-sanitizable ID should reject.
+RUN(mediaKeySession = mediaKeys.createSession(&quot;persistent-license&quot;))
+EXPECTED (typeof mediaKeySession == 'object') OK
+RUN(promise = mediaKeySession.load(&quot;non-sanitizable-loaded-session&quot;))
+Promise rejected correctly OK
+
+Loading MediaKeySession should resolve.
+RUN(mediaKeySession = mediaKeys.createSession(&quot;persistent-license&quot;))
+EXPECTED (typeof mediaKeySession == 'object') OK
+RUN(promise = mediaKeySession.load(&quot;valid-loaded-session&quot;))
+EXPECTED (event.messageType == 'license-renewal') OK
+EXPECTED (new Uint8Array(event.message).length == '14') OK
+EXPECTED (new Uint8Array(event.message)[0] == '115') OK
+EXPECTED (new Uint8Array(event.message)[1] == '101') OK
+EXPECTED (new Uint8Array(event.message)[2] == '115') OK
+EXPECTED (new Uint8Array(event.message)[3] == '115') OK
+EXPECTED (new Uint8Array(event.message)[4] == '105') OK
+EXPECTED (new Uint8Array(event.message)[5] == '111') OK
+EXPECTED (new Uint8Array(event.message)[6] == '110') OK
+EXPECTED (new Uint8Array(event.message)[7] == '32') OK
+EXPECTED (new Uint8Array(event.message)[8] == '108') OK
+EXPECTED (new Uint8Array(event.message)[9] == '111') OK
+EXPECTED (new Uint8Array(event.message)[10] == '97') OK
+EXPECTED (new Uint8Array(event.message)[11] == '100') OK
+EXPECTED (new Uint8Array(event.message)[12] == '101') OK
+EXPECTED (new Uint8Array(event.message)[13] == '100') OK
+Promise resolved OK
+Load was successful.
+END OF TEST
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediaencryptedmediamockMediaKeySessionloadhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load.html (0 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load.html                                (rev 0)
+++ trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-load.html        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -0,0 +1,105 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;script src=../video-test.js&gt;&lt;/script&gt;
+    &lt;script type=&quot;text/javascript&quot;&gt;
+    var mock;
+    var promise;
+    var mediaKeySystemAccess;
+    var mediaKeys;
+    var mediaKeySession;
+    var capabilities = {};
+    var kids;
+    var encoder = new TextEncoder();
+
+    function doTest()
+    {
+        if (!window.internals) {
+            failTest(&quot;Internals is required for this test.&quot;)
+            return;
+        }
+
+        run('internals.initializeMockMediaSource()');
+        run('mock = internals.registerMockCDM()');
+        run('mock.supportedDataTypes = [&quot;keyids&quot;]');
+        run('capabilities.initDataTypes = [&quot;keyids&quot;]');
+        run(`capabilities.videoCapabilities = [{ contentType: 'video/mock; codecs=&quot;mock&quot;' }] `);
+        run('capabilities.sessionTypes = [ &quot;temporary&quot;, &quot;persistent-license&quot; ]');
+        run('promise = navigator.requestMediaKeySystemAccess(&quot;org.webkit.mock&quot;, [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('Loading MediaKeySession with a persistent license and empty ID should reject.');
+            run('mediaKeySession = mediaKeys.createSession(&quot;persistent-license&quot;)');
+            testExpected('typeof mediaKeySession', 'object');
+            run('promise = mediaKeySession.load(&quot;&quot;)');
+            shouldReject(promise).then(next, next);
+        },
+
+        function() {
+            consoleWrite('Loading MediaKeySession with a temporary license should reject.');
+            run('mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;)');
+            testExpected('typeof mediaKeySession', 'object');
+            run('promise = mediaKeySession.load(&quot;valid-loaded-session&quot;)');
+            shouldReject(promise).then(next, next);
+        },
+
+        function() {
+            consoleWrite('Loading MediaKeySession with a non-sanitizable ID should reject.');
+            run('mediaKeySession = mediaKeys.createSession(&quot;persistent-license&quot;)');
+            testExpected('typeof mediaKeySession', 'object');
+            run('promise = mediaKeySession.load(&quot;non-sanitizable-loaded-session&quot;)');
+            shouldReject(promise).then(next, next);
+        },
+
+        function() {
+            consoleWrite('Loading MediaKeySession should resolve.');
+            run('mediaKeySession = mediaKeys.createSession(&quot;persistent-license&quot;)');
+            testExpected('typeof mediaKeySession', 'object');
+            run('promise = mediaKeySession.load(&quot;valid-loaded-session&quot;)');
+            mediaKeySession.addEventListener('message', function(event) {
+                testExpected('event.messageType', 'license-renewal');
+                testArraysEqual('new Uint8Array(event.message)', encoder.encode('session loaded'));
+
+                shouldResolve(promise).then(function(result) {
+                    if (result)
+                        consoleWrite('Load was successful.');
+                    next();
+                }, next);
+            }, true);
+        },
+    ];
+    &lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;doTest()&quot;&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsplatformeflTestExpectations"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/platform/efl/TestExpectations (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/platform/efl/TestExpectations        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/LayoutTests/platform/efl/TestExpectations        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -2983,6 +2983,7 @@
</span><span class="cx"> Bug(EFL) media/encrypted-media/encrypted-media-syntax.html [ Failure ]
</span><span class="cx"> Bug(EFL) media/encrypted-media/mock-MediaKeySession-close.html [ Failure ]
</span><span class="cx"> Bug(EFL) media/encrypted-media/mock-MediaKeySession-generateRequest.html [ Failure ]
</span><ins>+Bug(EFL) media/encrypted-media/mock-MediaKeySession-load.html [ Failure ]
</ins><span class="cx"> Bug(EFL) media/encrypted-media/mock-MediaKeySession-remove.html [ Failure ]
</span><span class="cx"> Bug(EFL) media/encrypted-media/mock-MediaKeySession-update.html [ Failure ]
</span><span class="cx"> Bug(EFL) media/encrypted-media/mock-MediaKeySystemAccess.html [ Failure ]
</span></span></pre></div>
<a id="trunkLayoutTestsplatformmacTestExpectations"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/platform/mac/TestExpectations (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/platform/mac/TestExpectations        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/LayoutTests/platform/mac/TestExpectations        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -1497,6 +1497,7 @@
</span><span class="cx"> media/encrypted-media/mock-MediaKeys-createSession.html [ Skip ]
</span><span class="cx"> media/encrypted-media/mock-MediaKeySession-close.html [ Skip ]
</span><span class="cx"> media/encrypted-media/mock-MediaKeySession-generateRequest.html [ Skip ]
</span><ins>+media/encrypted-media/mock-MediaKeySession-load.html [ Skip ]
</ins><span class="cx"> media/encrypted-media/mock-MediaKeySession-remove.html [ Skip ]
</span><span class="cx"> media/encrypted-media/mock-MediaKeySession-update.html [ Skip ]
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/ChangeLog        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -1,5 +1,52 @@
</span><span class="cx"> 2017-02-10  Zan Dobersek  &lt;zdobersek@igalia.com&gt;
</span><span class="cx"> 
</span><ins>+        [EME] Implement MediaKeySession::load()
+        https://bugs.webkit.org/show_bug.cgi?id=168041
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Implement the MediaKeySession::load() method, tracing the steps as they
+        are defined in the EME specification. The only exception is step 8.3,
+        which requires additional facility that tracks currently open sessions
+        and provides information whether for a given session ID there's already
+        a MediaKeySession that's not yet been closed.
+
+        Session ID sanitization is done through the CDM::sanitizeSessionId()
+        method, which relays the task to the CDMPrivate implementation.
+
+        The CDMInstance::loadSession() virtual method is called with the session
+        type, sanitized ID, the Document's origin (in string form) and the
+        callback that's invoked upon completion of the task. The callback
+        checks whether the operation was successful, or examines the reason for
+        the load failure in case it wasn't, rejecting the promise in the latter
+        case either immediately or in the following task at the latest.
+
+        When the load was successful, the optional known keys, expiration time
+        and message are handled appropriately, and the promise is resolved.
+
+        MockCDM::sanitizeSessionId() implementation only treats
+        'valid-loaded-session' as a valid session ID.
+        MockCDMInstance::loadSession() implementation is kept slim for now, only
+        providing the 'license-renewal' message when invoking the passed-in
+        callback. Known keys and expiration time will also be tested once the
+        relevant MediaKeySession algorithms are implemented.
+
+        Test: media/encrypted-media/mock-MediaKeySession-load.html
+
+        * Modules/encryptedmedia/CDM.cpp:
+        (WebCore::CDM::sanitizeSessionId):
+        * Modules/encryptedmedia/CDM.h:
+        * Modules/encryptedmedia/CDMInstance.h:
+        * Modules/encryptedmedia/CDMPrivate.h:
+        * Modules/encryptedmedia/MediaKeySession.cpp:
+        (WebCore::MediaKeySession::load):
+        * testing/MockCDMFactory.cpp:
+        (WebCore::MockCDM::sanitizeSessionId):
+        (WebCore::MockCDMInstance::loadSession):
+        * testing/MockCDMFactory.h:
+
+2017-02-10  Zan Dobersek  &lt;zdobersek@igalia.com&gt;
+
</ins><span class="cx">         [EME] Implement MediaKeySession::sessionClosed()
</span><span class="cx">         https://bugs.webkit.org/show_bug.cgi?id=168039
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaCDMcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/CDM.cpp (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/CDM.cpp        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/Modules/encryptedmedia/CDM.cpp        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -651,6 +651,13 @@
</span><span class="cx">     return m_private-&gt;sanitizeResponse(response);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+std::optional&lt;String&gt; CDM::sanitizeSessionId(const String&amp; sessionId)
+{
+    if (!m_private)
+        return std::nullopt;
+    return m_private-&gt;sanitizeSessionId(sessionId);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+}
+
</ins><span class="cx"> #endif
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaCDMh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/CDM.h (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/CDM.h        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/Modules/encryptedmedia/CDM.h        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -82,6 +82,8 @@
</span><span class="cx"> 
</span><span class="cx">     RefPtr&lt;SharedBuffer&gt; sanitizeResponse(const SharedBuffer&amp;);
</span><span class="cx"> 
</span><ins>+    std::optional&lt;String&gt; sanitizeSessionId(const String&amp; sessionId);
+
</ins><span class="cx"> private:
</span><span class="cx">     CDM(Document&amp;, const String&amp; keySystem);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaCDMInstanceh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -67,6 +67,17 @@
</span><span class="cx">     using LicenseUpdateCallback = Function&lt;void(bool sessionWasClosed, std::optional&lt;KeyStatusVector&gt;&amp;&amp; changedKeys, std::optional&lt;double&gt;&amp;&amp; changedExpiration, std::optional&lt;Message&gt;&amp;&amp; message, SuccessValue succeeded)&gt;;
</span><span class="cx">     virtual void updateLicense(const String&amp; sessionId, LicenseType, const SharedBuffer&amp; response, LicenseUpdateCallback) = 0;
</span><span class="cx"> 
</span><ins>+    enum class SessionLoadFailure {
+        None,
+        NoSessionData,
+        MismatchedSessionType,
+        QuotaExceeded,
+        Other,
+    };
+
+    using LoadSessionCallback = Function&lt;void(std::optional&lt;KeyStatusVector&gt;&amp;&amp;, std::optional&lt;double&gt;&amp;&amp;, std::optional&lt;Message&gt;&amp;&amp;, SuccessValue, SessionLoadFailure)&gt;;
+    virtual void loadSession(LicenseType, const String&amp; sessionId, const String&amp; origin, LoadSessionCallback) = 0;
+
</ins><span class="cx">     using CloseSessionCallback = Function&lt;void()&gt;;
</span><span class="cx">     virtual void closeSession(const String&amp; sessionId, CloseSessionCallback) = 0;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaCDMPrivateh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/CDMPrivate.h (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/CDMPrivate.h        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/Modules/encryptedmedia/CDMPrivate.h        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -55,6 +55,7 @@
</span><span class="cx">     virtual bool supportsSessions() const = 0;
</span><span class="cx">     virtual bool supportsInitData(const AtomicString&amp;, const SharedBuffer&amp;) const = 0;
</span><span class="cx">     virtual RefPtr&lt;SharedBuffer&gt; sanitizeResponse(const SharedBuffer&amp;) const = 0;
</span><ins>+    virtual std::optional&lt;String&gt; sanitizeSessionId(const String&amp;) const = 0;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaMediaKeySessioncpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -33,11 +33,13 @@
</span><span class="cx"> 
</span><span class="cx"> #include &quot;CDM.h&quot;
</span><span class="cx"> #include &quot;CDMInstance.h&quot;
</span><ins>+#include &quot;Document.h&quot;
</ins><span class="cx"> #include &quot;EventNames.h&quot;
</span><span class="cx"> #include &quot;MediaKeyMessageEvent.h&quot;
</span><span class="cx"> #include &quot;MediaKeyMessageType.h&quot;
</span><span class="cx"> #include &quot;MediaKeyStatusMap.h&quot;
</span><span class="cx"> #include &quot;NotImplemented.h&quot;
</span><ins>+#include &quot;SecurityOrigin.h&quot;
</ins><span class="cx"> #include &quot;SharedBuffer.h&quot;
</span><span class="cx"> #include &lt;wtf/NeverDestroyed.h&gt;
</span><span class="cx"> 
</span><span class="lines">@@ -231,9 +233,116 @@
</span><span class="cx">     // 11. Return promise.
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MediaKeySession::load(const String&amp;, Ref&lt;DeferredPromise&gt;&amp;&amp;)
</del><ins>+void MediaKeySession::load(const String&amp; sessionId, Ref&lt;DeferredPromise&gt;&amp;&amp; promise)
</ins><span class="cx"> {
</span><del>-    notImplemented();
</del><ins>+    // https://w3c.github.io/encrypted-media/#dom-mediakeysession-load
+    // W3C Editor's Draft 09 November 2016
+
+    // 1. If this object is closed, return a promise rejected with an InvalidStateError.
+    // 2. If this object's uninitialized value is false, return a promise rejected with an InvalidStateError.
+    if (m_closed || !m_uninitialized) {
+        promise-&gt;reject(INVALID_STATE_ERR);
+        return;
+    }
+
+    // 3. Let this object's uninitialized value be false.
+    m_uninitialized = false;
+
+    // 4. If sessionId is the empty string, return a promise rejected with a newly created TypeError.
+    // 5. If the result of running the Is persistent session type? algorithm on this object's session type is false, return a promise rejected with a newly created TypeError.
+    if (sessionId.isEmpty() || m_sessionType == MediaKeySessionType::Temporary) {
+        promise-&gt;reject(TypeError);
+        return;
+    }
+
+    // 6. Let origin be the origin of this object's Document.
+    // This is retrieved in the following task.
+
+    // 7. Let promise be a new promise.
+    // 8. Run the following steps in parallel:
+    m_taskQueue.enqueueTask([this, sessionId, promise = WTFMove(promise)] () mutable {
+        // 8.1. Let sanitized session ID be a validated and/or sanitized version of sessionId.
+        // 8.2. If the preceding step failed, or if sanitized session ID is empty, reject promise with a newly created TypeError.
+        std::optional&lt;String&gt; sanitizedSessionId = m_implementation-&gt;sanitizeSessionId(sessionId);
+        if (!sanitizedSessionId || sanitizedSessionId-&gt;isEmpty()) {
+            promise-&gt;reject(TypeError);
+            return;
+        }
+
+        // 8.3. If there is a MediaKeySession object that is not closed in this object's Document whose sessionId attribute is sanitized session ID, reject promise with a QuotaExceededError.
+        // FIXME: This needs a global MediaKeySession tracker.
+
+        String origin;
+        if (auto* document = downcast&lt;Document&gt;(scriptExecutionContext()))
+            origin = document-&gt;securityOrigin().toString();
+
+        // 8.4. Let expiration time be NaN.
+        // 8.5. Let message be null.
+        // 8.6. Let message type be null.
+        // 8.7. Let cdm be the CDM instance represented by this object's cdm instance value.
+        // 8.8. Use the cdm to execute the following steps:
+        m_instance-&gt;loadSession(m_sessionType, *sanitizedSessionId, origin, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise), sanitizedSessionId = *sanitizedSessionId] (std::optional&lt;CDMInstance::KeyStatusVector&gt;&amp;&amp; knownKeys, std::optional&lt;double&gt;&amp;&amp; expiration, std::optional&lt;CDMInstance::Message&gt;&amp;&amp; message, CDMInstance::SuccessValue succeeded, CDMInstance::SessionLoadFailure failure) mutable {
+            // 8.8.1. If there is no data stored for the sanitized session ID in the origin, resolve promise with false and abort these steps.
+            // 8.8.2. If the stored session's session type is not the same as the current MediaKeySession session type, reject promise with a newly created TypeError.
+            // 8.8.3. 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.4. If there is a MediaKeySession object that is not closed in any Document and that represents the session data, reject promise with a QuotaExceededError.
+            // 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.7. If the CDM needs to send a message:
+            //   8.8.7.1. Let message be a message generated by the CDM based on the session data.
+            //   8.8.7.2. Let message type be the appropriate MediaKeyMessageType for the message.
+            // NOTE: Steps 8.8.1. through 8.8.7. should be implemented in CDMInstance.
+
+            if (succeeded == CDMInstance::SuccessValue::Failed) {
+                switch (failure) {
+                case CDMInstance::SessionLoadFailure::NoSessionData:
+                    promise-&gt;resolve&lt;IDLBoolean&gt;(false);
+                    return;
+                case CDMInstance::SessionLoadFailure::MismatchedSessionType:
+                    promise-&gt;reject(TypeError);
+                    return;
+                case CDMInstance::SessionLoadFailure::QuotaExceeded:
+                    promise-&gt;reject(QUOTA_EXCEEDED_ERR);
+                    return;
+                case CDMInstance::SessionLoadFailure::None:
+                case CDMInstance::SessionLoadFailure::Other:
+                    // In any other case, the session load failure will cause a rejection in the following task.
+                    break;
+                }
+            }
+
+            // 8.9. Queue a task to run the following steps:
+            m_taskQueue.enqueueTask([this, knownKeys = WTFMove(knownKeys), expiration = WTFMove(expiration), message = WTFMove(message), sanitizedSessionId, succeeded, promise = WTFMove(promise)] () mutable {
+                // 8.9.1. If any of the preceding steps failed, reject promise with a the appropriate error name.
+                if (succeeded == CDMInstance::SuccessValue::Failed) {
+                    promise-&gt;reject(NOT_SUPPORTED_ERR);
+                    return;
+                }
+
+                // 8.9.2. Set the sessionId attribute to sanitized session ID.
+                // 8.9.3. Let this object's callable value be true.
+                m_sessionId = sanitizedSessionId;
+                m_callable = true;
+
+                // 8.9.4. If the loaded session contains information about any keys (there are known keys), run the Update Key Statuses algorithm on the session, providing each key's key ID along with the appropriate MediaKeyStatus.
+                if (knownKeys)
+                    updateKeyStatuses(WTFMove(*knownKeys));
+
+                // 8.9.5. Run the Update Expiration algorithm on the session, providing expiration time.
+                // This must be run, and NaN is the default value if the CDM instance doesn't provide one.
+                updateExpiration(expiration.value_or(std::numeric_limits&lt;double&gt;::quiet_NaN()));
+
+                // 8.9.6. If message is not null, run the Queue a &quot;message&quot; Event algorithm on the session, providing message type and message.
+                if (message)
+                    enqueueMessage(message-&gt;first, WTFMove(message-&gt;second));
+
+                // 8.9.7. Resolve promise with true.
+                promise-&gt;resolve&lt;IDLBoolean&gt;(true);
+            });
+        });
+    });
+
+    // 9. Return promise.
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MediaKeySession::update(const BufferSource&amp; response, Ref&lt;DeferredPromise&gt;&amp;&amp; promise)
</span></span></pre></div>
<a id="trunkSourceWebCoretestingMockCDMFactorycpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/testing/MockCDMFactory.cpp (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/testing/MockCDMFactory.cpp        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/testing/MockCDMFactory.cpp        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -208,6 +208,13 @@
</span><span class="cx">     return response.copy();
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+std::optional&lt;String&gt; MockCDM::sanitizeSessionId(const String&amp; sessionId) const
+{
+    if (equalLettersIgnoringASCIICase(sessionId, &quot;valid-loaded-session&quot;))
+        return sessionId;
+    return std::nullopt;
+}
+
</ins><span class="cx"> MockCDMInstance::MockCDMInstance(WeakPtr&lt;MockCDM&gt; cdm)
</span><span class="cx">     : m_cdm(cdm)
</span><span class="cx"> {
</span><span class="lines">@@ -319,6 +326,22 @@
</span><span class="cx">     callback(false, WTFMove(changedKeys), std::nullopt, std::nullopt, SuccessValue::Succeeded);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void MockCDMInstance::loadSession(LicenseType, const String&amp;, const String&amp;, LoadSessionCallback callback)
+{
+    MockCDMFactory* factory = m_cdm ? m_cdm-&gt;factory() : nullptr;
+    if (!factory) {
+        callback(std::nullopt, std::nullopt, std::nullopt, SuccessValue::Failed, SessionLoadFailure::Other);
+        return;
+    }
+
+    // FIXME: Key status and expiration handling should be implemented once the relevant algorithms are supported.
+
+    CString messageData { &quot;session loaded&quot; };
+    Message message { MessageType::LicenseRenewal, SharedBuffer::create(messageData.data(), messageData.length()) };
+
+    callback(std::nullopt, std::nullopt, WTFMove(message), SuccessValue::Succeeded, SessionLoadFailure::None);
+}
+
</ins><span class="cx"> void MockCDMInstance::closeSession(const String&amp; sessionID, CloseSessionCallback callback)
</span><span class="cx"> {
</span><span class="cx">     MockCDMFactory* factory = m_cdm ? m_cdm-&gt;factory() : nullptr;
</span></span></pre></div>
<a id="trunkSourceWebCoretestingMockCDMFactoryh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/testing/MockCDMFactory.h (212109 => 212110)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/testing/MockCDMFactory.h        2017-02-10 10:31:57 UTC (rev 212109)
+++ trunk/Source/WebCore/testing/MockCDMFactory.h        2017-02-10 10:40:29 UTC (rev 212110)
</span><span class="lines">@@ -116,6 +116,7 @@
</span><span class="cx">     bool supportsSessions() const final;
</span><span class="cx">     bool supportsInitData(const AtomicString&amp;, const SharedBuffer&amp;) const final;
</span><span class="cx">     RefPtr&lt;SharedBuffer&gt; sanitizeResponse(const SharedBuffer&amp;) const final;
</span><ins>+    std::optional&lt;String&gt; sanitizeSessionId(const String&amp;) const final;
</ins><span class="cx"> 
</span><span class="cx">     WeakPtr&lt;MockCDMFactory&gt; m_factory;
</span><span class="cx">     WeakPtrFactory&lt;MockCDM&gt; m_weakPtrFactory;
</span><span class="lines">@@ -132,6 +133,7 @@
</span><span class="cx">     SuccessValue setServerCertificate(Ref&lt;SharedBuffer&gt;&amp;&amp;) final;
</span><span class="cx">     void requestLicense(LicenseType, const AtomicString&amp; initDataType, Ref&lt;SharedBuffer&gt;&amp;&amp; initData, LicenseCallback) final;
</span><span class="cx">     void updateLicense(const String&amp;, LicenseType, const SharedBuffer&amp;, LicenseUpdateCallback) final;
</span><ins>+    void loadSession(LicenseType, const String&amp;, const String&amp;, LoadSessionCallback) final;
</ins><span class="cx">     void closeSession(const String&amp;, CloseSessionCallback) final;
</span><span class="cx">     void removeSessionData(const String&amp;, LicenseType, RemoveSessionDataCallback) final;
</span><span class="cx">     void storeRecordOfKeyUsage(const String&amp;) final;
</span></span></pre>
</div>
</div>

</body>
</html>