<!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>[212107] 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/212107">212107</a></dd>
<dt>Author</dt> <dd>zandobersek@gmail.com</dd>
<dt>Date</dt> <dd>2017-02-10 02:22:50 -0800 (Fri, 10 Feb 2017)</dd>
</dl>

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

Reviewed by Xabier Rodriguez-Calvar.

Source/WebCore:

Implement MediaKeySession::updateKeyStatuses(), transforming the passed-in
KeyStatusVector into a Vector mapping the key IDs to MediaKeyStatus values.
A keystatuseschange event is fired on the MediaKeySession object afterwards.
The queueing of the task that runs the 'attemp to resume playback' on the
related HTMLMediaElement objects isn't done yet since that algorithm isn't
implemented yet.

The statuses Vector is stored on the MediaKeySession object. That Vector is
then exposed through the MediaKeyStatusMap object, each such object being
unique to one MediaKeySession object. The implementation of MediaKeyStatusMap
thus keeps a reference to the session object as long as that object is alive,
and queries the MediaKeySession::statuses() getter to access the Vector that
contains status information for all the key IDs.

MediaKeyStatusMap::Iterator object keeps a reference to the MediaKeyStatusMap
object and accesses the statuses by indexing into the status Vector of the
related MediaKeySession object.

CDMInstance::updateLicense() now accepts the session ID string as the first
argument, making it possible to specify which session should be updated.

MockCDMFactory::keysForSessionWithID() returns an optional reference to the
Vector value in the session map that lists all the key IDs that are being
stored for that session.

MockCDMInstance::updateLicense() now detects the 'keys-changed' entry in the
passed-in response data, and upon detecting that constructs a KeyStatusVector
object containing all the keys for that session. KeyStatus::Usable is returned
for each object at the moment, but this should be adjustable in the future
through additional parameters passed through the response data. The Vector
object is then passed to the callback and is then passed to the 'update key
statuses' algorithm in MediaKeySession.

Covered by a test case in media/encrypted-media/mock-MediaKeySession-update.html.

* Modules/encryptedmedia/CDMInstance.h:
* Modules/encryptedmedia/MediaKeySession.cpp:
(WebCore::MediaKeySession::MediaKeySession):
(WebCore::MediaKeySession::~MediaKeySession):
(WebCore::MediaKeySession::update):
(WebCore::MediaKeySession::updateKeyStatuses):
* Modules/encryptedmedia/MediaKeySession.h:
* Modules/encryptedmedia/MediaKeyStatusMap.cpp:
(WebCore::MediaKeyStatusMap::MediaKeyStatusMap):
(WebCore::MediaKeyStatusMap::detachSession):
(WebCore::MediaKeyStatusMap::size):
(WebCore::keyIdsMatch):
(WebCore::MediaKeyStatusMap::has):
(WebCore::MediaKeyStatusMap::get):
(WebCore::MediaKeyStatusMap::Iterator::Iterator):
(WebCore::MediaKeyStatusMap::Iterator::next):
* Modules/encryptedmedia/MediaKeyStatusMap.h:
(WebCore::MediaKeyStatusMap::create):
* Modules/encryptedmedia/MediaKeyStatusMap.idl:
* testing/MockCDMFactory.cpp:
(WebCore::MockCDMFactory::keysForSessionWithID):
(WebCore::MockCDMInstance::updateLicense):
* testing/MockCDMFactory.h:

LayoutTests:

Add another test case to the mock-MediaKeySession-update.html test that
ensures the keystatuseschange event is fired on the MediaKeySession object
and that the status of the keys is properly reported through the
MediaKeyStatusMap object associated with this MediaKeySession.

