chromium/third_party/blink/web_tests/media/encrypted-media/encrypted-media-syntax.html

<!DOCTYPE html>
<html>
    <head>
        <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>
        <script>
            // 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.

            // This function checks that calling |testCase.func| returns a
            // rejected Promise with the error.name equal to
            // |testCase.exception|.
            function test_exception(testCase /*...*/)
            {
                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 kRequestMediaKeySystemAccessExceptionsTestCases = [
                // Too few parameters.
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess(); }
                },
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess('org.w3.clearkey'); }
                },
                // Invalid key systems. Note that JavaScript converts all these
                // values into strings by calling toString(), so they fail due
                // to the key system not being supported, not due to the type.
                {
                    exception: 'NotSupportedError',
                    func: function() { return navigator.requestMediaKeySystemAccess(null, [{}]); }
                },
                {
                    exception: 'NotSupportedError',
                    func: function() { return navigator.requestMediaKeySystemAccess(undefined, [{}]); }
                },
                {
                    exception: 'NotSupportedError',
                    func: function() { return navigator.requestMediaKeySystemAccess(1, [{}]); }
                },
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess(new Uint8Array(0), [{}]); }
                },
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess('', [{}]); }
                },
                {
                    exception: 'NotSupportedError',
                    func: function() { return navigator.requestMediaKeySystemAccess('unsupported', [{}]); }
                },
                // Non-ASCII names.
                {
                    exception: 'NotSupportedError',
                    func: function() { return navigator.requestMediaKeySystemAccess('org.w3\u263A.clearkey', [{}]); }
                },
                // Empty sequence of MediaKeySystemConfiguration.
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess('org.w3.clearkey', []); }
                },
                // Invalid sequences of MediaKeySystemConfigurations.
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess('org.w3.clearkey', {}); }
                },
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess('org.w3.clearkey', "invalid"); }
                },
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}, 6]); }
                },
                {
                    exception: 'TypeError',
                    func: function() { return navigator.requestMediaKeySystemAccess('org.w3.clearkey', ["invalid", "upsupported"]); }
                }
            ];

            async_test(function(test)
            {
                var createPromises = kRequestMediaKeySystemAccessExceptionsTestCases.map(function(testCase) {
                    return test_exception(testCase);
                });

                Promise.all(createPromises).then(function(result) {
                    test.done();
                }).catch(function(error) {
                    forceTestFailureFromPromise(test, error, 'requestMediaKeySystemAccess() tests failed');
                });
            }, 'Test Navigator.requestMediaKeySystemAccess() exceptions.');

            async_test(function(test)
            {
                assert_equals(typeof navigator.requestMediaKeySystemAccess, 'function');
                navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(access) {
                    assert_not_equals(access, null);
                    assert_equals(typeof access, 'object');
                    assert_equals(access.keySystem, 'org.w3.clearkey');
                    assert_equals(typeof access.getConfiguration, 'function');
                    assert_equals(typeof access.createMediaKeys, 'function');
                    test.done();
                }).catch(function(error) {
                    forceTestFailureFromPromise(test, error, 'requestMediaKeySystemAccess() tests failed');
                });
            }, 'Test Navigator.requestMediaKeySystemAccess().');

            async_test(function(test)
            {
                var access;

                navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(result) {
                    access = result;
                    assert_equals(access.keySystem, 'org.w3.clearkey');
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    assert_not_equals(mediaKeys, null);
                    assert_equals(typeof mediaKeys, 'object');
                    assert_equals(typeof mediaKeys.createSession, 'function');
                    assert_equals(typeof mediaKeys.setServerCertificate, 'function');

                    // Test creation of a second MediaKeys.
                    // The extra parameter is ignored.
                    return access.createMediaKeys('extra');
                }).then(function(mediaKeys) {
                    assert_not_equals(mediaKeys, null);
                    assert_equals(typeof mediaKeys, 'object');
                    assert_equals(typeof mediaKeys.createSession, 'function');
                    assert_equals(typeof mediaKeys.setServerCertificate, 'function');
                    test.done();
                }).catch(function(error) {
                    forceTestFailureFromPromise(test, error, 'create() tests failed');
                });
            }, 'Test MediaKeySystemAccess createMediaKeys().');

            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'); }
                }
            ];

            // This function checks that calling createSession() with an
            // unsupported session type doesn't create a MediaKeySession object.
            // Since requestMediaKeySystemAccess() is called without specifying
            // persistent sessions, only temporary sessions will be allowed.
            function test_unsupported_sessionType(mediaKeys)
            {
                var mediaKeySession = 'test';

                try {
                    mediaKeySession = mediaKeys.createSession('persistent-license');
                    assert_unreached('Session should not be created.');
                } catch (error) {
                    assert_equals(error.name, 'NotSupportedError');
                    assert_not_equals(error.message, "");

                    // Since createSession() failed, |mediaKeySession| is not
                    // touched.
                    assert_equals(mediaKeySession, 'test');
                }
            }

            async_test(function(test)
            {
                navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var sessionPromises = kCreateSessionExceptionsTestCases.map(function(testCase) {
                        return test_exception(testCase, mediaKeys);
                    });
                    sessionPromises = sessionPromises.concat(test_unsupported_sessionType(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: 'TypeError',
                    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: 'TypeError',
                    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/webm', initData); }
                },
                {
                    exception: 'NotSupportedError',
                    func: function(mk15, _, initData) { return mk15.createSession().generateRequest('video/mp4', initData); }
                },
                {
                    exception: 'NotSupportedError',
                    func: function(mk16, _, initData) { return mk16.createSession().generateRequest('video/cenc', initData); }
                },
                {
                    exception: 'NotSupportedError',
                    func: function(mk17, _, initData) { return mk17.createSession().generateRequest('web\u263A', 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: 'TypeError',
                    func: function(mk6, type) { return mk6.createSession().generateRequest(type, new Uint8Array(0)); }
                }
            ];

            async_test(function(test)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    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 (isWebmSupported) {
                        var WebmSessionPromises = kTypeSpecificGenerateRequestExceptionsTestCases.map(function(testCase) {
                            return test_exception(testCase, mediaKeys, 'webm', getInitData('webm'));
                        });
                        sessionPromises = sessionPromises.concat(WebmSessionPromises);
                    }

                    // Repeat for MP4, if supported.
                    if (isCencSupported) {
                        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.');

            var kLoadExceptionsTestCases = [
                // Too few parameters.
                {
                    exception: 'TypeError',
                    func: function(mk1) { return mk1.createSession('temporary').load(); }
                },
                // 'temporary' sessions are never allowed, so always return
                // 'TypeError'.
                {
                    exception: 'TypeError',
                    func: function(mk3) { return mk3.createSession('temporary').load(''); }
                },
                {
                    exception: 'TypeError',
                    func: function(mk4) { return mk4.createSession('temporary').load(1); }
                },
                {
                    exception: 'TypeError',
                    func: function(mk5) { return mk5.createSession('temporary').load('!@#$%^&*()'); }
                },
                {
                    exception: 'TypeError',
                    func: function(mk6) { return mk6.createSession('temporary').load('1234'); }
                }
            ];

            async_test(function(test)
            {
                navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var initData = stringToUint8Array('init data');
                    var sessionPromises = kLoadExceptionsTestCases.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, 'load() tests failed');
                });
            }, 'Test MediaKeys load() 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-license'); },
                    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;
                }

                assert_equals(typeof mediaKeySession, 'object');
                assert_equals(typeof mediaKeySession.addEventListener, 'function');
                assert_equals(typeof mediaKeySession.sessionId, 'string');
                assert_equals(typeof mediaKeySession.expiration, 'number');
                assert_equals(typeof mediaKeySession.closed, 'object');
                assert_equals(typeof mediaKeySession.keyStatuses, 'object');
                assert_equals(typeof mediaKeySession.onkeystatuseschange, 'object');
                assert_equals(typeof mediaKeySession.onmessage, 'object');
                assert_equals(typeof mediaKeySession.generateRequest, 'function');
                assert_equals(typeof mediaKeySession.load, 'function');
                assert_equals(typeof mediaKeySession.update, 'function');
                assert_equals(typeof mediaKeySession.close, 'function');
                assert_equals(typeof mediaKeySession.remove, 'function');
                assert_equals(mediaKeySession.sessionId, '');
            }

            async_test(function(test)
            {
                navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(access) {
                    return access.createMediaKeys();
                }).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)
            {
                try {
                    var mediaKeySession = testCase.func.call(null, mediaKeys);
                    return mediaKeySession.generateRequest(type, initData);
                } catch (e) {
                    assert_true(testCase.isNotSupportedAllowed);
                }
            }

            async_test(function(test)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var sessionPromises = [];

                    // Test that WebM sessions generate the expected error, if
                    // supported.
                    if (isWebmSupported) {
                        var WebmSessionPromises = kCreateSessionTestCases.map(function(testCase) {
                            return test_generateRequest(testCase, mediaKeys, 'webm', getInitData('webm'));
                        });
                        sessionPromises = sessionPromises.concat(WebmSessionPromises);
                    }

                    // Repeat for MP4, if supported.
                    if (isCencSupported) {
                        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: 'TypeError',
                    func: function(s) { return s.update(new Uint8Array(0)); }
                }
            ];

            async_test(function(test)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var promises = [];

                    if (isWebmSupported) {
                        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 (isCencSupported) {
                        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)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                  var promises = [];

                  if (isWebmSupported) {
                      promises.push(create_update_test(mediaKeys, 'webm', getInitData('webm')));
                  }

                  if (isCencSupported) {
                      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_close_exception_test(mediaKeys, type, initData)
            {
                var mediaKeySession = mediaKeys.createSession();
                return mediaKeySession.close().then(function(result) {
                    assert_unreached('close() should not succeed if session uninitialized');
                }).catch(function(error) {
                    assert_equals(error.name, 'InvalidStateError');
                    // Return something so the promise resolves.
                    return Promise.resolve();
                });
            }

            async_test(function(test)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var promises = [];

                    if (isWebmSupported) {
                        promises.push(create_close_exception_test(mediaKeys, 'webm', getInitData('webm')));
                    }

                    if (isCencSupported) {
                        promises.push(create_close_exception_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, 'close() exception tests failed');
                });
            }, 'Test MediaKeySession close() exceptions.');


            function create_close_test(mediaKeys, type, initData)
            {
                var mediaKeySession = mediaKeys.createSession();
                var promise = mediaKeySession.generateRequest(type, initData).then(function(result) {
                    return mediaKeySession.close();
                }).then(function(result) {
                    // Call close() again with an extra parameter. The extra
                    // parameter is ignored.
                    return mediaKeySession.close('extra');
                });
                return promise;
            }

            async_test(function(test)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var promises = [];

                    if (isWebmSupported) {
                        promises.push(create_close_test(mediaKeys, 'webm', getInitData('webm')));
                    }

                    if (isCencSupported) {
                        promises.push(create_close_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, 'close() tests failed');
                });
            }, 'Test MediaKeySession close().');

            function create_remove_exception_test(mediaKeys, type, initData)
            {
                // remove() on an uninitialized session should fail.
                var mediaKeySession = mediaKeys.createSession('temporary');
                return mediaKeySession.remove().then(function(result) {
                    assert_unreached('remove() should not succeed if session uninitialized');
                }, function(error) {
                    assert_equals(error.name, 'InvalidStateError');
                });
            }

            async_test(function(test)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var promises = [];

                    if (isWebmSupported) {
                        promises.push(create_remove_exception_test(mediaKeys, 'webm', getInitData('webm')));
                    }

                    if (isCencSupported) {
                        promises.push(create_remove_exception_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, 'remove() exception tests failed');
                });
            }, 'Test MediaKeySession remove() exceptions.');

            function create_remove_test(mediaKeys, type, initData)
            {
                var mediaKeySession = mediaKeys.createSession();
                var promise = mediaKeySession.generateRequest(type, initData).then(function(result) {
                    return mediaKeySession.remove();
                }).then(function() {
                    // remove() doesn't close the session, so must call close().
                    return mediaKeySession.close();
                }).then(function() {
                    try {
                        // Clear Key may not support persistent-license sessions.
                        mediaKeySession = mediaKeys.createSession('persistent-license');
                        return mediaKeySession.generateRequest(type, initData).then(function(result) {
                            return mediaKeySession.remove();
                        }).then(function() {
                            return mediaKeySession.close();
                        });
                    } catch (error) {
                        // Not supported, so return a resolved promise.
                        assert_equals(error.name, 'NotSupportedError');
                        return Promise.resolve();
                    }
                });
            }

            async_test(function(test)
            {
                var isWebmSupported;
                var isCencSupported;

                isInitDataTypeSupported('webm').then(function(result) {
                    isWebmSupported = result;
                    return isInitDataTypeSupported('cenc');
                }).then(function(result) {
                    isCencSupported = result;
                    return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration());
                }).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var promises = [];

                    if (isWebmSupported) {
                        promises.push(create_remove_test(mediaKeys, 'webm', getInitData('webm')));
                    }

                    if (isCencSupported) {
                        promises.push(create_remove_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, 'remove() tests failed');
                });
            }, 'Test MediaKeySession remove().');

            var kSetServerCertificateExceptionsTestCases = [
                // Too few parameters.
                {
                    exception: 'TypeError',
                    func: function(mk) { return mk.setServerCertificate(); }
                },
                // Invalid parameters.
                {
                    exception: 'TypeError',
                    func: function(mk) { return mk.setServerCertificate(''); }
                },
                {
                    exception: 'TypeError',
                    func: function(mk) { return mk.setServerCertificate(null); }
                },
                {
                    exception: 'TypeError',
                    func: function(mk) { return mk.setServerCertificate(undefined); }
                },
                {
                    exception: 'TypeError',
                    func: function(mk) { return mk.setServerCertificate(1); }
                },
                // Empty array.
                {
                    exception: 'TypeError',
                    func: function(mk) { return mk.setServerCertificate(new Uint8Array(0)); }
                }
            ];

            async_test(function(test)
            {
                navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var promises = kSetServerCertificateExceptionsTestCases.map(function(testCase) {
                        return test_exception(testCase, mediaKeys);
                    });

                    assert_not_equals(promises.length, 0);
                    return Promise.all(promises);
                }).then(function(result) {
                    test.done();
                }).catch(function(error) {
                    forceTestFailureFromPromise(test, error, 'setServerCertificate() exception tests failed');
                });
            }, 'Test MediaKeys setServerCertificate() exceptions.');

            // All calls to |func| in this group are expected to resolve.
            var kSetServerCertificateTestCases = [
                {
                    // Pass in ArrayBufferView
                    func: function(mk) {
                              var cert = new Uint8Array(200);
                              assert_true(ArrayBuffer.isView(cert));
                              return mk.setServerCertificate(cert);
                    },
                    expected: false,
                },
                {
                    // Pass in ArrayBuffer
                    func: function(mk) {
                              var buffer = new ArrayBuffer(200);
                              assert_false(ArrayBuffer.isView(buffer));
                              return mk.setServerCertificate(buffer);
                    },
                    expected: false,
                }
            ];

            async_test(function(test)
            {
                var expected_result;
                navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(access) {
                    return access.createMediaKeys();
                }).then(function(mediaKeys) {
                    var promises = kSetServerCertificateTestCases.map(function(testCase) {
                        return testCase.func.call(null, mediaKeys);
                    });
                    expected_result = kSetServerCertificateTestCases.map(function(testCase) {
                        return testCase.expected;
                    });

                    assert_not_equals(promises.length, 0);
                    return Promise.all(promises);
                }).then(function(result) {
                    assert_array_equals(result, expected_result);
                    test.done();
                }).catch(function(error) {
                    forceTestFailureFromPromise(test, error, 'setServerCertificate() test failed');
                });
            }, 'Test MediaKeys setServerCertificate().');

            function test_valid_message_type(message_type)
            {
                var event = new MediaKeyMessageEvent('eventType', { messageType: message_type, message: new ArrayBuffer(0) } );
                assert_equals(event.messageType, message_type);
            }

            test(function(test)
            {
                // Valid MediaKeyMessageType values.
                test_valid_message_type('license-request');
                test_valid_message_type('license-renewal');
                test_valid_message_type('license-release');
                // TODO(jrummell): Add 'individualization-request' if the final
                // spec includes it. http://crbug.com/628437.

                // Invalid MediaKeyMessageType values should throw a TypeError.
                assert_throws_js(TypeError, function() {
                    new MediaKeyMessageEvent('eventType', { messageType: 'something-else', message: new ArrayBuffer(0) } );
                });

                // Missing required values should throw a TypeError.
                assert_throws_js(TypeError, function() {
                    new MediaKeyMessageEvent('eventType', { message: new ArrayBuffer(0) } );
                });
                assert_throws_js(TypeError, function() {
                    new MediaKeyMessageEvent('eventType', { messageType: 'license-request' } );
                });
            }, 'Test MediaKeyMessageEvent.');

            // 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, onencrypted.
        </script>
    </body>
</html>