From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser_encrypted_play_time_telemetry.js | 266 +++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 dom/media/test/browser/browser_encrypted_play_time_telemetry.js (limited to 'dom/media/test/browser/browser_encrypted_play_time_telemetry.js') diff --git a/dom/media/test/browser/browser_encrypted_play_time_telemetry.js b/dom/media/test/browser/browser_encrypted_play_time_telemetry.js new file mode 100644 index 0000000000..aebc386e6b --- /dev/null +++ b/dom/media/test/browser/browser_encrypted_play_time_telemetry.js @@ -0,0 +1,266 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This test verifies that telemetry gathered around encrypted media playtime +// is gathered as expected. + +"use strict"; + +/* import-globals-from ../eme_standalone.js */ + +// Clears any existing telemetry data that has been accumulated. Returns a +// promise the will be resolved once the telemetry store is clear. +async function clearTelemetry() { + // There's an arbitrary interval of 2 seconds in which the content + // processes sync their event data with the parent process, we wait + // this out to ensure that we clear everything that is left over from + // previous tests and don't receive random events in the middle of our tests. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 2000)); + + Services.telemetry.clearEvents(); + return TestUtils.waitForCondition(() => { + let events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_ALL_CHANNELS, + true + ).content; + return !events || !events.length; + }); +} + +// Opens a tab containing a blank page, returns a promise that will resolve +// to that tab. +async function openTab() { + const emptyPageUri = + "https://example.com/browser/dom/media/test/browser/file_empty_page.html"; + return BrowserTestUtils.openNewForegroundTab(window.gBrowser, emptyPageUri); +} + +// Creates and configures a video element for EME playback in `tab`. Does not +// start playback for the element. Returns a promise that will resolve once +// the element is setup and ready for playback. +async function loadEmeVideo(tab) { + const emeHelperUri = + gTestPath.substr(0, gTestPath.lastIndexOf("/")) + "/eme_standalone.js"; + return SpecialPowers.spawn( + tab.linkedBrowser, + [emeHelperUri], + async _emeHelperUri => { + // Begin helper functions. + async function once(target, name) { + return new Promise(r => + target.addEventListener(name, r, { once: true }) + ); + } + + // Helper to clone data into content so the EME helper can use the data. + function cloneIntoContent(data) { + return Cu.cloneInto(data, content.wrappedJSObject); + } + // End helper functions. + + // Load the EME helper into content. + Services.scriptloader.loadSubScript(_emeHelperUri, content); + // Setup EME with the helper. + let video = content.document.createElement("video"); + video.id = "media"; + content.document.body.appendChild(video); + let emeHelper = new content.wrappedJSObject.EmeHelper(); + emeHelper.SetKeySystem( + content.wrappedJSObject.EmeHelper.GetClearkeyKeySystemString() + ); + emeHelper.SetInitDataTypes(cloneIntoContent(["webm"])); + emeHelper.SetVideoCapabilities( + cloneIntoContent([{ contentType: 'video/webm; codecs="vp9"' }]) + ); + emeHelper.AddKeyIdAndKey( + "2cdb0ed6119853e7850671c3e9906c3c", + "808b9adac384de1e4f56140f4ad76194" + ); + emeHelper.onerror = error => { + is(false, `Got unexpected error from EME helper: ${error}`); + }; + await emeHelper.ConfigureEme(video); + // Done setting up EME. + + // Setup MSE. + const ms = new content.wrappedJSObject.MediaSource(); + video.src = content.wrappedJSObject.URL.createObjectURL(ms); + await once(ms, "sourceopen"); + const sb = ms.addSourceBuffer("video/webm"); + const videoFile = "sintel-short-clearkey-subsample-encrypted-video.webm"; + let fetchResponse = await content.fetch(videoFile); + sb.appendBuffer(await fetchResponse.arrayBuffer()); + await once(sb, "updateend"); + ms.endOfStream(); + await once(ms, "sourceended"); + } + ); +} + +// Plays the media in `tab` until the 'ended' event is fire. Returns a promise +// that resolves once that state has been reached. +async function playMediaThrough(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + let video = content.document.getElementById("media"); + await Promise.all([new Promise(r => (video.onended = r)), video.play()]); + }); +} + +// Plays the media in `tab` until the 'timeupdate' event is fire. Returns a +// promise that resolves once that state has been reached. +async function playMediaToTimeUpdate(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + let video = content.document.getElementById("media"); + await Promise.all([ + new Promise(r => (video.ontimeupdate = r)), + video.play(), + ]); + }); +} + +// Aborts existing loads and replaces the media on the media element with an +// unencrypted file. +async function replaceMediaWithUnencrypted(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + let video = content.document.getElementById("media"); + video.src = "gizmo.mp4"; + video.load(); + }); +} + +// Clears/nulls the media keys on the media in `tab`. +async function clearMediaKeys(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + let video = content.document.getElementById("media"); + await video.setMediaKeys(null); + }); +} + +// Wait for telemetry information to be received from the content process +// then get the relevant histograms for the tests and return the sums of +// those histograms. If a histogram does not exist this will return a 0 +// sum. Returns a promise the resolves to an object with sums for +// - VIDEO_PLAY_TIME_MS +// - VIDEO_ENCRYPTED_PLAY_TIME_MS +// - VIDEO_CLEARKEY_PLAY_TIME_MS +// This function clears the histograms as it gets them. +async function getTelemetrySums() { + // The telemetry was gathered in the content process, so we have to wait + // until is arrived in the parent to check it. At time of writing there's + // not a more elegant way of doing this than polling. + return TestUtils.waitForCondition(() => { + let histograms = Services.telemetry.getSnapshotForHistograms( + "main", + true + ).content; + // All the histogram data should come at the same time, so we just check + // for playtime here as we always expect it in these tests, but we'll + // grab other values if present. + if (histograms.VIDEO_PLAY_TIME_MS) { + // We only expect to have one value for each histogram, so returning the + // sums is a short hand for returning that one value. + return { + VIDEO_PLAY_TIME_MS: histograms.VIDEO_PLAY_TIME_MS.sum, + VIDEO_ENCRYPTED_PLAY_TIME_MS: histograms.VIDEO_ENCRYPTED_PLAY_TIME_MS + ? histograms.VIDEO_ENCRYPTED_PLAY_TIME_MS.sum + : 0, + VIDEO_CLEARKEY_PLAY_TIME_MS: histograms.VIDEO_CLEARKEY_PLAY_TIME_MS + ? histograms.VIDEO_CLEARKEY_PLAY_TIME_MS.sum + : 0, + }; + } + return null; + }, "recorded telemetry from playing media"); +} + +// Clear telemetry before other tests. Internally the tests clear the telemetry +// when they check it, so we shouldn't need to do this between tests. +add_task(clearTelemetry); + +add_task(async function testEncryptedMediaPlayback() { + let testTab = await openTab(); + + await loadEmeVideo(testTab); + await playMediaThrough(testTab); + + BrowserTestUtils.removeTab(testTab); + + let telemetrySums = await getTelemetrySums(); + + ok(telemetrySums, "Should get play time telemetry"); + is( + telemetrySums.VIDEO_PLAY_TIME_MS, + telemetrySums.VIDEO_ENCRYPTED_PLAY_TIME_MS, + "Play time should be the same as encrypted play time" + ); + is( + telemetrySums.VIDEO_PLAY_TIME_MS, + telemetrySums.VIDEO_CLEARKEY_PLAY_TIME_MS, + "Play time should be the same as clearkey play time" + ); + ok( + telemetrySums.VIDEO_PLAY_TIME_MS > 0, + "Should have a play time greater than zero" + ); +}); + +add_task(async function testChangingFromEncryptedToUnencrypted() { + let testTab = await openTab(); + + await loadEmeVideo(testTab); + await replaceMediaWithUnencrypted(testTab); + await playMediaToTimeUpdate(testTab); + + BrowserTestUtils.removeTab(testTab); + + let telemetrySums = await getTelemetrySums(); + + ok(telemetrySums, "Should get play time telemetry"); + is( + telemetrySums.VIDEO_ENCRYPTED_PLAY_TIME_MS, + 0, + "Encrypted play time should be 0" + ); + is( + telemetrySums.VIDEO_PLAY_TIME_MS, + telemetrySums.VIDEO_CLEARKEY_PLAY_TIME_MS, + "Play time should be the same as clearkey play time because the media element still has a media keys attached" + ); + ok( + telemetrySums.VIDEO_PLAY_TIME_MS > 0, + "Should have a play time greater than zero" + ); +}); + +add_task( + async function testChangingFromEncryptedToUnencryptedAndClearingMediaKeys() { + let testTab = await openTab(); + + await loadEmeVideo(testTab); + await replaceMediaWithUnencrypted(testTab); + await clearMediaKeys(testTab); + await playMediaToTimeUpdate(testTab); + + BrowserTestUtils.removeTab(testTab); + + let telemetrySums = await getTelemetrySums(); + + ok(telemetrySums, "Should get play time telemetry"); + is( + telemetrySums.VIDEO_ENCRYPTED_PLAY_TIME_MS, + 0, + "Encrypted play time should be 0" + ); + is( + telemetrySums.VIDEO_CLEARKEY_PLAY_TIME_MS, + 0, + "Clearkey play time should be 0" + ); + ok( + telemetrySums.VIDEO_PLAY_TIME_MS > 0, + "Should have a play time greater than zero" + ); + } +); -- cgit v1.2.3