summaryrefslogtreecommitdiffstats
path: root/dom/media/test/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/test/chrome')
-rw-r--r--dom/media/test/chrome/chrome.toml12
-rw-r--r--dom/media/test/chrome/test_accumulated_play_time.html694
-rw-r--r--dom/media/test/chrome/test_telemetry_source_buffer_type.html105
3 files changed, 811 insertions, 0 deletions
diff --git a/dom/media/test/chrome/chrome.toml b/dom/media/test/chrome/chrome.toml
new file mode 100644
index 0000000000..57d1843841
--- /dev/null
+++ b/dom/media/test/chrome/chrome.toml
@@ -0,0 +1,12 @@
+[DEFAULT]
+subsuite = "media"
+support-files = [
+ "../gizmo.mp4",
+ "../gizmo-noaudio.mp4",
+ "../TestPatternHDR.mp4",
+ "../tone2s-silence4s-tone2s.opus",
+]
+
+["test_accumulated_play_time.html"]
+
+["test_telemetry_source_buffer_type.html"]
diff --git a/dom/media/test/chrome/test_accumulated_play_time.html b/dom/media/test/chrome/test_accumulated_play_time.html
new file mode 100644
index 0000000000..e59c0377b2
--- /dev/null
+++ b/dom/media/test/chrome/test_accumulated_play_time.html
@@ -0,0 +1,694 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test Video Play Time Related Permanent Telemetry Probes</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript">
+
+/**
+ * This test is used to ensure that we accumulate time for video playback
+ * correctly, and the results would be used in Telemetry probes.
+ * Currently this test covers following probes
+ * - VIDEO_PLAY_TIME_MS
+ * - VIDEO_HDR_PLAY_TIME_MS
+ * - VIDEO_HIDDEN_PLAY_TIME_MS
+ * - VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE
+ * - VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE
+ * - VIDEO_VISIBLE_PLAY_TIME_MS
+ * - MEDIA_PLAY_TIME_MS
+ * - MUTED_PLAY_TIME_PERCENT
+ * - AUDIBLE_PLAY_TIME_PERCENT
+ */
+const videoHistNames = [
+ "VIDEO_PLAY_TIME_MS",
+ "VIDEO_HIDDEN_PLAY_TIME_MS"
+];
+const videoHDRHistNames = [
+ "VIDEO_HDR_PLAY_TIME_MS"
+];
+const videoKeyedHistNames = [
+ "VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE",
+ "VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE",
+ "VIDEO_VISIBLE_PLAY_TIME_MS"
+];
+const audioKeyedHistNames = [
+ "MUTED_PLAY_TIME_PERCENT",
+ "AUDIBLE_PLAY_TIME_PERCENT"
+];
+
+add_task(async function setTestPref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.testing-only-events", true],
+ ["media.test.video-suspend", true],
+ ["media.suspend-background-video.enabled", true],
+ ["media.suspend-background-video.delay-ms", 0],
+ ["dom.media.silence_duration_for_audibility", 0.1]
+ ]});
+});
+
+add_task(async function testTotalPlayTime() {
+ const video = document.createElement('video');
+ video.src = "gizmo.mp4";
+ document.body.appendChild(video);
+
+ info(`all accumulated time should be zero`);
+ const videoChrome = SpecialPowers.wrap(video);
+ await new Promise(r => video.onloadeddata = r);
+ assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0);
+ assertValueEqualTo(videoChrome, "invisiblePlayTime", 0);
+
+ info(`start accumulating play time after media starts`);
+ video.autoplay = true;
+ await Promise.all([
+ once(video, "playing"),
+ once(video, "moztotalplaytimestarted"),
+ ]);
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime");
+
+ info(`should not accumulate time for paused video`);
+ video.pause();
+ await once(video, "moztotalplaytimepaused");
+ assertValueKeptUnchanged(videoChrome, "totalVideoPlayTime");
+ assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0);
+
+ info(`should start accumulating time again`);
+ let rv = await Promise.all([
+ onceWithTrueReturn(video, "moztotalplaytimestarted"),
+ video.play().then(_ => true, _ => false),
+ ]);
+ ok(returnTrueWhenAllValuesAreTrue(rv), "video started again");
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ await cleanUpMediaAndCheckTelemetry(video);
+});
+
+// The testHDRPlayTime task will only pass on platforms that accurately report
+// color depth in their VideoInfo structures. Presently, that is only true for
+// macOS.
+const {AppConstants} = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const reportsColorDepthFromVideoData = (AppConstants.platform == "macosx");
+if (reportsColorDepthFromVideoData) {
+ add_task(async function testHDRPlayTime() {
+ // This task is different from the others because the HTMLMediaElement does
+ // not expose a chrome property for video hdr play time. But we do capture
+ // telemety for VIDEO_HDR_PLAY_TIME_MS. To ensure that this telemetry is
+ // generated, this task follows the same structure as the other tasks, but
+ // doesn't actually check the properties of the video player, other than to
+ // confirm that video has played for at least some time.
+ const video = document.createElement('video');
+ video.src = "TestPatternHDR.mp4"; // This is an HDR video with no audio.
+ document.body.appendChild(video);
+
+ info(`load the HDR video`);
+ const videoChrome = SpecialPowers.wrap(video);
+ await new Promise(r => video.onloadeddata = r);
+
+ info(`start accumulating play time after media starts`);
+ video.autoplay = true;
+ await Promise.all([
+ once(video, "playing"),
+ once(video, "moztotalplaytimestarted"),
+ ]);
+ // Check that we have at least some video play time, because the
+ // HDR play time telemetry is emitted by the same process.
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ await cleanUpMediaAndCheckTelemetry(video, {hasVideo: true, hasAudio: false, hasVideoHDR: true});
+ });
+}
+
+add_task(async function testVisiblePlayTime() {
+ const video = document.createElement('video');
+ video.src = "gizmo.mp4";
+ document.body.appendChild(video);
+
+ info(`all accumulated time should be zero`);
+ const videoChrome = SpecialPowers.wrap(video);
+ await new Promise(r => video.onloadeddata = r);
+ assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0);
+ assertValueEqualTo(videoChrome, "visiblePlayTime", 0);
+ assertValueEqualTo(videoChrome, "invisiblePlayTime", 0);
+
+ info(`start accumulating play time after media starts`);
+ video.autoplay = true;
+ await Promise.all([
+ once(video, "playing"),
+ once(video, "moztotalplaytimestarted"),
+ ]);
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ await assertValueConstantlyIncreases(videoChrome, "visiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+
+ info(`make video invisible`);
+ video.style.display = "none";
+ await once(video, "mozinvisibleplaytimestarted");
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "visiblePlayTime");
+ await cleanUpMediaAndCheckTelemetry(video);
+});
+
+add_task(async function testAudibleAudioPlayTime() {
+ const audio = document.createElement('audio');
+ audio.src = "tone2s-silence4s-tone2s.opus";
+ audio.controls = true;
+ audio.loop = true;
+ document.body.appendChild(audio);
+
+ info(`all accumulated time should be zero`);
+ const audioChrome = SpecialPowers.wrap(audio);
+ await new Promise(r => audio.onloadeddata = r);
+ assertValueEqualTo(audioChrome, "totalVideoPlayTime", 0);
+ assertValueEqualTo(audioChrome, "totalAudioPlayTime", 0);
+ assertValueEqualTo(audioChrome, "mutedPlayTime", 0);
+ assertValueEqualTo(audioChrome, "audiblePlayTime", 0);
+
+ info(`start accumulating play time after media starts`);
+ await Promise.all([
+ audio.play(),
+ once(audio, "moztotalplaytimestarted"),
+ ]);
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "mutedPlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ info(`audio becomes inaudible for 4s`);
+ await once(audio, "mozinaudibleaudioplaytimestarted");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ assertValueKeptUnchanged(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "mutedPlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ info(`audio becomes audible after 4s`);
+ await once(audio, "mozinaudibleaudioplaytimepaused");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "mutedPlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ await cleanUpMediaAndCheckTelemetry(audio, {hasVideo: false});
+});
+
+add_task(async function testHiddenPlayTime() {
+ const invisibleReasons = ["notInTree", "notInConnectedTree", "invisibleInDisplay"];
+ for (let reason of invisibleReasons) {
+ const video = document.createElement('video');
+ video.src = "gizmo.mp4";
+ video.loop = true;
+ info(`invisible video due to '${reason}'`);
+
+ if (reason == "notInConnectedTree") {
+ let disconnected = document.createElement("div")
+ disconnected.appendChild(video);
+ } else if (reason == "invisibleInDisplay") {
+ document.body.appendChild(video);
+ video.style.display = "none";
+ } else if (reason == "notInTree") {
+ // video is already created in the `notInTree` situation.
+ } else {
+ ok(false, "undefined reason");
+ }
+
+ info(`start invisible video should start accumulating timers`);
+ const videoChrome = SpecialPowers.wrap(video);
+ let rv = await Promise.all([
+ onceWithTrueReturn(video, "mozinvisibleplaytimestarted"),
+ video.play().then(_ => true, _ => false),
+ ]);
+ ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing");
+ await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime");
+
+ info(`should not accumulate time for paused video`);
+ video.pause();
+ await once(video, "mozinvisibleplaytimepaused");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+
+ info(`should start accumulating time again`);
+ rv = await Promise.all([
+ onceWithTrueReturn(video, "mozinvisibleplaytimestarted"),
+ video.play().then(_ => true, _ => false),
+ ]);
+ ok(returnTrueWhenAllValuesAreTrue(rv), "video started again");
+ await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime");
+
+ info(`make video visible should stop accumulating invisible related time`);
+ if (reason == "notInTree" || reason == "notInConnectedTree") {
+ document.body.appendChild(video);
+ } else if (reason == "invisibleInDisplay") {
+ video.style.display = "block";
+ } else {
+ ok(false, "undefined reason");
+ }
+ await once(video, "mozinvisibleplaytimepaused");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ await cleanUpMediaAndCheckTelemetry(video);
+ }
+});
+
+add_task(async function testAudioProbesWithoutAudio() {
+ const video = document.createElement('video');
+ video.src = "gizmo-noaudio.mp4";
+ video.loop = true;
+ document.body.appendChild(video);
+
+ info(`all accumulated time should be zero`);
+ const videoChrome = SpecialPowers.wrap(video);
+ await new Promise(r => video.onloadeddata = r);
+ assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0);
+ assertValueEqualTo(videoChrome, "totalAudioPlayTime", 0);
+ assertValueEqualTo(videoChrome, "mutedPlayTime", 0);
+ assertValueEqualTo(videoChrome, "audiblePlayTime", 0);
+
+ info(`start accumulating play time after media starts`);
+ await Promise.all([
+ video.play(),
+ once(video, "moztotalplaytimestarted"),
+ ]);
+
+ async function checkInvariants() {
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ assertValueKeptUnchanged(videoChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "mutedPlayTime");
+ assertValueKeptUnchanged(videoChrome, "totalAudioPlayTime");
+ }
+
+ checkInvariants();
+
+ video.muted = true;
+
+ checkInvariants();
+
+ video.currentTime = 0.0;
+ await once(video, "seeked");
+
+ checkInvariants();
+
+ video.muted = false;
+
+ checkInvariants();
+
+ video.volume = 0.0;
+
+ checkInvariants();
+
+ video.volume = 1.0;
+
+ checkInvariants();
+
+ video.muted = true;
+
+ checkInvariants();
+
+ video.currentTime = 0.0;
+
+ checkInvariants();
+
+ await cleanUpMediaAndCheckTelemetry(video, {hasAudio: false});
+});
+
+add_task(async function testMutedAudioPlayTime() {
+ const audio = document.createElement('audio');
+ audio.src = "gizmo.mp4";
+ audio.controls = true;
+ audio.loop = true;
+ document.body.appendChild(audio);
+
+ info(`all accumulated time should be zero`);
+ const audioChrome = SpecialPowers.wrap(audio);
+ await new Promise(r => audio.onloadeddata = r);
+ assertValueEqualTo(audioChrome, "totalVideoPlayTime", 0);
+ assertValueEqualTo(audioChrome, "totalAudioPlayTime", 0);
+ assertValueEqualTo(audioChrome, "mutedPlayTime", 0);
+ assertValueEqualTo(audioChrome, "audiblePlayTime", 0);
+
+ info(`start accumulating play time after media starts`);
+ await Promise.all([
+ audio.play(),
+ once(audio, "moztotalplaytimestarted"),
+ ]);
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "mutedPlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ audio.muted = true;
+ await once(audio, "mozmutedaudioplaytimestarted");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ audio.currentTime = 0.0;
+ await once(audio, "seeked");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ audio.muted = false;
+ await once(audio, "mozmutedeaudioplaytimepaused");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "mutedPlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ audio.volume = 0.0;
+ await once(audio, "mozmutedaudioplaytimestarted");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ audio.volume = 1.0;
+ await once(audio, "mozmutedeaudioplaytimepaused");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "mutedPlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ audio.muted = true;
+ await once(audio, "mozmutedaudioplaytimestarted");
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ audio.currentTime = 0.0;
+
+ await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime");
+ await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime");
+ assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime");
+
+ // The media has a video track, but it's being played back in an
+ // HTMLAudioElement, without video frame location.
+ await cleanUpMediaAndCheckTelemetry(audio, {hasVideo: false});
+});
+
+// Note that video suspended time is not always align with the invisible play
+// time even if `media.suspend-background-video.delay-ms` is `0`, because not all
+// invisible videos would be suspended under current strategy.
+add_task(async function testDecodeSuspendedTime() {
+ const video = document.createElement('video');
+ video.src = "gizmo.mp4";
+ video.loop = true;
+ document.body.appendChild(video);
+
+ info(`start video should start accumulating timers`);
+ const videoChrome = SpecialPowers.wrap(video);
+ let rv = await Promise.all([
+ onceWithTrueReturn(video, "moztotalplaytimestarted"),
+ video.play().then(_ => true, _ => false),
+ ]);
+ ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing");
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime");
+
+ info(`make it invisible and force to suspend decoding`);
+ video.setVisible(false);
+ await once(video, "mozvideodecodesuspendedstarted");
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime");
+ await assertValueConstantlyIncreases(videoChrome, "videoDecodeSuspendedTime");
+
+ info(`make it visible and resume decoding`);
+ video.setVisible(true);
+ await Promise.all([
+ once(video, "mozinvisibleplaytimepaused"),
+ once(video, "mozvideodecodesuspendedpaused"),
+ ]);
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime");
+ await cleanUpMediaAndCheckTelemetry(video);
+});
+
+add_task(async function reuseSameElementForPlayback() {
+ const video = document.createElement('video');
+ video.src = "gizmo.mp4";
+ document.body.appendChild(video);
+
+ info(`start accumulating play time after media starts`);
+ const videoChrome = SpecialPowers.wrap(video);
+ let rv = await Promise.all([
+ onceWithTrueReturn(video, "moztotalplaytimestarted"),
+ video.play().then(_ => true, _ => false),
+ ]);
+ ok(returnTrueWhenAllValuesAreTrue(rv), "video started again");
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+
+ info(`reset its src and all accumulated value should be reset after then`);
+ // After setting its src to nothing, that would trigger a failed load and set
+ // the error. If the following step tries to set the new resource and `play()`
+ // , then they should be done after receving the `error` from that failed load
+ // first.
+ await Promise.all([
+ once(video, "error"),
+ cleanUpMediaAndCheckTelemetry(video),
+ ]);
+ // video doesn't have a decoder, so the return value would be -1 (error).
+ assertValueEqualTo(videoChrome, "totalVideoPlayTime", -1);
+ assertValueEqualTo(videoChrome, "invisiblePlayTime", -1);
+
+ info(`resue same element, make it visible and start playback again`);
+ video.src = "gizmo.mp4";
+ rv = await Promise.all([
+ onceWithTrueReturn(video, "moztotalplaytimestarted"),
+ video.play().then(_ => true, _ => false),
+ ]);
+ ok(returnTrueWhenAllValuesAreTrue(rv), "video started");
+ await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime");
+ await cleanUpMediaAndCheckTelemetry(video);
+});
+
+add_task(async function testNoReportedTelemetryResult() {
+ info(`No result for empty video`);
+ const video = document.createElement('video');
+ assertAllProbeRelatedAttributesKeptUnchanged(video);
+ await assertNoReportedTelemetryResult(video);
+
+ info(`No result for video which hasn't started playing`);
+ video.src = "gizmo.mp4";
+ document.body.appendChild(video);
+ ok(await once(video, "loadeddata").then(_ => true), "video loaded data");
+ assertAllProbeRelatedAttributesKeptUnchanged(video);
+ await assertNoReportedTelemetryResult(video);
+
+ info(`No result for video with error`);
+ video.src = "filedoesnotexist.mp4";
+ ok(await video.play().then(_ => false, _ => true), "video failed to play");
+ ok(video.error != undefined, "video got error");
+ assertAllProbeRelatedAttributesKeptUnchanged(video);
+ await assertNoReportedTelemetryResult(video);
+});
+
+/**
+ * Following are helper functions
+ */
+async function cleanUpMediaAndCheckTelemetry(media, { reportExpected = true, hasVideo = true, hasAudio = true, hasVideoHDR = false } = {}) {
+ media.src = "";
+ await checkReportedTelemetry(media, reportExpected, hasVideo, hasAudio, hasVideoHDR);
+}
+
+async function assertNoReportedTelemetryResult(media) {
+ await checkReportedTelemetry(media, false, true, true);
+}
+
+async function checkReportedTelemetry(media, reportExpected, hasVideo, hasAudio, hasVideoHDR) {
+ const reportResultPromise = once(media, "mozreportedtelemetry");
+ info(`check telemetry result, reportExpected=${reportExpected}`);
+ if (reportExpected) {
+ await reportResultPromise;
+ }
+ for (const name of videoHistNames) {
+ try {
+ const hist = SpecialPowers.Services.telemetry.getHistogramById(name);
+ /**
+ * Histogram's snapshot looks like that
+ * {
+ * "bucket_count": X,
+ * "histogram_type": Y,
+ * "sum": Z,
+ * "range": [min, max],
+ * "values": { "value1" : "num1", "value2" : "num2", ...}
+ * }
+ */
+ const entriesNums = Object.entries(hist.snapshot().values).length;
+ if (reportExpected && hasVideo) {
+ ok(entriesNums > 0, `Reported result for ${name}`);
+ } else {
+ ok(entriesNums == 0, `Reported nothing for ${name}`);
+ }
+ hist.clear();
+ } catch (e) {
+ ok(false , `histogram '${name}' doesn't exist`);
+ }
+ }
+ // videoHDRHistNames are checked for total time, not for number of samples.
+ for (const name of videoHDRHistNames) {
+ try {
+ const hist = SpecialPowers.Services.telemetry.getHistogramById(name);
+ const totalTimeMS = hist.snapshot().sum;
+ if (reportExpected && hasVideoHDR) {
+ ok(totalTimeMS > 0, `Reported some time for ${name}`);
+ } else {
+ ok(totalTimeMS == 0, `Reported no time for ${name}`);
+ }
+ hist.clear();
+ } catch (e) {
+ ok(false , `histogram '${name}' doesn't exist`);
+ }
+ }
+ for (const name of videoKeyedHistNames) {
+ try {
+ const hist = SpecialPowers.Services.telemetry.getKeyedHistogramById(name);
+ /**
+ * Keyed Histogram's snapshot looks like that
+ * {
+ * "Key1" : {
+ * "bucket_count": X,
+ * "histogram_type": Y,
+ * "sum": Z,
+ * "range": [min, max],
+ * "values": { "value1" : "num1", "value2" : "num2", ...}
+ * },
+ * "Key2" : {...},
+ * }
+ */
+ const items = Object.entries(hist.snapshot());
+ if (items.length) {
+ for (const [key, value] of items) {
+ const entriesNums = Object.entries(value.values).length;
+ ok(reportExpected && entriesNums > 0, `Reported ${key} for ${name}`);
+ }
+ } else if (reportExpected) {
+ ok(!hasVideo, `No video telemetry reported but no video track in the media`);
+ } else {
+ ok(true, `No video telemetry expected, none reported`);
+ }
+ // Avoid to pollute next test task.
+ hist.clear();
+ } catch (e) {
+ ok(false , `keyed histogram '${name}' doesn't exist`);
+ }
+ }
+
+ // In any case, the combined probe MEDIA_PLAY_TIME_MS should be reported, if
+ // expected
+ {
+ const hist =
+ SpecialPowers.Services.telemetry.getKeyedHistogramById("MEDIA_PLAY_TIME_MS");
+ const items = Object.entries(hist.snapshot());
+ if (items.length) {
+ for (const item of items) {
+ ok(item[0].includes("V") != -1 || !hasVideo, "Video time is reported if video was present");
+ }
+ hist.clear();
+ } else {
+ ok(!reportExpected, "MEDIA_PLAY_TIME_MS should always be reported if a report is expected");
+ }
+ }
+
+ for (const name of audioKeyedHistNames) {
+ try {
+ const hist = SpecialPowers.Services.telemetry.getKeyedHistogramById(name);
+ const items = Object.entries(hist.snapshot());
+ if (items.length) {
+ for (const [key, value] of items) {
+ const entriesNums = Object.entries(value.values).length;
+ ok(reportExpected && entriesNums > 0, `Reported ${key} for ${name}`);
+ }
+ } else {
+ ok(!reportExpected || !hasAudio, `No audio telemetry expected, none reported`);
+ }
+ // Avoid to pollute next test task.
+ hist.clear();
+ } catch (e) {
+ ok(false , `keyed histogram '${name}' doesn't exist`);
+ }
+ }
+}
+
+function once(target, name) {
+ return new Promise(r => target.addEventListener(name, r, { once: true }));
+}
+
+function onceWithTrueReturn(target, name) {
+ return once(target, name).then(_ => true);
+}
+
+function returnTrueWhenAllValuesAreTrue(arr) {
+ for (let val of arr) {
+ if (!val) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Block the main thread for a number of milliseconds
+function blockMainThread(durationMS) {
+ const start = Date.now();
+ while (Date.now() - start < durationMS) { /* spin */ }
+}
+
+// Allows comparing two values from the system clocks that are not gathered
+// atomically. Allow up to 1ms of fuzzing when lhs and rhs are seconds.
+function timeFuzzyEquals(lhs, rhs, str) {
+ ok(Math.abs(lhs - rhs) < 1e-3, str);
+}
+
+function assertAttributeDefined(mediaChrome, checkType) {
+ ok(mediaChrome[checkType] != undefined, `${checkType} exists`);
+}
+
+function assertValueEqualTo(mediaChrome, checkType, expectedValue) {
+ assertAttributeDefined(mediaChrome, checkType);
+ is(mediaChrome[checkType], expectedValue, `${checkType} equals to ${expectedValue}`);
+}
+
+async function assertValueConstantlyIncreases(mediaChrome, checkType) {
+ assertAttributeDefined(mediaChrome, checkType);
+ const valueSnapshot = mediaChrome[checkType];
+ // 30ms is long enough to have a low-resolution system clock tick, but short
+ // enough to not slow the test down.
+ blockMainThread(30);
+ const current = mediaChrome[checkType];
+ ok(current > valueSnapshot, `${checkType} keeps increasing (${current} > ${valueSnapshot})`);
+}
+
+function assertValueKeptUnchanged(mediaChrome, checkType) {
+ assertAttributeDefined(mediaChrome, checkType);
+ const valueSnapshot = mediaChrome[checkType];
+ // 30ms is long enough to have a low-resolution system clock tick, but short
+ // enough to not slow the test down.
+ blockMainThread(30);
+ const newValue = mediaChrome[checkType];
+ timeFuzzyEquals(newValue, valueSnapshot, `${checkType} keeps unchanged (${newValue} vs. ${valueSnapshot})`);
+}
+
+function assertAllProbeRelatedAttributesKeptUnchanged(video) {
+ const videoChrome = SpecialPowers.wrap(video);
+ assertValueKeptUnchanged(videoChrome, "totalVideoPlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime");
+}
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/media/test/chrome/test_telemetry_source_buffer_type.html b/dom/media/test/chrome/test_telemetry_source_buffer_type.html
new file mode 100644
index 0000000000..85ccfd1000
--- /dev/null
+++ b/dom/media/test/chrome/test_telemetry_source_buffer_type.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Telemetry : test permanent probe MSE_SOURCE_BUFFER_TYPE</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript">
+
+/**
+ * This test is used for the permanent Telemetry probe MSE_SOURCE_BUFFER_TYPE.
+ * which is used to measure the usage of mime type used in MSE.
+ */
+const VIDEO_HEVC = "video/hevc";
+const VIDEO_WEBM = "video/webm";
+const AUDIO_WEBM = "audio/webm";
+const VIDEO_MP4 = "video/mp4";
+const AUDIO_MP4 = "audio/mp4";
+const VIDEO_MP2T = "video/mp2t";
+const AUDIO_MP2T = "audio/mp2t";
+const AUDIO_MP3 = "audio/mpeg";
+const AUDIO_AAC = "audio/aac";
+
+// The order follows `MSE_SOURCE_BUFFER_TYPE` in `Histogram.json`.
+const gLabelNames = [VIDEO_HEVC, VIDEO_WEBM, AUDIO_WEBM, VIDEO_MP4, AUDIO_MP4,
+ VIDEO_MP2T, AUDIO_MP2T, AUDIO_MP3, AUDIO_AAC];
+
+add_task(async function setTestPref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.testing-only-events", true]]});
+});
+
+add_task(async function testSourceBufferTypeProbe() {
+ for (let label of gLabelNames) {
+ // HEVC will be tested in another task.
+ if (label == VIDEO_HEVC) {
+ continue;
+ }
+ info(`calling 'isTypeSupported()' should increase the probe value`);
+ MediaSource.isTypeSupported(label);
+ assertLabelValueEqualTo(label, 1);
+
+ info(`calling 'addSourceBuffer()' should increase the probe value`);
+ try {
+ const source = new MediaSource();
+ source.addSourceBuffer(label);
+ } catch (e) {
+ info(`ignore error for unsupported type ${label}`);
+ }
+ assertLabelValueEqualTo(label, 2);
+ }
+});
+
+add_task(async function testSourceBufferHEVCTypeProbe() {
+ // These are all possible mime types for HEVC
+ const mimeTypes = [
+ "video/mp4; codecs=\"hvc1\"",
+ "video/mp4; codecs=\"hev1\"",
+ "video/mp4; codecs=\"hvc1.1.6.L93.B0\"",
+ "video/mp4; codecs=\"hev1.1.6.L93.B0\"",
+ ];
+ for (let idx = 0; idx < mimeTypes.length; idx++) {
+ const mimeType = mimeTypes[idx];
+ info(`calling 'isTypeSupported()' should increase the probe value`);
+ MediaSource.isTypeSupported(mimeType);
+ assertLabelValueEqualTo(VIDEO_HEVC, idx * 2 + 1);
+
+ info(`calling 'addSourceBuffer()' should increase the probe value`);
+ try {
+ const source = new MediaSource();
+ source.addSourceBuffer(mimeType);
+ } catch (e) {
+ info(`ignore error for unsupported type ${mimeType}`);
+ }
+ assertLabelValueEqualTo(VIDEO_HEVC, idx * 2 + 2);
+ }
+});
+
+/**
+ * Following are helper functions
+ */
+async function assertLabelValueEqualTo(labelName, value) {
+ const hist = SpecialPowers.Services.telemetry.getHistogramById("MSE_SOURCE_BUFFER_TYPE");
+ /**
+ * Histogram's snapshot looks like that
+ * {
+ * "bucket_count": X,
+ * "histogram_type": Y,
+ * "sum": Z,
+ * "range": [min, max],
+ * "values": { "value1" : "num1", "value2" : "num2", ...}
+ * }
+ */
+ if (!gLabelNames.includes(labelName)) {
+ ok(false, `undefined label name=${labelName}`);
+ return;
+ }
+ const labelIdx = gLabelNames.indexOf(labelName);
+ is(hist.snapshot().values[labelIdx], value, `${labelName} equal to ${value}`);
+}
+
+</script>
+</head>
+<body>
+</body>
+</html>