summaryrefslogtreecommitdiffstats
path: root/dom/media/test/test_eme_protection_query.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/test/test_eme_protection_query.html')
-rw-r--r--dom/media/test/test_eme_protection_query.html250
1 files changed, 250 insertions, 0 deletions
diff --git a/dom/media/test/test_eme_protection_query.html b/dom/media/test/test_eme_protection_query.html
new file mode 100644
index 0000000000..8bf97d8100
--- /dev/null
+++ b/dom/media/test/test_eme_protection_query.html
@@ -0,0 +1,250 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions - Protection Query</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// Tests in this file check that output protection queries are performed and
+// handled correctly. This is done by using a special clear key key system that
+// emits key status to track protection status.
+
+// Special key system used for these tests.
+const kClearKeyWithProtectionQuery =
+ "org.mozilla.clearkey_with_protection_query";
+const kTestFile = "bipbop-cenc-video-10s.mp4";
+const kTestMimeType = 'video/mp4; codecs="avc1.4d4015"';
+const kTestKeyId = "7e571d037e571d037e571d037e571d11"; // Hex representation
+const kTestKey = "7e5733337e5733337e5733337e573311";
+
+// This is the special key-id used by the mozilla clearkey CDM to signal
+// protection query status. As hex it is "6f75747075742d70726f74656374696f6e",
+// the hex translates to ascii "output-protection".
+const kProtectionQueryKeyIdString = "output-protection";
+
+// Options for requestMediaKeySystemAccess
+const kKeySystemOptions = [
+ {
+ initDataTypes: ["cenc"],
+ videoCapabilities: [{ contentType: kTestMimeType }],
+ },
+];
+
+// Helper to setup EME on `video`.
+// @param video the HTMLMediaElement to configure EME on.
+// @returns a media key session for the video. Callers can use this to
+// configure the `onkeystatuseschange` event handler for each test. Callers
+// *should not* configure other aspects of the session as this helper already
+// does so.
+async function setupEme(video) {
+ // Start setting up EME.
+ let access = await navigator.requestMediaKeySystemAccess(
+ kClearKeyWithProtectionQuery,
+ kKeySystemOptions
+ );
+ let mediaKeys = await access.createMediaKeys();
+ await video.setMediaKeys(mediaKeys);
+
+ let session = video.mediaKeys.createSession();
+
+ video.onencrypted = async encryptedEvent => {
+ session.onmessage = messageEvent => {
+ // Handle license messages. Hard code the license because we always test
+ // with the same file and we know what the license should be.
+ const license = {
+ keys: [
+ {
+ kty: "oct",
+ kid: HexToBase64(kTestKeyId),
+ k: HexToBase64(kTestKey),
+ },
+ ],
+ type: "temporary",
+ };
+
+ const encodedLicense = new TextEncoder().encode(JSON.stringify(license));
+
+ session.update(encodedLicense);
+ };
+
+ session.generateRequest(
+ encryptedEvent.initDataType,
+ encryptedEvent.initData
+ );
+ };
+
+ return session;
+}
+
+// Helper to setup MSE media on `video`.
+// @param video the HTMLMediaElement to configure MSE on.
+async function setupMse(video) {
+ const mediaSource = new MediaSource();
+ video.src = URL.createObjectURL(mediaSource);
+ await once(mediaSource, "sourceopen");
+ const sourceBuffer = mediaSource.addSourceBuffer("video/mp4");
+ let fetchResponse = await fetch(kTestFile);
+ sourceBuffer.appendBuffer(await fetchResponse.arrayBuffer());
+ await once(sourceBuffer, "updateend");
+ mediaSource.endOfStream();
+ await once(mediaSource, "sourceended");
+}
+
+// Helper to create a video element and append it to the page.
+function createAndAppendVideo() {
+ const video = document.createElement("video");
+ video.id = "video";
+ // Loop in case tests run slowly, we want video to keep playing until we
+ // get expected events.
+ video.loop = true;
+ document.body.appendChild(video);
+ return video;
+}
+
+// Helper to remove a video from the page.
+function removeVideo() {
+ let video = document.getElementById("video");
+ CleanUpMedia(video);
+}
+
+// Helper to get the status for the kProtectionQueryKeyIdString key id. A
+// session can (and will) have other keys with their own status, but we want
+// to check this special key to find the protection query status.
+function getKeyStatusForProtectionKeyId(session) {
+ for (let [keyId, status] of session.keyStatuses) {
+ if (ArrayBufferToString(keyId) == kProtectionQueryKeyIdString) {
+ return status;
+ }
+ }
+ return null;
+}
+
+async function getDisplayMedia() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ return navigator.mediaDevices.getDisplayMedia();
+}
+
+// Tests playing encrypted media, starting a screen capture during playback,
+// then stopping the capture while playback continues.
+async function testProtectionQueryWithCaptureDuringVideo() {
+ let video = createAndAppendVideo();
+
+ // Configure the video and start it playing. KeyId should be usable (not restricted).
+ let session = await setupEme(video);
+ let keyStatusChangedPromise1 = new Promise(
+ resolve =>
+ (session.onkeystatuseschange = () => {
+ // We may get status changes prior to kProtectionQueryKeyIdString changing,
+ // ensure we wait for the first kProtectionQueryKeyIdString change.
+ if (getKeyStatusForProtectionKeyId(session)) {
+ resolve();
+ }
+ })
+ );
+ await setupMse(video);
+ await Promise.all([video.play(), keyStatusChangedPromise1]);
+ is(
+ getKeyStatusForProtectionKeyId(session),
+ "usable",
+ "Should be usable as capture hasn't started"
+ );
+
+ let keyStatusChangedPromise2 = new Promise(
+ resolve => (session.onkeystatuseschange = resolve)
+ );
+ let [displayMediaStream] = await Promise.all([
+ // Start a screen capture, this should restrict output.
+ getDisplayMedia(),
+ keyStatusChangedPromise2,
+ ]);
+ is(
+ getKeyStatusForProtectionKeyId(session),
+ "output-restricted",
+ "Should be output-restricted as capture is happening"
+ );
+
+ // Stop the screen capture, output should be usable again.
+ let keyStatusChangedPromise3 = new Promise(
+ resolve => (session.onkeystatuseschange = resolve)
+ );
+ displayMediaStream.getTracks().forEach(track => track.stop());
+ displayMediaStream = null;
+ await keyStatusChangedPromise3;
+ is(
+ getKeyStatusForProtectionKeyId(session),
+ "usable",
+ "Should be usable as capture has stopped"
+ );
+
+ removeVideo();
+}
+
+// Tests starting a screen capture, then starting encrypted playback, then
+// stopping the screen capture while encrypted playback continues.
+async function testProtectionQueryWithCaptureStartingBeforeVideo() {
+ // Start capture before setting up video.
+ let displayMediaStream = await getDisplayMedia();
+
+ let video = createAndAppendVideo();
+
+ // Configure the video and start it playing. KeyId should be restricted already.
+ let session = await setupEme(video);
+ let keyStatusChangedPromise1 = new Promise(
+ resolve =>
+ (session.onkeystatuseschange = () => {
+ // We may get status changes prior to kProtectionQueryKeyIdString changing,
+ // ensure we wait for the first kProtectionQueryKeyIdString change. In
+ // rare cases the first protection status can be "usable" due to racing
+ // between playback and the machinery that detects WebRTC capture. To
+ // avoid this, wait for the first 'output-restricted' notification,
+ // which will either be the first event, or will quickly follow 'usable'.
+ if (getKeyStatusForProtectionKeyId(session) == "output-restricted") {
+ resolve();
+ }
+ })
+ );
+ await setupMse(video);
+ await Promise.all([video.play(), keyStatusChangedPromise1]);
+ is(
+ getKeyStatusForProtectionKeyId(session),
+ "output-restricted",
+ "Should be restricted as capture is happening"
+ );
+
+ // Stop the screen capture, output should be usable again.
+ let keyStatusChangedPromise2 = new Promise(
+ resolve => (session.onkeystatuseschange = resolve)
+ );
+ displayMediaStream.getTracks().forEach(track => track.stop());
+ displayMediaStream = null;
+ await keyStatusChangedPromise2;
+ is(
+ getKeyStatusForProtectionKeyId(session),
+ "usable",
+ "Should be usable as capture has stopped"
+ );
+
+ removeVideo();
+}
+
+add_task(async function setupEnvironment() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Need test key systems for test key system.
+ ["media.clearkey.test-key-systems.enabled", true],
+ // Need relaxed navigator permissions for getDisplayMedia.
+ ["media.navigator.permission.disabled", true],
+ ],
+ });
+});
+add_task(testProtectionQueryWithCaptureDuringVideo);
+add_task(testProtectionQueryWithCaptureStartingBeforeVideo);
+</script>
+</pre>
+</body>
+</html>