<!DOCTYPE html>
<html>
<head>
- <title>MediaKeys</title>
- <script src=../video-test.js></script>
+ <title>Test EME syntax</title>
+ <script src="encrypted-media-utils.js"></script>
+ <script src="../../resources/testharness.js"></script>
+ <script src="../../resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
<script>
- function stringToUint8Array(str)
+ // This function checks that calling |testCase.func| returns a
+ // rejected Promise with the error.name equal to
+ // |testCase.exception|.
+ function test_exception(testCase /*...*/)
{
- var arr=[];
- for(var i=0,j=str.length;i<j;++i)
- arr[i]=str.charCodeAt(i);
- return new Uint8Array(arr);
+ var func = testCase.func;
+ var exception = testCase.exception;
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ // Currently blink throws for TypeErrors rather than returning
+ // a rejected promise (http://crbug.com/359386).
+ // FIXME: Remove try/catch once they become failed promises.
+ try {
+ return func.apply(null, args).then(
+ function(result)
+ {
+ assert_unreached(format_value(func));
+ },
+ function(error)
+ {
+ assert_equals(error.name, exception, format_value(func));
+ assert_not_equals(error.message, "", format_value(func));
+ }
+ );
+ } catch (e) {
+ // Only allow 'TypeError' exceptions to be thrown.
+ // Everything else should be a failed promise.
+ assert_equals('TypeError', exception, format_value(func));
+ assert_equals(e.name, exception, format_value(func));
+ }
}
- var mediaKeys;
- var mediaKeySession;
- var initData = stringToUint8Array('mock');
-
- function runTest()
- {
- consoleWrite("Test MediaKeys.");
- testExpected('typeof window.MediaKeys', 'function');
- testDOMException('new MediaKeys("")', "DOMException.INVALID_ACCESS_ERR");
- testDOMException('new MediaKeys("unsupported")', "DOMException.NOT_SUPPORTED_ERR");
- run('mediaKeys = new MediaKeys("org.w3.clearkey")');
- testExpected('typeof mediaKeys', 'object');
- testExpected('mediaKeys.keySystem', 'org.w3.clearkey');
- testExpected('typeof mediaKeys.createSession', 'function');
- testDOMException('mediaKeys.createSession("", new Uint8Array(1))', "DOMException.INVALID_ACCESS_ERR");
- testDOMException('mediaKeys.createSession("unsupported/type")', "DOMException.NOT_SUPPORTED_ERR");
- consoleWrite("");
-
- consoleWrite("Test MediaKeySession.");
- run('mediaKeySession = mediaKeys.createSession("video/webm", initData)');
- testExpected('typeof mediaKeySession', 'object');
- testExpected('typeof mediaKeySession.addEventListener', 'function');
- testExpected('typeof mediaKeySession.update', 'function');
- testExpected('mediaKeySession.error', null);
- testExpected('mediaKeySession.keySystem', 'org.w3.clearkey');
- testExpected('mediaKeySession.sessionId', null, '!=');
- testExpected('mediaKeySession.onwebkitkeyadded', null);
- testExpected('mediaKeySession.onwebkitkeyerror', null);
- testExpected('mediaKeySession.onwebkitkeymessage', null);
- testDOMException('mediaKeySession.update(null)', "DOMException.INVALID_ACCESS_ERR");
- endTest();
+ var kCreateMediaKeysExceptionsTestCases = [
+ // Too few parameters.
+ {
+ exception: 'TypeError',
+ func: function() { return MediaKeys.create(); }
+ },
+ // Invalid parameters.
+ {
+ exception: 'InvalidAccessError',
+ func: function() { return MediaKeys.create(''); }
+ },
+ // Invalid key systems.
+ {
+ exception: 'NotSupportedError',
+ func: function() { return MediaKeys.create(null); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function() { return MediaKeys.create(undefined); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function() { return MediaKeys.create(1); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function() { return MediaKeys.create(new Uint8Array(0)); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function() { return MediaKeys.create('unsupported'); }
+ },
+ // Non-ASCII names.
+ {
+ exception: 'NotSupportedError',
+ func: function() { return MediaKeys.create('org.w3\u263A.clearkey'); }
+ }
+ ];
+
+ async_test(function(test)
+ {
+ // Since promises catch any exception and convert it into a
+ // rejected Promise, there is no current way to have the W3C
+ // test framework report a failed test. For now, simply force
+ // a timeout to indicate failure.
+ // FIXME: Once W3C test framework handles Promises, fix this.
+
+ var createPromises = kCreateMediaKeysExceptionsTestCases.map(function(testCase) {
+ return test_exception(testCase);
+ });
+
+ Promise.all(createPromises).then(function(result) {
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'create() tests failed');
+ });
+ }, 'Test MediaKeys create() exceptions.');
+
+ async_test(function(test)
+ {
+ assert_equals(typeof window.MediaKeys, 'function');
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ assert_not_equals(mediaKeys, null);
+
+ // Test creation of a second MediaKeys.
+ // The extra parameter is ignored.
+ return MediaKeys.create('org.w3.clearkey', 'extra');
+ }).then(function(mediaKeys) {
+ assert_not_equals(mediaKeys, null);
+ assert_equals(typeof mediaKeys, 'object');
+ assert_equals(mediaKeys.keySystem, 'org.w3.clearkey');
+ assert_equals(typeof mediaKeys.createSession, 'function');
+ assert_equals(typeof mediaKeys.addEventListener, 'undefined');
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'create() tests failed');
+ });
+ }, 'Test MediaKeys create().');
+
+ var kCreateSessionExceptionsTestCases = [
+ // Tests in this set use a shortened parameter name due to
+ // format_value() only returning the first 60 characters as the
+ // result. With a longer name the first 60 characters is not
+ // enough to determine which test failed.
+
+ // Invalid parameters.
+ {
+ exception: 'TypeError',
+ func: function(mk) { return mk.createSession(); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk) { return mk.createSession(''); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk) { return mk.createSession(null); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk) { return mk.createSession(undefined); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk) { return mk.createSession(1); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk) { return mk.createSession(new Uint8Array(0)); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk) { return mk.createSession('TEMPORARY'); }
+ }
+ ];
+
+ async_test(function(test)
+ {
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ var sessionPromises = kCreateSessionExceptionsTestCases.map(function(testCase) {
+ return test_exception(testCase, mediaKeys);
+ });
+
+ assert_not_equals(sessionPromises.length, 0);
+ return Promise.all(sessionPromises);
+ }).then(function(result) {
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'createSession() tests failed');
+ });
+ }, 'Test MediaKeys createSession() exceptions.');
+
+ var kGenerateRequestExceptionsTestCases = [
+ // Tests in this set use a shortened parameter name due to
+ // format_value() only returning the first 60 characters as the
+ // result. With a longer name the first 60 characters is not
+ // enough to determine which test failed. Even with the
+ // shortened name, the error message for the last couple of
+ // tests is the same.
+ // Too few parameters.
+ {
+ exception: 'TypeError',
+ func: function(mk1) { return mk1.createSession().generateRequest(); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk2) { return mk2.createSession().generateRequest(''); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk3) { return mk3.createSession().generateRequest(null); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk4) { return mk4.createSession().generateRequest(undefined); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk5) { return mk5.createSession().generateRequest(1); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk6) { return mk6.createSession().generateRequest(new Uint8Array(0)); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk7, _, initData) { return mk7.createSession().generateRequest(initData); }
+ },
+ // Invalid parameters.
+ {
+ exception: 'InvalidAccessError',
+ func: function(mk8, _, initData) { return mk8.createSession().generateRequest('', initData); }
+ },
+ // Not supported initDataTypes.
+ {
+ exception: 'NotSupportedError',
+ func: function(mk9, _, initData) { return mk9.createSession().generateRequest(null, initData); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function(mk10, _, initData) { return mk10.createSession().generateRequest(undefined, initData); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function(mk11, _, initData) { return mk11.createSession().generateRequest(1, initData); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function(mk12, _, initData) { return mk12.createSession().generateRequest(new Uint8Array(0), initData); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function(mk13, _, initData) { return mk13.createSession().generateRequest('unsupported', initData); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function(mk14, _, initData) { return mk14.createSession().generateRequest('video/foo', initData); }
+ },
+ {
+ exception: 'NotSupportedError',
+ func: function(mk15, _, initData) { return mk15.createSession().generateRequest('text/webm', initData); }
+ }
+ // FIXME: Enable when switching to initDataType from MIME type.
+ // http://crbug.com/385874.
+ // {
+ // exception: 'NotSupportedError',
+ // func: function(mk16, _, initData) { return mk16.createSession('video/webm', initData); }
+ // }
+ ];
+
+ var kTypeSpecificGenerateRequestExceptionsTestCases = [
+ // Tests in this set use a shortened parameter name due to
+ // format_value() only returning the first 60 characters as the
+ // result. With a longer name the first 60 characters is not
+ // enough to determine which test failed. Even with the
+ // shortened name, the error message for the last couple of
+ // tests is the same.
+
+ // Too few parameters.
+ {
+ exception: 'TypeError',
+ func: function(mk1, type) { return mk1.createSession().generateRequest(type); }
+ },
+ // Invalid parameters.
+ {
+ exception: 'TypeError',
+ func: function(mk2, type) { return mk2.createSession().generateRequest(type, ''); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk3, type) { return mk3.createSession().generateRequest(type, null); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk4, type) { return mk4.createSession().generateRequest(type, undefined); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(mk5, type) { return mk5.createSession().generateRequest(type, 1); }
+ },
+ {
+ exception: 'InvalidAccessError',
+ func: function(mk6, type) { return mk6.createSession().generateRequest(type, new Uint8Array(0)); }
+ }
+ ];
+
+ async_test(function(test)
+ {
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ // FIXME: Remove "video/" from the calls to isTypeSupported() once it is updated.
+ // http://crbug.com/405731.
+ var initData = stringToUint8Array('init data');
+ var sessionPromises = kGenerateRequestExceptionsTestCases.map(function(testCase) {
+ return test_exception(testCase, mediaKeys, '', initData);
+ });
+
+ // Test that WebM sessions generate the expected error, if
+ // supported.
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
+ var WebmSessionPromises = kTypeSpecificGenerateRequestExceptionsTestCases.map(function(testCase) {
+ return test_exception(testCase, mediaKeys, 'webm', getInitData('webm'));
+ });
+ sessionPromises = sessionPromises.concat(WebmSessionPromises);
+ }
+
+ // Repeat for MP4, if supported.
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
+ var mp4SessionPromises = kTypeSpecificGenerateRequestExceptionsTestCases.map(function(testCase) {
+ return test_exception(testCase, mediaKeys, 'cenc', getInitData('cenc'));
+ });
+ sessionPromises = sessionPromises.concat(mp4SessionPromises);
+ }
+
+ assert_not_equals(sessionPromises.length, 0);
+ return Promise.all(sessionPromises);
+ }).then(function(result) {
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'generateRequest() tests failed');
+ });
+ }, 'Test MediaKeys generateRequest() exceptions.');
+
+ // All calls to |func| in this group are supposed to succeed.
+ // However, the spec notes that some things are optional for
+ // Clear Key. In particular, support for persistent sessions
+ // is optional. Since some implementations won't support some
+ // features, a NotSupportedError is treated as a success
+ // if |isNotSupportedAllowed| is true.
+ var kCreateSessionTestCases = [
+ // Use the default sessionType.
+ {
+ func: function(mk) { return mk.createSession(); },
+ isNotSupportedAllowed: false
+ },
+ // Try variations of sessionType.
+ {
+ func: function(mk) { return mk.createSession('temporary'); },
+ isNotSupportedAllowed: false
+ },
+ {
+ func: function(mk) { return mk.createSession(undefined); },
+ isNotSupportedAllowed: false
+ },
+ {
+ // Since this is optional, some Clear Key implementations
+ // will succeed, others will return a "NotSupportedError".
+ // Both are allowed results.
+ func: function(mk) { return mk.createSession('persistent'); },
+ isNotSupportedAllowed: true
+ },
+ // Try additional parameter, which should be ignored.
+ {
+ func: function(mk) { return mk.createSession('temporary', 'extra'); },
+ isNotSupportedAllowed: false
+ }
+ ];
+
+ // This function checks that calling |testCase.func| creates a
+ // MediaKeySession object with some default values. It also
+ // allows for an NotSupportedError to be generated and treated as a
+ // success, if allowed. See comment above kCreateSessionTestCases.
+ function test_createSession(testCase, mediaKeys)
+ {
+ var mediaKeySession;
+ try {
+ mediaKeySession = testCase.func.call(null, mediaKeys);
+ } catch (e) {
+ assert_true(testCase.isNotSupportedAllowed);
+ return;
+ }
+
+ // FIXME: Update this set of tests when done
+ // implementing the latest spec.
+ assert_equals(typeof mediaKeySession, 'object');
+ assert_equals(typeof mediaKeySession.addEventListener, 'function');
+ assert_equals(typeof mediaKeySession.generateRequest, 'function');
+ assert_equals(typeof mediaKeySession.update, 'function');
+ assert_equals(typeof mediaKeySession.release, 'function');
+ assert_equals(mediaKeySession.error, null);
+ assert_equals(mediaKeySession.sessionId, '');
+ assert_equals(typeof mediaKeySession.sessionId, 'string');
+ assert_equals(typeof mediaKeySession.onopen, 'undefined');
+ assert_equals(typeof mediaKeySession.onmessage, 'undefined');
+ assert_equals(typeof mediaKeySession.onclose, 'undefined');
+ assert_equals(typeof mediaKeySession.onerror, 'undefined');
+ }
+
+ async_test(function(test)
+ {
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ kCreateSessionTestCases.map(function(testCase) {
+ test_createSession(testCase, mediaKeys);
+ });
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'createSession() tests failed');
+ });
+ }, 'Test MediaKeys createSession().');
+
+ // This function checks that calling generateRequest() works for
+ // various sessions. |testCase.func| creates a MediaKeySession
+ // object, and then generateRequest() is called on that object. It
+ // allows for an NotSupportedError to be generated and treated as a
+ // success, if allowed. See comment above kCreateSessionTestCases.
+ function test_generateRequest(testCase, mediaKeys, type, initData)
+ {
+ var mediaKeySession;
+ try {
+ mediaKeySession = testCase.func.call(null, mediaKeys);
+ } catch (e) {
+ assert_true(testCase.isNotSupportedAllowed);
+ return new Promise(new Promise(function() {}));
+ }
+ return mediaKeySession.generateRequest(type, initData);
}
+
+ async_test(function(test)
+ {
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ var sessionPromises = [];
+
+ // Test that WebM sessions generate the expected error, if
+ // supported.
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
+ var WebmSessionPromises = kCreateSessionTestCases.map(function(testCase) {
+ return test_generateRequest(testCase, mediaKeys, 'webm', getInitData('webm'));
+ });
+ sessionPromises = sessionPromises.concat(WebmSessionPromises);
+ }
+
+ // Repeat for MP4, if supported.
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
+ var mp4SessionPromises = kCreateSessionTestCases.map(function(testCase) {
+ return test_generateRequest(testCase, mediaKeys, 'cenc', getInitData('cenc'));
+ });
+ sessionPromises = sessionPromises.concat(mp4SessionPromises);
+ }
+
+ assert_not_equals(sessionPromises.length, 0);
+ return Promise.all(sessionPromises);
+ }).then(function(result) {
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'generateRequest() tests failed');
+ });
+ }, 'Test MediaKeys generateRequest().');
+
+ var kUpdateSessionExceptionsTestCases = [
+ // Tests in this set use a shortened parameter name due to
+ // format_value() only returning the first 60 characters as the
+ // result. With a longer name (mediaKeySession) the first 60
+ // characters is not enough to determine which test failed.
+
+ // Too few parameters.
+ {
+ exception: 'TypeError',
+ func: function(s) { return s.update(); }
+ },
+ // Invalid parameters.
+ {
+ exception: 'TypeError',
+ func: function(s) { return s.update(''); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(s) { return s.update(null); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(s) { return s.update(undefined); }
+ },
+ {
+ exception: 'TypeError',
+ func: function(s) { return s.update(1); }
+ },
+ {
+ exception: 'InvalidAccessError',
+ func: function(s) { return s.update(new Uint8Array(0)); }
+ }
+ ];
+
+ async_test(function(test)
+ {
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ var promises = [];
+
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
+ var WebmSessionPromises = kUpdateSessionExceptionsTestCases.map(function(testCase) {
+ var mediaKeySession = mediaKeys.createSession();
+ return mediaKeySession.generateRequest('webm', getInitData('webm')).then(function(result) {
+ return test_exception(testCase, mediaKeySession);
+ });
+ });
+ promises = promises.concat(WebmSessionPromises);
+ }
+
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
+ var mp4SessionPromises = kUpdateSessionExceptionsTestCases.map(function(testCase) {
+ var mediaKeySession = mediaKeys.createSession();
+ return mediaKeySession.generateRequest('cenc', getInitData('cenc')).then(function(result) {
+ return test_exception(testCase, mediaKeySession);
+ });
+ });
+ promises = promises.concat(mp4SessionPromises);
+ }
+
+ assert_not_equals(promises.length, 0);
+ return Promise.all(promises);
+ }).then(function(result) {
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'update() tests failed');
+ });
+ }, 'Test MediaKeySession update() exceptions.');
+
+ function create_update_test(mediaKeys, type, initData)
+ {
+ var mediaKeySession = mediaKeys.createSession();
+ var promise = mediaKeySession.generateRequest(type, initData).then(function(result) {
+ var validLicense = stringToUint8Array(createJWKSet(createJWK(stringToUint8Array('123'), stringToUint8Array('1234567890abcdef'))));
+ return mediaKeySession.update(validLicense);
+ }).then(function(result) {
+ // Call update() with a different license and an extra
+ // parameter. The extra parameter is ignored.
+ var validLicense = stringToUint8Array(createJWKSet(createJWK(stringToUint8Array('4567890'), stringToUint8Array('01234567890abcde'))));
+ return mediaKeySession.update(validLicense, 'extra');
+ });
+ return promise;
+ }
+
+ async_test(function(test)
+ {
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ var promises = [];
+
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
+ promises.push(create_update_test(mediaKeys, 'webm', getInitData('webm')));
+ }
+
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
+ promises.push(create_update_test(mediaKeys, 'cenc', getInitData('cenc')));
+ }
+
+ assert_not_equals(promises.length, 0);
+ return Promise.all(promises);
+ }).then(function(result) {
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'update() tests failed');
+ });
+ }, 'Test MediaKeySession update().');
+
+ function create_release_test(mediaKeys, type, initData)
+ {
+ var mediaKeySession = mediaKeys.createSession();
+ var promise = mediaKeySession.generateRequest(type, initData).then(function(result) {
+ return mediaKeySession.release();
+ // FIXME: Uncomment once the code supports multiple release() calls.
+// }).then(function(result) {
+// // Call release() again with an extra parameter. The extra
+// // parameter is ignored.
+// return mediaKeySession.release('extra');
+ });
+ return promise;
+ }
+
+ async_test(function(test)
+ {
+ MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
+ var promises = [];
+
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
+ promises.push(create_release_test(mediaKeys, 'webm', getInitData('webm')));
+ }
+
+ if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
+ promises.push(create_release_test(mediaKeys, 'cenc', getInitData('cenc')));
+ }
+
+ assert_not_equals(promises.length, 0);
+ return Promise.all(promises);
+ }).then(function(result) {
+ test.done();
+ }).catch(function(error) {
+ forceTestFailureFromPromise(test, error, 'release() tests failed');
+ });
+ }, 'Test MediaKeySession release().');
+
+ // FIXME: Add syntax checks for MediaKeys.IsTypeSupported().
+ // FIXME: Add syntax checks for MediaKeyError and MediaKeySession events.
+ // FIXME: Add HTMLMediaElement syntax checks, e.g. setMediaKeys, mediakeys, onneedkey.
</script>
- </head>
- <body onload="runTest()">
- <p>This tests the basic API of MediaKeys and MediaKeySession.</p>
</body>
</html>