mirror of
https://github.com/servo/servo.git
synced 2025-09-03 03:28:20 +01:00
Update web-platform-tests to revision 346d5b51a122f7bb1c7747064499ef281a0200f7
This commit is contained in:
parent
581c8ba1c8
commit
79b1e6c40c
1728 changed files with 20243 additions and 5349 deletions
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test asynchronous creation of MediaKeys and MediaKeySession while running garbage collection</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
// Run garbage collection often.
|
||||
setInterval(asyncGC, 0);
|
||||
|
||||
var initDataType;
|
||||
var initData;
|
||||
var mediaKeySession;
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
mediaKeySession = mediaKeys.createSession();
|
||||
return mediaKeySession.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
return mediaKeySession.close();
|
||||
}).then(function(result) {
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Test asynchronous creation of MediaKeys and MediaKeySession while running garbage collection.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test asynchronous setServerCertificate while running garbage collection</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// Run garbage collection continuously.
|
||||
setInterval(asyncGC, 0);
|
||||
|
||||
promise_test(function(test)
|
||||
{
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
var cert = new Uint8Array(200);
|
||||
return mediaKeys.setServerCertificate(cert);
|
||||
}).then(function(result) {
|
||||
assert_false(result);
|
||||
});
|
||||
}, 'Test asynchronous setServerCertificate while running garbage collection.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test support of different initDataTypes.</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
function checkInitDataType(initDataType)
|
||||
{
|
||||
return isInitDataTypeSupported(initDataType).then(function(result) {
|
||||
// If |initDataType| is not supported, simply succeed.
|
||||
if (!result)
|
||||
return Promise.resolve('Not supported');
|
||||
|
||||
var options = [ { initDataTypes: [initDataType] } ];
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', options)
|
||||
.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');
|
||||
}, 'Clear key support for "webm".');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
return checkInitDataType('cenc');
|
||||
}, 'Clear key support for "cenc".');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
return checkInitDataType('keyids');
|
||||
}, 'Clear key support for "keyids".');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Invalid Clear Key License.</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var initData;
|
||||
var invalidLicense = new Uint8Array(
|
||||
[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]);
|
||||
|
||||
function handleMessage(event) {
|
||||
event.target.update(invalidLicense).then(function(event) {
|
||||
assert_unreached('Error: update() succeeded unexpectedly.');
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
assert_equals(error.name, 'InvalidAccessError');
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
var keySession = mediaKeys.createSession();
|
||||
keySession.addEventListener('message', handleMessage, false);
|
||||
keySession.generateRequest(initDataType, initData);
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Invalid Clear Key License.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Clear Key handling of non-ASCII responses for update().</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// This test passes |response| to update() as a JSON Web Key Set.
|
||||
// CDMs other than Clear Key won't expect |response| in this format.
|
||||
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var mediaKeySession;
|
||||
|
||||
function processMessage(event)
|
||||
{
|
||||
// |jwkSet| includes some non-ASCII characters.
|
||||
var jwkSet = '{"keys":[{'
|
||||
+ '"kty":"oct\uDC00\uD800",'
|
||||
+ '"k":"MDEyMzQ1Njc4OTAxMjM0NQ",'
|
||||
+ '"kid":"MDEyMzQ1Njc4OTAxMjM0NQ"'
|
||||
+ '\xff\xfe}]';
|
||||
mediaKeySession.update(stringToUint8Array(jwkSet)).then(function() {
|
||||
forceTestFailureFromPromise(test, 'Error: update() succeeded');
|
||||
}, function(error) {
|
||||
assert_equals(error.name, 'InvalidAccessError');
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
mediaKeySession = mediaKeys.createSession();
|
||||
waitForEventAndRunStep('message', mediaKeySession, processMessage, test);
|
||||
return mediaKeySession.generateRequest(initDataType, getInitData(initDataType));
|
||||
});
|
||||
}, 'Clear Key update() with non-ASCII response.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Verify v2 events</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// Currently Clear Key only generates aynchronous "message" and
|
||||
// "keychange" events.
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var initData;
|
||||
var mediaKeySession;
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
|
||||
function processMessage(event)
|
||||
{
|
||||
assert_true(event instanceof window.MediaKeyMessageEvent);
|
||||
assert_equals(event.target, mediaKeySession);
|
||||
assert_equals(event.type, 'message');
|
||||
assert_equals(event.messageType, 'license-request');
|
||||
|
||||
var keyId = extractSingleKeyIdFromMessage(event.message);
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, rawKey)));
|
||||
|
||||
waitForEventAndRunStep('keystatuseschange', mediaKeySession, test.step_func(processKeyStatusesChange), test);
|
||||
|
||||
mediaKeySession.update(jwkSet).catch(test.step_func(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
}));
|
||||
}
|
||||
|
||||
function processKeyStatusesChange(event)
|
||||
{
|
||||
assert_true(event instanceof Event);
|
||||
assert_equals(event.target, mediaKeySession);
|
||||
assert_equals(event.type, 'keystatuseschange');
|
||||
test.done();
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
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(test.step_func(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
}));
|
||||
}, 'Verify v2 events.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test handling of invalid initData for generateRequest().</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// Create a session and call generateRequest() with |initDataType|
|
||||
// and |initData|. generateRequest() should fail with an
|
||||
// InvalidAccessError. Returns a promise that resolves successfully
|
||||
// if the error happened, rejects otherwise.
|
||||
function test_session(initDataType, initData)
|
||||
{
|
||||
return isInitDataTypeSupported(initDataType).then(function(result) {
|
||||
// If |initDataType| is not supported, simply succeed.
|
||||
if (!result)
|
||||
return Promise.resolve('Not supported');
|
||||
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
var mediaKeySession = mediaKeys.createSession();
|
||||
return mediaKeySession.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
assert_unreached('generateRequest() succeeded');
|
||||
}, function(error) {
|
||||
assert_equals(error.name, 'InvalidAccessError');
|
||||
return Promise.resolve('success');
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
var initData = new Uint8Array(70000);
|
||||
return test_session('webm', initData);
|
||||
}, 'generateRequest() with webm initData longer than 64Kb characters.');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
var initData = new Uint8Array(70000);
|
||||
return test_session('cenc', initData);
|
||||
}, 'generateRequest() with cenc initData longer than 64Kb characters.');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
var initData = new Uint8Array(70000);
|
||||
return test_session('keyids', initData);
|
||||
}, 'generateRequest() with keyids initData longer than 64Kb characters.');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
// Invalid 'pssh' box as the size specified is larger than what
|
||||
// is provided.
|
||||
var 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
|
||||
]);
|
||||
return test_session('cenc', initData);
|
||||
}, 'generateRequest() with invalid pssh data.');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
// Invalid data as type = 'psss'.
|
||||
var 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
|
||||
]);
|
||||
return test_session('cenc', initData);
|
||||
}, 'generateRequest() with non pssh data.');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
// Valid key ID size must be at least 1 character for keyids.
|
||||
var keyId = new Uint8Array(0);
|
||||
var initData = stringToUint8Array(createKeyIDs(keyId));
|
||||
return test_session('keyids', initData);
|
||||
}, 'generateRequest() with too short key ID.');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
// Valid key ID size must be less than 512 characters for keyids.
|
||||
var keyId = new Uint8Array(600);
|
||||
var initData = stringToUint8Array(createKeyIDs(keyId));
|
||||
return test_session('keyids', initData);
|
||||
}, 'generateRequest() with too long key ID.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,120 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Verify MediaKeySession.keyStatuses with multiple sessions</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeySession1;
|
||||
var mediaKeySession2;
|
||||
var initDataType;
|
||||
var initData;
|
||||
|
||||
// Even though key ids are uint8, using printable values so that
|
||||
// they can be verified easily.
|
||||
var key1 = stringToUint8Array('123');
|
||||
var key2 = stringToUint8Array('4567890');
|
||||
var rawKey1 = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
var rawKey2 = new Uint8Array([0x3c, 0xae, 0xe4, 0xfc, 0x2a, 0x12, 0xef, 0x68,
|
||||
0x7b, 0xd2, 0x14, 0x68, 0xf1, 0x62, 0xdd, 0xeb]);
|
||||
|
||||
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.
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(key1, rawKey1)));
|
||||
mediaKeySession1.update(jwkSet).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function processKeyStatusesChange1(event)
|
||||
{
|
||||
// This should only be called for session1.
|
||||
assert_equals(event.target, mediaKeySession1);
|
||||
|
||||
// Check that keyStatuses contains the expected key1 only.
|
||||
dumpKeyStatuses(mediaKeySession1.keyStatuses);
|
||||
verifyKeyStatuses(mediaKeySession1.keyStatuses, { expected: [key1], unexpected: [key2] });
|
||||
|
||||
// Now trigger a message event on session2.
|
||||
mediaKeySession2.generateRequest(initDataType, initData).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(key2, rawKey2)));
|
||||
mediaKeySession2.update(jwkSet).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function processKeyStatusesChange2(event)
|
||||
{
|
||||
// This should only be called for session2.
|
||||
assert_equals(event.target, mediaKeySession2);
|
||||
|
||||
// Check that keyStatuses contains the expected key2 only.
|
||||
dumpKeyStatuses(mediaKeySession2.keyStatuses);
|
||||
verifyKeyStatuses(mediaKeySession2.keyStatuses, { expected: [key2], unexpected: [key1] });
|
||||
|
||||
// session1 should still have 1 key.
|
||||
verifyKeyStatuses(mediaKeySession1.keyStatuses, { expected: [key1], unexpected: [key2] });
|
||||
|
||||
test.done();
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).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(initDataType, initData);
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Verify MediaKeySession.keyStatuses with multiple sessions.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,86 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Verify MediaKeySession.keyStatuses with multiple updates</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var initData;
|
||||
var mediaKeySession;
|
||||
var firstEvent;
|
||||
|
||||
// Even though key ids are uint8, using printable values so that
|
||||
// they can be verified easily.
|
||||
var key1 = stringToUint8Array('123');
|
||||
var key2 = stringToUint8Array('4567890');
|
||||
var rawKey1 = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
var rawKey2 = new Uint8Array([0x3c, 0xae, 0xe4, 0xfc, 0x2a, 0x12, 0xef, 0x68,
|
||||
0x7b, 0xd2, 0x14, 0x68, 0xf1, 0x62, 0xdd, 0xeb]);
|
||||
|
||||
function processMessage(event)
|
||||
{
|
||||
// No keys added yet.
|
||||
verifyKeyStatuses(mediaKeySession.keyStatuses, { expected: [], unexpected: [key1, key2] });
|
||||
|
||||
// Add key1 to the session.
|
||||
firstEvent = true;
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(key1, rawKey1)));
|
||||
mediaKeySession.update(jwkSet).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function processKeyStatusesChange(event)
|
||||
{
|
||||
if (firstEvent) {
|
||||
// Verify that the session only contains key1.
|
||||
dumpKeyStatuses(mediaKeySession.keyStatuses);
|
||||
verifyKeyStatuses(mediaKeySession.keyStatuses, { expected: [key1], unexpected: [key2] });
|
||||
|
||||
// Now add key2 to the session.
|
||||
firstEvent = false;
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(key2, rawKey2)));
|
||||
mediaKeySession.update(jwkSet).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
} else {
|
||||
// Verify that the session now contains key1 and key2.
|
||||
dumpKeyStatuses(mediaKeySession.keyStatuses);
|
||||
verifyKeyStatuses(mediaKeySession.keyStatuses, { expected: [key1, key2] });
|
||||
|
||||
test.done();
|
||||
}
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
mediaKeySession = mediaKeys.createSession();
|
||||
|
||||
// There should be no keys defined yet.
|
||||
assert_equals(mediaKeySession.keyStatuses.size, 0);
|
||||
|
||||
waitForEventAndRunStep('message', mediaKeySession, processMessage, test);
|
||||
waitForEventAndRunStep('keystatuseschange', mediaKeySession, processKeyStatusesChange, test);
|
||||
|
||||
return mediaKeySession.generateRequest(initDataType, initData);
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Verify MediaKeySession.keyStatuses with multiple updates.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,124 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Verify MediaKeySession.keyStatuses</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeySession;
|
||||
var initDataType;
|
||||
var initData;
|
||||
|
||||
// Even though key ids are uint8, using printable values so that
|
||||
// they can be verified easily.
|
||||
var key1String = '123';
|
||||
var key2String = '4567890';
|
||||
var key1 = stringToUint8Array(key1String);
|
||||
var key2 = stringToUint8Array(key2String);
|
||||
var rawKey1 = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
var rawKey2 = new Uint8Array([0x3c, 0xae, 0xe4, 0xfc, 0x2a, 0x12, 0xef, 0x68,
|
||||
0x7b, 0xd2, 0x14, 0x68, 0xf1, 0x62, 0xdd, 0xeb]);
|
||||
|
||||
function processMessage(event)
|
||||
{
|
||||
// No keys added yet.
|
||||
assert_equals(mediaKeySession.keyStatuses.size, 0);
|
||||
|
||||
waitForEventAndRunStep('keystatuseschange', mediaKeySession, processKeyStatusesChange, test);
|
||||
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(key1, rawKey1), createJWK(key2, rawKey2)));
|
||||
mediaKeySession.update(jwkSet).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function processKeyStatusesChange(event)
|
||||
{
|
||||
// Two keys added, so both should show up in |keyStatuses|.
|
||||
assert_equals(mediaKeySession.keyStatuses.size, 2);
|
||||
dumpKeyStatuses(mediaKeySession.keyStatuses);
|
||||
|
||||
// Check |keyStatuses| for 2 entries.
|
||||
var result = [];
|
||||
for (var entry of mediaKeySession.keyStatuses) {
|
||||
result.push({ key: arrayBufferAsString(entry[0]), value: entry[1] });
|
||||
}
|
||||
assert_object_equals(result,
|
||||
[{ key: key1String, value: 'usable'}, { key: key2String, value: 'usable'}],
|
||||
'keyStatuses fails');
|
||||
|
||||
// |keyStatuses| must contain both keys.
|
||||
result = [];
|
||||
for (var key of mediaKeySession.keyStatuses.keys()) {
|
||||
result.push(arrayBufferAsString(key));
|
||||
}
|
||||
assert_array_equals(result,
|
||||
[key1String, key2String],
|
||||
'keyStatuses.keys() fails');
|
||||
|
||||
// Both values in |mediaKeySession| should be 'usable'.
|
||||
result = [];
|
||||
for (var value of mediaKeySession.keyStatuses.values()) {
|
||||
result.push(value);
|
||||
}
|
||||
assert_array_equals(result,
|
||||
['usable', 'usable'],
|
||||
'keyStatuses.values() fails');
|
||||
|
||||
// Check |keyStatuses.entries()|.
|
||||
result = [];
|
||||
for (var entry of mediaKeySession.keyStatuses.entries()) {
|
||||
result.push({ key: arrayBufferAsString(entry[0]), value: entry[1] });
|
||||
}
|
||||
assert_object_equals(result,
|
||||
[{ key: key1String, value: 'usable'}, { key: key2String, value: 'usable'}],
|
||||
'keyStatuses.entries() fails');
|
||||
|
||||
// forEach() should return both entries.
|
||||
result = [];
|
||||
mediaKeySession.keyStatuses.forEach(function(value, key, map) {
|
||||
result.push({ key: arrayBufferAsString(key), value: value });
|
||||
});
|
||||
assert_object_equals(result,
|
||||
[{ key: key1String, value: 'usable'}, { key: key2String, value: 'usable'}],
|
||||
'keyStatuses.forEach() fails');
|
||||
|
||||
assert_true(mediaKeySession.keyStatuses.has(key1));
|
||||
assert_true(mediaKeySession.keyStatuses.has(key2));
|
||||
assert_false(mediaKeySession.keyStatuses.has(stringToUint8Array('123456')));
|
||||
assert_equals(mediaKeySession.keyStatuses.get(key1), 'usable');
|
||||
assert_equals(mediaKeySession.keyStatuses.get(key2), 'usable');
|
||||
assert_equals(mediaKeySession.keyStatuses.get(stringToUint8Array('123456')), undefined);
|
||||
|
||||
test.done();
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
mediaKeySession = mediaKeys.createSession();
|
||||
|
||||
// There should be no keys defined yet.
|
||||
assert_equals(mediaKeySession.keyStatuses.size, 0);
|
||||
|
||||
waitForEventAndRunStep('message', mediaKeySession, processMessage, test);
|
||||
return mediaKeySession.generateRequest(initDataType, initData);
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Verify MediaKeySession.keyStatuses.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaKeys lifetime when adding a session</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// MediaKeySessions remain as long as:
|
||||
// JavaScript has a reference to it
|
||||
// OR (MediaKeys is around
|
||||
// AND the session has not received a close() event)
|
||||
// In the tests below, we do not close any session nor keep a
|
||||
// Javascript reference to any session, so MediaKeySessions remain
|
||||
// as long as the associated MediaKeys object is around.
|
||||
|
||||
// For this test, create a MediaKeySession and verify lifetime.
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var initData;
|
||||
var mediaKeys;
|
||||
var startingActiveDOMObjectCount = window.internals.activeDOMObjectCount(document);
|
||||
|
||||
function numActiveDOMObjectsCreated()
|
||||
{
|
||||
return window.internals.activeDOMObjectCount(document) - startingActiveDOMObjectCount;
|
||||
}
|
||||
|
||||
// Create a MediaKeys object with a session.
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
assert_equals(access.keySystem, 'org.w3.clearkey');
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
|
||||
// Verify MediaKeys is an ActiveDOMObject.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 1.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 4.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 1 MediaKeySystemAccessInitializer (navigator.requestMediaKeySystemAccess() use above),
|
||||
// 1 MediaKeySystemAccessInitializer (isInitDataSupported() (via getSupportedInitDataType())))
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 4, 'MediaKeys.create()');
|
||||
|
||||
var mediaKeySession = mediaKeys.createSession();
|
||||
return mediaKeySession.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
// Should be 1 MediaKeys + 1 MediaKeySession.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 2.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 6.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 2 MediaKeySystemAccessInitializer,
|
||||
// 1 ContentDecryptionModuleResultPromise and
|
||||
// 1 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 6, 'MediaKeys.createSession()');
|
||||
|
||||
// Run gc(), should not affect MediaKeys object nor the
|
||||
// session since we still have a reference to it.
|
||||
|
||||
// When enabling oilpan GC, the in-active
|
||||
// ScriptPromiseResolvers will be destroyed.
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
assert_equals(typeof mediaKeys.createSession, 'function');
|
||||
|
||||
// MediaKeys + MediaKeySessions should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 3, 'After gc()');
|
||||
|
||||
// Drop reference to the MediaKeys object and run gc()
|
||||
// again. Object should be collected this time. Since
|
||||
// MediaKeySessions remain alive as long as MediaKeys is
|
||||
// around, it is possible that gc() checks the
|
||||
// MediaKeySession object first, and doesn't collect it
|
||||
// since MediaKeys hasn't been collected yet. Thus run gc()
|
||||
// twice, to ensure that the unreferenced MediaKeySession
|
||||
// object get collected.
|
||||
mediaKeys = null;
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// No MediaKeySessions should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 0, 1, 'After final gc()');
|
||||
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'MediaKeys lifetime with session');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaKeys lifetime</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
// Create a MediaKeys object and free immediately.
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
// Do nothing with the created object
|
||||
}).then(function(result) {
|
||||
// No way to verify that MediaKeys object is actually
|
||||
// collected, but make sure it doesn't crash.
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Creating and destroying MediaKeys does not crash');
|
||||
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeys;
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Check that the object still exists.
|
||||
assert_equals(typeof mediaKeys.createSession, 'function');
|
||||
mediaKeys = null;
|
||||
|
||||
// Now that the reference is dropped, it should be
|
||||
// collected. No way to verify that it is actually
|
||||
// collected, but make sure it doesn't crash.
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'MediaKeys is not collected as long as we have a reference');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,157 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaKeySession lifetime without release()</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// Since MediaKeySession (and MediaKeys) are ActiveDOMObjects,
|
||||
// we can determine when they are garbage collected.
|
||||
// MediaKeySessions remain as long as:
|
||||
// JavaScript has a reference to it
|
||||
// OR (MediaKeys is around
|
||||
// AND the session has not received a close() event)
|
||||
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeys;
|
||||
var mediaKeySession1;
|
||||
var mediaKeySession2;
|
||||
var mediaKeySession3;
|
||||
var initDataType;
|
||||
var initData;
|
||||
var startingActiveDOMObjectCount = window.internals.activeDOMObjectCount(document);
|
||||
|
||||
function numActiveDOMObjectsCreated()
|
||||
{
|
||||
return window.internals.activeDOMObjectCount(document) - startingActiveDOMObjectCount;
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
assert_equals(access.keySystem, 'org.w3.clearkey');
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
assert_equals(typeof mediaKeys.createSession, 'function');
|
||||
|
||||
// Verify MediaKeys is an ActiveDOMObject.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 1.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 4.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 1 MediaKeySystemAccessInitializer (navigator.requestMediaKeySystemAccess() use above),
|
||||
// 1 MediaKeySystemAccessInitializer (isInitDataSupported() (via getSupportedInitDataType())))
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 4, 'MediaKeys.create()');
|
||||
|
||||
// Create 3 sessions.
|
||||
mediaKeySession1 = mediaKeys.createSession();
|
||||
return mediaKeySession1.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
assert_true(mediaKeySession1.sessionId && mediaKeySession1.sessionId.length > 0);
|
||||
|
||||
// Should be 1 MediaKeys + 1 MediaKeySession.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 2.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 6.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 2 MediaKeySystemAccessInitializer,
|
||||
// 1 ContentDecryptionModuleResultPromise and
|
||||
// 1 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 6, 'MediaKeys.createSession(1)');
|
||||
|
||||
mediaKeySession2 = mediaKeys.createSession();
|
||||
return mediaKeySession2.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
assert_true(mediaKeySession2.sessionId && mediaKeySession2.sessionId.length > 0);
|
||||
|
||||
// Should be 1 MediaKeys + 2 MediaKeySessions.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 3.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 8.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 2 MediaKeySystemAccessInitializers,
|
||||
// 2 ContentDecryptionModuleResultPromise and
|
||||
// 2 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 3, 8, 'mediaKeys.createSession(2)');
|
||||
|
||||
mediaKeySession3 = mediaKeys.createSession();
|
||||
return mediaKeySession3.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
assert_true(mediaKeySession3.sessionId && mediaKeySession3.sessionId.length > 0);
|
||||
|
||||
// Should be 1 MediaKeys + 3 MediaKeySessions.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 4.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 10.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 2 MediaKeySystemAccessInitializers,
|
||||
// 3 ContentDecryptionModuleResultPromise and
|
||||
// 3 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 4, 10, 'mediaKeys.createSession(3)');
|
||||
|
||||
// Run gc(). All sessions should remain as we have a
|
||||
// reference to each one. However, running gc()
|
||||
// asynchronously should free up the last PromiseResolver.
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Only MediaKeys + 3 MediaKeySessions should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 4, 5, 'After gc()');
|
||||
|
||||
// Now drop references to 2 of the sessions. Even though we
|
||||
// don't have a reference, MediaKeys is still around (and
|
||||
// the sessions aren't closed), so the objects won't be
|
||||
// collected.
|
||||
mediaKeySession1 = null;
|
||||
mediaKeySession2 = null;
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// MediaKeys + 3 MediaKeySessions should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 4, 5, 'After second gc()');
|
||||
|
||||
// Now drop the reference to MediaKeys. It and the 2
|
||||
// unreferenced sessions should be collected. Since
|
||||
// MediaKeySessions remain alive as long as MediaKeys is
|
||||
// around, it is possible that gc() checks one or both
|
||||
// MediaKeySession objects first, and doesn't collect them
|
||||
// since MediaKeys hasn't been collected yet. Thus run gc()
|
||||
// twice, to ensure that the unreferenced MediaKeySession
|
||||
// objects get collected.
|
||||
mediaKeys = null;
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Only 1 MediaKeySessions should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 2, 'After mediaKeys = null');
|
||||
|
||||
// Drop the reference to the last session. It should get
|
||||
// collected now since MediaKeys is gone.
|
||||
mediaKeySession3 = null;
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// No MediaKeySessions should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 0, 1, 'After final gc()');
|
||||
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'MediaKeySession lifetime without release()');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,129 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaKeySession lifetime after release() without references</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// Since MediaKeySession (and MediaKeys) are ActiveDOMObjects,
|
||||
// we can determine when they are garbage collected.
|
||||
// MediaKeySessions remain as long as:
|
||||
// JavaScript has a reference to it
|
||||
// OR (MediaKeys is around
|
||||
// AND the session has not received a close() event)
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var initData;
|
||||
var startingActiveDOMObjectCount = window.internals.activeDOMObjectCount(document);
|
||||
|
||||
function numActiveDOMObjectsCreated()
|
||||
{
|
||||
return window.internals.activeDOMObjectCount(document) - startingActiveDOMObjectCount;
|
||||
}
|
||||
|
||||
// Create 2 sessions.
|
||||
var mediaKeys;
|
||||
var mediaKeySession1;
|
||||
var mediaKeySession2;
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
|
||||
// Verify MediaKeys is an ActiveDOMObject.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 1.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 4.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 1 MediaKeySystemAccessInitializer (navigator.requestMediaKeySystemAccess() use above),
|
||||
// 1 MediaKeySystemAccessInitializer (isInitDataSupported() (via getSupportedInitDataType())))
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 4, 'MediaKeys.create()');
|
||||
|
||||
mediaKeySession1 = mediaKeys.createSession();
|
||||
return mediaKeySession1.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
assert_true(mediaKeySession1.sessionId && mediaKeySession1.sessionId.length > 0);
|
||||
|
||||
// Should be 1 MediaKeys + 1 MediaKeySession.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 2.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 6.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 2 MediaKeySystemAccessInitializer,
|
||||
// 1 ContentDecryptionModuleResultPromise and
|
||||
// 1 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 6, 'MediaKeys.createSession(1)');
|
||||
|
||||
mediaKeySession2 = mediaKeys.createSession();
|
||||
return mediaKeySession2.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
assert_true(mediaKeySession2.sessionId && mediaKeySession2.sessionId.length > 0);
|
||||
|
||||
// Should be 1 MediaKeys + 2 MediaKeySessions.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 3.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 8.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 2 MediaKeySystemAccessInitializers,
|
||||
// 2 ContentDecryptionModuleResultPromise and
|
||||
// 2 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 3, 8, 'mediaKeys.createSession(2)');
|
||||
}).then(function(result) {
|
||||
// Run gc(). All sessions should remain as we have a
|
||||
// reference to each one.
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Should be just 1 MediaKeys + 2 MediaKeySessions.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 3, 4, 'After gc()');
|
||||
|
||||
// Close the sessions. Once the close() event is received,
|
||||
// they should get garbage collected as there are no JS
|
||||
// references to them.
|
||||
var promise = mediaKeySession1.close();
|
||||
mediaKeySession1 = null;
|
||||
return promise;
|
||||
}).then(function(result) {
|
||||
// Give time so that the close event can be processed by
|
||||
// MediaKeySession.
|
||||
return delayToAllowEventProcessingPromise();
|
||||
}).then(function(result) {
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Only MediaKeys + mediaKeySession2 should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 3, 'mediaKeySession1 not collected');
|
||||
|
||||
var promise = mediaKeySession2.close();
|
||||
mediaKeySession2 = null;
|
||||
return promise;
|
||||
}).then(function(result) {
|
||||
// Provide time for the mediaKeySession2 close event to be
|
||||
// handled.
|
||||
return delayToAllowEventProcessingPromise();
|
||||
}).then(function(result) {
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Only MediaKeys should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 2, 'mediaKeySession2 not collected');
|
||||
|
||||
assert_not_equals(mediaKeys, null);
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'MediaKeySession lifetime after release() without references');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,117 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>MediaKeySession lifetime after release()</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// Since MediaKeySession (and MediaKeys) are ActiveDOMObjects,
|
||||
// we can determine when they are garbage collected.
|
||||
// MediaKeySessions remain as long as:
|
||||
// JavaScript has a reference to it
|
||||
// OR (MediaKeys is around
|
||||
// AND the session has not received a close() event)
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeys;
|
||||
var mediaKeySession1;
|
||||
var mediaKeySession2;
|
||||
var initDataType;
|
||||
var initData;
|
||||
var startingActiveDOMObjectCount = window.internals.activeDOMObjectCount(document);
|
||||
|
||||
function numActiveDOMObjectsCreated()
|
||||
{
|
||||
return window.internals.activeDOMObjectCount(document) - startingActiveDOMObjectCount;
|
||||
}
|
||||
|
||||
// Create 2 sessions.
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
|
||||
// Verify MediaKeys is an ActiveDOMObject.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 1.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 4.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer and
|
||||
// 1 MediaKeySystemAccessInitializer (navigator.requestMediaKeySystemAccess() use above),
|
||||
// 1 MediaKeySystemAccessInitializer (isInitDataSupported() (via getSupportedInitDataType())))
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 4, 'MediaKeys.create()');
|
||||
|
||||
mediaKeySession1 = mediaKeys.createSession();
|
||||
return mediaKeySession1.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
// Should be 1 MediaKeys + 1 MediaKeySession.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 2.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 6.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer,
|
||||
// 2 MediaKeySystemAccessInitializers,
|
||||
// 1 ContentDecryptionModuleResultPromise and
|
||||
// 1 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 6, 'MediaKeys.createSession(1)');
|
||||
|
||||
mediaKeySession2 = mediaKeys.createSession();
|
||||
return mediaKeySession2.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
// Should be 1 MediaKeys + 2 MediaKeySessions.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 3.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 8.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer,
|
||||
// 2 MediaKeySystemAccessInitializers,
|
||||
// 2 ContentDecryptionModuleResultPromise and
|
||||
// 2 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 3, 8, 'mediaKeys.createSession(2)');
|
||||
|
||||
// Close the sessions. Once completed, only the JS
|
||||
// reference to them keeps them around.
|
||||
return mediaKeySession1.close();
|
||||
}).then(function(result) {
|
||||
return mediaKeySession2.close();
|
||||
}).then(function(result) {
|
||||
// Since both sessions have been closed, dropping the
|
||||
// reference to them from JS will result in the session
|
||||
// being garbage-collected.
|
||||
// Should be 1 MediaKeys + 2 MediaKeySessions.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 3.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 10.
|
||||
// (1 MediaKeys,
|
||||
// 1 MediaKeysInitializer,
|
||||
// 2 MediaKeySystemAccessInitializers,
|
||||
// 4 ContentDecryptionModuleResultPromise and
|
||||
// 2 MediaKeySession).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 3, 10, 'after close');
|
||||
|
||||
mediaKeySession1 = null;
|
||||
return createGCPromise();
|
||||
}).then(function() {
|
||||
// Only MediaKeys + mediaKeySession2 should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 3, 'mediaKeySession1 not collected');
|
||||
|
||||
mediaKeySession2 = null;
|
||||
return createGCPromise();
|
||||
}).then(function() {
|
||||
// Only MediaKeys should remain.
|
||||
// In non-Oilpan, there is also something from createGCPromise().
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 2, 'mediaKeySession2 not collected');
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'MediaKeySession lifetime after release()');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,130 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test multiple MediaKeys lifetimes</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// For this test, create several MediaKeys and verify lifetime.
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeys;
|
||||
var startingActiveDOMObjectCount = window.internals.activeDOMObjectCount(document);
|
||||
|
||||
function numActiveDOMObjectsCreated()
|
||||
{
|
||||
return window.internals.activeDOMObjectCount(document) - startingActiveDOMObjectCount;
|
||||
}
|
||||
|
||||
// Create a MediaKeys object. Returns a promise that resolves
|
||||
// with the new MediaKeys object.
|
||||
function createMediaKeys()
|
||||
{
|
||||
return getSupportedInitDataType().then(function(type) {
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
return mediaKeys;
|
||||
});
|
||||
}
|
||||
|
||||
// Create a few MediaKeys objects. Only keep a reference to the
|
||||
// last one created.
|
||||
createMediaKeys().then(function(result) {
|
||||
// Should be 1 MediaKeys.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 1.
|
||||
// In Oilpan, numActiveDOMObjectsCreated() <= 4.
|
||||
// (1 MediaKeysInitializer,
|
||||
// 1 MediaKeySystemAccessInitializer (navigator.requestMediaKeySystemAccess() use above),
|
||||
// 1 MediaKeySystemAccessInitializer (isInitDataSupported() (via getSupportedInitDataType())) and
|
||||
// 1 ContentDecryptionModuleResultPromise).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 1, 4);
|
||||
|
||||
return createMediaKeys();
|
||||
}).then(function(result) {
|
||||
// Should be 2 MediaKeys.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 2.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 8.
|
||||
// (2 MediaKeysInitializer,
|
||||
// 4 MediaKeySystemAccessInitializer and
|
||||
// 2 ContentDecryptionModuleResultPromise).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 2, 8);
|
||||
|
||||
return createMediaKeys();
|
||||
}).then(function(result) {
|
||||
// Should be 3 MediaKeys.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 3.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 12.
|
||||
// (3 MediaKeysInitializer,
|
||||
// 6 MediaKeySystemAccessInitializer and
|
||||
// 3 ContentDecryptionModuleResultPromise).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 3, 12);
|
||||
|
||||
return createMediaKeys();
|
||||
}).then(function(result) {
|
||||
// Should be 4 MediaKeys.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 4.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 16.
|
||||
// (4 MediaKeysInitializer,
|
||||
// 8 MediaKeySystemAccessInitializer and
|
||||
// 4 ContentDecryptionModuleResultPromise).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 4, 16);
|
||||
|
||||
return createMediaKeys();
|
||||
}).then(function(result) {
|
||||
// Should be 5 MediaKeys.
|
||||
// In non-Oilpan, numActiveDOMObjectsCreate() == 5.
|
||||
// In Oilpan, numActiveDOMObjectsCreate() <= 20.
|
||||
// (5 MediaKeysInitializer,
|
||||
// 10 MediaKeySystemAccessInitializer and
|
||||
// 5 ContentDecryptionModuleResultPromise).
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 5, 20);
|
||||
|
||||
// |mediaKeys| refers to the most recently created MediaKeys
|
||||
// object.
|
||||
mediaKeys = result;
|
||||
|
||||
// In order for the MediaKey objects to be garbage
|
||||
// collected, it needs time to process any pending events.
|
||||
return delayToAllowEventProcessingPromise();
|
||||
}).then(function(result) {
|
||||
// In non-Oilpan, numActiveDOMObjectsCreated() == 5
|
||||
// (5 MediaKeySession objects).
|
||||
// In Oilpan, numActiveDOMObjectsCreated() <= 23
|
||||
// (5 MediaKeysInitializer,
|
||||
// 12 MediaKeySystemAccessInitializer,
|
||||
// 5 ContentDecryptionModuleResultPromise and
|
||||
// 1 DOMTimer (in delayToAllowEventProcessingPromise))
|
||||
assert_between_inclusive(numActiveDOMObjectsCreated(), 5, 23);
|
||||
|
||||
// As we only have a reference (|mediaKeys|) to the last
|
||||
// created MediaKeys object, the other 4 MediaKeys objects
|
||||
// are available to be garbage collected.
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Should be 1 MediaKeys and DOMTimer.
|
||||
assert_less_than_equal(numActiveDOMObjectsCreated(), 2);
|
||||
assert_equals(typeof mediaKeys.createSession, 'function');
|
||||
|
||||
// Release the last MediaKeys object created.
|
||||
mediaKeys = null;
|
||||
|
||||
// Run gc() again to reclaim the remaining MediaKeys object.
|
||||
return createGCPromise();
|
||||
}).then(function(result) {
|
||||
// Should be just a DOMTimer.
|
||||
assert_less_than_equal(numActiveDOMObjectsCreated(), 1);
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Multiple MediaKeys lifetime');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Reloading during encrypted media playback</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var mediaKeySession = null;
|
||||
var hasSessionUpdateSucceeded = false;
|
||||
var encryptedEventCount = 0;
|
||||
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
|
||||
function onEncrypted(event)
|
||||
{
|
||||
assert_equals(event.target, video);
|
||||
assert_true(event instanceof window.MediaEncryptedEvent);
|
||||
assert_equals(event.type, 'encrypted');
|
||||
|
||||
// The same decryption key is used by both the audio and
|
||||
// the video streams so only create a session once. To
|
||||
// avoid issues when comparing the expected.txt file
|
||||
// (which logs the events in the order they occur), create
|
||||
// the session on the second event. This also ensures we
|
||||
// see both events.
|
||||
if (++encryptedEventCount != 2)
|
||||
return;
|
||||
|
||||
mediaKeySession = video.mediaKeys.createSession();
|
||||
waitForEventAndRunStep('message', mediaKeySession, onMessage, test);
|
||||
mediaKeySession.generateRequest(event.initDataType, event.initData).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onMessage(event)
|
||||
{
|
||||
assert_true(event instanceof window.MediaKeyMessageEvent);
|
||||
assert_equals(event.target, mediaKeySession);
|
||||
assert_equals(event.type, 'message');
|
||||
assert_equals(event.messageType, 'license-request');
|
||||
|
||||
var keyId = extractSingleKeyIdFromMessage(event.message);
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, rawKey)));
|
||||
mediaKeySession.update(jwkSet).then(function(result) {
|
||||
hasSessionUpdateSucceeded = true;
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onPlaying(event)
|
||||
{
|
||||
// Not using waitForEventAndRunStep() to avoid too many
|
||||
// EVENT(onTimeUpdate) logs.
|
||||
video.addEventListener('timeupdate', onTimeUpdate, true);
|
||||
}
|
||||
|
||||
function onTimeUpdate(event)
|
||||
{
|
||||
if (event.target.currentTime < 0.2 || !hasSessionUpdateSucceeded)
|
||||
return;
|
||||
|
||||
// Reload the page to catch any possible teardown issues.
|
||||
if (location.hash == '#x') {
|
||||
test.done();
|
||||
return;
|
||||
}
|
||||
|
||||
location.hash += 'x';
|
||||
location.reload();
|
||||
}
|
||||
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
waitForEventAndRunStep('encrypted', video, onEncrypted, test);
|
||||
waitForEventAndRunStep('playing', video, onPlaying, test);
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function(result) {
|
||||
video.play();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Reloading during encrypted media playback.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaKeySession not callable immediately after CreateSession().</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// After creation, the MediaKeySession object is not
|
||||
// callable, and we should get a InvalidStateError.
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).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');
|
||||
});
|
||||
});
|
||||
}, 'Update() immediately after CreateSession().');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).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');
|
||||
});
|
||||
});
|
||||
}, 'Close() immediately after CreateSession().');
|
||||
|
||||
promise_test(function()
|
||||
{
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).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');
|
||||
});
|
||||
});
|
||||
}, 'Remove() immediately after CreateSession().');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>onencrypted</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo" controls></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
var expectedInitData = stringToUint8Array('0123456789012345');
|
||||
|
||||
// Will get 2 identical events, one for audio, one for video.
|
||||
var expectedEvents = 2;
|
||||
|
||||
async_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
|
||||
var onEncrypted = function(event)
|
||||
{
|
||||
assert_equals(event.target, video);
|
||||
assert_true(event instanceof window.MediaEncryptedEvent);
|
||||
assert_equals(event.type, 'encrypted');
|
||||
assert_equals(event.initDataType, 'webm');
|
||||
assert_array_equals(new Uint8Array(event.initData), expectedInitData);
|
||||
|
||||
if (--expectedEvents == 0)
|
||||
test.done();
|
||||
};
|
||||
|
||||
waitForEventAndRunStep('encrypted', video, onEncrypted, test);
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
}, 'encrypted fired on encrypted media file.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,121 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Multiple playbacks alternating between encrypted and clear sources.</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var isUpdatePromiseResolved = false;
|
||||
var encryptedEventCount = 0;
|
||||
var playbackCount = 0;
|
||||
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
|
||||
function onEncrypted(event)
|
||||
{
|
||||
assert_equals(event.target, video);
|
||||
assert_true(event instanceof window.MediaEncryptedEvent);
|
||||
assert_equals(event.type, 'encrypted');
|
||||
|
||||
// The same decryption key is used by both the audio and
|
||||
// the video streams so only create a session once. To
|
||||
// avoid issues when comparing the expected.txt file
|
||||
// (which logs the events in the order they occur), create
|
||||
// the session on the second event. This also ensures we
|
||||
// see both events.
|
||||
if (++encryptedEventCount != 2)
|
||||
return;
|
||||
|
||||
assert_false(video.mediaKeys === null, "video.mediaKeys is null.");
|
||||
var mediaKeySession = video.mediaKeys.createSession();
|
||||
waitForEventAndRunStep('message', mediaKeySession, onMessage, test);
|
||||
mediaKeySession.generateRequest(event.initDataType, event.initData).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onMessage(event)
|
||||
{
|
||||
assert_true(event instanceof window.MediaKeyMessageEvent);
|
||||
assert_equals(event.type, 'message');
|
||||
assert_equals(event.messageType, 'license-request');
|
||||
|
||||
var keyId = extractSingleKeyIdFromMessage(event.message);
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, rawKey)));
|
||||
event.target.update(jwkSet).then(function(result) {
|
||||
isUpdatePromiseResolved = true;
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onPlaying(event)
|
||||
{
|
||||
// Not using waitForEventAndRunStep() to avoid too many
|
||||
// EVENT(onTimeUpdate) logs.
|
||||
video.addEventListener('timeupdate', onTimeUpdate, true);
|
||||
}
|
||||
|
||||
function onTimeUpdate(event)
|
||||
{
|
||||
if (event.target.currentTime < 0.2 || !isUpdatePromiseResolved)
|
||||
return;
|
||||
|
||||
video.removeEventListener('timeupdate', onTimeUpdate, true);
|
||||
|
||||
if (playbackCount > 2) {
|
||||
test.done();
|
||||
return;
|
||||
}
|
||||
|
||||
playbackCount++;
|
||||
|
||||
resetSrc().then(function(){
|
||||
startPlayback();
|
||||
});
|
||||
}
|
||||
|
||||
function resetSrc() {
|
||||
encryptedEventCount = 0;
|
||||
video.removeAttribute('src');
|
||||
video.load();
|
||||
return video.setMediaKeys(null);
|
||||
}
|
||||
|
||||
function startPlayback() {
|
||||
if (playbackCount % 2) {
|
||||
video.src = '../content/test-vp8-vorbis-webvtt.webm';
|
||||
video.play();
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function(result) {
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
assert_false(video.mediaKeys === null, "video.mediaKeys is null.");
|
||||
video.play();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
waitForEventAndRunStep('playing', video, onPlaying, test);
|
||||
waitForEventAndRunStep('encrypted', video, onEncrypted, test);
|
||||
startPlayback();
|
||||
}, 'Multiple playbacks alternating between encrypted and clear sources.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,139 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Clear Key Playback with Multiple Sessions</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var audioMediaKeySession = null;
|
||||
var videoMediaKeySession = null;
|
||||
var audioInitDataType = null;
|
||||
var videoInitDataType = null;
|
||||
var audioInitData = null;
|
||||
var videoInitData = null;
|
||||
var audioKeyProvided = false;
|
||||
var videoKeyProvided = false;
|
||||
|
||||
// The 2 streams use different key ids and different keys.
|
||||
var audioKeyId = '1234567890123456';
|
||||
var audioKey = new Uint8Array([0x30, 0x30, 0x62, 0xF1, 0x68, 0x14, 0xD2, 0x7B,
|
||||
0x68, 0xEF, 0x12, 0x2A, 0xFC, 0xE4, 0xAE, 0x0A]);
|
||||
var videoKeyId = '0123456789012345';
|
||||
var videoKey = new Uint8Array([0x7A, 0x7A, 0x62, 0xF1, 0x68, 0x14, 0xD2, 0x7B,
|
||||
0x68, 0xEF, 0x12, 0x2A, 0xFC, 0xE4, 0xAE, 0x0A]);
|
||||
|
||||
function onEncrypted(event)
|
||||
{
|
||||
var keyId = String.fromCharCode.apply(null, new Uint8Array(event.initData));
|
||||
|
||||
// To avoid issues when comparing the expected.txt file
|
||||
// (which logs the events in the order they occur), save
|
||||
// the initData and make the calls to generateRequest()
|
||||
// only after both "onencrypted" events are received.
|
||||
// This prevents a "message" event from occurring before
|
||||
// both "onencrypted" events are received.
|
||||
var mediaKeySession = video.mediaKeys.createSession();
|
||||
if (keyId == videoKeyId) {
|
||||
assert_equals(videoMediaKeySession, null);
|
||||
videoMediaKeySession = mediaKeySession;
|
||||
videoInitDataType = event.initDataType;
|
||||
videoInitData = event.initData;
|
||||
// Return if audio "onencrypted" event not yet received.
|
||||
if (audioMediaKeySession == null)
|
||||
return;
|
||||
} else {
|
||||
assert_equals(keyId, audioKeyId);
|
||||
assert_equals(audioMediaKeySession, null);
|
||||
audioMediaKeySession = mediaKeySession;
|
||||
audioInitDataType = event.initDataType;
|
||||
audioInitData = event.initData;
|
||||
// Return if video "onencrypted" event not yet received.
|
||||
if (videoMediaKeySession == null)
|
||||
return;
|
||||
}
|
||||
|
||||
// Both sessions have been created.
|
||||
assert_not_equals(videoMediaKeySession, null);
|
||||
assert_not_equals(audioMediaKeySession, null);
|
||||
|
||||
var promises = [];
|
||||
waitForEventAndRunStep('message', videoMediaKeySession, onMessage, test);
|
||||
promises.push(videoMediaKeySession.generateRequest(videoInitDataType, videoInitData));
|
||||
|
||||
waitForEventAndRunStep('message', audioMediaKeySession, onMessage, test);
|
||||
promises.push(audioMediaKeySession.generateRequest(audioInitDataType, audioInitData));
|
||||
|
||||
Promise.all(promises).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onMessage(event)
|
||||
{
|
||||
assert_equals(event.messageType, 'license-request');
|
||||
var keyId = extractSingleKeyIdFromMessage(event.message);
|
||||
if (event.target == videoMediaKeySession) {
|
||||
assert_equals(String.fromCharCode.apply(null, keyId), videoKeyId);
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, videoKey)));
|
||||
videoMediaKeySession.update(jwkSet).then(function(result) {
|
||||
videoKeyProvided = true;
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
} else {
|
||||
assert_equals(event.target, audioMediaKeySession);
|
||||
assert_equals(String.fromCharCode.apply(null, keyId), audioKeyId);
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, audioKey)));
|
||||
audioMediaKeySession.update(jwkSet).then(function(result) {
|
||||
audioKeyProvided = true;
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onPlaying(event)
|
||||
{
|
||||
// Video should not start playing until both keys have been
|
||||
// provided.
|
||||
assert_true(videoKeyProvided);
|
||||
assert_true(audioKeyProvided);
|
||||
|
||||
// Not using waitForEventAndRunStep() to avoid too many
|
||||
// EVENT(onTimeUpdate) logs.
|
||||
video.addEventListener('timeupdate', onTimeUpdate, true);
|
||||
}
|
||||
|
||||
function onTimeUpdate(event)
|
||||
{
|
||||
if (event.target.currentTime < 0.2)
|
||||
return;
|
||||
|
||||
test.done();
|
||||
}
|
||||
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
waitForEventAndRunStep('encrypted', video, onEncrypted, test);
|
||||
waitForEventAndRunStep('playing', video, onPlaying, test);
|
||||
video.src = '../content/test-encrypted-different-av-keys.webm';
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function(result) {
|
||||
video.play();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Playback using Clear Key key system with multiple sessions.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,92 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Clear Key Playback</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var isUpdatePromiseResolved = false;
|
||||
var encryptedEventCount = 0;
|
||||
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
|
||||
function onEncrypted(event)
|
||||
{
|
||||
assert_equals(event.target, video);
|
||||
assert_true(event instanceof window.MediaEncryptedEvent);
|
||||
assert_equals(event.type, 'encrypted');
|
||||
|
||||
// The same decryption key is used by both the audio and
|
||||
// the video streams so only create a session once. To
|
||||
// avoid issues when comparing the expected.txt file
|
||||
// (which logs the events in the order they occur), create
|
||||
// the session on the second event. This also ensures we
|
||||
// see both events.
|
||||
if (++encryptedEventCount != 2)
|
||||
return;
|
||||
|
||||
var mediaKeySession = video.mediaKeys.createSession();
|
||||
waitForEventAndRunStep('message', mediaKeySession, onMessage, test);
|
||||
mediaKeySession.generateRequest(event.initDataType, event.initData).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onMessage(event)
|
||||
{
|
||||
assert_true(event instanceof window.MediaKeyMessageEvent);
|
||||
assert_equals(event.type, 'message');
|
||||
assert_equals(event.messageType, 'license-request');
|
||||
|
||||
var keyId = extractSingleKeyIdFromMessage(event.message);
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, rawKey)));
|
||||
event.target.update(jwkSet).then(function(result) {
|
||||
isUpdatePromiseResolved = true;
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onPlaying(event)
|
||||
{
|
||||
// Not using waitForEventAndRunStep() to avoid too many
|
||||
// EVENT(onTimeUpdate) logs.
|
||||
video.addEventListener('timeupdate', onTimeUpdate, true);
|
||||
}
|
||||
|
||||
function onTimeUpdate(event)
|
||||
{
|
||||
if (event.target.currentTime < 0.2 || !isUpdatePromiseResolved)
|
||||
return;
|
||||
|
||||
test.done();
|
||||
}
|
||||
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
|
||||
waitForEventAndRunStep('encrypted', video, onEncrypted, test);
|
||||
waitForEventAndRunStep('playing', video, onPlaying, test);
|
||||
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function(result) {
|
||||
video.play();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Playback using Clear Key key system, calling setMediaKeys() after setting src attribute.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,92 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Clear Key Playback</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var isUpdatePromiseResolved = false;
|
||||
var encryptedEventCount = 0;
|
||||
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
|
||||
function onEncrypted(event)
|
||||
{
|
||||
assert_equals(event.target, video);
|
||||
assert_true(event instanceof window.MediaEncryptedEvent);
|
||||
assert_equals(event.type, 'encrypted');
|
||||
|
||||
// The same decryption key is used by both the audio and
|
||||
// the video streams so only create a session once. To
|
||||
// avoid issues when comparing the expected.txt file
|
||||
// (which logs the events in the order they occur), create
|
||||
// the session on the second event. This also ensures we
|
||||
// see both events.
|
||||
if (++encryptedEventCount != 2)
|
||||
return;
|
||||
|
||||
var mediaKeySession = video.mediaKeys.createSession();
|
||||
waitForEventAndRunStep('message', mediaKeySession, onMessage, test);
|
||||
mediaKeySession.generateRequest(event.initDataType, event.initData).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onMessage(event)
|
||||
{
|
||||
assert_true(event instanceof window.MediaKeyMessageEvent);
|
||||
assert_equals(event.type, 'message');
|
||||
assert_equals(event.messageType, 'license-request');
|
||||
|
||||
var keyId = extractSingleKeyIdFromMessage(event.message);
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, rawKey)));
|
||||
event.target.update(jwkSet).then(function(result) {
|
||||
isUpdatePromiseResolved = true;
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}
|
||||
|
||||
function onPlaying(event)
|
||||
{
|
||||
// Not using waitForEventAndRunStep() to avoid too many
|
||||
// EVENT(onTimeUpdate) logs.
|
||||
video.addEventListener('timeupdate', onTimeUpdate, true);
|
||||
}
|
||||
|
||||
function onTimeUpdate(event)
|
||||
{
|
||||
if (event.target.currentTime < 0.2 || !isUpdatePromiseResolved)
|
||||
return;
|
||||
|
||||
test.done();
|
||||
}
|
||||
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
|
||||
waitForEventAndRunStep('encrypted', video, onEncrypted, test);
|
||||
waitForEventAndRunStep('playing', video, onPlaying, test);
|
||||
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function(result) {
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
video.play();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Playback using Clear Key key system, calling setMediaKeys() before setting src attribute.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,86 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Clear Key Play Two Videos At Same Time</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<video id="secondVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// As this code doesn't wait for the 'message' event to simplify
|
||||
// the code, specify the key ID and key used by the encrypted
|
||||
// content.
|
||||
var keyId = stringToUint8Array('0123456789012345');
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
|
||||
promise_test(function(test)
|
||||
{
|
||||
var promises = [
|
||||
play_video_as_promise(document.getElementById('testVideo'), '../content/test-encrypted.webm'),
|
||||
play_video_as_promise(document.getElementById('secondVideo'), '../content/test-encrypted.webm')
|
||||
];
|
||||
return Promise.all(promises);
|
||||
}, 'Play two videos at the same time.');
|
||||
|
||||
function play_video_as_promise(video, content)
|
||||
{
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function(result) {
|
||||
video.src = content;
|
||||
video.play();
|
||||
return wait_for_encrypted_message(video);
|
||||
}).then(function(result) {
|
||||
return wait_for_timeupdate_message(video);
|
||||
});
|
||||
};
|
||||
|
||||
function wait_for_encrypted_message(video)
|
||||
{
|
||||
var encryptedEventCount = 0;
|
||||
return new Promise(function(resolve) {
|
||||
video.addEventListener('encrypted', function listener(e) {
|
||||
// The same decryption key is used by both the audio
|
||||
// and the video streams so only create a session once.
|
||||
// Create the session on the second event. This also
|
||||
// ensures we see both events.
|
||||
if (++encryptedEventCount != 2)
|
||||
return;
|
||||
video.removeEventListener('encrypted', listener);
|
||||
|
||||
var mediaKeySession = video.mediaKeys.createSession();
|
||||
mediaKeySession.generateRequest(e.initDataType, e.initData).then(function(result) {
|
||||
// Don't bother waiting for the 'message' event.
|
||||
// Just call update() since we know the keyId
|
||||
// needed.
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, rawKey)));
|
||||
return mediaKeySession.update(jwkSet);
|
||||
}).then(function(result) {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function wait_for_timeupdate_message(video)
|
||||
{
|
||||
return new Promise(function(resolve) {
|
||||
video.addEventListener('timeupdate', function listener(e) {
|
||||
if (e.target.currentTime < 0.2)
|
||||
return;
|
||||
video.removeEventListener('timeupdate', listener);
|
||||
resolve(e);
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,319 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test navigator.requestMediaKeySystemAccess()</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
function expect_error(keySystem, configurations, expectedError, testName) {
|
||||
promise_test(function(test) {
|
||||
return navigator.requestMediaKeySystemAccess(keySystem, configurations).then(function(a) {
|
||||
assert_unreached('Unexpected requestMediaKeySystemAccess() success.');
|
||||
}, function(e) {
|
||||
assert_equals(e.name, expectedError);
|
||||
});
|
||||
}, testName);
|
||||
}
|
||||
|
||||
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, 'getConfiguration()');
|
||||
});
|
||||
}, testName);
|
||||
}
|
||||
|
||||
// Tests for keySystem.
|
||||
expect_error('', [{}], 'InvalidAccessError', 'Empty keySystem');
|
||||
expect_error('com.example.unsupported', [{}], 'NotSupportedError', 'Unsupported keySystem');
|
||||
expect_error('org.w3.clearkey.', [{}], 'NotSupportedError', 'keySystem ends with "."');
|
||||
expect_error('org.w3.ClearKey', [{}], 'NotSupportedError', 'Capitalized keySystem');
|
||||
expect_error('org.w3.clearke\u028F', [{}], 'NotSupportedError', 'Non-ASCII keySystem');
|
||||
|
||||
// Parent of Clear Key not supported.
|
||||
expect_error('org', [{}], 'NotSupportedError', 'Parent of Clear Key (org)');
|
||||
expect_error('org.', [{}], 'NotSupportedError', 'Parent of Clear Key (org.)');
|
||||
expect_error('org.w3', [{}], 'NotSupportedError', 'Parent of Clear Key (org.w3)');
|
||||
expect_error('org.w3.', [{}], 'NotSupportedError', 'Parent of Clear Key (org.w3.)');
|
||||
|
||||
// Child of Clear Key not supported.
|
||||
expect_error('org.w3.clearkey.foo', [{}], 'NotSupportedError', 'Child of Clear Key');
|
||||
|
||||
// Prefixed Clear Key not supported.
|
||||
expect_error('webkit-org.w3.clearkey', [{}], 'NotSupportedError', 'Prefixed Clear Key');
|
||||
|
||||
// Incomplete names.
|
||||
expect_error('org.w3.learkey', [{}], 'NotSupportedError', 'Incomplete name org.w3.learkey');
|
||||
expect_error('org.w3.clearke', [{}], 'NotSupportedError', 'Incomplete name org.w3.clearke');
|
||||
expect_error('or.w3.clearkey', [{}], 'NotSupportedError', 'Incomplete name or.w3.clearkey');
|
||||
|
||||
// Spaces in key system name not supported.
|
||||
expect_error(' org.w3.clearkey', [{}], 'NotSupportedError', 'Leading space in key system name');
|
||||
expect_error('org.w3. clearkey', [{}], 'NotSupportedError', 'Extra space in key system name');
|
||||
expect_error('org.w3.clearkey ', [{}], 'NotSupportedError', 'Trailing space in key system name');
|
||||
|
||||
// Extra dots in key systems names not supported.
|
||||
expect_error('.org.w3.clearkey', [{}], 'NotSupportedError', 'Leading dot in key systems name');
|
||||
expect_error('org.w3.clearkey.', [{}], 'NotSupportedError', 'Trailing dot in key systems name');
|
||||
expect_error('org.w3..clearkey', [{}], 'NotSupportedError', 'Double dot in key systems name');
|
||||
expect_error('org.w3.clear.key', [{}], 'NotSupportedError', 'Extra dot in key systems name');
|
||||
|
||||
// Key system name is case sensitive.
|
||||
expect_error('org.w3.Clearkey', [{}], 'NotSupportedError', 'Key system name is case sensitive');
|
||||
expect_error('Org.w3.clearkey', [{}], 'NotSupportedError', 'Key system name is case sensitive');
|
||||
|
||||
// Tests for trivial configurations.
|
||||
expect_error('org.w3.clearkey', [], 'InvalidAccessError', 'Empty supportedConfigurations');
|
||||
expect_config('org.w3.clearkey', [{}], {}, 'Empty configuration');
|
||||
|
||||
// Various combinations of supportedConfigurations.
|
||||
// TODO(jrummell): Specifying contentType without codecs is
|
||||
// deprecated, so this test should fail. http://crbug.com/605661.
|
||||
expect_config('org.w3.clearkey', [{
|
||||
initDataTypes: ['webm'],
|
||||
audioCapabilities: [{contentType: 'audio/webm'}],
|
||||
videoCapabilities: [{contentType: 'video/webm'}],
|
||||
}], {
|
||||
initDataTypes: ['webm'],
|
||||
audioCapabilities: [{contentType: 'audio/webm'}],
|
||||
videoCapabilities: [{contentType: 'video/webm'}],
|
||||
}, 'Basic supported configuration');
|
||||
|
||||
// TODO(jrummell): Specifying contentType without codecs is
|
||||
// deprecated, so this test should fail. http://crbug.com/605661.
|
||||
expect_config('org.w3.clearkey', [{
|
||||
initDataTypes: ['fakeidt', 'webm'],
|
||||
audioCapabilities: [{contentType: 'audio/fake'}, {contentType: 'audio/webm'}],
|
||||
videoCapabilities: [{contentType: 'video/fake'}, {contentType: 'video/webm'}],
|
||||
}], {
|
||||
initDataTypes: ['webm'],
|
||||
audioCapabilities: [{contentType: 'audio/webm'}],
|
||||
videoCapabilities: [{contentType: 'video/webm'}],
|
||||
}, 'Partially supported configuration');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
audioCapabilities: [{contentType: 'audio/webm; codecs=vorbis'}],
|
||||
}], {
|
||||
audioCapabilities: [{contentType: 'audio/webm; codecs=vorbis'}],
|
||||
}, 'Supported audio codec');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
audioCapabilities: [{contentType: 'audio/webm; codecs="vorbis"'}],
|
||||
}], {
|
||||
audioCapabilities: [{contentType: 'audio/webm; codecs="vorbis"'}],
|
||||
}, 'ContentType formatting must be preserved');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
audioCapabilities: [{contentType: 'audio/webm; codecs=fake'}],
|
||||
}], 'NotSupportedError', 'Unsupported audio codec');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
audioCapabilities: [
|
||||
{contentType: 'audio/webm; codecs=mp4a'},
|
||||
{contentType: 'audio/webm; codecs=mp4a.40.2'}
|
||||
],
|
||||
}], 'NotSupportedError', 'Mismatched audio container/codec');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs=vp8'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs=vp8'}],
|
||||
}, 'Supported video codec');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
audioCapabilities: [{contentType: 'video/webm; codecs=vp8'}],
|
||||
}], 'NotSupportedError', 'Video codec specified in audio field');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'audio/webm; codecs=vorbis'}],
|
||||
}], 'NotSupportedError', 'Audio codec specified in video field');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
audioCapabilities: [{contentType: 'video/webm; codecs=fake'}],
|
||||
}], 'NotSupportedError', 'Unsupported video codec');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
audioCapabilities: [
|
||||
{contentType: 'audio/webm; codecs=avc1'},
|
||||
{contentType: 'audio/webm; codecs=avc1.42e01e'}
|
||||
],
|
||||
}], 'NotSupportedError', 'Mismatched video container/codec');
|
||||
|
||||
expect_config('org.w3.clearkey', [
|
||||
{initDataTypes: ['fakeidt']},
|
||||
{initDataTypes: ['webm']}
|
||||
], {initDataTypes: ['webm']}, 'Two configurations, one supported');
|
||||
|
||||
expect_config('org.w3.clearkey', [
|
||||
{initDataTypes: ['webm']},
|
||||
{}
|
||||
], {initDataTypes: ['webm']}, 'Two configurations, both supported');
|
||||
|
||||
// Audio MIME type does not support video codecs.
|
||||
expect_error('org.w3.clearkey', [{
|
||||
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.');
|
||||
|
||||
// Video MIME type does not support audio codecs.
|
||||
expect_error('org.w3.clearkey', [{
|
||||
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 does not support AVC1/AAC.
|
||||
expect_error('org.w3.clearkey', [{
|
||||
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.');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
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.');
|
||||
|
||||
// Extra space is allowed in contentType.
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: ' video/webm; codecs="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: ' video/webm; codecs="vp8"'}],
|
||||
}, 'Leading space in contentType');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm ; codecs="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/webm ; codecs="vp8"'}],
|
||||
}, 'Space before ; in contentType');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs="vp8"'}],
|
||||
}, 'Extra spaces after ; in contentType');
|
||||
|
||||
// TODO(jrummell): contentType should allow white space at the
|
||||
// end of the string. http://crbug.com/487392
|
||||
// expect_config('org.w3.clearkey', [{
|
||||
// videoCapabilities: [{contentType: 'video/webm; codecs="vp8" '}],
|
||||
// }], {
|
||||
// videoCapabilities: [{contentType: 'video/webm; codecs="vp8" '}],
|
||||
// }, 'Trailing space in contentType');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs=" vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs=" vp8"'}],
|
||||
}, 'Space at start of codecs parameter');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs="vp8 "'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs="vp8 "'}],
|
||||
}, 'Space at end of codecs parameter');
|
||||
|
||||
// contentType is not case sensitive (except the codec names).
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'Video/webm; codecs="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'Video/webm; codecs="vp8"'}],
|
||||
}, 'Video/webm');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/Webm; codecs="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/Webm; codecs="vp8"'}],
|
||||
}, 'video/Webm');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; Codecs="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/webm; Codecs="vp8"'}],
|
||||
}, 'Codecs=');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'VIDEO/WEBM; codecs="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'VIDEO/WEBM; codecs="vp8"'}],
|
||||
}, 'VIDEO/WEBM');
|
||||
|
||||
expect_config('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; CODECS="vp8"'}],
|
||||
}], {
|
||||
videoCapabilities: [{contentType: 'video/webm; CODECS="vp8"'}],
|
||||
}, 'CODECS=');
|
||||
|
||||
// Unrecognized attributes are not allowed.
|
||||
// TODO(jrummell): Unrecognized attributes are ignored currently.
|
||||
// http://crbug.com/449690
|
||||
// expect_error('org.w3.clearkey', [{
|
||||
// videoCapabilities: [{contentType: 'video/webm; foo="bar"'}],
|
||||
// }], 'NotSupportedError', 'Unrecognized foo');
|
||||
// expect_error('org.w3.clearkey', [{
|
||||
// videoCapabilities: [{contentType: 'video/webm; foo="bar"; codecs="vp8"'}],
|
||||
// }], 'NotSupportedError', 'Unrecognized foo with codecs');
|
||||
|
||||
// Invalid contentTypes.
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'fake'}],
|
||||
}], 'NotSupportedError', 'contentType fake');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
audioCapabilities: [{contentType: 'audio/fake'}],
|
||||
}], 'NotSupportedError', 'contentType audio/fake');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/fake'}],
|
||||
}], 'NotSupportedError', 'contentType video/fake');
|
||||
|
||||
// The actual codec names are case sensitive.
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs="Vp8"'}],
|
||||
}], 'NotSupportedError', 'codecs Vp8');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs="VP8"'}],
|
||||
}], 'NotSupportedError', 'codecs VP8');
|
||||
|
||||
// Extra comma is not allowed in codecs.
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs=",vp8"'}],
|
||||
}], 'NotSupportedError', 'Leading , in codecs');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs="vp8,"'}],
|
||||
}], 'NotSupportedError', 'Trailing , in codecs');
|
||||
|
||||
expect_error('org.w3.clearkey', [{
|
||||
videoCapabilities: [{contentType: 'video/webm; codecs=",vp8,"'}],
|
||||
}], 'NotSupportedError', 'Leading and trailing , in codecs');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Reset src after setMediaKeys()</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeys;
|
||||
var encryptedEventIndex = 0;
|
||||
var video = document.getElementById('testVideo');
|
||||
assert_not_equals(video, null);
|
||||
|
||||
var onEncrypted = function(event)
|
||||
{
|
||||
++encryptedEventIndex;
|
||||
assert_not_equals(video.mediaKeys, null);
|
||||
assert_true(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; set src to a different video.
|
||||
video.src = '../content/test-encrypted-different-av-keys.webm';
|
||||
} else if (encryptedEventIndex == 4) {
|
||||
// Finished second video.
|
||||
test.done();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a MediaKeys object and assign it to video.
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}])
|
||||
.then(function(access) {
|
||||
assert_equals(access.keySystem, 'org.w3.clearkey');
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
assert_not_equals(mediaKeys, null);
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function(result) {
|
||||
assert_not_equals(video.mediaKeys, null, 'not set initially');
|
||||
assert_true(video.mediaKeys === mediaKeys);
|
||||
|
||||
// Set src to a video.
|
||||
waitForEventAndRunStep('encrypted', video, onEncrypted, test);
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Reset src after setMediaKeys().');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaKeySession closed event</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var initData;
|
||||
var mediaKeySession;
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
initData = getInitData(initDataType);
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
mediaKeySession = mediaKeys.createSession();
|
||||
return mediaKeySession.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
// Wait for the session to be closed.
|
||||
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);
|
||||
test.done();
|
||||
});
|
||||
|
||||
// release() should result in the closed promise being
|
||||
// fulfilled.
|
||||
return mediaKeySession.close();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Test MediaKeySession closed event.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>setMediaKeys() again after playback</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
promise_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var keyId = stringToUint8Array('0123456789012345');
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
var content = '../content/test-encrypted.webm';
|
||||
var duration = 0.2;
|
||||
|
||||
return createMediaKeys(keyId, rawKey).then(function(mediaKeys) {
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
return playVideoAndWaitForTimeupdate(video, content, duration);
|
||||
}).then(function() {
|
||||
// Now create a second MediaKeys.
|
||||
return createMediaKeys(keyId, rawKey);
|
||||
}).then(function(mediaKeys) {
|
||||
// video is currently playing, so should not be able to
|
||||
// change MediaKeys now.
|
||||
assert_false(video.ended);
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
assert_unreached('Able to change MediaKeys while playing.');
|
||||
}, function(error) {
|
||||
// Error expected.
|
||||
assert_equals(error.name, 'InvalidStateError');
|
||||
return Promise.resolve('success');
|
||||
});
|
||||
}, 'setMediaKeys() again after playback');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>setMediaKeys() again after resetting src</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
promise_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var keyId = stringToUint8Array('0123456789012345');
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
var content = '../content/test-encrypted.webm';
|
||||
var duration = 0.2;
|
||||
|
||||
return createMediaKeys(keyId, rawKey).then(function(mediaKeys) {
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
return playVideoAndWaitForTimeupdate(video, content, duration);
|
||||
}).then(function() {
|
||||
// Now create a second MediaKeys and repeat.
|
||||
return createMediaKeys(keyId, rawKey);
|
||||
}).then(function(mediaKeys) {
|
||||
// MediaKeys is use by previous video, so clear .src
|
||||
// so that MediaKeys can be assigned.
|
||||
video.src = '';
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
return playVideoAndWaitForTimeupdate(video, content, duration);
|
||||
});
|
||||
}, 'setMediaKeys() again after resetting src');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,105 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Set MediaKeys multiple times in parallel</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// Wait for an 'encrypted' event as a promise.
|
||||
function wait_for_encrypted_event(video)
|
||||
{
|
||||
return new Promise(function(resolve) {
|
||||
video.addEventListener('encrypted', function listener(e) {
|
||||
video.removeEventListener('encrypted', listener);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Return a promise that calls setMediaKeys() and returns 1 if
|
||||
// resolved, 0 if rejected. If |must_succeed| is true, then
|
||||
// setMediaKeys() should not fail.
|
||||
function setMediaKeys_as_count(video, mediaKeys, must_succeed)
|
||||
{
|
||||
return video.setMediaKeys(mediaKeys).then(function() {
|
||||
return 1;
|
||||
}, function() {
|
||||
assert_false(must_succeed);
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
// Return the sum of the results from |promises|. Each promise
|
||||
// must return a number.
|
||||
function count_promise_results(promises)
|
||||
{
|
||||
var count = 0;
|
||||
var result = Promise.resolve(null);
|
||||
|
||||
promises.forEach(function(promise) {
|
||||
result = result.then(function() {
|
||||
return promise;
|
||||
}).then(function(i) {
|
||||
count += i;
|
||||
});
|
||||
});
|
||||
|
||||
return result.then(function() { return count; });
|
||||
};
|
||||
|
||||
promise_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('video');
|
||||
var access;
|
||||
var mediaKeys1;
|
||||
var mediaKeys2;
|
||||
var mediaKeys3;
|
||||
var mediaKeys4;
|
||||
var mediaKeys5;
|
||||
|
||||
// Start a video now so that it is waiting for MediaKeys
|
||||
// in order to continue.
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
video.play();
|
||||
return wait_for_encrypted_event(video).then(function() {
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(result) {
|
||||
access = result;
|
||||
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;
|
||||
// Create 5 calls to setMediaKeys(). The first one must
|
||||
// succeed, the others are optional.
|
||||
var p1 = setMediaKeys_as_count(video, mediaKeys1, true);
|
||||
var p2 = setMediaKeys_as_count(video, mediaKeys2, false);
|
||||
var p3 = setMediaKeys_as_count(video, mediaKeys3, false);
|
||||
var p4 = setMediaKeys_as_count(video, mediaKeys4, false);
|
||||
var p5 = setMediaKeys_as_count(video, mediaKeys5, false);
|
||||
return count_promise_results([p1, p2, p3, p4, p5]);
|
||||
}).then(function(count) {
|
||||
// At least one of the setMediaKeys() calls should have
|
||||
// succeeded.
|
||||
assert_greater_than(count, 0);
|
||||
});
|
||||
}, 'Set MediaKeys multiple times in parallel.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,79 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>setMediaKeys() multiple times with different MediaKeys.</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
promise_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('video');
|
||||
var keySystemAccess;
|
||||
var mediaKeys1;
|
||||
var mediaKeys2;
|
||||
|
||||
assert_equals(video.mediaKeys, null);
|
||||
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
keySystemAccess = access;
|
||||
// Create a mediaKeys.
|
||||
return keySystemAccess.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys1 = result;
|
||||
assert_not_equals(mediaKeys1, null);
|
||||
// Create a second mediaKeys.
|
||||
return keySystemAccess.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys2 = result;
|
||||
assert_not_equals(mediaKeys2, null);
|
||||
// Set mediaKeys1 on video.
|
||||
return video.setMediaKeys(mediaKeys1);
|
||||
}).then(function() {
|
||||
assert_true(video.mediaKeys === mediaKeys1);
|
||||
// Set mediaKeys2 on video (switching MediaKeys).
|
||||
return video.setMediaKeys(mediaKeys2);
|
||||
}).then(function() {
|
||||
assert_true(video.mediaKeys === mediaKeys2);
|
||||
// Clear mediaKeys from video.
|
||||
return video.setMediaKeys(null);
|
||||
}).then(function() {
|
||||
assert_equals(video.mediaKeys, null);
|
||||
// Set mediaKeys1 on video again.
|
||||
return video.setMediaKeys(mediaKeys1);
|
||||
}).then(function() {
|
||||
assert_true(video.mediaKeys === mediaKeys1);
|
||||
// Load the media element to create the WebMediaPlayer.
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
// Set mediaKeys2 on video (switching MediaKeys) not
|
||||
// supported after WebMediaPlayer is created.
|
||||
return video.setMediaKeys(mediaKeys2);
|
||||
}).then(function() {
|
||||
assert_unreached('Switching mediaKeys after setting src should have failed.');
|
||||
}, function(error) {
|
||||
assert_true(video.mediaKeys === mediaKeys1);
|
||||
assert_equals(error.name, 'InvalidStateError');
|
||||
assert_not_equals(error.message, '');
|
||||
// Return something so the promise resolves properly.
|
||||
return Promise.resolve();
|
||||
}).then(function() {
|
||||
// Set null mediaKeys on video (clearing MediaKeys) not
|
||||
// supported after WebMediaPlayer is created.
|
||||
return video.setMediaKeys(null);
|
||||
}).then(function() {
|
||||
assert_unreached('Clearing mediaKeys after setting src should have failed.');
|
||||
}, function(error) {
|
||||
assert_true(video.mediaKeys === mediaKeys1);
|
||||
assert_equals(error.name, 'InvalidStateError');
|
||||
assert_not_equals(error.message, '');
|
||||
return Promise.resolve();
|
||||
});
|
||||
}, 'setMediaKeys() multiple times with different MediaKeys.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>setMediaKeys() multiple times with the same MediaKeys.</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
promise_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('video');
|
||||
var mediaKeys;
|
||||
|
||||
assert_equals(video.mediaKeys, null);
|
||||
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
assert_not_equals(mediaKeys, null);
|
||||
// Set mediaKeys on video should work.
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
assert_true(video.mediaKeys === mediaKeys);
|
||||
// Set mediaKeys on video again should return a resolved
|
||||
// promise.
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
assert_true(video.mediaKeys === mediaKeys);
|
||||
// Load the media element to create the WebMediaPlayer.
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
// Set mediaKeys again on video should still return a
|
||||
// resolved promise.
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
assert_true(video.mediaKeys === mediaKeys);
|
||||
});
|
||||
}, 'setMediaKeys() multiple times with the same MediaKeys.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>setMediaKeys() on multiple video elements.</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video1"></video>
|
||||
<video id="video2"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
promise_test(function(test)
|
||||
{
|
||||
var video1 = document.getElementById('video1');
|
||||
var video2 = document.getElementById('video2');
|
||||
var mediaKeys;
|
||||
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
// Assignment to video1 should work.
|
||||
return video1.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
// Assignment to video2 should fail.
|
||||
return video2.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
assert_unreached('Second setMediaKeys should have failed.');
|
||||
}, 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);
|
||||
});
|
||||
}, 'setMediaKeys() on multiple video elements.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>setMediaKeys</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
async_test(function(test)
|
||||
{
|
||||
var mediaKeys;
|
||||
var video = document.getElementById('video');
|
||||
assert_not_equals(video, null);
|
||||
|
||||
// Test MediaKeys assignment.
|
||||
assert_equals(video.mediaKeys, null);
|
||||
assert_equals(typeof video.setMediaKeys, 'function');
|
||||
|
||||
// Try setting mediaKeys to null.
|
||||
video.setMediaKeys(null).then(function(result) {
|
||||
assert_equals(video.mediaKeys, null);
|
||||
|
||||
// Try setting mediaKeys to the wrong type of object.
|
||||
return video.setMediaKeys(new Date());
|
||||
}).then(function(result) {
|
||||
assert_unreached('setMediaKeys did not fail when setting to Date()');
|
||||
}, function(error) {
|
||||
// TypeError expected.
|
||||
assert_equals(error.name, 'TypeError');
|
||||
|
||||
// Create a MediaKeys object and assign it to video.
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
assert_equals(access.keySystem, 'org.w3.clearkey');
|
||||
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_true(video.mediaKeys === mediaKeys);
|
||||
test.done();
|
||||
}).catch(function(error) {
|
||||
forceTestFailureFromPromise(test, error);
|
||||
});
|
||||
}, 'Setting MediaKeys on a video object.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Unique origin is unable to create MediaKeys</title>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// 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.src = 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 = 'data:text/html,' +
|
||||
'<script>' +
|
||||
' window.onmessage = function(e) {' +
|
||||
' navigator.requestMediaKeySystemAccess(\'org.w3.clearkey\', [{}]).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.
|
||||
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
// Success, so now create the iframe and try there.
|
||||
return load_iframe(script, 'allow-scripts')
|
||||
}).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');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,70 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test handling of invalid responses for update().</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// This test passes |response| to update() as a JSON Web Key Set.
|
||||
// CDMs other than Clear Key won't expect |response| in this format.
|
||||
|
||||
async_test(function(test)
|
||||
{
|
||||
var initDataType;
|
||||
var mediaKeySession;
|
||||
|
||||
function repeat(pattern, count) {
|
||||
var result = '';
|
||||
while (count > 1) {
|
||||
if (count & 1) result += pattern;
|
||||
count >>= 1;
|
||||
pattern += pattern;
|
||||
}
|
||||
return result + pattern;
|
||||
}
|
||||
|
||||
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 + repeat(',"test":"unknown"', 4000) + '}';
|
||||
}
|
||||
|
||||
function processMessage(event)
|
||||
{
|
||||
var jwkSet = createReallyLongJWKSet();
|
||||
assert_greater_than(jwkSet.length, 65536);
|
||||
var jwkSetArray = stringToUint8Array(jwkSet);
|
||||
mediaKeySession.update(jwkSetArray).then(function() {
|
||||
forceTestFailureFromPromise(test, 'Error: update() succeeded');
|
||||
}, function(error) {
|
||||
assert_equals(error.name, 'InvalidAccessError');
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
getSupportedInitDataType().then(function(type) {
|
||||
initDataType = type;
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]);
|
||||
}).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
mediaKeySession = mediaKeys.createSession();
|
||||
waitForEventAndRunStep('message', mediaKeySession, processMessage, test);
|
||||
return mediaKeySession.generateRequest(initDataType, getInitData(initDataType));
|
||||
});
|
||||
}, 'update() with response longer than 64Kb characters.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,300 @@
|
|||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
|
||||
var consoleDiv = null;
|
||||
|
||||
function consoleWrite(text)
|
||||
{
|
||||
if (!consoleDiv && document.body) {
|
||||
consoleDiv = document.createElement('div');
|
||||
document.body.appendChild(consoleDiv);
|
||||
}
|
||||
var span = document.createElement('span');
|
||||
span.appendChild(document.createTextNode(text));
|
||||
span.appendChild(document.createElement('br'));
|
||||
consoleDiv.appendChild(span);
|
||||
}
|
||||
|
||||
// Returns a promise that is fulfilled with true if |initDataType| is supported,
|
||||
// or false if not.
|
||||
function isInitDataTypeSupported(initDataType)
|
||||
{
|
||||
return navigator.requestMediaKeySystemAccess(
|
||||
"org.w3.clearkey", [{ initDataTypes : [initDataType] }])
|
||||
.then(function() { return(true); }, function() { return(false); });
|
||||
}
|
||||
|
||||
// Returns a promise that is fulfilled with an initDataType that is supported,
|
||||
// rejected if none are supported.
|
||||
function getSupportedInitDataType()
|
||||
{
|
||||
var configuration = [{ initDataTypes : [ 'webm', 'cenc', 'keyids' ] }];
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', configuration)
|
||||
.then(function(access) {
|
||||
var initDataTypes = access.getConfiguration().initDataTypes;
|
||||
assert_greater_than(initDataTypes.length, 0);
|
||||
return Promise.resolve(initDataTypes[0]);
|
||||
}, function(error) {
|
||||
return Promise.reject('No supported initDataType.');
|
||||
});
|
||||
}
|
||||
|
||||
function getInitData(initDataType)
|
||||
{
|
||||
if (initDataType == 'webm') {
|
||||
return new Uint8Array([
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
|
||||
]);
|
||||
}
|
||||
|
||||
if (initDataType == 'cenc') {
|
||||
return new Uint8Array([
|
||||
0x00, 0x00, 0x00, 0x00, // size = 0
|
||||
0x70, 0x73, 0x73, 0x68, // 'pssh'
|
||||
0x01, // version = 1
|
||||
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, 0x01, // key count
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // key
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x00, 0x00, 0x00, 0x00 // datasize
|
||||
]);
|
||||
}
|
||||
|
||||
if (initDataType == 'keyids') {
|
||||
var keyId = new Uint8Array([
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
|
||||
]);
|
||||
return stringToUint8Array(createKeyIDs(keyId));
|
||||
}
|
||||
|
||||
throw 'initDataType ' + initDataType + ' not supported.';
|
||||
}
|
||||
|
||||
function waitForEventAndRunStep(eventName, element, func, stepTest)
|
||||
{
|
||||
var eventCallback = function(event) {
|
||||
if (func)
|
||||
func(event);
|
||||
}
|
||||
if (stepTest)
|
||||
eventCallback = stepTest.step_func(eventCallback);
|
||||
|
||||
element.addEventListener(eventName, eventCallback, true);
|
||||
}
|
||||
|
||||
// Copied from LayoutTests/resources/js-test.js.
|
||||
// See it for details of why this is necessary.
|
||||
function asyncGC(callback)
|
||||
{
|
||||
GCController.collectAll();
|
||||
setTimeout(callback, 0);
|
||||
}
|
||||
|
||||
function createGCPromise()
|
||||
{
|
||||
// Run gc() as a promise.
|
||||
return new Promise(
|
||||
function(resolve, reject) {
|
||||
asyncGC(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function delayToAllowEventProcessingPromise()
|
||||
{
|
||||
return new Promise(
|
||||
function(resolve, reject) {
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function stringToUint8Array(str)
|
||||
{
|
||||
var result = new Uint8Array(str.length);
|
||||
for(var i = 0; i < str.length; i++) {
|
||||
result[i] = str.charCodeAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function arrayBufferAsString(buffer)
|
||||
{
|
||||
// MediaKeySession.keyStatuses iterators return an ArrayBuffer,
|
||||
// so convert it into a printable string.
|
||||
return String.fromCharCode.apply(null, new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
function dumpKeyStatuses(keyStatuses)
|
||||
{
|
||||
consoleWrite("for (var entry of keyStatuses)");
|
||||
for (var entry of keyStatuses) {
|
||||
consoleWrite(arrayBufferAsString(entry[0]) + ", " + entry[1]);
|
||||
}
|
||||
consoleWrite("for (var key of keyStatuses.keys())");
|
||||
for (var key of keyStatuses.keys()) {
|
||||
consoleWrite(arrayBufferAsString(key));
|
||||
}
|
||||
consoleWrite("for (var value of keyStatuses.values())");
|
||||
for (var value of keyStatuses.values()) {
|
||||
consoleWrite(value);
|
||||
}
|
||||
consoleWrite("for (var entry of keyStatuses.entries())");
|
||||
for (var entry of keyStatuses.entries()) {
|
||||
consoleWrite(arrayBufferAsString(entry[0]) + ", " + entry[1]);
|
||||
}
|
||||
consoleWrite("keyStatuses.forEach()");
|
||||
keyStatuses.forEach(function(value, key, map) {
|
||||
consoleWrite(arrayBufferAsString(key) + ", " + value);
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that |keyStatuses| contains just the keys in |keys.expected|
|
||||
// and none of the keys in |keys.unexpected|. All keys should have status
|
||||
// 'usable'. Example call: verifyKeyStatuses(mediaKeySession.keyStatuses,
|
||||
// { expected: [key1], unexpected: [key2] });
|
||||
function verifyKeyStatuses(keyStatuses, keys)
|
||||
{
|
||||
var expected = keys.expected || [];
|
||||
var unexpected = keys.unexpected || [];
|
||||
|
||||
// |keyStatuses| should have same size as number of |keys.expected|.
|
||||
assert_equals(keyStatuses.size, expected.length);
|
||||
|
||||
// All |keys.expected| should be found.
|
||||
expected.map(function(key) {
|
||||
assert_true(keyStatuses.has(key));
|
||||
assert_equals(keyStatuses.get(key), 'usable');
|
||||
});
|
||||
|
||||
// All |keys.unexpected| should not be found.
|
||||
unexpected.map(function(key) {
|
||||
assert_false(keyStatuses.has(key));
|
||||
assert_equals(keyStatuses.get(key), undefined);
|
||||
});
|
||||
}
|
||||
|
||||
// Encodes |data| into base64url string. There is no '=' padding, and the
|
||||
// characters '-' and '_' must be used instead of '+' and '/', respectively.
|
||||
function base64urlEncode(data)
|
||||
{
|
||||
var result = btoa(String.fromCharCode.apply(null, data));
|
||||
return result.replace(/=+$/g, '').replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
|
||||
// Decode |encoded| using base64url decoding.
|
||||
function base64urlDecode(encoded)
|
||||
{
|
||||
return atob(encoded.replace(/\-/g, "+").replace(/\_/g, "/"));
|
||||
}
|
||||
|
||||
// For Clear Key, the License Format is a JSON Web Key (JWK) Set, which contains
|
||||
// a set of cryptographic keys represented by JSON. These helper functions help
|
||||
// wrap raw keys into a JWK set.
|
||||
// See:
|
||||
// https://w3c.github.io/encrypted-media/#clear-key-license-format
|
||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-key
|
||||
//
|
||||
// Creates a JWK from raw key ID and key.
|
||||
// |keyId| and |key| are expected to be ArrayBufferViews, not base64-encoded.
|
||||
function createJWK(keyId, key)
|
||||
{
|
||||
var jwk = '{"kty":"oct","alg":"A128KW","kid":"';
|
||||
jwk += base64urlEncode(keyId);
|
||||
jwk += '","k":"';
|
||||
jwk += base64urlEncode(key);
|
||||
jwk += '"}';
|
||||
return jwk;
|
||||
}
|
||||
|
||||
// Creates a JWK Set from multiple JWKs.
|
||||
function createJWKSet()
|
||||
{
|
||||
var jwkSet = '{"keys":[';
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
if (i != 0)
|
||||
jwkSet += ',';
|
||||
jwkSet += arguments[i];
|
||||
}
|
||||
jwkSet += ']}';
|
||||
return jwkSet;
|
||||
}
|
||||
|
||||
// Clear Key can also support Key IDs Initialization Data.
|
||||
// ref: http://w3c.github.io/encrypted-media/keyids-format.html
|
||||
// Each parameter is expected to be a key id in an Uint8Array.
|
||||
function createKeyIDs()
|
||||
{
|
||||
var keyIds = '{"kids":["';
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
if (i != 0)
|
||||
keyIds += '","';
|
||||
keyIds += base64urlEncode(arguments[i]);
|
||||
}
|
||||
keyIds += '"]}';
|
||||
return keyIds;
|
||||
}
|
||||
|
||||
function forceTestFailureFromPromise(test, error, message)
|
||||
{
|
||||
// Promises convert exceptions into rejected Promises. Since there is
|
||||
// currently no way to report a failed test in the test harness, errors
|
||||
// are reported using force_timeout().
|
||||
if (message)
|
||||
consoleWrite(message + ': ' + error.message);
|
||||
else if (error)
|
||||
consoleWrite(error);
|
||||
|
||||
test.force_timeout();
|
||||
test.done();
|
||||
}
|
||||
|
||||
function extractSingleKeyIdFromMessage(message)
|
||||
{
|
||||
var json = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(message)));
|
||||
// Decode the first element of 'kids'.
|
||||
assert_equals(1, json.kids.length);
|
||||
var decoded_key = base64urlDecode(json.kids[0]);
|
||||
// Convert to an Uint8Array and return it.
|
||||
return stringToUint8Array(decoded_key);
|
||||
}
|
||||
|
||||
// Create a MediaKeys object for Clear Key with 1 session. KeyId and key
|
||||
// required for the video are already known and provided. Returns a promise
|
||||
// that resolves to the MediaKeys object created.
|
||||
function createMediaKeys(keyId, key)
|
||||
{
|
||||
var mediaKeys;
|
||||
var mediaKeySession;
|
||||
var request = stringToUint8Array(createKeyIDs(keyId));
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, key)));
|
||||
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
return access.createMediaKeys();
|
||||
}).then(function(result) {
|
||||
mediaKeys = result;
|
||||
mediaKeySession = mediaKeys.createSession();
|
||||
return mediaKeySession.generateRequest('keyids', request);
|
||||
}).then(function() {
|
||||
return mediaKeySession.update(jwkSet);
|
||||
}).then(function() {
|
||||
return Promise.resolve(mediaKeys);
|
||||
});
|
||||
}
|
||||
|
||||
// Play the specified |content| on |video|. Returns a promise that is resolved
|
||||
// after the video plays for |duration| seconds.
|
||||
function playVideoAndWaitForTimeupdate(video, content, duration)
|
||||
{
|
||||
video.src = content;
|
||||
video.play();
|
||||
return new Promise(function(resolve) {
|
||||
video.addEventListener('timeupdate', function listener(event) {
|
||||
if (event.target.currentTime < duration)
|
||||
return;
|
||||
video.removeEventListener('timeupdate', listener);
|
||||
resolve('success');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Waiting for a key.</title>
|
||||
<script src="encrypted-media-utils.js"></script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="testVideo"></video>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
// For debugging timeouts, keep track of the number of the
|
||||
// various events received.
|
||||
var debugEncryptedEventCount = 0;
|
||||
var debugWaitingForKeyEventCount = 0;
|
||||
var debugTimeUpdateEventCount = 0;
|
||||
var debugMessage = '';
|
||||
|
||||
promise_test(function(test)
|
||||
{
|
||||
var video = document.getElementById('testVideo');
|
||||
var initData;
|
||||
var initDataType;
|
||||
var mediaKeySession;
|
||||
|
||||
test.timeout = function()
|
||||
{
|
||||
var message = 'timeout. message = ' + debugMessage
|
||||
+ ', encrypted: ' + debugEncryptedEventCount
|
||||
+ ', waitingforkey: ' + debugWaitingForKeyEventCount
|
||||
+ ', timeupdate: ' + debugTimeUpdateEventCount;
|
||||
test.force_timeout();
|
||||
test.timeout_id = null;
|
||||
test.set_status(2, message);
|
||||
test.done();
|
||||
};
|
||||
|
||||
// As this code doesn't wait for the 'message' event to avoid
|
||||
// race conditions with 'waitingforkey', specify the key ID and
|
||||
// key used by the encrypted content.
|
||||
var keyId = stringToUint8Array('0123456789012345');
|
||||
var rawKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
|
||||
|
||||
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
|
||||
debugMessage = 'createMediaKeys()';
|
||||
return access.createMediaKeys();
|
||||
}).then(function(mediaKeys) {
|
||||
debugMessage = 'setMediaKeys()';
|
||||
return video.setMediaKeys(mediaKeys);
|
||||
}).then(function() {
|
||||
video.src = '../content/test-encrypted.webm';
|
||||
video.play();
|
||||
debugMessage = 'wait_for_encrypted_event()';
|
||||
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.
|
||||
initData = e.initData;
|
||||
initDataType = e.initDataType;
|
||||
|
||||
// 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_equals(video.currentTime, 0);
|
||||
|
||||
// Create a session.
|
||||
mediaKeySession = video.mediaKeys.createSession();
|
||||
debugMessage = 'generateRequest()';
|
||||
return mediaKeySession.generateRequest(initDataType, initData);
|
||||
}).then(function() {
|
||||
// generateRequest() will cause a 'message' event to
|
||||
// occur specifying the keyId that is needed, but we
|
||||
// ignore it since we already know what keyId is needed.
|
||||
// Add the key needed to decrypt.
|
||||
var jwkSet = stringToUint8Array(createJWKSet(createJWK(keyId, rawKey)));
|
||||
debugMessage = 'update()';
|
||||
return mediaKeySession.update(jwkSet);
|
||||
}).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);
|
||||
});
|
||||
|
||||
// Typical test duration is 6 seconds on release builds
|
||||
// (12 seconds on debug). Since the test is timing out anyway,
|
||||
// make the duration 5 seconds so that the timeout function
|
||||
// is actually called (instead of simply aborting the test).
|
||||
}, 'Waiting for a key.', { timeout: 5000 });
|
||||
|
||||
// Wait for a pair of 'encrypted' events. Promise resolved on
|
||||
// second event.
|
||||
function wait_for_encrypted_event(video)
|
||||
{
|
||||
var encryptedEventCount = 0;
|
||||
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');
|
||||
|
||||
// The same decryption key is used by both the audio
|
||||
// and the video streams so wait for the second event
|
||||
// to ensure we see both events.
|
||||
++debugEncryptedEventCount;
|
||||
if (++encryptedEventCount != 2)
|
||||
return;
|
||||
|
||||
video.removeEventListener('encrypted', listener);
|
||||
resolve(e);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Wait for a 'waitingforkey' event. Promise resolved when the
|
||||
// event is received.
|
||||
function wait_for_waitingforkey_event(video)
|
||||
{
|
||||
var waitingForKeyEventCount = 0;
|
||||
return new Promise(function(resolve) {
|
||||
video.addEventListener('waitingforkey', function listener(e) {
|
||||
assert_equals(e.target, video);
|
||||
assert_equals(e.type, 'waitingforkey');
|
||||
|
||||
++debugWaitingForKeyEventCount;
|
||||
++waitingForKeyEventCount;
|
||||
// TODO(jrummell): waitingforkey event should only
|
||||
// occur once. http://crbug.com/461903
|
||||
// assert_equals(waitingForKeyEventCount, 1, 'Multiple waitingforkey events');
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Binary file not shown.
1
tests/wpt/web-platform-tests/encrypted-media/OWNERS
Normal file
1
tests/wpt/web-platform-tests/encrypted-media/OWNERS
Normal file
|
@ -0,0 +1 @@
|
|||
@ddorwin
|
Loading…
Add table
Add a link
Reference in a new issue