3 function consoleWrite(text)
5 if (!consoleDiv && document.body) {
6 consoleDiv = document.createElement('div');
7 document.body.appendChild(consoleDiv);
9 var span = document.createElement('span');
10 span.appendChild(document.createTextNode(text));
11 span.appendChild(document.createElement('br'));
12 consoleDiv.appendChild(span);
15 // Returns a promise that is fulfilled with true if |initDataType| is supported,
17 function isInitDataTypeSupported(initDataType)
19 return navigator.requestMediaKeySystemAccess(
20 "org.w3.clearkey", [{ initDataTypes : [initDataType] }])
21 .then(function() { return(true); }, function() { return(false); });
24 // Returns a promise that is fulfilled with an initDataType that is supported,
25 // rejected if none are supported.
26 function getSupportedInitDataType()
28 var configuration = [{ initDataTypes : [ 'webm', 'cenc', 'keyids' ] }];
29 return navigator.requestMediaKeySystemAccess('org.w3.clearkey', configuration)
30 .then(function(access) {
31 var initDataTypes = access.getConfiguration().initDataTypes;
32 assert_greater_than(initDataTypes.length, 0);
33 return Promise.resolve(initDataTypes[0]);
35 return Promise.reject('No supported initDataType.');
39 function getInitData(initDataType)
41 if (initDataType == 'webm') {
42 return new Uint8Array([
43 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
44 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
48 if (initDataType == 'cenc') {
49 return new Uint8Array([
50 0x00, 0x00, 0x00, 0x00, // size = 0
51 0x70, 0x73, 0x73, 0x68, // 'pssh'
53 0x00, 0x00, 0x00, // flags
54 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID
55 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
56 0x00, 0x00, 0x00, 0x01, // key count
57 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // key
58 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
59 0x00, 0x00, 0x00, 0x00 // datasize
63 if (initDataType == 'keyids') {
64 var keyId = new Uint8Array([
65 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
66 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
68 return stringToUint8Array(createKeyIDs(keyId));
71 throw 'initDataType ' + initDataType + ' not supported.';
74 function waitForEventAndRunStep(eventName, element, func, stepTest)
76 var eventCallback = function(event) {
81 eventCallback = stepTest.step_func(eventCallback);
83 element.addEventListener(eventName, eventCallback, true);
86 // Copied from LayoutTests/resources/js-test.js.
87 // See it for details of why this is necessary.
88 function asyncGC(callback)
90 GCController.collectAll();
91 setTimeout(callback, 0);
94 function createGCPromise()
96 // Run gc() as a promise.
98 function(resolve, reject) {
103 function delayToAllowEventProcessingPromise()
106 function(resolve, reject) {
107 setTimeout(resolve, 0);
111 function stringToUint8Array(str)
113 var result = new Uint8Array(str.length);
114 for(var i = 0; i < str.length; i++) {
115 result[i] = str.charCodeAt(i);
120 function arrayBufferAsString(buffer)
122 // MediaKeySession.keyStatuses iterators return an ArrayBuffer,
123 // so convert it into a printable string.
124 return String.fromCharCode.apply(null, new Uint8Array(buffer));
127 function dumpKeyStatuses(keyStatuses)
129 consoleWrite("for (var entry of keyStatuses)");
130 for (var entry of keyStatuses) {
131 consoleWrite(arrayBufferAsString(entry[0]) + ", " + entry[1]);
133 consoleWrite("for (var key of keyStatuses.keys())");
134 for (var key of keyStatuses.keys()) {
135 consoleWrite(arrayBufferAsString(key));
137 consoleWrite("for (var value of keyStatuses.values())");
138 for (var value of keyStatuses.values()) {
141 consoleWrite("for (var entry of keyStatuses.entries())");
142 for (var entry of keyStatuses.entries()) {
143 consoleWrite(arrayBufferAsString(entry[0]) + ", " + entry[1]);
145 consoleWrite("keyStatuses.forEach()");
146 keyStatuses.forEach(function(value, key, map) {
147 consoleWrite(arrayBufferAsString(key) + ", " + value);
151 // Verify that |keyStatuses| contains just the keys in |keys.expected|
152 // and none of the keys in |keys.unexpected|. All keys should have status
153 // 'usable'. Example call: verifyKeyStatuses(mediaKeySession.keyStatuses,
154 // { expected: [key1], unexpected: [key2] });
155 function verifyKeyStatuses(keyStatuses, keys)
157 var expected = keys.expected || [];
158 var unexpected = keys.unexpected || [];
160 // |keyStatuses| should have same size as number of |keys.expected|.
161 assert_equals(keyStatuses.size, expected.length);
163 // All |keys.expected| should be found.
164 expected.map(function(key) {
165 assert_true(keyStatuses.has(key));
166 assert_equals(keyStatuses.get(key), 'usable');
169 // All |keys.unexpected| should not be found.
170 unexpected.map(function(key) {
171 assert_false(keyStatuses.has(key));
172 assert_equals(keyStatuses.get(key), undefined);
176 // Encodes |data| into base64url string. There is no '=' padding, and the
177 // characters '-' and '_' must be used instead of '+' and '/', respectively.
178 function base64urlEncode(data)
180 var result = btoa(String.fromCharCode.apply(null, data));
181 return result.replace(/=+$/g, '').replace(/\+/g, "-").replace(/\//g, "_");
184 // Decode |encoded| using base64url decoding.
185 function base64urlDecode(encoded)
187 return atob(encoded.replace(/\-/g, "+").replace(/\_/g, "/"));
190 // For Clear Key, the License Format is a JSON Web Key (JWK) Set, which contains
191 // a set of cryptographic keys represented by JSON. These helper functions help
192 // wrap raw keys into a JWK set.
194 // https://w3c.github.io/encrypted-media/#clear-key-license-format
195 // http://tools.ietf.org/html/draft-ietf-jose-json-web-key
197 // Creates a JWK from raw key ID and key.
198 // |keyId| and |key| are expected to be ArrayBufferViews, not base64-encoded.
199 function createJWK(keyId, key)
201 var jwk = '{"kty":"oct","alg":"A128KW","kid":"';
202 jwk += base64urlEncode(keyId);
204 jwk += base64urlEncode(key);
209 // Creates a JWK Set from multiple JWKs.
210 function createJWKSet()
212 var jwkSet = '{"keys":[';
213 for (var i = 0; i < arguments.length; i++) {
216 jwkSet += arguments[i];
222 // Clear Key can also support Key IDs Initialization Data.
223 // ref: http://w3c.github.io/encrypted-media/keyids-format.html
224 // Each parameter is expected to be a key id in an Uint8Array.
225 function createKeyIDs()
227 var keyIds = '{"kids":["';
228 for (var i = 0; i < arguments.length; i++) {
231 keyIds += base64urlEncode(arguments[i]);
237 function forceTestFailureFromPromise(test, error, message)
239 // Promises convert exceptions into rejected Promises. Since there is
240 // currently no way to report a failed test in the test harness, errors
241 // are reported using force_timeout().
243 consoleWrite(message + ': ' + error.message);
247 test.force_timeout();
251 function extractSingleKeyIdFromMessage(message)
253 var json = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(message)));
254 // Decode the first element of 'kids'.
255 assert_equals(1, json.kids.length);
256 var decoded_key = base64urlDecode(json.kids[0]);
257 // Convert to an Uint8Array and return it.
258 return stringToUint8Array(decoded_key);
261 // Create a MediaKeys object for Clear Key with 1 session. KeyId and key
262 // required for the video are already known and provided. Returns a promise
263 // that resolves to the MediaKeys object created.
264 function createMediaKeys(keyId, key)
268 var request = stringToUint8Array(createKeyIDs(keyId));
269 var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, key)));
271 return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
272 return access.createMediaKeys();
273 }).then(function(result) {
275 mediaKeySession = mediaKeys.createSession();
276 return mediaKeySession.generateRequest('keyids', request);
278 return mediaKeySession.update(jwkSet);
280 return Promise.resolve(mediaKeys);
284 // Play the specified |content| on |video|. Returns a promise that is resolved
285 // after the video plays for |duration| seconds.
286 function playVideoAndWaitForTimeupdate(video, content, duration)
290 return new Promise(function(resolve) {
291 video.addEventListener('timeupdate', function listener(event) {
292 if (event.target.currentTime < duration)
294 video.removeEventListener(listener);