diff options
Diffstat (limited to 'testing/web-platform/tests/encrypted-media/scripts')
48 files changed, 4686 insertions, 0 deletions
diff --git a/testing/web-platform/tests/encrypted-media/scripts/check-encryption-scheme.js b/testing/web-platform/tests/encrypted-media/scripts/check-encryption-scheme.js new file mode 100644 index 0000000000..ffab4a3491 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/check-encryption-scheme.js @@ -0,0 +1,46 @@ +function runTest(config, qualifier) +{ + function checkEncryptionScheme(encryptionScheme) + { + var simpleConfig = getSimpleConfiguration(); + assert_greater_than(simpleConfig[0].audioCapabilities.length, 0); + simpleConfig[0].audioCapabilities.forEach(function(capability) { + capability.encryptionScheme = encryptionScheme; + }); + + return navigator.requestMediaKeySystemAccess(config.keysystem, simpleConfig) + .then( + function(access) { + var actualConfiguration = access.getConfiguration(); + for (let i = 0; i < actualConfiguration.audioCapabilities.length; i++) { + const capability = actualConfiguration.audioCapabilities[i]; + + // If "encryptionScheme" is not supported, fail. + if (!('encryptionScheme' in capability)) { + return Promise.reject('Not implemented'); + } + + // If "encryptionScheme" is supported, it should be returned. + assert_equals(capability.encryptionScheme, encryptionScheme); + } + return Promise.resolve('Supported'); + }, + function error() { + // CDM does not support "encryptionScheme". Test should still pass. + return Promise.resolve('Not supported'); + }); + } + + promise_test( + () => checkEncryptionScheme('cenc'), + testnamePrefix(qualifier, config.keysystem) + ' support for "cenc" encryption scheme.'); + + promise_test( + () => checkEncryptionScheme('cbcs'), + testnamePrefix(qualifier, config.keysystem) + ' support for "cbcs" encryption scheme.'); + + promise_test( + () => checkEncryptionScheme('cbcs-1-9'), + testnamePrefix(qualifier, config.keysystem) + + ' support for "cbcs-1-9" encryption scheme.'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/check-initdata-type.js b/testing/web-platform/tests/encrypted-media/scripts/check-initdata-type.js new file mode 100644 index 0000000000..5c7cb6e4b9 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/check-initdata-type.js @@ -0,0 +1,35 @@ + function runTest( config, qualifier ) + { + function checkInitDataType(initDataType) + { + return isInitDataTypeSupported(initDataType).then(function(result) { + // If |initDataType| is not supported, simply succeed. + if (!result) + return Promise.resolve('Not supported'); + + return navigator.requestMediaKeySystemAccess( config.keysystem, getSimpleConfigurationForInitDataType(initDataType)) + .then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + var mediaKeySession = mediaKeys.createSession(); + var initData = getInitData(initDataType); + return mediaKeySession.generateRequest(initDataType, initData); + }); + }); + } + + promise_test(function() + { + return checkInitDataType('webm'); + }, testnamePrefix( qualifier, config.keysystem ) + ' support for "webm".'); + + promise_test(function() + { + return checkInitDataType('cenc'); + }, testnamePrefix( qualifier, config.keysystem ) + ' support for "cenc".'); + + promise_test(function() + { + return checkInitDataType('keyids'); + }, testnamePrefix( qualifier, config.keysystem ) + ' support for "keyids".'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/check-status-for-hdcp.js b/testing/web-platform/tests/encrypted-media/scripts/check-status-for-hdcp.js new file mode 100644 index 0000000000..ac30819695 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/check-status-for-hdcp.js @@ -0,0 +1,26 @@ +function runTest(config, qualifier) +{ + function checkStatusForMinHdcpVersionPolicy(hdcpVersion) + { + return navigator.requestMediaKeySystemAccess(config.keysystem, getSimpleConfiguration()) + .then(function(access) { + return access.createMediaKeys(); + }) + .then(function(mediaKeys) { + // As HDCP policy depends on the hardware running this test, + // don't bother checking the result returned as it may or + // may not be supported. This simply verifies that + // getStatusForPolicy() exists and doesn't blow up. + return mediaKeys.getStatusForPolicy({minHdcpVersion: hdcpVersion}); + }); + } + + promise_test( + () => checkStatusForMinHdcpVersionPolicy(''), + testnamePrefix(qualifier, config.keysystem) + + ' support for empty HDCP version.'); + + promise_test( + () => checkStatusForMinHdcpVersionPolicy('1.0'), + testnamePrefix(qualifier, config.keysystem) + ' support for HDCP 1.0.'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/clearkey-update-non-ascii-input.js b/testing/web-platform/tests/encrypted-media/scripts/clearkey-update-non-ascii-input.js new file mode 100644 index 0000000000..7a5c073bfa --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/clearkey-update-non-ascii-input.js @@ -0,0 +1,52 @@ +// This test is only applicable to clearkey +function runTest(config, qualifier) +{ + var testname = testnamePrefix(qualifier, config.keysystem) + ' test handling of non-ASCII responses for update()'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if (config.initDataType) { + configuration.initDataTypes = [config.initDataType]; + } + + promise_test(function (test) { + var initDataType; + var initData; + var mediaKeySession; + var messageEventFired = false; + + var p = navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function (access) { + initDataType = access.getConfiguration().initDataTypes[0]; + initData = getInitData(config.content, initDataType); + return access.createMediaKeys(); + }).then(function (mediaKeys) { + mediaKeySession = mediaKeys.createSession(); + var eventWatcher = new EventWatcher(test, mediaKeySession, ['message']); + var promise = eventWatcher.wait_for('message'); + mediaKeySession.generateRequest(initDataType, initData); + return promise; + }).then(function (messageEvent) { + // |jwkSet| contains a non-ASCII character \uDC00. + var jwkSet = '{"keys":[{' + + '"kty":"oct",' + + '"k":"MDEyMzQ1Njc4OTAxMjM0NQ",' + + '"kid":"MDEyMzQ1Njc4O\uDC00TAxMjM0NQ"' + + '}]}'; + messageEventFired = true; + return messageEvent.target.update(stringToUint8Array(jwkSet)); + }).catch(function (error) { + // Ensure we reached the update() call we are trying to test. + if (!messageEventFired) { + assert_unreached( + `Failed to reach the update() call. Error: '${error.name}' '${error.message}'`); + } + + // Propagate the error on through. + throw error; + }); + + return promise_rejects_js( + test, TypeError, p, + 'update() should fail because the processed message has non-ASCII character.'); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/events-session-closed-event.js b/testing/web-platform/tests/encrypted-media/scripts/events-session-closed-event.js new file mode 100644 index 0000000000..44f683eac8 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/events-session-closed-event.js @@ -0,0 +1,52 @@ +function runTest(config, qualifier) +{ + var testname = testnamePrefix(qualifier, config.keysystem) + ' test MediaKeySession closed event.'; + + var configuration = { + initDataTypes: [config.initDataType], + audioCapabilities: [{ + contentType: config.audioType + }], + videoCapabilities: [{ + contentType: config.videoType + }], + sessionTypes: ['temporary'] + }; + + promise_test(function (test) { + var initDataType; + var initData; + var mediaKeySession; + + return navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function (access) { + initDataType = access.getConfiguration().initDataTypes[0]; + return access.createMediaKeys(); + }).then(function (mediaKeys) { + mediaKeySession = mediaKeys.createSession(); + if(config.initData) { + initData = config.initData; + } else { + initData = stringToUint8Array(atob(config.content.keys[0].initData)); + } + return mediaKeySession.generateRequest(initDataType, initData); + }).then(function() { + // close() should result in the closed promise being + // fulfilled. + return mediaKeySession.close(); + }).then(function (result) { + assert_equals(result, undefined); + // Wait for the session to be closed. + return mediaKeySession.closed; + }).then(function (result) { + assert_equals(result, undefined); + // Now that the session is closed, verify that the + // closed attribute immediately returns a fulfilled + // promise. + return mediaKeySession.closed; + }).then(function (result) { + assert_equals(result, undefined); + }).catch(function(error) { + assert_unreached('Error: ' + error.name); + }); + }, testname); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/encrypted-media/scripts/events.js b/testing/web-platform/tests/encrypted-media/scripts/events.js new file mode 100644 index 0000000000..85c86ae78d --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/events.js @@ -0,0 +1,59 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + ', basic events'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if (config.initDataType && config.initData) { + configuration.initDataTypes = [config.initDataType]; + } + + async_test(function(test) + { + var initDataType; + var initData; + var mediaKeySession; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function processMessage(event) + { + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.target, mediaKeySession); + assert_equals(event.type, 'message'); + assert_in_array(event.messageType,['license-request', 'individualization-request']); + + config.messagehandler( event.messageType, event.message ).then(function(response) { + waitForEventAndRunStep('keystatuseschange', mediaKeySession, test.step_func(processKeyStatusesChange), test); + return mediaKeySession.update( response ); + }).catch(onFailure); + } + + function processKeyStatusesChange(event) + { + assert_true(event instanceof Event); + assert_equals(event.target, mediaKeySession); + assert_equals(event.type, 'keystatuseschange'); + test.done(); + } + + navigator.requestMediaKeySystemAccess(config.keysystem,[configuration]).then(function(access) { + initDataType = access.getConfiguration().initDataTypes[0]; + + if (config.initDataType && config.initData) { + initData = config.initData; + } else { + initData = getInitData(config.content, initDataType); + } + + return access.createMediaKeys(); + }).then(test.step_func(function(mediaKeys) { + mediaKeySession = mediaKeys.createSession(); + waitForEventAndRunStep('message', mediaKeySession, test.step_func(processMessage), test); + return mediaKeySession.generateRequest(initDataType, initData); + })).catch(onFailure); + }, testname ); + +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/expiration.js b/testing/web-platform/tests/encrypted-media/scripts/expiration.js new file mode 100644 index 0000000000..96b7fbfeef --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/expiration.js @@ -0,0 +1,43 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + ', expiration'; + + var configuration = getSimpleConfigurationForContent(config.content); + if (config.initDataType && config.initData) { + configuration.initDataTypes = [config.initDataType]; + } + + async_test(function(test) { + + var _mediaKeys, + _mediaKeySession; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array(event.messageType, [ 'license-request', 'individualization-request' ] ); + + config.messagehandler(event.messageType, event.message, {expiration: config.expiration}).then(function(response) { + return event.target.update(response); + }).then(test.step_func(function() { + assert_approx_equals(event.target.expiration, config.expiration, 4000, "expiration attribute should equal provided expiration time"); + test.done(); + })).catch(onFailure); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + _mediaKeySession = _mediaKeys.createSession( 'temporary' ); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + return _mediaKeySession.generateRequest(config.initDataType, config.initData); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/generate-request-disallowed-input.js b/testing/web-platform/tests/encrypted-media/scripts/generate-request-disallowed-input.js new file mode 100644 index 0000000000..9fd42ee85f --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/generate-request-disallowed-input.js @@ -0,0 +1,72 @@ +function runTest(config,qualifier) { + var tests = [ ], initData, keyId; + function push_test(keysystem, initDataType, initData, testname) { + tests.push({ keysystem: keysystem, initDataType: initDataType, initData: initData, testname: testname }); + } + + initData = new Uint8Array(70000); + push_test(config.keysystem, 'webm', initData, testnamePrefix( qualifier, config.keysystem ) + ', temporary, webm, initData longer than 64Kb characters'); + + initData = new Uint8Array(70000); + push_test(config.keysystem, 'cenc', initData, testnamePrefix( qualifier, config.keysystem ) + ', temporary, cenc, initData longer than 64Kb characters'); + + initData = new Uint8Array(70000); + push_test(config.keysystem, 'keyids', initData, testnamePrefix( qualifier, config.keysystem ) + ', temporary, keyids, initData longer than 64Kb characters'); + + // Invalid 'pssh' box as the size specified is larger than what + // is provided. + initData = new Uint8Array([ + 0x00, 0x00, 0xff, 0xff, // size = huge + 0x70, 0x73, 0x73, 0x68, // 'pssh' + 0x00, // version = 0 + 0x00, 0x00, 0x00, // flags + 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID + 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B, + 0x00, 0x00, 0x00, 0x00 // datasize + ]); + push_test(config.keysystem, 'cenc', initData, testnamePrefix( qualifier, config.keysystem ) + ', temporary, cenc, invalid initdata (invalid pssh)'); + + // Invalid data as type = 'psss'. + initData = new Uint8Array([ + 0x00, 0x00, 0x00, 0x00, // size = 0 + 0x70, 0x73, 0x73, 0x73, // 'psss' + 0x00, // version = 0 + 0x00, 0x00, 0x00, // flags + 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID + 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B, + 0x00, 0x00, 0x00, 0x00 // datasize + ]); + push_test(config.keysystem, 'cenc', initData, testnamePrefix( qualifier, config.keysystem ) + ', temporary, cenc, invalid initdata (not pssh)'); + + // Valid key ID size must be at least 1 character for keyids. + keyId = new Uint8Array(0); + initData = stringToUint8Array(createKeyIDs(keyId)); + push_test(config.keysystem, 'keyids', initData, testnamePrefix( qualifier, config.keysystem ) + ', temporary, keyids, invalid initdata (too short key ID)'); + + // Valid key ID size must be less than 512 characters for keyids. + keyId = new Uint8Array(600); + initData = stringToUint8Array(createKeyIDs(keyId)); + push_test(config.keysystem, 'keyids', initData, testnamePrefix( qualifier, config.keysystem ) + ', temporary, keyids, invalid initdata (too long key ID)'); + + Promise.all( tests.map(function(testspec) { + return isInitDataTypeSupported(testspec.keysystem,testspec.initDataType); + })).then(function(results) { + tests.filter(function(testspec, i) { return results[i]; } ).forEach(function(testspec) { + promise_test(function(test) { + // Create a "temporary" session for |keysystem| and call generateRequest() + // with the provided initData. generateRequest() should fail with an + // TypeError. Returns a promise that is resolved + // if the error occurred and rejected otherwise. + var p = navigator.requestMediaKeySystemAccess(testspec.keysystem, getSimpleConfigurationForInitDataType(testspec.initDataType)).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + var mediaKeySession = mediaKeys.createSession("temporary"); + return mediaKeySession.generateRequest(testspec.initDataType, testspec.initData); + }); + + return promise_rejects_js(test, TypeError, p, + "generateRequest() should fail"); + }, testspec.testname); + }); + }); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/invalid-license.js b/testing/web-platform/tests/encrypted-media/scripts/invalid-license.js new file mode 100644 index 0000000000..89d43769e5 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/invalid-license.js @@ -0,0 +1,38 @@ +function runTest(config) +{ + promise_test(function (test) { + var initDataType; + var initData; + var keySystem = config.keysystem; + var invalidLicense = new Uint8Array([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]); + var messageEventFired = false; + + var p = navigator.requestMediaKeySystemAccess(keySystem, getSimpleConfiguration()).then(function (access) { + initDataType = access.getConfiguration().initDataTypes[0]; + initData = getInitData(initDataType); + return access.createMediaKeys(); + }).then(function (mediaKeys) { + var keySession = mediaKeys.createSession(); + var eventWatcher = new EventWatcher(test, keySession, ['message']); + var promise = eventWatcher.wait_for('message'); + keySession.generateRequest(initDataType, initData); + return promise; + }).then(function (messageEvent) { + messageEventFired = true; + return messageEvent.target.update(invalidLicense); + }).catch(function (error) { + // Ensure we reached the update() call we are trying to test. + if (!messageEventFired) { + assert_unreached( + `Failed to reach the update() call. Error: '${error.name}' '${error.message}'`); + } + + // Propagate the error on through. + throw error; + }); + + return promise_rejects_js( + test, TypeError, p, + 'update() should fail because of an invalid license.'); + }, 'Update with invalid Clear Key license'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/keystatuses-multiple-sessions.js b/testing/web-platform/tests/encrypted-media/scripts/keystatuses-multiple-sessions.js new file mode 100644 index 0000000000..e9bf10e886 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/keystatuses-multiple-sessions.js @@ -0,0 +1,103 @@ +function runTest(config,qualifier) +{ + var testname = testnamePrefix(qualifier, config.keysystem) + ', temporary, keystatuses, multiple sessions'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if (config.initDataType && config.initData) configuration.initDataTypes = [config.initDataType]; + + async_test(function(test) + { + var mediaKeySession1; + var mediaKeySession2; + + // Even though key ids are uint8, using printable values so that + // they can be verified easily. + var key1 = new Uint8Array(config.content.keys[0].kid), + key2 = new Uint8Array(config.content.keys[1].kid), + variant1 = config.content.keys[0].variantId, + variant2 = config.content.keys[1].variantId; + + function onFailure(error) { + forceTestFailureFromPromise(test,error); + } + + function processMessage1(event) + { + // This should only be called for session1. + assert_equals(event.target, mediaKeySession1); + + // No keys added yet. + verifyKeyStatuses(mediaKeySession1.keyStatuses, {expected: [], unexpected: [key1, key2]}); + + // Add key1 to session1. + config.messagehandler(event.messageType, event.message, {variantId:variant1}).then(function(response) { + return event.target.update(response); + }).catch(onFailure); + + } + + function processKeyStatusesChange1(event) + { + // This should only be called for session1. + assert_equals(event.target, mediaKeySession1); + + // Check that keyStatuses contains the expected key1 only. + verifyKeyStatuses(mediaKeySession1.keyStatuses, {expected: [key1], unexpected: [key2]}); + + // Now trigger a message event on session2. + mediaKeySession2.generateRequest(config.initDataType, config.initData[1]).catch(onFailure); + } + + function processMessage2(event) + { + // This should only be called for session2. + assert_equals(event.target, mediaKeySession2); + + // session2 has no keys added yet. + verifyKeyStatuses(mediaKeySession2.keyStatuses, {expected: [], unexpected: [key1, key2]}); + + // session1 should still have 1 key. + verifyKeyStatuses(mediaKeySession1.keyStatuses, {expected: [key1], unexpected: [key2]}); + + // Add key2 to session2. + config.messagehandler(event.messageType, event.message, {variantId:variant2}).then(function(response) { + return event.target.update(response); + }).catch(onFailure); + } + + function processKeyStatusesChange2(event) + { + // This should only be called for session2. + assert_equals(event.target, mediaKeySession2); + + // Check that keyStatuses contains the expected key2 only. + verifyKeyStatuses(mediaKeySession2.keyStatuses, {expected: [key2], unexpected: [key1]}); + + // session1 should still have 1 key. + verifyKeyStatuses(mediaKeySession1.keyStatuses, {expected: [key1], unexpected: [key2]}); + + test.done(); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + mediaKeySession1 = mediaKeys.createSession(); + mediaKeySession2 = mediaKeys.createSession(); + + // There should be no keys defined on either session. + verifyKeyStatuses(mediaKeySession1.keyStatuses, {expected: [], unexpected: [key1, key2]}); + verifyKeyStatuses(mediaKeySession2.keyStatuses, {expected: [], unexpected: [key1, key2]}); + + // Bind all the event handlers now. + waitForEventAndRunStep('message', mediaKeySession1, processMessage1, test); + waitForEventAndRunStep('message', mediaKeySession2, processMessage2, test); + waitForEventAndRunStep('keystatuseschange', mediaKeySession1, processKeyStatusesChange1, test); + waitForEventAndRunStep('keystatuseschange', mediaKeySession2, processKeyStatusesChange2, test); + + // Generate a request on session1. + return mediaKeySession1.generateRequest(config.initDataType, config.initData[0]); + }).catch(onFailure); + }, testname ); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/keystatuses.js b/testing/web-platform/tests/encrypted-media/scripts/keystatuses.js new file mode 100644 index 0000000000..8d33dd4210 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/keystatuses.js @@ -0,0 +1,165 @@ +function runTest(config,qualifier) +{ + var testname = testnamePrefix(qualifier, config.keysystem) + ', temporary, keystatuses'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if (config.initDataType && config.initData) { + configuration.initDataTypes = [config.initDataType]; + } + + async_test(function(test) + { + var mediaKeySession; + var initDataType; + var initData; + var closed = false; + + // Even though key ids are uint8, using printable values so that + // they can be verified easily. + var key1 = new Uint8Array(config.content.keys[0].kid), + key2 = new Uint8Array(config.content.keys[1].kid), + key1String = arrayBufferAsString(key1), + key2String = arrayBufferAsString(key2); + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function processMessage(event) + { + // No keys added yet. + assert_equals(mediaKeySession.keyStatuses.size, 0); + + waitForEventAndRunStep('keystatuseschange', mediaKeySession, processKeyStatusesChange, test); + + // Add keys to session + config.messagehandler(event.messageType, event.message).then(function(response) { + return event.target.update(response); + }).catch(onFailure); + } + + function checkKeyStatusFor2Keys() + { + // Two keys added, so both should show up in |keyStatuses|. + assert_equals(mediaKeySession.keyStatuses.size, 2); + + // Check |keyStatuses| for 2 entries. + var result = []; + for (let item of mediaKeySession.keyStatuses) { + result.push({ key: arrayBufferAsString(item[0]), value: item[1] }); + } + function lexicographical( a, b ) { return a < b ? -1 : a === b ? 0 : +1; } + function lexicographicalkey( a, b ) { return lexicographical( a.key, b.key ); } + var expected1 = [{ key: key1String, value: 'usable'}, { key: key2String, value: 'usable'}].sort( lexicographicalkey ); + var expected2 = [{ key: key1String, value: 'status-pending'}, { key: key2String, value: 'status-pending'}].sort( lexicographicalkey ); + assert_in_array( JSON.stringify(result), + [ JSON.stringify(expected1),JSON.stringify(expected2) ], + "keystatuses should have the two expected keys with keystatus 'usable' or 'status-pending'"); + + // |keyStatuses| must contain both keys. + result = []; + for (var key of mediaKeySession.keyStatuses.keys()) { + result.push(arrayBufferAsString(key)); + } + assert_array_equals(result, + [key1String, key2String].sort( lexicographical ), + "keyStatuses.keys() should return an iterable over the two expected keys"); + + // Both values in |mediaKeySession| should be 'usable' or 'status-pending'. + result = []; + for (var value of mediaKeySession.keyStatuses.values()) { + result.push(value); + } + + assert_equals( result.length, 2, "keyStatuses.values() should have two elements" ); + assert_equals( result[0], result[1], "the values in keyStatuses.values() should be equal" ); + assert_in_array( result[0], [ 'usable', 'status-pending' ] ); + + // Check |keyStatuses.entries()|. + result = []; + for (var entry of mediaKeySession.keyStatuses.entries()) { + result.push({ key: arrayBufferAsString(entry[0]), value: entry[1] }); + } + assert_in_array(JSON.stringify(result), + [ JSON.stringify(expected1), JSON.stringify(expected2) ], + "keyStatuses.entries() should return an iterable over the two expected keys, with keystatus 'usable' or 'status-pending'"); + + // forEach() should return both entries. + result = []; + mediaKeySession.keyStatuses.forEach(function(status, keyId) { + result.push({ key: arrayBufferAsString(keyId), value: status }); + }); + assert_in_array(JSON.stringify(result), + [ JSON.stringify(expected1), JSON.stringify(expected2) ], + "keyStatuses.forEach() should iterate over the two expected keys, with keystatus 'usable' or 'status-pending'"); + + // has() and get() should return the expected values. + assert_true(mediaKeySession.keyStatuses.has(key1), "keyStatuses should have key1"); + assert_true(mediaKeySession.keyStatuses.has(key2), "keyStatuses should have key2"); + assert_in_array(mediaKeySession.keyStatuses.get(key1), [ 'usable', 'status-pending' ], "key1 should have status 'usable' or 'status-pending'"); + assert_in_array(mediaKeySession.keyStatuses.get(key2), [ 'usable', 'status-pending' ], "key2 should have status 'usable' or 'status-pending'"); + + // Try some invalid keyIds. + var invalid1 = key1.subarray(0, key1.length - 1); + assert_false(mediaKeySession.keyStatuses.has(invalid1), "keystatuses should not have invalid key (1)"); + assert_equals(mediaKeySession.keyStatuses.get(invalid1), undefined, "keystatus value for invalid key should be undefined (1)"); + + var invalid2 = key1.subarray(1); + assert_false(mediaKeySession.keyStatuses.has(invalid2), "keystatuses should not have invalid key (2)"); + assert_equals(mediaKeySession.keyStatuses.get(invalid2), undefined, "keystatus value for invalid key should be undefined (2)"); + + var invalid3 = new Uint8Array(key1); + invalid3[0] += 1; + assert_false(mediaKeySession.keyStatuses.has(invalid3), "keystatuses should not have invalid key (3)"); + assert_equals(mediaKeySession.keyStatuses.get(invalid3), undefined, "keystatus value for invalid key should be undefined (3)"); + + var invalid4 = new Uint8Array(key1); + invalid4[invalid4.length - 1] -= 1; + assert_false(mediaKeySession.keyStatuses.has(invalid4), "keystatuses should not have invalid key (4)"); + assert_equals(mediaKeySession.keyStatuses.get(invalid4), undefined, "keystatus value for invalid key should be undefined (4)"); + + var invalid5 = new Uint8Array(key1.length + 1); + invalid5.set(key1, 1); // First element will be 0. + assert_false(mediaKeySession.keyStatuses.has(invalid5), "keystatuses should not have invalid key (5)"); + assert_equals(mediaKeySession.keyStatuses.get(invalid5), undefined, "keystatus value for invalid key should be undefined (5)"); + + var invalid6 = new Uint8Array(key1.length + 1); + invalid6.set(key1, 0); // Last element will be 0. + assert_false(mediaKeySession.keyStatuses.has(invalid6), "keystatuses should not have invalid key (6)"); + assert_equals(mediaKeySession.keyStatuses.get(invalid6), undefined, "keystatus value for invalid key should be undefined (6)"); + } + + function processKeyStatusesChange(event) + { + if (!closed) + { + // The first keystatuseschange (caused by update()) + // should include both keys. + checkKeyStatusFor2Keys(); + + mediaKeySession.close().catch(onFailure); + closed = true; + } + else + { + // The second keystatuseschange (caused by close()) + // should not have any keys. + assert_equals(mediaKeySession.keyStatuses.size, 0); + test.done(); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(test.step_func(function(mediaKeys) { + mediaKeySession = mediaKeys.createSession(); + + // There should be no keys defined yet. + //verifyKeyStatuses(mediaKeySession.keyStatuses, { expected: [], unexpected: [key1, key2] }); + + waitForEventAndRunStep('message', mediaKeySession, processMessage, test); + return mediaKeySession.generateRequest(config.initDataType, config.initData); + })).catch(onFailure); + }, testname ); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/not-callable-after-createsession.js b/testing/web-platform/tests/encrypted-media/scripts/not-callable-after-createsession.js new file mode 100644 index 0000000000..2642c71e0e --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/not-callable-after-createsession.js @@ -0,0 +1,50 @@ + function runTest(config,qualifier) { + // After creation, the MediaKeySession object is not + // callable, and we should get a InvalidStateError. + + promise_test(function() + { + return navigator.requestMediaKeySystemAccess(config.keysystem, getSimpleConfiguration()).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + var mediaKeySession = mediaKeys.createSession(); + + var arbitraryResponse = new Uint8Array([0x00, 0x11]); + return mediaKeySession.update(arbitraryResponse).then(function(result) { + assert_unreached('update() succeeded unexpectedly.'); + }).catch(function(error) { + assert_equals(error.name, 'InvalidStateError'); + }); + }); + }, testnamePrefix( qualifier, config.keysystem ) + ', temporary, update() immediately after createSession()'); + + promise_test(function() + { + return navigator.requestMediaKeySystemAccess(config.keysystem, getSimpleConfiguration()).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + var mediaKeySession = mediaKeys.createSession(); + + return mediaKeySession.close().then(function(result) { + assert_unreached('close() succeeded unexpectedly.'); + }).catch(function(error) { + assert_equals(error.name, 'InvalidStateError'); + }); + }); + }, testnamePrefix( qualifier, config.keysystem ) + ', temporary, close() immediately after createSession()'); + + promise_test(function() + { + return navigator.requestMediaKeySystemAccess(config.keysystem, getSimpleConfiguration()).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + var mediaKeySession = mediaKeys.createSession(); + + return mediaKeySession.remove().then(function(result) { + assert_unreached('remove() succeeded unexpectedly.'); + }).catch(function(error) { + assert_equals(error.name, 'InvalidStateError'); + }); + }); + }, testnamePrefix( qualifier, config.keysystem ) + ', temporary, remove() immediately after createSession()'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/onencrypted.js b/testing/web-platform/tests/encrypted-media/scripts/onencrypted.js new file mode 100644 index 0000000000..acf0ffacc0 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/onencrypted.js @@ -0,0 +1,48 @@ +function runTest(config) { + var expectedInitData = []; + expectedInitData.push(stringToUint8Array(atob(config.keys[0].initData))); + expectedInitData.push(stringToUint8Array(atob(config.keys[1].initData))); + + // Will get 2 identical events, one for audio, one for video. + var expectedEvents = 2; + var currentData; + + async_test(function (test) { + var video = config.video, + mediaSource, + onEncrypted = function (event) { + currentData = new Uint8Array(event.initData); + assert_equals(event.target, config.video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + assert_equals(event.initDataType, 'cenc'); + // At this point we do not know if the event is related to audio or video. So check for both expected init data + assert_true(checkInitData(currentData, expectedInitData[0]) || checkInitData(currentData, expectedInitData[1])); + + if (--expectedEvents === 0) { + test.done(); + } + }; + + waitForEventAndRunStep('encrypted', video, onEncrypted, test); + testmediasource(config).then(function (source) { + mediaSource = source; + config.video.src = URL.createObjectURL(mediaSource); + return source.done; + }).then(function(){ + video.play(); + }); + }, 'encrypted fired on encrypted media file.'); +} + +function checkInitData(data, expectedData) { + if (data.length !== expectedData.length) { + return false; + } + for (var i = 0; i < data.length; i++) { + if (data[i] !== expectedData[i]) { + return false; + } + } + return true; +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-destroy-persistent-license.js b/testing/web-platform/tests/encrypted-media/scripts/playback-destroy-persistent-license.js new file mode 100644 index 0000000000..8a6cacedb4 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-destroy-persistent-license.js @@ -0,0 +1,95 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix( qualifier, config.keysystem ) + + ', persistent-license, ' + + /video\/([^;]*)/.exec( config.videoType )[ 1 ] + + ', playback, destroy and acknowledge'; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'persistent-license' ] }; + + async_test( function(test) { + + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource, + _sessionId, + _startedReleaseSequence = false; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + config.messagehandler(event.messageType, event.message).then(function(response) { + return _mediaKeySession.update(response); + }).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ).then( test.step_func(function() { + assert_not_equals( _mediaKeySession.sessionId, undefined, "SessionId should be defined" ); + _sessionId = _mediaKeySession.sessionId; + })).catch(onFailure); + } + + function onTimeupdate(event) { + if (_video.currentTime > ( config.duration || 1 ) && !_startedReleaseSequence) { + _video.removeEventListener('timeupdate', onTimeupdate); + _video.pause(); + _video.removeAttribute('src'); + _video.load(); + + _startedReleaseSequence = true; + _mediaKeySession.closed.then(onClosed); + _mediaKeySession.remove().catch(onFailure); + } + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + function onClosed() { + // Try and reload and check this fails + var mediaKeySession = _mediaKeys.createSession( 'persistent-license' ); + mediaKeySession.load(_sessionId).then( test.step_func(function(success) { + assert_false( success, "Load of removed session shouold fail" ); + test.done(); + })).catch(onFailure); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + _mediaKeySession = _mediaKeys.createSession('persistent-license'); + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-license-events.js b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-license-events.js new file mode 100644 index 0000000000..2d99f679f4 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-license-events.js @@ -0,0 +1,158 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', persistent-license, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback, check events'; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'persistent-license' ] }; + + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource, + _receivedTimeupdateEvent = false, + _startedReleaseSequence = false, + _events = [ ]; + + function recordEventFunc(eventType) { + return function() { _events.push(eventType); }; + } + + function recordEventFuncAndCheckExpirationForNaN(eventType) { + return function() { + _events.push(eventType); + assert_equals(_mediaKeySession.expiration, NaN); + }; + } + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals( event.target, _mediaKeySession ); + assert_true( event instanceof window.MediaKeyMessageEvent ); + assert_equals( event.type, 'message'); + + if (!_startedReleaseSequence) { + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + } else { + assert_equals(event.messageType, 'license-release'); + } + + if (event.messageType !== 'individualization-request') { + _events.push(event.messageType); + } + + config.messagehandler(event.messageType, event.message ).then(function(response) { + _events.push(event.messageType + '-response'); + return _mediaKeySession.update(response); + }).then(test.step_func(function() { + _events.push(event.messageType + '-response-resolved'); + if (event.messageType === 'license-release') { + test.step_timeout(function() { + checkEventSequence(_events, [ + 'generaterequest', + [ // potentially repeating + 'license-request', + 'license-request-response', + 'license-request-response-resolved' + ], + 'keystatuseschange-usablekey', + 'playing', + 'remove-resolved', + 'keystatuseschange-allkeysreleased', + 'license-release', + 'license-release-response', + 'closed-attribute-resolved', + 'license-release-response-resolved', + 'keystatuseschange-empty' + ]); + test.done(); + }, 100); + } + })).catch(onFailure); + } + + function onKeyStatusesChange(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.Event); + assert_equals(event.type, 'keystatuseschange'); + var hasKeys = false, + usableKey = false; // true if any key usable. + _mediaKeySession.keyStatuses.forEach(function(value, keyid) { + assert_in_array(value, ['usable', 'released']); + hasKeys = true; + usableKey = usableKey || (value === 'usable'); + }); + + if (!hasKeys) { + _events.push('keystatuseschange-empty'); + } else if (usableKey) { + _events.push('keystatuseschange-usablekey'); + } else { + _events.push('keystatuseschange-allkeysreleased'); + } + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + _mediaKeySession.generateRequest( config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ).then(recordEventFunc('generaterequest') + ).catch(onFailure); + } + + function onTimeupdate(event) { + if ( _video.currentTime > ( config.duration || 1 ) && !_receivedTimeupdateEvent ) { + _receivedTimeupdateEvent = true; + _video.pause(); + _video.removeAttribute('src'); + _video.load(); + + _startedReleaseSequence = true; + _mediaKeySession.remove() + .then(recordEventFuncAndCheckExpirationForNaN('remove-resolved')) + .catch(onFailure); + } + } + + function onPlaying(event) { + _events.push( 'playing' ); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [ configuration ]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + _mediaKeySession = _mediaKeys.createSession( 'persistent-license' ); + waitForEventAndRunStep('keystatuseschange', _mediaKeySession, onKeyStatusesChange, test); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.closed + .then(recordEventFuncAndCheckExpirationForNaN('closed-attribute-resolved')) + .catch(onFailure); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-license.js b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-license.js new file mode 100644 index 0000000000..c7e56e3aea --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-license.js @@ -0,0 +1,77 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', persistent-license, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + 'playback'; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'persistent-license' ] }; + + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals( event.target, _mediaKeySession ); + assert_true( event instanceof window.MediaKeyMessageEvent ); + assert_equals( event.type, 'message'); + + assert_in_array( event.messageType, [ 'license-request', 'individualization-request' ] ); + + config.messagehandler(event.messageType, event.message).then( function( response ) { + return _mediaKeySession.update(response) + }).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ).catch(onFailure); + } + + function onTimeupdate(event) { + if (_video.currentTime > (config.duration || 1)) { + _video.pause(); + test.done(); + } + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [ configuration ]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + _mediaKeySession = _mediaKeys.createSession( 'persistent-license' ); + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-usage-record-events.js b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-usage-record-events.js new file mode 100644 index 0000000000..e8e1e54790 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-usage-record-events.js @@ -0,0 +1,109 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', persistent-usage-record, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback, check events'; + + var configuration = { initDataTypes: [config.initDataType ], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + sessionTypes: ['persistent-usage-record']}; + + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _sessionId, + _timeupdateEvent = false, + _events = [ ]; + + function recordEventFunc(eventType) { + return function() { _events.push(eventType); }; + } + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + if (event.messageType !== 'individualization-request') { + _events.push(event.messageType); + } + + config.messagehandler(event.messageType, event.message).then(function(response) { + _events.push(event.messageType + '-response'); + return _mediaKeySession.update(response); + }).then(test.step_func(function() { + _events.push('update-resolved'); + if (event.messageType === 'license-release') { + checkEventSequence( _events, + ['encrypted','generaterequest-done', + ['license-request', 'license-request-response', 'update-resolved'], // potentially repeating + 'keystatuseschange', + 'playing', + 'remove-resolved', + 'keystatuseschange', + 'license-release', + 'license-release-response', + 'closed-attribute-resolved', + 'update-resolved' ]); + test.done(); + } + + if ( event.messageType === 'license-request' ) { + _video.setMediaKeys(_mediaKeys); + } + })).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + _events.push(event.type); + _mediaKeySession.generateRequest( config.initDataType || event.initDataType, + config.initData || event.initData ).then( function() { + _events.push( 'generaterequest-done' ); + _sessionId = _mediaKeySession.sessionId; + }).catch(onFailure); + } + + function onTimeupdate(event) { + if (_video.currentTime > (config.duration || 1) && !_timeupdateEvent) { + _timeupdateEvent = true; + _video.pause(); + _mediaKeySession.remove().then(recordEventFunc('remove-resolved')).catch(onFailure); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, recordEventFunc('playing'), test); + + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + + _mediaKeySession = _mediaKeys.createSession( 'persistent-usage-record' ); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + waitForEventAndRunStep('keystatuseschange', _mediaKeySession, recordEventFunc('keystatuseschange'), test); + _mediaKeySession.closed.then(recordEventFunc('closed-attribute-resolved')); + return config.servercertificate ? _mediaKeys.setServerCertificate(config.servercertificate) : true; + }).then(function( success ) { + return testmediasource(config); + }).then(function(source) { + _video.src = URL.createObjectURL(source); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-usage-record.js b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-usage-record.js new file mode 100644 index 0000000000..1772b4bd5f --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-persistent-usage-record.js @@ -0,0 +1,104 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', persistent-usage-record, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + 'playback'; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'persistent-usage-record' ] }; + + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource, + _releaseSequence = false; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + // event instance verification failing on CastTV + // assert_true( event instanceof window.MediaKeyMessageEvent ); + assert_equals(event.type, 'message'); + + if (!_releaseSequence) + { + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + } + else + { + assert_equals(event.messageType, 'license-release'); + } + + config.messagehandler(event.messageType, event.message).then(function(response) { + return _mediaKeySession.update(response); + }).then(function() { + if(event.messageType === 'license-request') { + return _video.setMediaKeys(_mediaKeys); + } else if(event.messageType === 'license-release') { + test.done(); + } + }).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ) + .catch(onFailure); + } + + function onClosed(event) { + _video.src = ""; + _video.setMediaKeys( null ); + } + + function onTimeupdate(event) { + if (_video.currentTime > ( config.duration || 1) && !_releaseSequence) { + _video.removeEventListener('timeupdate', onTimeupdate ); + _video.pause(); + _releaseSequence = true; + + _mediaKeySession.closed.then(test.step_func(onClosed)); + _mediaKeySession.remove().catch(onFailure); + + _video.removeEventListener('timeupdate', onTimeupdate); + } + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [ configuration ]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + _mediaKeySession = _mediaKeys.createSession('persistent-usage-record'); + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return config.servercertificate ? _mediaKeys.setServerCertificate(config.servercertificate) : true; + }).then(function(success) { + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-retrieve-persistent-license.js b/testing/web-platform/tests/encrypted-media/scripts/playback-retrieve-persistent-license.js new file mode 100644 index 0000000000..83cba34028 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-retrieve-persistent-license.js @@ -0,0 +1,115 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', persistent-license, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', ' + config.testcase; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'persistent-license' ] }; + + + async_test( function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource, + _sessionId; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ).then( function() { + _sessionId = _mediaKeySession.sessionId; + }).catch(onFailure); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message).then(function(response) { + return _mediaKeySession.update(response); + }).catch(onFailure); + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate); + } + + function onTimeupdate(event) { + if (_video.currentTime > (config.duration || 1)) { + _video.removeEventListener('timeupdate', onTimeupdate); + _video.pause(); + _video.removeAttribute('src'); + _video.load(); + + _mediaKeySession.closed + .then(test.step_func(onClosed)) + .catch(onFailure); + _mediaKeySession.close() + .catch(onFailure); + } + } + + function onClosed() { + // Open a new window in which we will attempt to play with the persisted license + var win = window.open(config.windowscript); + assert_not_equals(win, null, "Popup windows not allowed?"); + + // Lisen for an event from the new window containing its test assertions + window.addEventListener('message', test.step_func(function(messageEvent) { + if (messageEvent.data.testResult) { + messageEvent.data.testResult.forEach(test.step_func(function(assertion) { + assert_equals(assertion.actual, assertion.expected, assertion.message); + })); + + win.close(); + test.done(); + } + })); + + // Delete things which can't be cloned and posted over to the new window + delete config.video; + delete config.messagehandler; + + // Post the config and session id to the new window when it is ready + win.onload = function() { + win.postMessage({config: config, sessionId: _sessionId}, '*'); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys( mediaKeys ); + }).then(function() { + _mediaKeySession = _mediaKeys.createSession('persistent-license'); + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-retrieve-persistent-usage-record.js b/testing/web-platform/tests/encrypted-media/scripts/playback-retrieve-persistent-usage-record.js new file mode 100644 index 0000000000..a04f97d2ca --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-retrieve-persistent-usage-record.js @@ -0,0 +1,114 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', persistent-usage-record, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback, retrieve in new window'; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'persistent-usage-record' ] }; + + + async_test( function( test ) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource, + _sessionId, + _isClosing = false; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( config.initDataType || event.initDataType, + config.initData || event.initData ).then( function() { + _sessionId = _mediaKeySession.sessionId; + }).catch(onFailure); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array( event.messageType,['license-request', 'individualization-request']); + + config.messagehandler( event.messageType, event.message ).then(function(response) { + return _mediaKeySession.update(response); + }).then(function() { + return _video.setMediaKeys(_mediaKeys); + }).catch(onFailure); + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + function onTimeupdate(event) { + if (!_isClosing && _video.currentTime > (config.duration || 1)) { + _isClosing = true; + _video.removeEventListener('timeupdate', onTimeupdate); + _video.pause(); + _mediaKeySession.closed.then( test.step_func(onClosed)); + _mediaKeySession.close(); + } + } + + function onClosed(event) { + _video.src = ""; + _video.setMediaKeys( null ); + + var win = window.open(config.windowscript); + assert_not_equals(win, null, "Popup windows not allowed?"); + + window.addEventListener('message', test.step_func(function(event) { + if (event.data.testResult) { + event.data.testResult.forEach(test.step_func(function(assertion) { + assert_equals(assertion.actual, assertion.expected, assertion.message); + })); + + win.close(); + test.done(); + } + })); + + delete config.video; + delete config.messagehandler; + + win.onload = function() { + win.postMessage({ config: config, sessionId: _sessionId }, '*'); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(mediaKeys); + }).then(function(){ + _mediaKeySession = _mediaKeys.createSession( 'persistent-usage-record' ); + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return config.servercertificate ? _mediaKeys.setServerCertificate(config.servercertificate) : true; + }).then(function(success) { + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-encrypted-clear-segmented-sources.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-encrypted-clear-segmented-sources.js new file mode 100644 index 0000000000..8f5af2cd6b --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-encrypted-clear-segmented-sources.js @@ -0,0 +1,109 @@ +function runTest(configEncrypted,configClear,qualifier) { + + var testname = testnamePrefix(qualifier, configEncrypted.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(configEncrypted.videoType)[1] + + ', playback, encrypted and clear sources in separate segments'; + + var configuration = { initDataTypes: [ configEncrypted.initDataType ], + audioCapabilities: [ { contentType: configEncrypted.audioType } ], + videoCapabilities: [ { contentType: configEncrypted.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + var didAppendEncrypted = false, + _video = configEncrypted.video, + _mediaKeys, + _mediaKeySession, + _mediaSource, + _sourceBuffer; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onVideoError(event) { + var message = (_video.error || {}).message || 'Got unknown error from <video>'; + forceTestFailureFromPromise(test, new Error(message)); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + configEncrypted.messagehandler(event.messageType, event.message).then(function(response) { + return _mediaKeySession.update(response); + }).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + var initDataType = configEncrypted.initDataType || event.initDataType; + var initData = configEncrypted.initData || event.initData; + + _mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest(initDataType, initData).catch(onFailure); + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + function onTimeupdate(event) { + if (_video.currentTime > (configEncrypted.duration || 1) + (configClear.duration || 1)) { + _video.pause(); + test.done(); + } + if (_video.currentTime > 1 && !didAppendEncrypted) { + didAppendEncrypted = true; + _sourceBuffer.timestampOffset = configClear.duration; + fetchAndAppend(configEncrypted.videoPath).then(function() { + _mediaSource.endOfStream(); + }).catch(onFailure); + } + } + + function fetchAndAppend(path) { + return fetch(path).then(function(response) { + if (!response.ok) throw new Error('Resource fetch failed'); + return response.arrayBuffer(); + }).then(function(data) { + return new Promise(function(resolve, reject) { + _sourceBuffer.appendBuffer(data); + _sourceBuffer.addEventListener('updateend', resolve); + _sourceBuffer.addEventListener('error', reject); + }); + }); + } + + _video.addEventListener('error', onVideoError); + navigator.requestMediaKeySystemAccess(configEncrypted.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function(){ + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + + return new Promise(function(resolve, reject) { + _mediaSource = new MediaSource(); + _mediaSource.addEventListener('sourceopen', resolve); + _video.src = URL.createObjectURL(_mediaSource); + }); + }).then(function() { + _sourceBuffer = _mediaSource.addSourceBuffer(configEncrypted.videoType); + return fetchAndAppend(configClear.videoPath); + }).then(function() { + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-encrypted-clear-sources.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-encrypted-clear-sources.js new file mode 100644 index 0000000000..41ea183f2e --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-encrypted-clear-sources.js @@ -0,0 +1,109 @@ +function runTest(configEncrypted,configClear,qualifier) { + + var testname = testnamePrefix(qualifier, configEncrypted.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(configEncrypted.videoType)[1] + + ', playback, encrypted and clear sources'; + + var configuration = { initDataTypes: [ configEncrypted.initDataType ], + audioCapabilities: [ { contentType: configEncrypted.audioType } ], + videoCapabilities: [ { contentType: configEncrypted.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + var playbackCount = 0, + _video = configEncrypted.video, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + configEncrypted.messagehandler(event.messageType, event.message).then(function(response) { + return _mediaKeySession.update( response ); + }).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest(configEncrypted.initData ? configEncrypted.initDataType : event.initDataType, + configEncrypted.initData || event.initData).then(function(){ + return _video.setMediaKeys(_mediaKeys); + }).catch(onFailure); + } + + function onPlaying(event) + { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeUpdate, true); + } + + function onTimeUpdate(event) { + if (_video.currentTime < (configEncrypted.duration || 0.5)) { + return; + } + + _video.removeEventListener('timeupdate', onTimeUpdate, true); + + resetSrc().then(function(){ + if (playbackCount >= 2) { + test.done(); + } else { + playbackCount++; + startPlayback(); + } + }).catch(onFailure); + } + + function resetSrc() { + _video.pause(); + _video.removeAttribute('src'); + _video.load(); + return _video.setMediaKeys(null); + } + + function startPlayback() { + // Alternate between encrypted and unencrypted files. + if (playbackCount % 2) { + // Unencrypted files don't require MediaKeys + testmediasource( configClear ).then(function( source ) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + _video.play(); + }).catch(onFailure); + } else { + navigator.requestMediaKeySystemAccess(configEncrypted.keysystem, [ configuration ]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + _mediaKeySession = _mediaKeys.createSession( 'temporary' ); + }).then(function() { + return testmediasource(configEncrypted); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + } + } + + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + startPlayback(); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-events.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-events.js new file mode 100644 index 0000000000..28f45222cf --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-events.js @@ -0,0 +1,131 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback, check events'; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource, + _timeupdateEvent = false, + _events = [ ]; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array( event.messageType, ['license-request', 'individualization-request']); + + if (event.messageType !== 'individualization-request') { + _events.push(event.messageType); + } + + config.messagehandler(event.messageType, event.message).then(function(response) { + _events.push('license-request-response'); + waitForEventAndRunStep('keystatuseschange', _mediaKeySession, onKeyStatusesChange, test); + return _mediaKeySession.update( response ); + }).then(function() { + _events.push('update-resolved'); + }).catch(onFailure); + } + + function onKeyStatusesChange(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.Event); + assert_equals(event.type, 'keystatuseschange'); + var hasKeys = false, pendingKeys = false; + _mediaKeySession.keyStatuses.forEach(function(value, keyid) { + assert_in_array(value, ['status-pending', 'usable']); + hasKeys = true; + pendingKeys = pendingKeys || (value === 'status-pending'); + }); + + if (!hasKeys) { + _events.push('emptykeyslist'); + } else if (!pendingKeys ) { + _events.push('allkeysusable'); + _video.setMediaKeys(_mediaKeys).catch(onFailure); + } else { + assert_unreached('unexpected ' + event.type + ' event'); + } + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest(config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ).then(function() { + _events.push('generaterequest'); + }).catch(onFailure); + } + + function onClosed(event) { + _events.push('closed-attribute-resolved'); + setTimeout(test.step_func(function() { + checkEventSequence( _events, + ['generaterequest', + ['license-request', 'license-request-response', 'update-resolved'], // potentially repeating + 'allkeysusable', + 'playing', + 'closed-attribute-resolved', + 'close-promise-resolved', + 'emptykeyslist']); + test.done(); + } ), 0); + } + + function onTimeupdate(event) { + if (_video.currentTime > (config.duration || 1) && !_timeupdateEvent) { + _timeupdateEvent = true; + _video.pause(); + + _mediaKeySession.closed.then(test.step_func(onClosed)); + _mediaKeySession.close().then(function() { + _events.push('close-promise-resolved'); + }).catch(onFailure); + } + } + + function onPlaying(event) { + _events.push('playing'); + + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + _mediaKeySession = _mediaKeys.createSession('temporary'); + + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + }).then(function() { + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-expired.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-expired.js new file mode 100644 index 0000000000..3d1bd9591d --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-expired.js @@ -0,0 +1,95 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', expired license'; + + var configuration = { initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + sessionTypes: ['temporary'] }; + + async_test(function(test) { + + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + // Only create the session for the first encrypted event + if (_mediaKeySession !== undefined) return; + + var initDataType = config.initData ? config.initDataType : event.initDataType; + var initData = config.initData || event.initData; + + _mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest(initDataType, initData).catch(onFailure); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + // Generate a license that expires 1 second from now. + var expiration = Date.now().valueOf() + 1000; + config.messagehandler(event.messageType, event.message, { expiration: expiration }).then(function(response) { + // Wait 2 seconds before calling update() to ensure that the + // license has really expired. This is to avoid problems + // where the browser starts buffering frames as soon as a + // valid license is received. + test.step_timeout(function() { + event.target.update(response).then(function() { + // License server may only have second granularity, so check + // that session expiration time is close to the desired value. + assert_approx_equals(event.target.expiration, expiration, 3000, + "expiration attribute should equal provided expiration time"); + assert_greater_than(Date.now().valueOf(), expiration, "Starting play before license expired"); + _video.play(); + // Wait 2 seconds to ensure that the video does not play. + test.step_timeout(function() { test.done(); }, 2000); + }).catch(onFailure); + }, 2000); + }).catch(onFailure); + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', test.step_func(onTimeupdate), true); + } + + function onTimeupdate(event) { + _video.pause(); + assert_unreached("Playback should not start with expired license"); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function(){ + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-multikey-sequential.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-multikey-sequential.js new file mode 100644 index 0000000000..597e8f9b0c --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-multikey-sequential.js @@ -0,0 +1,122 @@ +function runTest(config,qualifier) { + + // config.initData contains a list of keys. We expect those to be needed in order and get + // one waitingforkey event for each one. + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', successful playback, temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', multiple keys, sequential' + + (config.checkReadyState ? ', readyState' : ''); + + var configuration = { initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + sessionTypes: ['temporary'] }; + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySessions = [], + _mediaSource, + _waitingForKey = false, + _playingCount = 0, + _canplayCount = 0, + _timeupdateWhileWaitingCount = 0; + + function startNewSession() { + assert_less_than(_mediaKeySessions.length, config.initData.length); + var mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('message', mediaKeySession, onMessage, test); + _mediaKeySessions.push(mediaKeySession); + mediaKeySession.variantId = config.variantIds ? config.variantIds[_mediaKeySessions.length - 1] : undefined; + mediaKeySession.generateRequest(config.initDataType, config.initData[_mediaKeySessions.length - 1]).catch(onFailure); + } + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + var firstMessage = !_video.src; + config.messagehandler(event.messageType, event.message, {variantId: event.target.variantId}).then(function(response) { + return event.target.update(response); + }).then(function(){ + if (firstMessage) { + _video.src = URL.createObjectURL(_mediaSource); + return _mediaSource.done; + } else if (event.target.keyStatuses.size > 0){ + _waitingForKey = false; + return Promise.resolve(); + } + }).then(function(){ + if (firstMessage) { + _video.play(); + } + }).catch(onFailure); + } + + function onWaitingForKey(event) { + _waitingForKey = true; + if (config.checkReadyState) { + // This test does not start playing until the first license has been provided, + // so this event should occur when transitioning between keys. + // Thus, the frame at the current playback position is available and readyState + // should be HAVE_CURRENT_DATA. + assert_equals(_video.readyState, _video.HAVE_CURRENT_DATA, "Video readyState should be HAVE_CURRENT_DATA on watingforkey event"); + } + startNewSession(); + } + + function onPlaying(event) { + _playingCount++; + assert_equals(_mediaKeySessions.length, _playingCount, "Should get one 'playing' event per key / session added"); + assert_less_than_equal(_playingCount, 2, "Should not get more than two 'playing' events."); + } + + function onCanPlay(event) { + _canplayCount++; + assert_equals(_mediaKeySessions.length, _canplayCount, "Should get one 'canplay' event per key / session added"); + assert_less_than_equal(_canplayCount, 2, "Should not get more than two 'canplay' events."); + } + + function onTimeupdate(event) { + // We should not receive 'timeupdate' events due to playing while waiting for a key, except + // when we first start waiting for key we should change the readyState to HAVE_CURRENT_DATA + // which will trigger the "If the previous ready state was HAVE_FUTURE_DATA or more, and + // the new ready state is HAVE_CURRENT_DATA or less" case of the readyState change + // algorithm which requires a "timeupdate" event be fired. + if (_waitingForKey) { + assert_equals(++_timeupdateWhileWaitingCount, 1, "Should only receive one timeupdate while waiting for key"); + assert_equals(_video.readyState, _video.HAVE_CURRENT_DATA, "Video readyState should be HAVE_CURRENT_DATA while wating for key"); + } + + if (_video.currentTime > config.duration) { + assert_equals(_mediaKeySessions.length, config.initData.length, "It should require all keys to reach end of content"); + assert_equals(_timeupdateWhileWaitingCount, 1, "Should have only received exactly one timeupdate while waiting for key"); + _video.pause(); + test.done(); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function(){ + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', test.step_func(onTimeupdate), true); + + waitForEventAndRunStep('waitingforkey', _video, onWaitingForKey, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + waitForEventAndRunStep('canplay', _video, onCanPlay, test); + + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + startNewSession(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-multisession.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-multisession.js new file mode 100644 index 0000000000..d23bd1097e --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-multisession.js @@ -0,0 +1,76 @@ +function runTest(config,qualifier) { + + // This test assumes one session is required for each provided initData + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback with multiple sessions, ' + + config.testcase; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySessions = [], + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_any(assert_equals, event.target, _mediaKeySessions); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message, {variantId: event.target._variantId}).then(function(response) { + return event.target.update(response); + }).catch(onFailure); + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + function onTimeupdate(event) { + if (_video.currentTime > (config.duration || 1)) { + _video.removeEventListener('timeupdate', onTimeupdate); + _video.pause(); + test.done(); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + waitForEventAndRunStep('playing', _video, onPlaying, test); + + config.initData.forEach(function(initData,i) { + var mediaKeySession = _mediaKeys.createSession( 'temporary' ); + mediaKeySession._variantId = config.variantIds ? config.variantIds[i] : undefined; + waitForEventAndRunStep('message', mediaKeySession, onMessage, test); + _mediaKeySessions.push(mediaKeySession); + mediaKeySession.generateRequest(config.initDataType, initData).catch(onFailure); + } ); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-playduration-keystatus.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-playduration-keystatus.js new file mode 100644 index 0000000000..e21b8f8f2a --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-playduration-keystatus.js @@ -0,0 +1,78 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback with limited playduration, check keystatus, ' + config.testcase; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + // Only create the session for the first encrypted event + if (_mediaKeySession !== undefined) return; + + var initDataType = config.initData ? config.initDataType : event.initDataType; + var initData = config.initData || event.initData; + + _mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( initDataType, initData ).catch(onFailure); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message, {playDuration: config.playduration}).then(function(response) { + return event.target.update(response); + }).catch(onFailure); + } + + function onPlaying(event) { + waitForEventAndRunStep('keystatuseschange', _mediaKeySession, onKeystatuseschange, test); + } + + function onKeystatuseschange(event) { + for (var status of event.target.keyStatuses.values()) { + assert_equals(status, "expired", "All keys should have keyStatus expired"); + } + test.done(); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function(){ + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-playduration.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-playduration.js new file mode 100644 index 0000000000..6ce6157529 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-playduration.js @@ -0,0 +1,80 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback with limited playduration, ' + config.testcase; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + // Only create the session for the first encrypted event + if (_mediaKeySession !== undefined) return; + + var initDataType = config.initData ? config.initDataType : event.initDataType; + var initData = config.initData || event.initData; + + _mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( initDataType, initData ).catch(onFailure); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message, {playDuration: config.playduration}).then(function(response) { + return event.target.update(response); + }).catch(onFailure); + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', test.step_func(onTimeupdate), true); + test.step_timeout(function(){ + test.done(); + },config.playduration * 2); + } + + function onTimeupdate(event) { + assert_less_than(_video.currentTime * 1000, config.playduration, "Video should not play for more than playDuration from licence"); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function(){ + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-setMediaKeys.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-setMediaKeys.js new file mode 100644 index 0000000000..fddcf56774 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-setMediaKeys.js @@ -0,0 +1,105 @@ +SETMEDIAKEYS_IMMEDIATELY = 0; +SETMEDIAKEYS_AFTER_SRC = 1; +SETMEDIAKEYS_ONENCRYPTED = 2; +SETMEDIAKEYS_AFTER_UPDATE = 3; + +function runTest(config,qualifier) { + + var testcase = (config.testcase === SETMEDIAKEYS_IMMEDIATELY) ? 'setMediaKeys first' + : (config.testcase === SETMEDIAKEYS_AFTER_SRC) ? 'setMediaKeys after setting video.src' + : (config.testcase === SETMEDIAKEYS_ONENCRYPTED) ? 'setMediaKeys in encrypted event' + : (config.testcase === SETMEDIAKEYS_AFTER_UPDATE) ? 'setMediaKeys after updating session' + : 'unknown'; + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback, ' + testcase; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array( event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message).then(function(response) { + return _mediaKeySession.update( response ); + }).then(function() { + if (config.testcase === SETMEDIAKEYS_AFTER_UPDATE) { + return _video.setMediaKeys(_mediaKeys); + } + }).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + var promise = ( config.testcase === SETMEDIAKEYS_ONENCRYPTED ) + ? _video.setMediaKeys(_mediaKeys) + : Promise.resolve(); + + promise.then( function() { + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + return _mediaKeySession.generateRequest(config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ); + }).catch(onFailure); + } + + function onTimeupdate(event) { + if (_video.currentTime > (config.duration || 1)) { + _video.pause(); + test.done(); + } + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(test.step_func(function(mediaKeys) { + _mediaKeys = mediaKeys; + if ( config.testcase === SETMEDIAKEYS_IMMEDIATELY ) { + return _video.setMediaKeys( _mediaKeys ); + } + })).then(function(){ + _mediaKeySession = _mediaKeys.createSession( 'temporary' ); + + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + + if (config.testcase === SETMEDIAKEYS_AFTER_SRC) { + return _video.setMediaKeys(_mediaKeys); + } + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-two-videos.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-two-videos.js new file mode 100644 index 0000000000..b17168d113 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-two-videos.js @@ -0,0 +1,83 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback two videos'; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + promise_test(function(test) + { + var promises = config.video.map(function(video) { return play_video_as_promise(test,video); }); + return Promise.all(promises); + + }, testname); + + function play_video_as_promise(test, _video) { + var _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array( event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message).then(function(response) { + return _mediaKeySession.update(response); + }).catch(onFailure); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + + _mediaKeySession.generateRequest(config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData).catch(onFailure); + } + + function wait_for_timeupdate_message(video) + { + return new Promise(function(resolve) { + video.addEventListener('timeupdate', function listener(event) { + if (event.target.currentTime > (config.duration || 1)) + { + video.removeEventListener('timeupdate', listener); + resolve(event); + } + }); + }); + }; + + return navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + _mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + return wait_for_timeupdate_message(_video); + }).catch(onFailure); + } +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-waitingforkey.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-waitingforkey.js new file mode 100644 index 0000000000..daa9f3b169 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary-waitingforkey.js @@ -0,0 +1,71 @@ +function runTest(config,qualifier) { + + // config.initData contains a list of keys. We expect those to be needed in order and get + // one waitingforkey event for each one. + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', successful playback, temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', waitingforkey event, ' + + config.initData.length + ' key' + (config.initData.length > 1 ? 's' : ''); + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + var _video = config.video, + _mediaKeys, + _mediaKeySessions = [], + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + config.messagehandler( event.messageType, event.message ).then( function( response ) { + return event.target.update( response ); + }).catch(onFailure); + } + + function onWaitingForKey(event) { + // Expect one waitingforkey event for each initData we were given + assert_less_than(_mediaKeySessions.length, config.initData.length); + var mediaKeySession = _mediaKeys.createSession( 'temporary' ); + waitForEventAndRunStep('message', mediaKeySession, onMessage, test); + _mediaKeySessions.push(mediaKeySession); + mediaKeySession.generateRequest(config.initDataType, config.initData[_mediaKeySessions.length - 1]).catch(onFailure); + } + + function onTimeupdate(event) { + if (_video.currentTime > (config.duration || 1)) { + assert_equals(_mediaKeySessions.length, config.initData.length); + _video.removeEventListener('timeupdate', onTimeupdate); + _video.pause(); + test.done(); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function(){ + waitForEventAndRunStep('waitingforkey', _video, onWaitingForKey, test); + + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/playback-temporary.js b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary.js new file mode 100644 index 0000000000..2f79824ec7 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/playback-temporary.js @@ -0,0 +1,82 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + + ', temporary, ' + + /video\/([^;]*)/.exec(config.videoType)[1] + + ', playback, ' + config.testcase; + + var configuration = { initDataTypes: [ config.initDataType ], + audioCapabilities: [ { contentType: config.audioType } ], + videoCapabilities: [ { contentType: config.videoType } ], + sessionTypes: [ 'temporary' ] }; + + async_test(function(test) { + + var _video = config.video, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onEncrypted(event) { + assert_equals(event.target, _video); + assert_true(event instanceof window.MediaEncryptedEvent); + assert_equals(event.type, 'encrypted'); + + // Only create the session for the firs encrypted event + if (_mediaKeySession !== undefined) return; + + var initDataType = config.initData ? config.initDataType : event.initDataType; + var initData = config.initData || event.initData; + + _mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( initDataType, initData ).catch(onFailure); + } + + function onMessage(event) { + assert_equals(event.target, _mediaKeySession); + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.type, 'message'); + + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message).then(function(response) { + return event.target.update(response); + }).catch(onFailure); + } + + function onPlaying(event) { + // Not using waitForEventAndRunStep() to avoid too many + // EVENT(onTimeUpdate) logs. + _video.addEventListener('timeupdate', onTimeupdate, true); + } + + function onTimeupdate(event) { + if ( _video.currentTime > (config.duration || 1)) { + _video.pause(); + test.done(); + } + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(mediaKeys) { + _mediaKeys = mediaKeys; + return _video.setMediaKeys(_mediaKeys); + }).then(function(){ + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + waitForEventAndRunStep('playing', _video, onPlaying, test); + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + _video.src = URL.createObjectURL(_mediaSource); + return source.done; + }).then(function(){ + _video.play(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/requestmediakeysystemaccess.js b/testing/web-platform/tests/encrypted-media/scripts/requestmediakeysystemaccess.js new file mode 100644 index 0000000000..a60d4a1e76 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/requestmediakeysystemaccess.js @@ -0,0 +1,341 @@ +function runTest(config, qualifier) { + + var prefix = testnamePrefix(qualifier, config.keysystem) + ', requestMediaKeySystemAccess: '; + + function expect_error(keySystem, configurations, expectedError, testname) { + + var audioCapabilities = configurations.length ? configurations[0].audioCapabilities : undefined, + videoCapabilities = configurations.length ? configurations[0].videoCapabilities : undefined, + audiocontenttypes = audioCapabilities ? audioCapabilities.map( function(ac) { return "'" + ac.contentType + "'"; } ).join(',') : '', + videocontenttypes = videoCapabilities ? videoCapabilities.map( function(ac) { return "'" + ac.contentType + "'"; } ).join(',') : '', + modifiedtestname = testname.replace( '%audiocontenttype', audiocontenttypes ).replace( '%videocontenttype', videocontenttypes ); + + promise_test(function(test) { + var p = navigator.requestMediaKeySystemAccess(keySystem, configurations); + // expectedError is a string name for the error. We can differentiate + // JS Errors from DOMExceptions by checking whether + // window[expectedError] exists. If it does, expectedError is the name + // of a JS Error subclass and window[expectedError] is the constructor + // for that subclass. Otherwise it's a name for a DOMException. + if (window[expectedError]) { + return promise_rejects_js(test, window[expectedError], p); + } else { + return promise_rejects_dom(test, expectedError, p); + } + }, prefix + modifiedtestname + ' should result in ' + expectedError ); + } + + function assert_subset(actual, expected, path) { + if (typeof expected == 'string') { + assert_equals(actual, expected, path); + } else { + if (expected.hasOwnProperty('length')) { + assert_equals(actual.length, expected.length, path + '.length'); + } + for (property in expected) { + assert_subset(actual[property], expected[property], path + '.' + property); + } + } + } + + function expect_config(keySystem, configurations, expectedConfiguration, testname) { + promise_test(function(test) { + return navigator.requestMediaKeySystemAccess(keySystem, configurations).then(function(a) { + assert_subset(a.getConfiguration(), expectedConfiguration, testname + ': '); + }); + }, testname); + } + + // Tests for Key System. + expect_error('', [{}], 'TypeError', 'Empty Key System'); + expect_error('com.example.unsupported', [{}], 'NotSupportedError', 'Unsupported Key System'); + expect_error(config.keysystem + '.', [{}], 'NotSupportedError', 'Key System ending in "."'); + expect_error(config.keysystem.toUpperCase(), [{}], 'NotSupportedError', 'Capitalized Key System'); + expect_error(config.keysystem + '\u028F', [{}], 'NotSupportedError', 'Non-ASCII Key System'); + + // Parent of Clear Key not supported. + expect_error(config.keysystem.match(/^(.*?)\./)[1], [{}], 'NotSupportedError', 'Root domain of Key System alone'); + expect_error(config.keysystem.match(/^(.*?)\./)[0], [{}], 'NotSupportedError', 'Root domain of Key System, with dot'); + expect_error(config.keysystem.match(/^(.*?\..*?)\./)[1], [{}], 'NotSupportedError', 'Domain of Key System along'); + expect_error(config.keysystem.match(/^(.*?\..*?)\./)[0], [{}], 'NotSupportedError', 'Domain of Key System, with dot'); + + // Child of Clear Key not supported. + expect_error(config.keysystem+'.foo', [{}], 'NotSupportedError', 'Child of Key System'); + + // Prefixed Clear Key not supported. + expect_error('webkit-'+config.keysystem, [{}], 'NotSupportedError', 'Prefixed Key System'); + + // Incomplete names. + expect_error(config.keysystem.substr(0,7)+config.keysystem.substr(8), [{}], 'NotSupportedError', 'Missing characters in middle of Key System name'); + expect_error(config.keysystem.substr(0,config.keysystem.length-1), [{}], 'NotSupportedError', 'Missing characters at end of Key System name'); + + // Spaces in key system name not supported. + expect_error(' '+config.keysystem, [{}], 'NotSupportedError', 'Leading space in Key System name'); + expect_error(config.keysystem.substr(0,6) + ' ' + config.keysystem.substr(6), [{}], 'NotSupportedError', 'Extra space in Key System name'); + expect_error(config.keysystem + ' ', [{}], 'NotSupportedError', 'Trailing space in Key System name'); + + // Extra dots in key systems names not supported. + expect_error('.' + config.keysystem, [{}], 'NotSupportedError', 'Leading dot in Key System name'); + expect_error(config.keysystem.substr(0,6) + '.' + config.keysystem.substr(6), [{}], 'NotSupportedError', 'Extra dot in middle of Key System name'); + expect_error(config.keysystem + '.', [{}], 'NotSupportedError', 'Trailing dot in Key System name'); + + // Key system name is case sensitive. + if (config.keysystem !== config.keysystem.toUpperCase()) { + expect_error(config.keysystem.toUpperCase(), [{}], 'NotSupportedError', 'Key System name is case sensitive'); + } + + if (config.keysystem !== config.keysystem.toLowerCase()) { + expect_error(config.keysystem.toLowerCase(), [{}], 'NotSupportedError', 'Key System name is case sensitive'); + } + + // Tests for trivial configurations. + expect_error(config.keysystem, [], 'TypeError', 'Empty supportedConfigurations'); + expect_error(config.keysystem, [{}], 'NotSupportedError', 'Empty configuration'); + + // Various combinations of supportedConfigurations. + expect_config(config.keysystem, [{ + initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + label: 'abcd', + }], { + initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + label: 'abcd', + }, 'Basic supported configuration'); + + expect_config(config.keysystem, [{ + initDataTypes: ['fakeidt', config.initDataType], + audioCapabilities: [{contentType: 'audio/fake'}, {contentType: config.audioType}], + videoCapabilities: [{contentType: 'video/fake'}, {contentType: config.videoType}], + }], { + initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + }, 'Partially supported configuration'); + + expect_config(config.keysystem, [{ + audioCapabilities: [{contentType: config.audioType}], + }], { + audioCapabilities: [{contentType: config.audioType}], + }, 'Supported audio codec'); + + expect_config(config.keysystem, [{ + audioCapabilities: [{contentType: config.audioType.replace(/^(.*?);(.*)/, "$1; $2")}], + }], { + audioCapabilities: [{contentType: config.audioType.replace(/^(.*?);(.*)/, "$1; $2")}], + }, 'ContentType formatting must be preserved'); + + expect_error(config.keysystem, [{ + audioCapabilities: [{contentType: 'audio/webm; codecs=fake'}], + }], 'NotSupportedError', 'Unsupported audio codec (%audiocontenttype)'); + + expect_error(config.keysystem, [{ + audioCapabilities: [{contentType: 'video/webm; codecs=fake'}], + }], 'NotSupportedError', 'Unsupported video codec (%videocontenttype)'); + + expect_error(config.keysystem, [{ + audioCapabilities: [ + {contentType: 'audio/webm; codecs=mp4a'}, + {contentType: 'audio/webm; codecs=mp4a.40.2'} + ], + }], 'NotSupportedError', 'Mismatched audio container/codec (%audiocontenttype)'); + + expect_error(config.keysystem, [{ + audioCapabilities: [{contentType: config.videoType}], + }], 'NotSupportedError', 'Video codec specified in audio field (%audiocontenttype)'); + + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: config.audioType}], + }], 'NotSupportedError', 'Audio codec specified in video field (%videocontenttype)'); + + expect_error(config.keysystem, [{ + audioCapabilities: [ + {contentType: 'audio/webm; codecs=avc1'}, + {contentType: 'audio/webm; codecs=avc1.42e01e'} + ], + }], 'NotSupportedError', 'Mismatched audio container/codec (%audiocontenttype)'); + + expect_error(config.keysystem, [{ + audioCapabilities: [ + {contentType: 'audio/mp4; codecs=vorbis'} + ], + }], 'NotSupportedError', 'Mismatched audio container/codec (%audiocontenttype)'); + + expect_config(config.keysystem, [{ + initDataTypes: ['fakeidt'], + videoCapabilities: [{contentType: config.videoType}] + }, { + initDataTypes: [config.initDataType], + videoCapabilities: [{contentType: config.videoType}] + } + ], { + initDataTypes: [config.initDataType], + videoCapabilities: [{contentType: config.videoType}] + }, 'Two configurations, one supported'); + + expect_config(config.keysystem, [{ + initDataTypes: [config.initDataType], + videoCapabilities: [{contentType: config.videoType}] + }, { + videoCapabilities: [{contentType: config.videoType}] + } + ], { + initDataTypes: [config.initDataType], + videoCapabilities: [{contentType: config.videoType}] + }, 'Two configurations, both supported'); + + // Audio MIME type does not support video codecs. + expect_error(config.keysystem, [{ + audioCapabilities: [ + {contentType: 'audio/webm; codecs="vp8,vorbis"'}, + {contentType: 'audio/webm; codecs="vorbis, vp8"'}, + {contentType: 'audio/webm; codecs="vp8"'} + ], + }], 'NotSupportedError', 'Audio MIME type does not support video codecs (webm) (%audiocontenttype)'); + + expect_error(config.keysystem, [{ + audioCapabilities: [ + {contentType: 'audio/mp4; codecs="avc1"'}, + {contentType: 'audio/mp4; codecs="avc1.4d401e"'}, + ], + }], 'NotSupportedError', 'Audio MIME type does not support video codecs (mp4) (%audiocontenttype)'); + + // Video MIME type does not support audio codecs. + expect_error(config.keysystem, [{ + videoCapabilities: [ + {contentType: 'video/webm; codecs="vp8,vorbis"'}, + {contentType: 'video/webm; codecs="vorbis, vp8"'}, + {contentType: 'video/webm; codecs="vorbis"'} + ], + }], 'NotSupportedError', 'Video MIME type does not support audio codecs (webm) (%videocontenttype)'); + + expect_error(config.keysystem, [{ + videoCapabilities: [ + {contentType: 'video/mp4; codecs="mp4a"'}, + {contentType: 'video/mp4; codecs="mp4a.40.2"'} + ], + }], 'NotSupportedError', 'Video MIME type does not support audio codecs (mp4) (%videocontenttype)'); + + // WebM does not support AVC1/AAC. + expect_error(config.keysystem, [{ + audioCapabilities: [ + {contentType: 'audio/webm; codecs="aac"'}, + {contentType: 'audio/webm; codecs="avc1"'}, + {contentType: 'audio/webm; codecs="vp8,aac"'} + ], + }], 'NotSupportedError', 'WebM audio does not support AVC1/AAC (%audiocontenttype)'); + + expect_error(config.keysystem, [{ + videoCapabilities: [ + {contentType: 'video/webm; codecs="aac"'}, + {contentType: 'video/webm; codecs="avc1"'}, + {contentType: 'video/webm; codecs="vp8,aac"'} + ], + }], 'NotSupportedError', 'WebM video does not support AVC1/AAC (%videocontenttype)'); + + // Extra space is allowed in contentType. + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: ' ' + config.videoType}], + }], { + videoCapabilities: [{contentType: ' ' + config.videoType}], + }, 'Leading space in contentType'); + + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?);(.*)/, "$1 ;$2")}], + }], { + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?);(.*)/, "$1 ;$2")}], + }, 'Space before ; in contentType'); + + + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: config.videoType + ' '}], + }], { + videoCapabilities: [{contentType: config.videoType + ' '}], + }, 'Trailing space in contentType'); + + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?codecs=\")(.*)/, "$1 $2")}], + }], { + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?codecs=\")(.*)/, "$1 $2")}], + }, 'Space at start of codecs parameter'); + + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?codecs=\".*)\"/, "$1 \"")}], + }], { + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?codecs=\".*)\"/, "$1 \"")}], + }, 'Space at end of codecs parameter'); + + // contentType is not case sensitive (except the codec names). + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: 'V' + config.videoType.substr(1)}], + }], { + videoCapabilities: [{contentType: 'V' + config.videoType.substr(1)}], + }, 'Video/' ); + + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?)c(odecs.*)/, "$1C$2")}], + }], { + videoCapabilities: [{contentType: config.videoType.replace( /^(.*?)c(odecs.*)/, "$1C$2")}], + }, 'Codecs='); + + var t = config.videoType.match(/(.*?)(;.*)/); + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: t[1].toUpperCase() + t[2]}], + }], { + videoCapabilities: [{contentType: t[1].toUpperCase() + t[2]}], + }, 'Upper case MIME type'); + + t = config.videoType.match(/(.*?)codecs(.*)/); + expect_config(config.keysystem, [{ + videoCapabilities: [{contentType: t[1] + 'CODECS' + t[2]}], + }], { + videoCapabilities: [{contentType: t[1] + 'CODECS' + t[2]}], + }, 'CODECS='); + + // Unrecognized attributes are not allowed. + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: 'video/webm; foo="bar"'}], + }], 'NotSupportedError', 'Unrecognized foo with webm (%videocontenttype)'); + + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: 'video/mp4; foo="bar"'}], + }], 'NotSupportedError', 'Unrecognized foo with mp4 (%videocontenttype)'); + + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: config.videoType + '; foo="bar"'}], + }], 'NotSupportedError', 'Unrecognized foo with codecs (%videocontenttype)'); + + // Invalid contentTypes. + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: 'fake'}], + }], 'NotSupportedError', 'contentType: %videocontenttype'); + + expect_error(config.keysystem, [{ + audioCapabilities: [{contentType: 'audio/fake'}], + }], 'NotSupportedError', 'contentType: %audiocontenttype'); + + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: 'video/fake'}], + }], 'NotSupportedError', 'contentType: %videocontenttype'); + + // The actual codec names are case sensitive. + t = config.videoType.match( /(.*?codecs=\")(.*?\")(.*)/ ); + if (t[2] !== t[2].toUpperCase()) { + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: t[1] + t[2].toUpperCase() + t[3] }], + }], 'NotSupportedError', 'contentType: %videocontenttype'); + } + + if (t[2] !== t[2].toLowerCase()) { + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: t[1] + t[2].toLowerCase() + t[3] }], + }], 'NotSupportedError', 'contentType: %videocontenttype'); + } + + // Extra comma is not allowed in codecs. + expect_error(config.keysystem, [{ + videoCapabilities: [{contentType: t[1] + ',' + t[2] + t[3] }], + }], 'NotSupportedError', 'contentType: %videocontenttype'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/reset-src-after-setmediakeys.js b/testing/web-platform/tests/encrypted-media/scripts/reset-src-after-setmediakeys.js new file mode 100644 index 0000000000..0ccce3275e --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/reset-src-after-setmediakeys.js @@ -0,0 +1,61 @@ +function runTest(config) +{ + async_test(function(test) { + var mediaKeys; + var mediaSource; + var encryptedEventIndex = 0; + var video = config.video; + var keysystem = config.keysystem; + var configuration = { + initDataTypes: [config.initDataType], + audioCapabilities: [{ + contentType: config.audioType + }], + videoCapabilities: [{ + contentType: config.videoType + }], + sessionTypes: ['temporary'] + }; + + assert_not_equals(video, null); + + var onEncrypted = function(event) { + ++encryptedEventIndex; + assert_equals(video.mediaKeys, mediaKeys); + + // This event is fired once for the audio stream and once + // for the video stream each time .src is set. + if (encryptedEventIndex === 2) { + // Finished first video; Create new media source and wait for two more encrypted events + return testmediasource(config).then(function (source) { + video.src = URL.createObjectURL(source); + }).catch(function (error) { + forceTestFailureFromPromise(test, error) + }); + } else if (encryptedEventIndex === 4) { + // Finished second video. + test.done(); + } + }; + + // Create a MediaKeys object and assign it to video. + navigator.requestMediaKeySystemAccess(keysystem, [configuration]).then(test.step_func(function (access) { + assert_equals(access.keySystem, keysystem); + return access.createMediaKeys(); + })).then(test.step_func(function (result) { + mediaKeys = result; + assert_not_equals(mediaKeys, null); + return video.setMediaKeys(mediaKeys); + })).then(test.step_func(function () { + assert_equals(video.mediaKeys, mediaKeys); + return testmediasource(config); + })).then(function (source) { + waitForEventAndRunStep('encrypted', video, onEncrypted, test); + mediaSource = source; + video.src = URL.createObjectURL(mediaSource); + }).catch(function (error) { + forceTestFailureFromPromise(test, error); + }); + + }, 'Reset src after setMediaKeys().'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-again-after-playback.js b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-again-after-playback.js new file mode 100644 index 0000000000..772bfcaa87 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-again-after-playback.js @@ -0,0 +1,79 @@ +function runTest(config, qualifier) { + var testname = testnamePrefix(qualifier, config.keysystem) + + ', setmediakeys again after playback'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if (config.initDataType && config.initData) { + configuration.initDataTypes = [config.initDataType]; + } + + async_test(function(test) { + var _video = config.video, + _access, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + config.messagehandler(event.messageType, event.message).then( function(response) { + _mediaKeySession.update(response).catch(onFailure).then(function() { + _video.play(); + }); + }); + } + + function onEncrypted(event) { + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ) + .catch(onFailure); + } + + function playVideo() + { + return new Promise(function(resolve) { + _mediaKeySession = _mediaKeys.createSession('temporary'); + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + _video.src = URL.createObjectURL(_mediaSource); + resolve('success'); + }); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + _access = access; + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys = result; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + return config.servercertificate ? _mediaKeys.setServerCertificate( config.servercertificate ) : true; + }).then(function( success ) { + return testmediasource(config); + }).then(function(source) { + _mediaSource = source; + return playVideo(); + }).then(function(results) { + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys = result; + return waitForEvent('playing', _video); + }).then(test.step_func(function(result) { + assert_false(_video.ended); + return _video.setMediaKeys(_mediaKeys); + })).then(function() { + // Able to change MediaKeys while playing. + // This is not required to fail. + _video.src=''; + test.done(); + }, test.step_func(function(error) { + assert_in_array(error.name, ['InvalidStateError','NotSupportedError']); + _video.src=''; + test.done(); + })).catch(onFailure); + }, testname); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-again-after-resetting-src.js b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-again-after-resetting-src.js new file mode 100644 index 0000000000..a870600982 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-again-after-resetting-src.js @@ -0,0 +1,79 @@ +function runTest(config, qualifier) { + var testname = testnamePrefix(qualifier, config.keysystem) + + ', setmediakeys again after resetting src'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if (config.initDataType && config.initData) { + configuration.initDataTypes = [config.initDataType]; + } + + async_test(function(test) { + var _video = config.video, + _access, + _mediaKeys, + _mediaKeySession, + _mediaSource; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function onMessage(event) { + config.messagehandler(event.messageType, event.message).then(function(response) { + _mediaKeySession.update(response).catch(onFailure).then(function() { + _video.play(); + }); + }); + } + + function onEncrypted(event) { + waitForEventAndRunStep('message', _mediaKeySession, onMessage, test); + _mediaKeySession.generateRequest( config.initData ? config.initDataType : event.initDataType, + config.initData || event.initData ) + .catch(onFailure); + } + + function playVideoAndWaitForTimeupdate() + { + return new Promise(function(resolve) { + testmediasource(config).then(function(source) { + _mediaKeySession = _mediaKeys.createSession('temporary'); + _video.src = URL.createObjectURL(source); + }); + _video.addEventListener('timeupdate', function listener(event) { + if (event.target.currentTime < (config.duration || 1)) + return; + _video.removeEventListener('timeupdate', listener); + resolve('success'); + }); + }); + } + + waitForEventAndRunStep('encrypted', _video, onEncrypted, test); + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + _access = access; + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys = result; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + return config.servercertificate ? _mediaKeys.setServerCertificate( config.servercertificate ) : true; + }).then(function( success ) { + return playVideoAndWaitForTimeupdate(); + }).then(function(results) { + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys = result; + _video.src = ''; + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + return config.servercertificate ? _mediaKeys.setServerCertificate( config.servercertificate ) : true; + }).then(function( success ) { + return playVideoAndWaitForTimeupdate(); + }).then(function() { + _video.src = ''; + test.done(); + }).catch(onFailure); + }, testname); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-at-same-time.js b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-at-same-time.js new file mode 100644 index 0000000000..6d67d95b1f --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-at-same-time.js @@ -0,0 +1,59 @@ +function runTest(config, qualifier) { + var testname = testnamePrefix(qualifier, config.keysystem) + + ', setmediakeys at same time'; + + var configuration = getSimpleConfigurationForContent(config.content); + + async_test(function(test) { + var _video = config.video, + _access, + _mediaKeys1, + _mediaKeys2, + _mediaKeys3, + _mediaKeys4, + _mediaKeys5; + + // Test MediaKeys assignment. + assert_equals(_video.mediaKeys, null); + assert_equals(typeof _video.setMediaKeys, 'function'); + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function setMediaKeys(mediaKeys) { + return _video.setMediaKeys(mediaKeys) + .then(function() {return 1}, function() {return 0}) + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + _access = access; + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys1 = result; + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys2 = result; + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys3 = result; + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys4 = result; + return _access.createMediaKeys(); + }).then(function(result) { + _mediaKeys5 = result; + return Promise.all([ + setMediaKeys(_mediaKeys1), + setMediaKeys(_mediaKeys2), + setMediaKeys(_mediaKeys3), + setMediaKeys(_mediaKeys4), + setMediaKeys(_mediaKeys5) + ]); + }).then(function(results) { + var sum = results.reduce((a, b) => a + b, 0); + assert_in_array(sum,[1,5]); + test.done(); + }).catch(onFailure); + }, testname); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-multiple-times-with-different-mediakeys.js b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-multiple-times-with-different-mediakeys.js new file mode 100644 index 0000000000..ef44477b16 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-multiple-times-with-different-mediakeys.js @@ -0,0 +1,98 @@ +function runTest(config, qualifier) { + var testname = testnamePrefix( qualifier, config.keysystem ) + + ', setmediakeys multiple times with different mediakeys'; + + var configuration = getSimpleConfigurationForContent( config.content ); + + async_test (function (test) { + var _video = config.video, + _access, + _mediaKeys1, + _mediaKeys2, + _usingMediaKeys2 = false;; + + // Test MediaKeys assignment. + assert_equals(_video.mediaKeys, null); + assert_equals(typeof _video.setMediaKeys, 'function'); + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + _access = access; + return _access.createMediaKeys(); + }).then(test.step_func(function(result) { + _mediaKeys1 = result; + assert_not_equals(_mediaKeys1, null); + // Create a second mediaKeys. + return _access.createMediaKeys(); + })).then(test.step_func(function(result) { + _mediaKeys2 = result; + assert_not_equals(_mediaKeys2, null); + // Set _mediaKeys1 on video. + return _video.setMediaKeys(_mediaKeys1); + })).then(test.step_func(function() { + assert_equals(_video.mediaKeys, _mediaKeys1); + // Set _mediaKeys2 on video (switching MediaKeys). + return _video.setMediaKeys(_mediaKeys2); + })).then(test.step_func(function() { + assert_equals(_video.mediaKeys, _mediaKeys2); + // Clear mediaKeys from video. + return _video.setMediaKeys(null); + })).then(test.step_func(function() { + assert_equals(_video.mediaKeys, null); + // Set _mediaKeys1 on video again. + return _video.setMediaKeys(_mediaKeys1); + })).then(test.step_func(function() { + assert_equals(_video.mediaKeys, _mediaKeys1); + return testmediasource(config); + })).then(function(source) { + // Set src attribute on Video Element + _video.src = URL.createObjectURL(source); + // According to the specification, support for changing the Media Keys object after + // the src attribute on the video element has been set is optional. The following operation + // may therefore either succeed or fail. We handle both cases. + return _video.setMediaKeys(_mediaKeys2); + }).then(test.step_func(function() { + // Changing the Media Keys object succeeded + _usingMediaKeys2 = true; + assert_equals(_video.mediaKeys, _mediaKeys2); + // Return something so the promise resolves properly. + return Promise.resolve(); + }), test.step_func(function(error) { + // Changing the Media Keys object failed + _usingMediaKeys2 = false; + assert_equals(_video.mediaKeys, _mediaKeys1); + // The specification allows either NotSupportedError or InvalidStateError depending on + // whether the failure was because changing Media Keys object is not supported + // or just not allowed at this time, respectively. + assert_in_array(error.name, ['InvalidStateError','NotSupportedError']); + assert_not_equals(error.message, ''); + // Return something so the promise resolves properly. + return Promise.resolve(); + })).then(function() { + // According to the specification, support for clearing the Media Keys object associated + // with the video element is optional. The following operation + // may therefore either succeed or fail. We handle both cases. + return _video.setMediaKeys(null); + }).then(test.step_func(function() { + // Clearing the media keys succeeded + assert_equals(_video.mediaKeys, null); + test.done(); + }), test.step_func(function(error) { + // Clearing the media keys failed + if(!_usingMediaKeys2) { + assert_equals(_video.mediaKeys, _mediaKeys1); + } else { + assert_equals(_video.mediaKeys, _mediaKeys2); + } + // The specification allows either NotSupportedError or InvalidStateError depending on + // whether the failure was because changing Media Keys object is not supported + // or just not allowed at this time, respectively. + assert_in_array(error.name, ['InvalidStateError','NotSupportedError']); + assert_not_equals(error.message, ''); + test.done(); + })).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-multiple-times-with-the-same-mediakeys.js b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-multiple-times-with-the-same-mediakeys.js new file mode 100644 index 0000000000..f6af8267f2 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-multiple-times-with-the-same-mediakeys.js @@ -0,0 +1,46 @@ +function runTest(config, qualifier) { + var testname = testnamePrefix( qualifier, config.keysystem ) + + ', setmediakeys multiple times with the same mediakeys'; + + var configuration = getSimpleConfigurationForContent( config.content ); + + if ( config.initDataType && config.initData ) { + configuration.initDataTypes = [ config.initDataType ]; + } + + async_test (function (test) { + var _video = config.video, + _mediaKeys; + + // Test MediaKeys assignment. + assert_equals(_video.mediaKeys, null); + assert_equals(typeof _video.setMediaKeys, 'function'); + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + return access.createMediaKeys(); + }).then(function(result) { + _mediaKeys = result; + // Set mediaKeys for first time on video should work. + return _video.setMediaKeys(_mediaKeys); + }).then(function(result) { + assert_equals(_video.mediaKeys, _mediaKeys); + // Set mediaKeys on video again should return a resolved promise. + return _video.setMediaKeys(_mediaKeys); + }).then(function (result) { + assert_equals(_video.mediaKeys, _mediaKeys); + return testmediasource(config); + }).then(function(source) { + // Set src attribute on Video Element + _video.src = URL.createObjectURL(source); + // Set mediaKeys again on video should still return a resolved promise. + return _video.setMediaKeys(_mediaKeys); + }).then(function() { + assert_equals(_video.mediaKeys, _mediaKeys); + test.done(); + }).catch(onFailure); + }, testname); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-to-multiple-video-elements.js b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-to-multiple-video-elements.js new file mode 100644 index 0000000000..4c7faef69d --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys-to-multiple-video-elements.js @@ -0,0 +1,54 @@ +function runTest(config, qualifier) { + var testname = testnamePrefix(qualifier, config.keysystem) + + ', setMediaKeys to multiple video elements'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if ( config.initDataType && config.initData ) { + configuration.initDataTypes = [ config.initDataType ]; + } + + async_test (function (test) { + var _video1 = config.video1, + _video2 = config.video2, + _mediaKeys; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + assert_equals(access.keySystem, config.keysystem) + return access.createMediaKeys(); + }).then(function(result) { + _mediaKeys = result; + assert_not_equals(_mediaKeys, null); + assert_equals(typeof _mediaKeys.createSession, 'function'); + return _video1.setMediaKeys(_mediaKeys); + }).then(function(result) { + assert_not_equals(_video1.mediaKeys, null); + assert_equals(_video1.mediaKeys, _mediaKeys); + // The specification allows this to fail, but it is not required to fail. + return _video2.setMediaKeys(_mediaKeys); + }).then(function(result) { + // Second setMediaKeys is not required to fail. + assert_equals(_video2.mediaKeys, _mediaKeys); + return Promise.resolve(); + }, function(error) { + assert_equals(error.name, 'QuotaExceededError'); + assert_not_equals(error.message, ''); + // Return something so the promise resolves properly. + return Promise.resolve(); + }).then(function() { + // Now clear it from video1. + return _video1.setMediaKeys(null); + }).then(function() { + // Should be assignable to video2. + return _video2.setMediaKeys(_mediaKeys); + }).then(function(result) { + assert_not_equals(_video2.mediaKeys, null); + assert_equals(_video2.mediaKeys, _mediaKeys); + test.done(); + }).catch(onFailure); + }, testname); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/encrypted-media/scripts/setmediakeys.js b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys.js new file mode 100644 index 0000000000..a85adeaeaf --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/setmediakeys.js @@ -0,0 +1,50 @@ +function runTest(config, qualifier) { + var testname = testnamePrefix( qualifier, config.keysystem ) + + ', setMediaKeys'; + + var configuration = getSimpleConfigurationForContent( config.content ); + + if ( config.initDataType && config.initData ) { + configuration.initDataTypes = [ config.initDataType ]; + } + + async_test (function (test) { + var _video = config.video, + _mediaKeys; + + // Test MediaKeys assignment. + assert_equals(_video.mediaKeys, null); + assert_equals(typeof _video.setMediaKeys, 'function'); + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + // Try setting mediaKeys to null. + _video.setMediaKeys(null).then(function(result) { + assert_equals(_video.mediaKeys, null); + + // setMediaKeys should fail when setting to the wrong type of object - Date. + return _video.setMediaKeys(new Date()); + }).then(function (result) { + assert_unreached('setMediaKeys should fail when setting to wrong kind of object (Date)'); + }, function(error) { + // The error should be TypeError. + assert_throws_js(TypeError, () => { throw error; }, + 'setMediaKeys should return a TypeError when setting to wrong kind of object (Date)'); + return navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]); + }).then(function(access) { + assert_equals(access.keySystem, config.keysystem) + return access.createMediaKeys(); + }).then(function(result) { + _mediaKeys = result; + assert_not_equals(_mediaKeys, null); + assert_equals(typeof _mediaKeys.createSession, 'function'); + return _video.setMediaKeys(_mediaKeys); + }).then(function(result) { + assert_not_equals(_video.mediaKeys, null); + assert_equals(_video.mediaKeys, _mediaKeys); + test.done(); + }).catch(onFailure); + }, testname); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeys.js b/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeys.js new file mode 100644 index 0000000000..4ec6551e8f --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeys.js @@ -0,0 +1,184 @@ +function runTest(config) { + var keysystem = config.keysystem; + var testname = testnamePrefix(null, config.keysystem); + var initDataType = config.initDataType; + var configuration = { + initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + sessionTypes: ['temporary'] + }; + + function createMediaKeysAttributeTest() { + return new Promise(function (resolve, reject) { + var access; + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_equals(typeof navigator.requestMediaKeySystemAccess, 'function'); + assert_true(isTypeSupported, "initDataType should be supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (result) { + access = result; + assert_equals(access.keySystem, keysystem); + 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'); + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + + promise_test(function() { + return createMediaKeysAttributeTest(); + }, testname + ' test MediaKeys attribute syntax'); + + 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)); + } + } + ]; + + + function setServerCertificateTestExceptions() { + return new Promise(function(resolve, reject) { + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_equals(typeof navigator.requestMediaKeySystemAccess, 'function'); + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).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 () { + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return setServerCertificateTestExceptions(); + }, testname + ' test MediaKeys setServerCertificate() exceptions.'); + + // All calls to |func| in this group resolve. setServerCertificate with these cert may either resolve with true + // for clearkey or throw a DOMException. + var kSetServerCertificateTestCases = [ + { + // Pass in ArrayBufferView + func: function (mk) { + var cert = new Uint8Array(200); + assert_true(ArrayBuffer.isView(cert)); + + return new Promise(function (resolve, reject) { + mk.setServerCertificate(cert).then(function (value) { + resolve(value); + }).catch(function (error) { + if (Object.prototype.toString.call(error) === "[object DOMException]") { + resolve(false); + } + }); + }) + }, + expected: false + }, + { + // Pass in ArrayBuffer. + func: function (mk) { + var cert = new ArrayBuffer(200); + assert_false(ArrayBuffer.isView(cert)); + return new Promise(function (resolve) { + mk.setServerCertificate(cert).then(function (resolveValue) { + resolve(resolveValue); + }).catch(function (error) { + if (Object.prototype.toString.call(error) === "[object DOMException]") { + resolve(false); + } + }); + }) + }, + expected: false + } + ]; + function setServerCertificateTest(){ + return new Promise(function(resolve, reject){ + var expected_result; + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_equals(typeof navigator.requestMediaKeySystemAccess, 'function'); + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).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); + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return setServerCertificateTest(); + }, testname + ' test MediaKeys setServerCertificate() syntax with non-empty certificate.'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeysession.js b/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeysession.js new file mode 100644 index 0000000000..fac31cbb3e --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeysession.js @@ -0,0 +1,452 @@ +function runTest(config) { + var keysystem = config.keysystem; + var testname = testnamePrefix(null, config.keysystem); + var initDataType = config.initDataType; + var initData = config.initData; + var configuration = { + initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + sessionTypes: ['temporary'] + }; + + 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); + } + }, + // (new Uint8Array(0)) returns empty array. So 'TypeError' should + // be returned. + { + exception: 'TypeError', + func: function (mk6, type) { + return mk6.createSession().generateRequest(type, new Uint8Array(0)); + } + }, + // Using an empty type should return a 'TypeError'. + { + exception: 'TypeError', + func: function (mk7, type) { + return mk7.createSession().generateRequest('', initData); + } + }, + ]; + function generateRequestTestExceptions(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + var mp4SessionPromises = kTypeSpecificGenerateRequestExceptionsTestCases.map(function (testCase) { + return test_exception(testCase, mediaKeys, initDataType, initData); + }); + assert_not_equals(mp4SessionPromises.length, 0); + return Promise.all(mp4SessionPromises); + }).then(function (result) { + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return generateRequestTestExceptions(); + }, testname + ' test MediaKeySession generateRequest() exceptions.'); + + var kLoadExceptionsTestCases = [ + // Too few parameters. + { + exception: 'TypeError', + func: function (mk1) { + return mk1.createSession('temporary').load(); + } + }, + { + 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'); + } + } + ]; + function loadTestExceptions(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + var sessionPromises = kLoadExceptionsTestCases.map(function (testCase) { + return test_exception(testCase, mediaKeys); + }); + assert_not_equals(sessionPromises.length, 0); + return Promise.all(sessionPromises); + }).then(function () { + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return loadTestExceptions(); + }, testname + ' test MediaKeySession 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 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); + assert_equals(e.name, 'NotSupportedError'); + return Promise.resolve('not supported'); + } + return mediaKeySession.generateRequest(type, initData); + } + function generateRequestForVariousSessions(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_true(isTypeSupported, "initDataType should be supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + var mp4SessionPromises = kCreateSessionTestCases.map(function (testCase) { + return test_generateRequest(testCase, mediaKeys, initDataType, initData); + }); + assert_not_equals(mp4SessionPromises.length, 0); + return Promise.all(mp4SessionPromises); + }).then(function () { + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return generateRequestForVariousSessions(); + }, testname + ' test if MediaKeySession generateRequest() resolves for various sessions'); + + 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); + } + }, + // (new Uint8Array(0)) returns empty array. So 'TypeError' should + // be returned. + { + exception: 'TypeError', + func: function (s) { + return s.update(new Uint8Array(0)); + } + } + ]; + + function updateTestExceptions(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + var mp4SessionPromises = kUpdateSessionExceptionsTestCases.map(function (testCase) { + var mediaKeySession = mediaKeys.createSession(); + return mediaKeySession.generateRequest(initDataType, initData).then(function (result) { + return test_exception(testCase, mediaKeySession); + }); + }); + assert_not_equals(mp4SessionPromises.length, 0); + return Promise.all(mp4SessionPromises); + }).then(function () { + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return updateTestExceptions(); + }, testname + ' test MediaKeySession update() exceptions.'); + + function create_close_exception_test(mediaKeys) { + 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(); + }); + } + function closeTestExceptions(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + return create_close_exception_test(mediaKeys); + }).then(function () { + resolve(); + }).catch(function (error) { + reject(error); + }); + }); + } + promise_test(function() { + return closeTestExceptions(); + }, testname + ' test MediaKeySession close() exceptions.'); + + 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'); + }); + } + function removeTestException(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + return create_remove_exception_test(mediaKeys, initDataType, initData); + }).then(function () { + resolve(); + }).catch(function (error) { + reject(error); + }); + }); + } + promise_test(function() { + return removeTestException(); + }, testname + ' test MediaKeySession remove() 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, ''); + } + function createSessionTest(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + kCreateSessionTestCases.map(function (testCase) { + test_createSession(testCase, mediaKeys); + }); + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return createSessionTest(); + }, testname + ' test MediaKeySession attribute syntax.'); + + +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeysystemaccess.js b/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeysystemaccess.js new file mode 100644 index 0000000000..bbc7b6700b --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/syntax-mediakeysystemaccess.js @@ -0,0 +1,147 @@ +function runTest(config) { + var keysystem = config.keysystem; + var testname = testnamePrefix(null, config.keysystem); + var initDataType = config.initDataType; + var configuration = { + initDataTypes: [config.initDataType], + audioCapabilities: [{contentType: config.audioType}], + videoCapabilities: [{contentType: config.videoType}], + sessionTypes: ['temporary'] + }; + + var kRequestMediaKeySystemAccessExceptionsTestCases = [ + // Too few parameters. + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(); + } + }, + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(keysystem); + } + }, + // 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: 'NotSupportedError', + func: function () { + return navigator.requestMediaKeySystemAccess('unsupported', [{}]); + } + }, + // Empty keysystem string should be rejected with TypeError. + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess('', [{}]); + } + }, + // (new Uint8Array(0)).toString() should return ''. So this case should be the same + // as the above. + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(new Uint8Array(0), [{}]); + } + }, + // Non-ASCII names. + { + exception: 'NotSupportedError', + func: function () { + return navigator.requestMediaKeySystemAccess(keysystem + '\u263A', [{}]); + } + }, + // Empty sequence of MediaKeySystemConfiguration. + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(keysystem, []); + } + }, + // Things that don't convert to valid sequences of MediaKeySystemConfigurations. + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(keysystem, {}); + } + }, + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(keysystem, "invalid"); + } + }, + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(keysystem, [{}, 6]); + } + }, + { + exception: 'TypeError', + func: function () { + return navigator.requestMediaKeySystemAccess(keysystem, ["invalid", "upsupported"]); + } + } + ]; + + function requestMediaKeySystemAccessTestExceptions(){ + return new Promise(function(resolve, reject){ + var createPromises = kRequestMediaKeySystemAccessExceptionsTestCases.map(function (testCase) { + return test_exception(testCase); + }); + Promise.all(createPromises).then(function (result) { + resolve(); + }).catch(function (error) { + reject(error); + }); + }) + } + promise_test(function() { + return requestMediaKeySystemAccessTestExceptions(); + }, testname + ' test requestMediaKeySystemAccess() exceptions.'); + + function requestMediaKeySystemAccessTestAttributes(){ + return new Promise(function(resolve, reject){ + isInitDataTypeSupported(keysystem, initDataType).then(function (isTypeSupported) { + assert_equals(typeof navigator.requestMediaKeySystemAccess, 'function'); + assert_true(isTypeSupported, "initDataType not supported"); + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]); + }).then(function (access) { + assert_not_equals(access, null); + assert_equals(typeof access, 'object'); + assert_equals(access.keySystem, keysystem); + assert_equals(typeof access.getConfiguration, 'function'); + assert_equals(typeof access.createMediaKeys, 'function'); + resolve(); + }).catch(function(error){ + reject(error); + }) + }) + } + promise_test(function() { + return requestMediaKeySystemAccessTestAttributes(); + }, testname + ' test MediaKeySystemAccess attribute syntax.'); + +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/temporary-license-type.js b/testing/web-platform/tests/encrypted-media/scripts/temporary-license-type.js new file mode 100644 index 0000000000..44c9f15808 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/temporary-license-type.js @@ -0,0 +1,61 @@ +function runTest(config,qualifier) { + + var testname = testnamePrefix(qualifier, config.keysystem) + ', cannot load persistent license into temporary session'; + + var configuration = getSimpleConfigurationForContent(config.content); + + if (config.initDataType && config.initData) { + configuration.initDataTypes = [config.initDataType]; + } + + async_test(function(test) + { + var initDataType; + var initData; + var mediaKeySession; + + function onFailure(error) { + forceTestFailureFromPromise(test, error); + } + + function processMessage(event) + { + assert_true(event instanceof window.MediaKeyMessageEvent); + assert_equals(event.target, mediaKeySession); + assert_equals(event.type, 'message'); + assert_in_array(event.messageType, ['license-request', 'individualization-request']); + + config.messagehandler(event.messageType, event.message).then( function(response) { + mediaKeySession.update(response).then( test.step_func( function() { + if ( event.messageType !== 'license-request' ) { + return; + } + assert_unreached( "Update with incorrect license type should fail" ) + } ) ).catch( test.step_func( function( error ) { + if ( event.messageType !== 'license-request' ) { + forceTestFailureFromPromise(test, error); + return; + } + + assert_throws_js(TypeError, () => { throw error; }); + test.done(); + } ) ); + }).catch(onFailure); + } + + navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) { + initDataType = access.getConfiguration().initDataTypes[0]; + if (config.initDataType && config.initData) { + initData = config.initData; + } else { + initData = getInitData(config.content, initDataType); + } + return access.createMediaKeys(); + }).then(test.step_func(function(mediaKeys) { + mediaKeySession = mediaKeys.createSession('temporary'); + waitForEventAndRunStep('message', mediaKeySession, test.step_func(processMessage), test); + return mediaKeySession.generateRequest(initDataType, initData); + })).catch(onFailure); + }, testname ); + +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/unique-origin.js b/testing/web-platform/tests/encrypted-media/scripts/unique-origin.js new file mode 100644 index 0000000000..015ea9d4e9 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/unique-origin.js @@ -0,0 +1,64 @@ +function runTest(config) { + // When the sandbox attribute is present on an iframe, it will + // treat the content as being from a unique origin. So try to + // call createMediaKeys() inside an iframe and it should fail. + + function load_iframe(src, sandbox) { + return new Promise(function (resolve) { + var iframe = document.createElement('iframe'); + iframe.onload = function () { + resolve(iframe); + }; + iframe.sandbox = sandbox; + iframe.srcdoc = src; + document.documentElement.appendChild(iframe); + }); + } + + function wait_for_message() { + return new Promise(function (resolve) { + self.addEventListener('message', function listener(e) { + resolve(e.data); + self.removeEventListener('message', listener); + }); + }); + } + + promise_test(function (test) { + var script = + '<script>' + + ' window.onmessage = function(e) {' + + ' navigator.requestMediaKeySystemAccess("' + config.keysystem + '", [{' + + ' initDataTypes: [\"' + config.initDataType + '\"],' + + ' audioCapabilities: [' + + ' { contentType:\'' + config.audioType + '\'},' + + ' ]' + + ' }]).then(function(access) {' + + ' return access.createMediaKeys();' + + ' }).then(function(mediaKeys) {' + + ' window.parent.postMessage({result: \'allowed\'}, \'*\');' + + ' }, function(error) {' + + ' window.parent.postMessage({result: \'failed\'}, \'*\');' + + ' });' + + ' };' + + '<\/script>'; + + // Verify that this page can create a MediaKeys first. + return navigator.requestMediaKeySystemAccess(config.keysystem, [{ + initDataTypes: [config.initDataType], + audioCapabilities: [ + {contentType: config.audioType}, + ] + }]).then(function (access) { + return access.createMediaKeys(); + }).then(function (mediaKeys) { + // Success, so now create the iframe and try there. + return load_iframe(script, 'allow-scripts allow-secure-context'); + }).then(function (iframe) { + iframe.contentWindow.postMessage({}, '*'); + return wait_for_message(); + }).then(function (message) { + assert_equals(message.result, 'failed'); + }); + }, 'Unique origin is unable to create MediaKeys'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/update-disallowed-input.js b/testing/web-platform/tests/encrypted-media/scripts/update-disallowed-input.js new file mode 100644 index 0000000000..2a30ad38d4 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/update-disallowed-input.js @@ -0,0 +1,43 @@ +function runTest(config) +{ + // This test passes |response| to update() as a JSON Web Key Set. + // CDMs other than Clear Key won't expect |response| in this format. + promise_test(function(test) { + var initDataType; + var initData; + var keySystem = config.keysystem; + var mediaKeySession; + + function createReallyLongJWKSet() + { + // This is just a standard JWKSet with a lot of + // extra items added to the end. Key ID and key + // doesn't really matter. + var jwkSet = '{"keys":[{' + + '"kty":"oct",' + + '"k":"MDEyMzQ1Njc4OTAxMjM0NQ",' + + '"kid":"MDEyMzQ1Njc4OTAxMjM0NQ"' + + '}]'; + return jwkSet + ',"test":"unknown"'.repeat(4000) + '}'; + } + + var p = navigator.requestMediaKeySystemAccess(keySystem, getSimpleConfiguration()).then(function(access) { + initDataType = access.getConfiguration().initDataTypes[0]; + initData = getInitData(initDataType); + return access.createMediaKeys(); + }).then(function(mediaKeys) { + mediaKeySession = mediaKeys.createSession(); + var eventWatcher = new EventWatcher(test, mediaKeySession, ['message']); + var promise = eventWatcher.wait_for('message'); + mediaKeySession.generateRequest(initDataType, initData); + return promise; + }).then(function () { + var jwkSet = createReallyLongJWKSet(); + assert_greater_than(jwkSet.length, 65536); + var jwkSetArray = stringToUint8Array(jwkSet); + return mediaKeySession.update(jwkSetArray); + }); + + return promise_rejects_js(test, TypeError, p); + }, 'update() with invalid response (longer than 64Kb characters) should fail.'); +} diff --git a/testing/web-platform/tests/encrypted-media/scripts/waiting-for-a-key.js b/testing/web-platform/tests/encrypted-media/scripts/waiting-for-a-key.js new file mode 100644 index 0000000000..fdfb855677 --- /dev/null +++ b/testing/web-platform/tests/encrypted-media/scripts/waiting-for-a-key.js @@ -0,0 +1,166 @@ +function runTest(config) +{ + // For debugging timeouts, keep track of the number of the + // various events received. + var debugEncryptedEventFired = false; + var debugWaitingForKeyEventFired = false; + var debugTimeUpdateEventCount = 0; + var debugMessage = ''; + + // Set global option explicit_timeout to true and control + // the timeout in the promise test below. + setup({ + explicit_timeout: true + }); + + promise_test(function (test) { + var video = config.video; + var keysystem = config.keysystem; + var configuration = { + initDataTypes: [config.initDataType], + audioCapabilities: [{ + contentType: config.audioType + }], + videoCapabilities: [{ + contentType: config.videoType + }], + sessionTypes: ['temporary'] + }; + var initData; + var initDataType; + var mediaKeySession; + // As this code doesn't wait for the 'message' event for clearkey to avoid + // race conditions with 'waitingforkey', specify the key ID and + // key used by the encrypted content. + var keyId = new Uint8Array(config.content.keys[0].kid); + var rawKey = new Uint8Array(config.content.keys[0].key); + // Use the message handler for non clearkey drm + var handler = config.messageHandler || null; + + // Override timeout() to use custom message instead of default + // message "Test timed out" + test.timeout = function () { + var message = 'timeout. message = ' + debugMessage + + ', encrypted: ' + debugEncryptedEventFired + + ', waitingforkey: ' + debugWaitingForKeyEventFired + + ', timeupdate count: ' + debugTimeUpdateEventCount; + + this.timeout_id = null; + this.set_status(this.TIMEOUT, message); + this.phase = this.phases.HAS_RESULT; + this.done(); + }; + + return navigator.requestMediaKeySystemAccess(keysystem, [configuration]).then(function (access) { + debugMessage = 'createMediaKeys()'; + return access.createMediaKeys(); + }).then(function (mediaKeys) { + debugMessage = 'setMediaKeys()'; + return video.setMediaKeys(mediaKeys); + }).then(function () { + return testmediasource(config); + }).then(function (source) { + debugMessage = 'wait_for_encrypted_event()'; + mediaSource = source; + video.src = URL.createObjectURL(mediaSource); + video.play(); + return wait_for_encrypted_event(video); + }).then(function (e) { + // Received the 'encrypted' event(s), so keep a copy of + // the initdata for use when creating the session later. + initDataType = config.initData ? config.initDataType : e.initDataType; + initData = config.initData || e.initData; + // Wait until the video indicates that it needs a key to + // continue. + debugMessage = 'wait_for_waitingforkey_event()'; + return wait_for_waitingforkey_event(video); + }).then(function () { + // Make sure the video is NOT paused and not progressing + // before a key is provided. This requires the video + // to NOT have a clear lead. + assert_false(video.paused); + assert_less_than(video.currentTime, 0.2); + // Create a session. + mediaKeySession = video.mediaKeys.createSession('temporary'); + debugMessage = 'generateRequest()'; + return mediaKeySession.generateRequest(initDataType, initData); + }).then(function () { + // generateRequest() will cause a 'message' event to + // occur specifying the keyId that is needed + // Add the key needed to decrypt. + return wait_for_message_event(mediaKeySession, handler); + }).then(function () { + // Video should start playing now that it can decrypt the + // streams, so wait until a little bit of the video has + // played. + debugMessage = 'wait_for_timeupdate_event()'; + return wait_for_timeupdate_event(video); + }).catch(function (error) { + assert_unreached('Error: ' + error.name); + }); + + // Typical test duration is 6 seconds on release builds + // (12 seconds on debug). + }, 'Waiting for a key.'); + + // Wait for an 'encrypted' event + function wait_for_encrypted_event(video) + { + return new Promise(function (resolve) { + video.addEventListener('encrypted', function listener(e) { + assert_equals(e.target, video); + assert_true(e instanceof window.MediaEncryptedEvent); + assert_equals(e.type, 'encrypted'); + debugEncryptedEventFired = true; + video.removeEventListener('encrypted', listener); + resolve(e); + }); + }); + }; + + // Wait for a 'waitingforkey' event. Promise resolved when the + // event is received. + function wait_for_waitingforkey_event(video) + { + return new Promise(function (resolve) { + video.addEventListener('waitingforkey', function listener(e) { + assert_equals(e.target, video); + assert_equals(e.type, 'waitingforkey'); + debugWaitingForKeyEventFired = true; + video.removeEventListener('waitingforkey', listener); + resolve(e); + }); + }); + }; + + // Wait for a 'timeupdate' event. Promise resolved if |video| has + // played for more than 0.2 seconds. + function wait_for_timeupdate_event(video) + { + return new Promise(function (resolve) { + video.addEventListener('timeupdate', function listener(e) { + assert_equals(e.target, video); + ++debugTimeUpdateEventCount; + if (video.currentTime < 0.2) + return; + video.removeEventListener('timeupdate', listener); + resolve(e); + }); + }); + }; + + // We need to wait for the message even if for non clearkey DRMs. + function wait_for_message_event(mediaKeySession, handler) + { + return new Promise(function (resolve, reject) { + mediaKeySession.addEventListener('message', function listener(e) { + assert_equals(e.target, mediaKeySession); + assert_equals(e.type, 'message'); + video.removeEventListener('message', listener); + return handler(e.messageType, e.message).then(function (response) { + return e.target.update(response) + }).then(resolve, reject); + }); + }); + } +} |