* media/encrypted-media/mock-MediaKeySession-update-expected.txt:
* media/encrypted-media/mock-MediaKeySession-update.html:</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsmediaencryptedmediamockMediaKeySessionupdateexpectedtxt">trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediaencryptedmediamockMediaKeySessionupdatehtml">trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update.html</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaCDMInstanceh">trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaMediaKeySessioncpp">trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaMediaKeySessionh">trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.h</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaMediaKeyStatusMapcpp">trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.cpp</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaMediaKeyStatusMaph">trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.h</a></li>
<li><a href="#trunkSourceWebCoreModulesencryptedmediaMediaKeyStatusMapidl">trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.idl</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>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/LayoutTests/ChangeLog        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -1,3 +1,18 @@
</span><ins>+2017-02-10  Zan Dobersek  &lt;zdobersek@igalia.com&gt;
+
+        [EME] Implement MediaKeySession::updateKeyStatuses(), MediaKeyStatusMap
+        https://bugs.webkit.org/show_bug.cgi?id=167888
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Add another test case to the mock-MediaKeySession-update.html test that
+        ensures the keystatuseschange event is fired on the MediaKeySession object
+        and that the status of the keys is properly reported through the
+        MediaKeyStatusMap object associated with this MediaKeySession.
+
+        * media/encrypted-media/mock-MediaKeySession-update-expected.txt:
+        * media/encrypted-media/mock-MediaKeySession-update.html:
+
</ins><span class="cx"> 2017-02-09  Carlos Garcia Campos  &lt;cgarcia@igalia.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Unreviewed GTK+ gardening. Update expectations of tests using CSS3 Text each-line and hanging indent.
</span></span></pre></div>
<a id="trunkLayoutTestsmediaencryptedmediamockMediaKeySessionupdateexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update-expected.txt (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update-expected.txt        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update-expected.txt        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -12,13 +12,13 @@
</span><span class="cx"> Using a non-callable MediaKeySession should reject.
</span><span class="cx"> RUN(mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;))
</span><span class="cx"> EXPECTED (typeof mediaKeySession == 'object') OK
</span><del>-RUN(promise = mediaKeySession.update(stringToUInt8Array(&quot;invalid-state&quot;)))
</del><ins>+RUN(promise = mediaKeySession.update(encoder.encode(&quot;invalid-state&quot;)))
</ins><span class="cx"> Promise rejected correctly OK
</span><span class="cx"> 
</span><span class="cx"> Using a zero-length response should reject.
</span><span class="cx"> RUN(kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] }))
</span><span class="cx"> RUN(mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;))
</span><del>-RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids)))
</del><ins>+RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids)))
</ins><span class="cx"> Promise resolved OK
</span><span class="cx"> RUN(promise = mediaKeySession.update(new Uint8Array(0)))
</span><span class="cx"> Promise rejected correctly OK
</span><span class="lines">@@ -26,25 +26,101 @@
</span><span class="cx"> Using a non-sanitizable response should reject.
</span><span class="cx"> RUN(kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] }))
</span><span class="cx"> RUN(mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;))
</span><del>-RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids)))
</del><ins>+RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids)))
</ins><span class="cx"> Promise resolved OK
</span><del>-RUN(promise = mediaKeySession.update(stringToUInt8Array(&quot;invalid-response&quot;)))
</del><ins>+RUN(promise = mediaKeySession.update(encoder.encode(&quot;invalid-response&quot;)))
</ins><span class="cx"> Promise rejected correctly OK
</span><span class="cx"> 
</span><span class="cx"> Using a sanitizable response should resolve.
</span><span class="cx"> RUN(kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] }))
</span><span class="cx"> RUN(mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;))
</span><del>-RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids)))
</del><ins>+RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids)))
</ins><span class="cx"> Promise resolved OK
</span><del>-RUN(promise = mediaKeySession.update(stringToUInt8Array(&quot;valid-response&quot;)))
</del><ins>+RUN(promise = mediaKeySession.update(encoder.encode(&quot;valid-response&quot;)))
</ins><span class="cx"> Promise resolved OK
</span><span class="cx"> 
</span><span class="cx"> Using a sanitizable response with invalid format should resolve.
</span><span class="cx"> RUN(kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] }))
</span><span class="cx"> RUN(mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;))
</span><del>-RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids)))
</del><ins>+RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids)))
</ins><span class="cx"> Promise resolved OK
</span><del>-RUN(promise = mediaKeySession.update(stringToUInt8Array(&quot;valid-response invalid-format&quot;)))
</del><ins>+RUN(promise = mediaKeySession.update(encoder.encode(&quot;valid-response invalid-format&quot;)))
</ins><span class="cx"> Promise rejected correctly OK
</span><ins>+
+A valid MediaKeySession properly dispatches keystatuseschange event.
+RUN(kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot;, &quot;Njc4OTA=&quot; ] }))
+RUN(mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;))
+RUN(promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids)))
+Promise resolved OK
+RUN(promise = mediaKeySession.update(encoder.encode(&quot;valid-response keys-changed&quot;)))
+EXPECTED (mediaKeySession.keyStatuses.size == '2') OK
+EXPECTED (mediaKeySession.keyStatuses.has(encoder.encode(&quot;12345&quot;)) == 'true') OK
+EXPECTED (mediaKeySession.keyStatuses.get(encoder.encode(&quot;12345&quot;)) == 'usable') OK
+EXPECTED (mediaKeySession.keyStatuses.has(encoder.encode(&quot;67890&quot;)) == 'true') OK
+EXPECTED (mediaKeySession.keyStatuses.get(encoder.encode(&quot;67890&quot;)) == 'usable') OK
+EXPECTED (mediaKeySession.keyStatuses.has(encoder.encode(&quot;invalid&quot;)) == 'false') OK
+EXPECTED (mediaKeySession.keyStatuses.get(encoder.encode(&quot;invalid&quot;)) == 'undefined') OK
+EXPECTED (iteratorArray.length == '2') OK
+EXPECTED (iteratorArray[0][0].length == '5') OK
+EXPECTED (iteratorArray[0][0][0] == '49') OK
+EXPECTED (iteratorArray[0][0][1] == '50') OK
+EXPECTED (iteratorArray[0][0][2] == '51') OK
+EXPECTED (iteratorArray[0][0][3] == '52') OK
+EXPECTED (iteratorArray[0][0][4] == '53') OK
+EXPECTED (iteratorArray[0][1] == 'usable') OK
+EXPECTED (iteratorArray[1][0].length == '5') OK
+EXPECTED (iteratorArray[1][0][0] == '54') OK
+EXPECTED (iteratorArray[1][0][1] == '55') OK
+EXPECTED (iteratorArray[1][0][2] == '56') OK
+EXPECTED (iteratorArray[1][0][3] == '57') OK
+EXPECTED (iteratorArray[1][0][4] == '48') OK
+EXPECTED (iteratorArray[1][1] == 'usable') OK
+EXPECTED (iteratorArray.length == '2') OK
+EXPECTED (iteratorArray[0].length == '5') OK
+EXPECTED (iteratorArray[0][0] == '49') OK
+EXPECTED (iteratorArray[0][1] == '50') OK
+EXPECTED (iteratorArray[0][2] == '51') OK
+EXPECTED (iteratorArray[0][3] == '52') OK
+EXPECTED (iteratorArray[0][4] == '53') OK
+EXPECTED (iteratorArray[1].length == '5') OK
+EXPECTED (iteratorArray[1][0] == '54') OK
+EXPECTED (iteratorArray[1][1] == '55') OK
+EXPECTED (iteratorArray[1][2] == '56') OK
+EXPECTED (iteratorArray[1][3] == '57') OK
+EXPECTED (iteratorArray[1][4] == '48') OK
+EXPECTED (iteratorArray.length == '2') OK
+EXPECTED (iteratorArray[0] == 'usable') OK
+EXPECTED (iteratorArray[1] == 'usable') OK
+EXPECTED (iteratorArray.length == '2') OK
+EXPECTED (iteratorArray[0].key.length == '5') OK
+EXPECTED (iteratorArray[0].key[0] == '49') OK
+EXPECTED (iteratorArray[0].key[1] == '50') OK
+EXPECTED (iteratorArray[0].key[2] == '51') OK
+EXPECTED (iteratorArray[0].key[3] == '52') OK
+EXPECTED (iteratorArray[0].key[4] == '53') OK
+EXPECTED (iteratorArray[0].value == 'usable') OK
+EXPECTED (iteratorArray[1].key.length == '5') OK
+EXPECTED (iteratorArray[1].key[0] == '54') OK
+EXPECTED (iteratorArray[1].key[1] == '55') OK
+EXPECTED (iteratorArray[1].key[2] == '56') OK
+EXPECTED (iteratorArray[1].key[3] == '57') OK
+EXPECTED (iteratorArray[1].key[4] == '48') OK
+EXPECTED (iteratorArray[1].value == 'usable') OK
+EXPECTED (iteratorArray.length == '2') OK
+EXPECTED (iteratorArray[0].key.length == '5') OK
+EXPECTED (iteratorArray[0].key[0] == '49') OK
+EXPECTED (iteratorArray[0].key[1] == '50') OK
+EXPECTED (iteratorArray[0].key[2] == '51') OK
+EXPECTED (iteratorArray[0].key[3] == '52') OK
+EXPECTED (iteratorArray[0].key[4] == '53') OK
+EXPECTED (iteratorArray[0].value == 'usable') OK
+EXPECTED (iteratorArray[1].key.length == '5') OK
+EXPECTED (iteratorArray[1].key[0] == '54') OK
+EXPECTED (iteratorArray[1].key[1] == '55') OK
+EXPECTED (iteratorArray[1].key[2] == '56') OK
+EXPECTED (iteratorArray[1].key[3] == '57') OK
+EXPECTED (iteratorArray[1].key[4] == '48') OK
+EXPECTED (iteratorArray[1].value == 'usable') OK
+Promise resolved OK
</ins><span class="cx"> END OF TEST
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkLayoutTestsmediaencryptedmediamockMediaKeySessionupdatehtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update.html (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update.html        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/LayoutTests/media/encrypted-media/mock-MediaKeySession-update.html        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -10,6 +10,8 @@
</span><span class="cx">     var mediaKeySession;
</span><span class="cx">     var capabilities = {};
</span><span class="cx">     var kids;
</span><ins>+    var iteratorArray;
+    var encoder = new TextEncoder();
</ins><span class="cx"> 
</span><span class="cx">     function doTest()
</span><span class="cx">     {
</span><span class="lines">@@ -49,14 +51,6 @@
</span><span class="cx">         next();
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    function stringToUInt8Array(str)
-    {
-       var array = new Uint8Array(str.length);
-       for (var i=0; i&lt;str.length; i++)
-            array[i] = str.charCodeAt(i);
-       return array;
-    }
-
</del><span class="cx">     tests = [
</span><span class="cx">         function() {
</span><span class="cx">             run('promise = mediaKeySystemAccess.createMediaKeys()');
</span><span class="lines">@@ -67,7 +61,7 @@
</span><span class="cx">             consoleWrite('Using a non-callable MediaKeySession should reject.');
</span><span class="cx">             run('mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;)');
</span><span class="cx">             testExpected('typeof mediaKeySession', 'object');
</span><del>-            run('promise = mediaKeySession.update(stringToUInt8Array(&quot;invalid-state&quot;))');
</del><ins>+            run('promise = mediaKeySession.update(encoder.encode(&quot;invalid-state&quot;))');
</ins><span class="cx">             shouldReject(promise).then(next, next);
</span><span class="cx">         },
</span><span class="cx"> 
</span><span class="lines">@@ -75,7 +69,7 @@
</span><span class="cx">             consoleWrite('Using a zero-length response should reject.');
</span><span class="cx">             run('kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] })');
</span><span class="cx">             run('mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;)');
</span><del>-            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids))');
</del><ins>+            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids))');
</ins><span class="cx">             shouldResolve(promise).then(function() {
</span><span class="cx">                 run('promise = mediaKeySession.update(new Uint8Array(0))');
</span><span class="cx">                 shouldReject(promise).then(next, next);
</span><span class="lines">@@ -86,9 +80,9 @@
</span><span class="cx">             consoleWrite('Using a non-sanitizable response should reject.');
</span><span class="cx">             run('kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] })');
</span><span class="cx">             run('mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;)');
</span><del>-            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids))');
</del><ins>+            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids))');
</ins><span class="cx">             shouldResolve(promise).then(function() {
</span><del>-                run('promise = mediaKeySession.update(stringToUInt8Array(&quot;invalid-response&quot;))');
</del><ins>+                run('promise = mediaKeySession.update(encoder.encode(&quot;invalid-response&quot;))');
</ins><span class="cx">                 shouldReject(promise).then(next, next);
</span><span class="cx">             }, next);
</span><span class="cx">         },
</span><span class="lines">@@ -97,9 +91,9 @@
</span><span class="cx">             consoleWrite('Using a sanitizable response should resolve.');
</span><span class="cx">             run('kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] })');
</span><span class="cx">             run('mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;)');
</span><del>-            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids))');
</del><ins>+            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids))');
</ins><span class="cx">             shouldResolve(promise).then(function() {
</span><del>-                run('promise = mediaKeySession.update(stringToUInt8Array(&quot;valid-response&quot;))');
</del><ins>+                run('promise = mediaKeySession.update(encoder.encode(&quot;valid-response&quot;))');
</ins><span class="cx">                 shouldResolve(promise).then(next, next);
</span><span class="cx">             }, next);
</span><span class="cx">         },
</span><span class="lines">@@ -108,12 +102,73 @@
</span><span class="cx">             consoleWrite('Using a sanitizable response with invalid format should resolve.');
</span><span class="cx">             run('kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot; ] })');
</span><span class="cx">             run('mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;)');
</span><del>-            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, stringToUInt8Array(kids))');
</del><ins>+            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids))');
</ins><span class="cx">             shouldResolve(promise).then(function() {
</span><del>-                run('promise = mediaKeySession.update(stringToUInt8Array(&quot;valid-response invalid-format&quot;))');
</del><ins>+                run('promise = mediaKeySession.update(encoder.encode(&quot;valid-response invalid-format&quot;))');
</ins><span class="cx">                 shouldReject(promise).then(next, next);
</span><span class="cx">             }, next);
</span><span class="cx">         },
</span><ins>+
+        function() {
+            consoleWrite('A valid MediaKeySession properly dispatches keystatuseschange event.');
+            run('kids = JSON.stringify({ kids: [ &quot;MTIzNDU=&quot;, &quot;Njc4OTA=&quot; ] })');
+            run('mediaKeySession = mediaKeys.createSession(&quot;temporary&quot;)');
+            run('promise = mediaKeySession.generateRequest(&quot;keyids&quot;, encoder.encode(kids))');
+            shouldResolve(promise).then(function() {
+                run('promise = mediaKeySession.update(encoder.encode(&quot;valid-response keys-changed&quot;))');
+                mediaKeySession.addEventListener('keystatuseschange', function(event) {
+                    testExpected('mediaKeySession.keyStatuses.size', 2);
+                    testExpected('mediaKeySession.keyStatuses.has(encoder.encode(&quot;12345&quot;))', true);
+                    testExpected('mediaKeySession.keyStatuses.get(encoder.encode(&quot;12345&quot;))', 'usable');
+                    testExpected('mediaKeySession.keyStatuses.has(encoder.encode(&quot;67890&quot;))', true);
+                    testExpected('mediaKeySession.keyStatuses.get(encoder.encode(&quot;67890&quot;))', 'usable');
+                    testExpected('mediaKeySession.keyStatuses.has(encoder.encode(&quot;invalid&quot;))', false);
+                    testExpected('mediaKeySession.keyStatuses.get(encoder.encode(&quot;invalid&quot;))', undefined);
+
+                    iteratorArray = [];
+                    for (var pair of mediaKeySession.keyStatuses)
+                        iteratorArray.push([ new Uint8Array(pair[0]), pair[1] ]);
+                    testExpected('iteratorArray.length', 2);
+                    testArraysEqual('iteratorArray[0][0]', encoder.encode('12345'));
+                    testExpected('iteratorArray[0][1]', 'usable');
+                    testArraysEqual('iteratorArray[1][0]', encoder.encode('67890'));
+                    testExpected('iteratorArray[1][1]', 'usable');
+
+                    iteratorArray = [];
+                    for (var key of mediaKeySession.keyStatuses.keys())
+                        iteratorArray.push(new Uint8Array(key));
+                    testExpected('iteratorArray.length', 2);
+                    testArraysEqual('iteratorArray[0]', encoder.encode('12345'));
+                    testArraysEqual('iteratorArray[1]', encoder.encode('67890'));
+
+                    iteratorArray = [];
+                    for (var value of mediaKeySession.keyStatuses.values())
+                        iteratorArray.push(value);
+                    testArraysEqual('iteratorArray', [ 'usable', 'usable' ]);
+
+                    iteratorArray = [];
+                    for (var entry of mediaKeySession.keyStatuses.entries())
+                        iteratorArray.push({ key: new Uint8Array(entry[0]), value: entry[1] });
+                    testExpected('iteratorArray.length', 2);
+                    testArraysEqual('iteratorArray[0].key', encoder.encode('12345'));
+                    testExpected('iteratorArray[0].value', 'usable');
+                    testArraysEqual('iteratorArray[1].key', encoder.encode('67890'));
+                    testExpected('iteratorArray[1].value', 'usable');
+
+                    iteratorArray = [];
+                    mediaKeySession.keyStatuses.forEach(function(value, key) {
+                        iteratorArray.push({ key: new Uint8Array(key), value: value });
+                    });
+                    testExpected('iteratorArray.length', 2);
+                    testArraysEqual('iteratorArray[0].key', encoder.encode('12345'));
+                    testExpected('iteratorArray[0].value', 'usable');
+                    testArraysEqual('iteratorArray[1].key', encoder.encode('67890'));
+                    testExpected('iteratorArray[1].value', 'usable');
+
+                    shouldResolve(promise).then(next, next);
+                }, false);
+            }, next);
+        },
</ins><span class="cx">     ];
</span><span class="cx">     &lt;/script&gt;
</span><span class="cx"> &lt;/head&gt;
</span></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/ChangeLog        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -1,3 +1,69 @@
</span><ins>+2017-02-10  Zan Dobersek  &lt;zdobersek@igalia.com&gt;
+
+        [EME] Implement MediaKeySession::updateKeyStatuses(), MediaKeyStatusMap
+        https://bugs.webkit.org/show_bug.cgi?id=167888
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Implement MediaKeySession::updateKeyStatuses(), transforming the passed-in
+        KeyStatusVector into a Vector mapping the key IDs to MediaKeyStatus values.
+        A keystatuseschange event is fired on the MediaKeySession object afterwards.
+        The queueing of the task that runs the 'attemp to resume playback' on the
+        related HTMLMediaElement objects isn't done yet since that algorithm isn't
+        implemented yet.
+
+        The statuses Vector is stored on the MediaKeySession object. That Vector is
+        then exposed through the MediaKeyStatusMap object, each such object being
+        unique to one MediaKeySession object. The implementation of MediaKeyStatusMap
+        thus keeps a reference to the session object as long as that object is alive,
+        and queries the MediaKeySession::statuses() getter to access the Vector that
+        contains status information for all the key IDs.
+
+        MediaKeyStatusMap::Iterator object keeps a reference to the MediaKeyStatusMap
+        object and accesses the statuses by indexing into the status Vector of the
+        related MediaKeySession object.
+
+        CDMInstance::updateLicense() now accepts the session ID string as the first
+        argument, making it possible to specify which session should be updated.
+
+        MockCDMFactory::keysForSessionWithID() returns an optional reference to the
+        Vector value in the session map that lists all the key IDs that are being
+        stored for that session.
+
+        MockCDMInstance::updateLicense() now detects the 'keys-changed' entry in the
+        passed-in response data, and upon detecting that constructs a KeyStatusVector
+        object containing all the keys for that session. KeyStatus::Usable is returned
+        for each object at the moment, but this should be adjustable in the future
+        through additional parameters passed through the response data. The Vector
+        object is then passed to the callback and is then passed to the 'update key
+        statuses' algorithm in MediaKeySession.
+
+        Covered by a test case in media/encrypted-media/mock-MediaKeySession-update.html.
+
+        * Modules/encryptedmedia/CDMInstance.h:
+        * Modules/encryptedmedia/MediaKeySession.cpp:
+        (WebCore::MediaKeySession::MediaKeySession):
+        (WebCore::MediaKeySession::~MediaKeySession):
+        (WebCore::MediaKeySession::update):
+        (WebCore::MediaKeySession::updateKeyStatuses):
+        * Modules/encryptedmedia/MediaKeySession.h:
+        * Modules/encryptedmedia/MediaKeyStatusMap.cpp:
+        (WebCore::MediaKeyStatusMap::MediaKeyStatusMap):
+        (WebCore::MediaKeyStatusMap::detachSession):
+        (WebCore::MediaKeyStatusMap::size):
+        (WebCore::keyIdsMatch):
+        (WebCore::MediaKeyStatusMap::has):
+        (WebCore::MediaKeyStatusMap::get):
+        (WebCore::MediaKeyStatusMap::Iterator::Iterator):
+        (WebCore::MediaKeyStatusMap::Iterator::next):
+        * Modules/encryptedmedia/MediaKeyStatusMap.h:
+        (WebCore::MediaKeyStatusMap::create):
+        * Modules/encryptedmedia/MediaKeyStatusMap.idl:
+        * testing/MockCDMFactory.cpp:
+        (WebCore::MockCDMFactory::keysForSessionWithID):
+        (WebCore::MockCDMInstance::updateLicense):
+        * testing/MockCDMFactory.h:
+
</ins><span class="cx"> 2017-02-09  Simon Fraser  &lt;simon.fraser@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Improve IOSurfacePool logging
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaCDMInstanceh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/Modules/encryptedmedia/CDMInstance.h        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -65,7 +65,7 @@
</span><span class="cx">     using KeyStatusVector = Vector&lt;std::pair&lt;Ref&lt;SharedBuffer&gt;, KeyStatus&gt;&gt;;
</span><span class="cx">     using Message = std::pair&lt;MessageType, Ref&lt;SharedBuffer&gt;&gt;;
</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><del>-    virtual void updateLicense(LicenseType, const SharedBuffer&amp; response, LicenseUpdateCallback) = 0;
</del><ins>+    virtual void updateLicense(const String&amp; sessionId, LicenseType, const SharedBuffer&amp; response, LicenseUpdateCallback) = 0;
</ins><span class="cx"> 
</span><span class="cx">     using CloseSessionCallback = Function&lt;void()&gt;;
</span><span class="cx">     virtual void closeSession(const String&amp; sessionId, CloseSessionCallback) = 0;
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaMediaKeySessioncpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.cpp        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -53,7 +53,7 @@
</span><span class="cx"> MediaKeySession::MediaKeySession(ScriptExecutionContext&amp; context, MediaKeySessionType sessionType, bool useDistinctiveIdentifier, Ref&lt;CDM&gt;&amp;&amp; implementation, Ref&lt;CDMInstance&gt;&amp;&amp; instance)
</span><span class="cx">     : ActiveDOMObject(&amp;context)
</span><span class="cx">     , m_expiration(std::numeric_limits&lt;double&gt;::quiet_NaN())
</span><del>-    , m_keyStatuses(MediaKeyStatusMap::create())
</del><ins>+    , m_keyStatuses(MediaKeyStatusMap::create(*this))
</ins><span class="cx">     , m_useDistinctiveIdentifier(useDistinctiveIdentifier)
</span><span class="cx">     , m_sessionType(sessionType)
</span><span class="cx">     , m_implementation(WTFMove(implementation))
</span><span class="lines">@@ -84,7 +84,10 @@
</span><span class="cx">     UNUSED_PARAM(m_uninitialized);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-MediaKeySession::~MediaKeySession() = default;
</del><ins>+MediaKeySession::~MediaKeySession()
+{
+    m_keyStatuses-&gt;detachSession();
+}
</ins><span class="cx"> 
</span><span class="cx"> const String&amp; MediaKeySession::sessionId() const
</span><span class="cx"> {
</span><span class="lines">@@ -270,7 +273,7 @@
</span><span class="cx">         // 6.5. Let session closed be false.
</span><span class="cx">         // 6.6. Let cdm be the CDM instance represented by this object's cdm instance value.
</span><span class="cx">         // 6.7. Use the cdm to execute the following steps:
</span><del>-        m_instance-&gt;updateLicense(m_sessionType, *sanitizedResponse, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] (bool sessionWasClosed, std::optional&lt;CDMInstance::KeyStatusVector&gt;&amp;&amp; changedKeys, std::optional&lt;double&gt;&amp;&amp; changedExpiration, std::optional&lt;CDMInstance::Message&gt;&amp;&amp; message, CDMInstance::SuccessValue succeeded) mutable {
</del><ins>+        m_instance-&gt;updateLicense(m_sessionId, m_sessionType, *sanitizedResponse, [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] (bool sessionWasClosed, std::optional&lt;CDMInstance::KeyStatusVector&gt;&amp;&amp; changedKeys, std::optional&lt;double&gt;&amp;&amp; changedExpiration, std::optional&lt;CDMInstance::Message&gt;&amp;&amp; message, CDMInstance::SuccessValue succeeded) mutable {
</ins><span class="cx">             if (!weakThis)
</span><span class="cx">                 return;
</span><span class="cx"> 
</span><span class="lines">@@ -481,9 +484,49 @@
</span><span class="cx">     m_eventQueue.enqueueEvent(WTFMove(messageEvent));
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MediaKeySession::updateKeyStatuses(CDMInstance::KeyStatusVector&amp;&amp;)
</del><ins>+void MediaKeySession::updateKeyStatuses(CDMInstance::KeyStatusVector&amp;&amp; inputStatuses)
</ins><span class="cx"> {
</span><del>-    notImplemented();
</del><ins>+    // https://w3c.github.io/encrypted-media/#update-key-statuses
+    // W3C Editor's Draft 09 November 2016
+
+    // 1. Let the session be the associated MediaKeySession object.
+    // 2. Let the input statuses be the sequence of pairs key ID and associated MediaKeyStatus pairs.
+    // 3. Let the statuses be session's keyStatuses attribute.
+    // 4. Run the following steps to replace the contents of statuses:
+    //   4.1. Empty statuses.
+    //   4.2. For each pair in input statuses.
+    //     4.2.1. Let pair be the pair.
+    //     4.2.2. Insert an entry for pair's key ID into statuses with the value of pair's MediaKeyStatus value.
+
+    static auto toMediaKeyStatus = [] (CDMInstance::KeyStatus status) -&gt; MediaKeyStatus {
+        switch (status) {
+        case CDMInstance::KeyStatus::Usable:
+            return MediaKeyStatus::Usable;
+        case CDMInstance::KeyStatus::Expired:
+            return MediaKeyStatus::Expired;
+        case CDMInstance::KeyStatus::Released:
+            return MediaKeyStatus::Released;
+        case CDMInstance::KeyStatus::OutputRestricted:
+            return MediaKeyStatus::OutputRestricted;
+        case CDMInstance::KeyStatus::OutputDownscaled:
+            return MediaKeyStatus::OutputDownscaled;
+        case CDMInstance::KeyStatus::StatusPending:
+            return MediaKeyStatus::StatusPending;
+        case CDMInstance::KeyStatus::InternalError:
+            return MediaKeyStatus::InternalError;
+        };
+    };
+
+    m_statuses.clear();
+    m_statuses.reserveCapacity(inputStatuses.size());
+    for (auto&amp; status : inputStatuses)
+        m_statuses.uncheckedAppend({ WTFMove(status.first), toMediaKeyStatus(status.second) });
+
+    // 5. Queue a task to fire a simple event named keystatuseschange at the session.
+    m_eventQueue.enqueueEvent(Event::create(eventNames().keystatuseschangeEvent, false, false));
+
+    // 6. Queue a task to run the Attempt to Resume Playback If Necessary algorithm on each of the media element(s) whose mediaKeys attribute is the MediaKeys object that created the session.
+    // FIXME: Implement.
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MediaKeySession::updateExpiration(double)
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaMediaKeySessionh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.h (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.h        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/Modules/encryptedmedia/MediaKeySession.h        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -38,6 +38,7 @@
</span><span class="cx"> #include &quot;JSDOMPromise.h&quot;
</span><span class="cx"> #include &quot;MediaKeyMessageType.h&quot;
</span><span class="cx"> #include &quot;MediaKeySessionType.h&quot;
</span><ins>+#include &quot;MediaKeyStatus.h&quot;
</ins><span class="cx"> #include &lt;wtf/RefCounted.h&gt;
</span><span class="cx"> #include &lt;wtf/Vector.h&gt;
</span><span class="cx"> #include &lt;wtf/WeakPtr.h&gt;
</span><span class="lines">@@ -69,6 +70,8 @@
</span><span class="cx">     void close(Ref&lt;DeferredPromise&gt;&amp;&amp;);
</span><span class="cx">     void remove(Ref&lt;DeferredPromise&gt;&amp;&amp;);
</span><span class="cx"> 
</span><ins>+    const Vector&lt;std::pair&lt;Ref&lt;SharedBuffer&gt;, MediaKeyStatus&gt;&gt;&amp; statuses() const { return m_statuses; }
+
</ins><span class="cx"> private:
</span><span class="cx">     MediaKeySession(ScriptExecutionContext&amp;, MediaKeySessionType, bool useDistinctiveIdentifier, Ref&lt;CDM&gt;&amp;&amp;, Ref&lt;CDMInstance&gt;&amp;&amp;);
</span><span class="cx">     void enqueueMessage(MediaKeyMessageType, const SharedBuffer&amp;);
</span><span class="lines">@@ -103,6 +106,7 @@
</span><span class="cx">     Vector&lt;Ref&lt;SharedBuffer&gt;&gt; m_recordOfKeyUsage;
</span><span class="cx">     double m_firstDecryptTime { 0 };
</span><span class="cx">     double m_latestDecryptTime { 0 };
</span><ins>+    Vector&lt;std::pair&lt;Ref&lt;SharedBuffer&gt;, MediaKeyStatus&gt;&gt; m_statuses;
</ins><span class="cx">     WeakPtrFactory&lt;MediaKeySession&gt; m_weakPtrFactory;
</span><span class="cx"> };
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaMediaKeyStatusMapcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.cpp (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.cpp        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.cpp        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -31,39 +31,80 @@
</span><span class="cx"> 
</span><span class="cx"> #if ENABLE(ENCRYPTED_MEDIA)
</span><span class="cx"> 
</span><del>-#include &quot;NotImplemented.h&quot;
</del><ins>+#include &quot;JSMediaKeyStatusMap.h&quot;
+#include &quot;MediaKeySession.h&quot;
+#include &quot;SharedBuffer.h&quot;
</ins><span class="cx"> 
</span><span class="cx"> namespace WebCore {
</span><span class="cx"> 
</span><del>-MediaKeyStatusMap::MediaKeyStatusMap() = default;
</del><ins>+MediaKeyStatusMap::MediaKeyStatusMap(const MediaKeySession&amp; session)
+    : m_session(&amp;session)
+{
+}
</ins><span class="cx"> 
</span><span class="cx"> MediaKeyStatusMap::~MediaKeyStatusMap() = default;
</span><span class="cx"> 
</span><ins>+void MediaKeyStatusMap::detachSession()
+{
+    m_session = nullptr;
+}
+
</ins><span class="cx"> unsigned long MediaKeyStatusMap::size()
</span><span class="cx"> {
</span><del>-    notImplemented();
-    return 0;
</del><ins>+    if (!m_session)
+        return 0;
+    return m_session-&gt;statuses().size();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-bool MediaKeyStatusMap::has(const BufferSource&amp;)
</del><ins>+static bool keyIdsMatch(const SharedBuffer&amp; a, const BufferSource&amp; b)
</ins><span class="cx"> {
</span><del>-    notImplemented();
-    return false;
</del><ins>+    auto length = a.size();
+    if (!length || length != b.length())
+        return false;
+    return !std::memcmp(a.data(), b.data(), length);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-JSC::JSValue MediaKeyStatusMap::get(const BufferSource&amp;)
</del><ins>+bool MediaKeyStatusMap::has(const BufferSource&amp; keyId)
</ins><span class="cx"> {
</span><del>-    notImplemented();
-    return JSC::jsUndefined();
</del><ins>+    if (!m_session)
+        return false;
+
+    auto&amp; statuses = m_session-&gt;statuses();
+    return std::any_of(statuses.begin(), statuses.end(),
+        [&amp;keyId] (auto&amp; it) { return keyIdsMatch(it.first, keyId); });
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-MediaKeyStatusMap::Iterator::Iterator(MediaKeyStatusMap&amp;)
</del><ins>+JSC::JSValue MediaKeyStatusMap::get(JSC::ExecState&amp; state, const BufferSource&amp; keyId)
</ins><span class="cx"> {
</span><ins>+    if (!m_session)
+        return JSC::jsUndefined();
+
+    auto&amp; statuses = m_session-&gt;statuses();
+    auto it = std::find_if(statuses.begin(), statuses.end(),
+        [&amp;keyId] (auto&amp; it) { return keyIdsMatch(it.first, keyId); });
+
+    if (it == statuses.end())
+        return JSC::jsUndefined();
+    return convertEnumerationToJS(state, it-&gt;second);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+MediaKeyStatusMap::Iterator::Iterator(MediaKeyStatusMap&amp; map)
+    : m_map(map)
+{
+}
+
</ins><span class="cx"> std::optional&lt;WTF::KeyValuePair&lt;BufferSource::VariantType, MediaKeyStatus&gt;&gt; MediaKeyStatusMap::Iterator::next()
</span><span class="cx"> {
</span><del>-    return std::nullopt;
</del><ins>+    if (!m_map-&gt;m_session)
+        return std::nullopt;
+
+    auto&amp; statuses = m_map-&gt;m_session-&gt;statuses();
+    if (m_index &gt;= statuses.size())
+        return std::nullopt;
+
+    auto&amp; pair = statuses[m_index++];
+    auto buffer = ArrayBuffer::create(pair.first-&gt;data(), pair.first-&gt;size());
+    return WTF::KeyValuePair&lt;BufferSource::VariantType, MediaKeyStatus&gt; { RefPtr&lt;ArrayBuffer&gt;(WTFMove(buffer)), pair.second };
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaMediaKeyStatusMaph"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.h (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.h        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.h        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -33,7 +33,6 @@
</span><span class="cx"> #include &quot;BufferSource.h&quot;
</span><span class="cx"> #include &quot;MediaKeyStatus.h&quot;
</span><span class="cx"> #include &lt;runtime/JSCJSValueInlines.h&gt;
</span><del>-#include &lt;wtf/HashTraits.h&gt;
</del><span class="cx"> #include &lt;wtf/Optional.h&gt;
</span><span class="cx"> #include &lt;wtf/Ref.h&gt;
</span><span class="cx"> #include &lt;wtf/RefCounted.h&gt;
</span><span class="lines">@@ -40,30 +39,41 @@
</span><span class="cx"> 
</span><span class="cx"> namespace WebCore {
</span><span class="cx"> 
</span><ins>+class MediaKeySession;
+class SharedBuffer;
+
</ins><span class="cx"> class MediaKeyStatusMap : public RefCounted&lt;MediaKeyStatusMap&gt; {
</span><span class="cx"> public:
</span><span class="cx">     using Status = MediaKeyStatus;
</span><span class="cx"> 
</span><del>-    static Ref&lt;MediaKeyStatusMap&gt; create()
</del><ins>+    static Ref&lt;MediaKeyStatusMap&gt; create(const MediaKeySession&amp; session)
</ins><span class="cx">     {
</span><del>-        return adoptRef(*new MediaKeyStatusMap);
</del><ins>+        return adoptRef(*new MediaKeyStatusMap(session));
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     virtual ~MediaKeyStatusMap();
</span><span class="cx"> 
</span><ins>+    void detachSession();
+
</ins><span class="cx">     unsigned long size();
</span><span class="cx">     bool has(const BufferSource&amp;);
</span><del>-    JSC::JSValue get(const BufferSource&amp;);
</del><ins>+    JSC::JSValue get(JSC::ExecState&amp;, const BufferSource&amp;);
</ins><span class="cx"> 
</span><span class="cx">     class Iterator {
</span><span class="cx">     public:
</span><span class="cx">         explicit Iterator(MediaKeyStatusMap&amp;);
</span><span class="cx">         std::optional&lt;WTF::KeyValuePair&lt;BufferSource::VariantType, MediaKeyStatus&gt;&gt; next();
</span><ins>+
+    private:
+        Ref&lt;MediaKeyStatusMap&gt; m_map;
+        size_t m_index { 0 };
</ins><span class="cx">     };
</span><span class="cx">     Iterator createIterator() { return Iterator(*this); }
</span><span class="cx"> 
</span><span class="cx"> private:
</span><del>-    MediaKeyStatusMap();
</del><ins>+    MediaKeyStatusMap(const MediaKeySession&amp;);
+
+    const MediaKeySession* m_session;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesencryptedmediaMediaKeyStatusMapidl"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.idl (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.idl        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/Modules/encryptedmedia/MediaKeyStatusMap.idl        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -43,5 +43,5 @@
</span><span class="cx">     iterable&lt;BufferSource, MediaKeyStatus&gt;;
</span><span class="cx">     readonly attribute unsigned long size;
</span><span class="cx">     boolean has(BufferSource keyId);
</span><del>-    any get(BufferSource keyId);
</del><ins>+    [CallWith=ScriptState] any get(BufferSource keyId);
</ins><span class="cx"> };
</span></span></pre></div>
<a id="trunkSourceWebCoretestingMockCDMFactorycpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/testing/MockCDMFactory.cpp (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/testing/MockCDMFactory.cpp        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/testing/MockCDMFactory.cpp        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -81,6 +81,14 @@
</span><span class="cx">     return WTFMove(it-&gt;value);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+std::optional&lt;const Vector&lt;Ref&lt;SharedBuffer&gt;&gt;&amp;&gt; MockCDMFactory::keysForSessionWithID(const String&amp; id) const
+{
+    auto it = m_sessions.find(id);
+    if (it == m_sessions.end())
+        return std::nullopt;
+    return it-&gt;value;
+}
+
</ins><span class="cx"> void MockCDMFactory::setSupportedDataTypes(Vector&lt;String&gt;&amp;&amp; types)
</span><span class="cx"> {
</span><span class="cx">     m_supportedDataTypes.clear();
</span><span class="lines">@@ -276,7 +284,7 @@
</span><span class="cx">     callback(SharedBuffer::create(license.data(), license.length()), sessionID, false, SuccessValue::Succeeded);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MockCDMInstance::updateLicense(LicenseType, const SharedBuffer&amp; response, LicenseUpdateCallback callback)
</del><ins>+void MockCDMInstance::updateLicense(const String&amp; sessionID, LicenseType, const SharedBuffer&amp; response, LicenseUpdateCallback callback)
</ins><span class="cx"> {
</span><span class="cx">     MockCDMFactory* factory = m_cdm ? m_cdm-&gt;factory() : nullptr;
</span><span class="cx">     if (!factory) {
</span><span class="lines">@@ -292,10 +300,23 @@
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    // FIXME: Session closure, key status, expiration and message handling should be implemented
</del><ins>+    std::optional&lt;KeyStatusVector&gt; changedKeys;
+    if (responseVector.contains(String(ASCIILiteral(&quot;keys-changed&quot;)))) {
+        std::optional&lt;const Vector&lt;Ref&lt;SharedBuffer&gt;&gt;&amp;&gt; keys = factory-&gt;keysForSessionWithID(sessionID);
+        if (keys) {
+            KeyStatusVector keyStatusVector;
+            keyStatusVector.reserveInitialCapacity(keys-&gt;size());
+            for (auto&amp; key : *keys)
+                keyStatusVector.uncheckedAppend({ key.copyRef(), KeyStatus::Usable });
+
+            changedKeys = WTFMove(keyStatusVector);
+        }
+    }
+
+    // FIXME: Session closure, expiration and message handling should be implemented
</ins><span class="cx">     // once the relevant algorithms are supported.
</span><span class="cx"> 
</span><del>-    callback(false, std::nullopt, std::nullopt, std::nullopt, SuccessValue::Succeeded);
</del><ins>+    callback(false, WTFMove(changedKeys), std::nullopt, std::nullopt, SuccessValue::Succeeded);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MockCDMInstance::closeSession(const String&amp; sessionID, CloseSessionCallback callback)
</span></span></pre></div>
<a id="trunkSourceWebCoretestingMockCDMFactoryh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/testing/MockCDMFactory.h (212106 => 212107)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/testing/MockCDMFactory.h        2017-02-10 09:48:40 UTC (rev 212106)
+++ trunk/Source/WebCore/testing/MockCDMFactory.h        2017-02-10 10:22:50 UTC (rev 212107)
</span><span class="lines">@@ -72,6 +72,7 @@
</span><span class="cx">     bool hasSessionWithID(const String&amp; id) { return m_sessions.contains(id); }
</span><span class="cx">     void removeSessionWithID(const String&amp; id) { m_sessions.remove(id); }
</span><span class="cx">     void addKeysToSessionWithID(const String&amp; id, Vector&lt;Ref&lt;SharedBuffer&gt;&gt;&amp;&amp;);
</span><ins>+    std::optional&lt;const Vector&lt;Ref&lt;SharedBuffer&gt;&gt;&amp;&gt; keysForSessionWithID(const String&amp; id) const;
</ins><span class="cx">     Vector&lt;Ref&lt;SharedBuffer&gt;&gt; removeKeysFromSessionWithID(const String&amp; id);
</span><span class="cx"> 
</span><span class="cx"> private:
</span><span class="lines">@@ -130,7 +131,7 @@
</span><span class="cx">     SuccessValue setPersistentStateAllowed(bool) final;
</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><del>-    void updateLicense(LicenseType, const SharedBuffer&amp;, LicenseUpdateCallback) final;
</del><ins>+    void updateLicense(const String&amp;, LicenseType, const SharedBuffer&amp;, LicenseUpdateCallback) 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"> 
</span></span></pre>
</div>
</div>

</body>
</html>