test_accumulated_play_time.html (25596B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <title>Test Video Play Time Related Permanent Telemetry Probes</title> 5 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 6 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 7 <script type="application/javascript"> 8 9 /** 10 * This test is used to ensure that we accumulate time for video playback 11 * correctly, and the results would be used in Telemetry probes. 12 * Currently this test covers following probes 13 * - VIDEO_PLAY_TIME_MS 14 * - VIDEO_HDR_PLAY_TIME_MS 15 * - VIDEO_HIDDEN_PLAY_TIME_MS 16 * - VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE 17 * - VIDEO_VISIBLE_PLAY_TIME_MS 18 * - MEDIA_PLAY_TIME_MS 19 * - MUTED_PLAY_TIME_PERCENT 20 * - AUDIBLE_PLAY_TIME_PERCENT 21 */ 22 const videoHistNames = [ 23 "VIDEO_PLAY_TIME_MS", 24 "VIDEO_HIDDEN_PLAY_TIME_MS" 25 ]; 26 const videoHDRHistNames = [ 27 "VIDEO_HDR_PLAY_TIME_MS" 28 ]; 29 const videoKeyedHistNames = [ 30 "VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE", 31 "VIDEO_VISIBLE_PLAY_TIME_MS" 32 ]; 33 const audioKeyedHistNames = [ 34 "MUTED_PLAY_TIME_PERCENT", 35 "AUDIBLE_PLAY_TIME_PERCENT" 36 ]; 37 38 add_task(async function setTestPref() { 39 await SpecialPowers.pushPrefEnv({ 40 set: [["media.testing-only-events", true], 41 ["media.test.video-suspend", true], 42 ["media.suspend-background-video.enabled", true], 43 ["media.suspend-background-video.delay-ms", 0], 44 ["dom.media.silence_duration_for_audibility", 0.1] 45 ]}); 46 }); 47 48 add_task(async function testTotalPlayTime() { 49 const video = document.createElement('video'); 50 video.src = "gizmo.mp4"; 51 document.body.appendChild(video); 52 53 info(`all accumulated time should be zero`); 54 const videoChrome = SpecialPowers.wrap(video); 55 await new Promise(r => video.onloadeddata = r); 56 assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0); 57 assertValueEqualTo(videoChrome, "invisiblePlayTime", 0); 58 59 info(`start accumulating play time after media starts`); 60 video.autoplay = true; 61 await Promise.all([ 62 once(video, "playing"), 63 once(video, "moztotalplaytimestarted"), 64 ]); 65 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 66 assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); 67 68 info(`should not accumulate time for paused video`); 69 video.pause(); 70 await once(video, "moztotalplaytimepaused"); 71 assertValueKeptUnchanged(videoChrome, "totalVideoPlayTime"); 72 assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0); 73 74 info(`should start accumulating time again`); 75 let rv = await Promise.all([ 76 onceWithTrueReturn(video, "moztotalplaytimestarted"), 77 video.play().then(_ => true, _ => false), 78 ]); 79 ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); 80 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 81 await cleanUpMediaAndCheckTelemetry(video); 82 }); 83 84 // The testHDRPlayTime task will only pass on platforms that accurately report 85 // color depth in their VideoInfo structures. Presently, that is only true for 86 // macOS. 87 const {AppConstants} = ChromeUtils.importESModule( 88 "resource://gre/modules/AppConstants.sys.mjs" 89 ); 90 const reportsColorDepthFromVideoData = 91 (AppConstants.platform == "macosx" || AppConstants.platform == "win"); 92 if (reportsColorDepthFromVideoData) { 93 add_task(async function testHDRPlayTime() { 94 // This task is different from the others because the HTMLMediaElement does 95 // not expose a chrome property for video hdr play time. But we do capture 96 // telemety for VIDEO_HDR_PLAY_TIME_MS. To ensure that this telemetry is 97 // generated, this task follows the same structure as the other tasks, but 98 // doesn't actually check the properties of the video player, other than to 99 // confirm that video has played for at least some time. 100 const video = document.createElement('video'); 101 if (AppConstants.platform == "macosx") { 102 video.src = "TestPatternHDR.mp4"; // This is an HDR video with no audio. 103 } else if (AppConstants.platform == "win") { 104 video.src = "gizmo_av1_10bit_420.webm"; 105 } 106 document.body.appendChild(video); 107 108 info(`load the HDR video`); 109 const videoChrome = SpecialPowers.wrap(video); 110 await new Promise(r => video.onloadeddata = r); 111 112 info(`start accumulating play time after media starts`); 113 video.autoplay = true; 114 await Promise.all([ 115 once(video, "playing"), 116 once(video, "moztotalplaytimestarted"), 117 ]); 118 // Check that we have at least some video play time, because the 119 // HDR play time telemetry is emitted by the same process. 120 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 121 await cleanUpMediaAndCheckTelemetry(video, {hasVideo: true, hasAudio: false, hasVideoHDR: true}); 122 }); 123 } 124 125 add_task(async function testVisiblePlayTime() { 126 const video = document.createElement('video'); 127 video.src = "gizmo.mp4"; 128 document.body.appendChild(video); 129 130 info(`all accumulated time should be zero`); 131 const videoChrome = SpecialPowers.wrap(video); 132 await new Promise(r => video.onloadeddata = r); 133 assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0); 134 assertValueEqualTo(videoChrome, "visiblePlayTime", 0); 135 assertValueEqualTo(videoChrome, "invisiblePlayTime", 0); 136 137 info(`start accumulating play time after media starts`); 138 video.autoplay = true; 139 await Promise.all([ 140 once(video, "playing"), 141 once(video, "moztotalplaytimestarted"), 142 ]); 143 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 144 await assertValueConstantlyIncreases(videoChrome, "visiblePlayTime"); 145 assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); 146 147 info(`make video invisible`); 148 video.style.display = "none"; 149 await once(video, "mozinvisibleplaytimestarted"); 150 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 151 await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); 152 assertValueKeptUnchanged(videoChrome, "visiblePlayTime"); 153 await cleanUpMediaAndCheckTelemetry(video); 154 }); 155 156 add_task(async function testAudibleAudioPlayTime() { 157 const audio = document.createElement('audio'); 158 audio.src = "tone2s-silence4s-tone2s.opus"; 159 audio.controls = true; 160 audio.loop = true; 161 document.body.appendChild(audio); 162 163 info(`all accumulated time should be zero`); 164 const audioChrome = SpecialPowers.wrap(audio); 165 await new Promise(r => audio.onloadeddata = r); 166 assertValueEqualTo(audioChrome, "totalVideoPlayTime", 0); 167 assertValueEqualTo(audioChrome, "totalAudioPlayTime", 0); 168 assertValueEqualTo(audioChrome, "mutedPlayTime", 0); 169 assertValueEqualTo(audioChrome, "audiblePlayTime", 0); 170 171 info(`start accumulating play time after media starts`); 172 await Promise.all([ 173 audio.play(), 174 once(audio, "moztotalplaytimestarted"), 175 ]); 176 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 177 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 178 assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); 179 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 180 181 info(`audio becomes inaudible for 4s`); 182 await once(audio, "mozinaudibleaudioplaytimestarted"); 183 184 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 185 assertValueKeptUnchanged(audioChrome, "audiblePlayTime"); 186 assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); 187 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 188 189 info(`audio becomes audible after 4s`); 190 await once(audio, "mozinaudibleaudioplaytimepaused"); 191 192 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 193 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 194 assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); 195 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 196 197 await cleanUpMediaAndCheckTelemetry(audio, {hasVideo: false}); 198 }); 199 200 add_task(async function testHiddenPlayTime() { 201 const invisibleReasons = ["notInTree", "notInConnectedTree", "invisibleInDisplay"]; 202 for (let reason of invisibleReasons) { 203 const video = document.createElement('video'); 204 video.src = "gizmo.mp4"; 205 video.loop = true; 206 info(`invisible video due to '${reason}'`); 207 208 if (reason == "notInConnectedTree") { 209 let disconnected = document.createElement("div") 210 disconnected.appendChild(video); 211 } else if (reason == "invisibleInDisplay") { 212 document.body.appendChild(video); 213 video.style.display = "none"; 214 } else if (reason == "notInTree") { 215 // video is already created in the `notInTree` situation. 216 } else { 217 ok(false, "undefined reason"); 218 } 219 220 info(`start invisible video should start accumulating timers`); 221 const videoChrome = SpecialPowers.wrap(video); 222 let rv = await Promise.all([ 223 onceWithTrueReturn(video, "mozinvisibleplaytimestarted"), 224 video.play().then(_ => true, _ => false), 225 ]); 226 ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); 227 await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); 228 229 info(`should not accumulate time for paused video`); 230 video.pause(); 231 await once(video, "mozinvisibleplaytimepaused"); 232 assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); 233 234 info(`should start accumulating time again`); 235 rv = await Promise.all([ 236 onceWithTrueReturn(video, "mozinvisibleplaytimestarted"), 237 video.play().then(_ => true, _ => false), 238 ]); 239 ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); 240 await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); 241 242 info(`make video visible should stop accumulating invisible related time`); 243 if (reason == "notInTree" || reason == "notInConnectedTree") { 244 document.body.appendChild(video); 245 } else if (reason == "invisibleInDisplay") { 246 video.style.display = "block"; 247 } else { 248 ok(false, "undefined reason"); 249 } 250 await once(video, "mozinvisibleplaytimepaused"); 251 assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); 252 await cleanUpMediaAndCheckTelemetry(video); 253 } 254 }); 255 256 add_task(async function testAudioProbesWithoutAudio() { 257 const video = document.createElement('video'); 258 video.src = "gizmo-noaudio.mp4"; 259 video.loop = true; 260 document.body.appendChild(video); 261 262 info(`all accumulated time should be zero`); 263 const videoChrome = SpecialPowers.wrap(video); 264 await new Promise(r => video.onloadeddata = r); 265 assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0); 266 assertValueEqualTo(videoChrome, "totalAudioPlayTime", 0); 267 assertValueEqualTo(videoChrome, "mutedPlayTime", 0); 268 assertValueEqualTo(videoChrome, "audiblePlayTime", 0); 269 270 info(`start accumulating play time after media starts`); 271 await Promise.all([ 272 video.play(), 273 once(video, "moztotalplaytimestarted"), 274 ]); 275 276 async function checkInvariants() { 277 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 278 assertValueKeptUnchanged(videoChrome, "audiblePlayTime"); 279 assertValueKeptUnchanged(videoChrome, "mutedPlayTime"); 280 assertValueKeptUnchanged(videoChrome, "totalAudioPlayTime"); 281 } 282 283 checkInvariants(); 284 285 video.muted = true; 286 287 checkInvariants(); 288 289 video.currentTime = 0.0; 290 await once(video, "seeked"); 291 292 checkInvariants(); 293 294 video.muted = false; 295 296 checkInvariants(); 297 298 video.volume = 0.0; 299 300 checkInvariants(); 301 302 video.volume = 1.0; 303 304 checkInvariants(); 305 306 video.muted = true; 307 308 checkInvariants(); 309 310 video.currentTime = 0.0; 311 312 checkInvariants(); 313 314 await cleanUpMediaAndCheckTelemetry(video, {hasAudio: false}); 315 }); 316 317 add_task(async function testMutedAudioPlayTime() { 318 const audio = document.createElement('audio'); 319 audio.src = "gizmo.mp4"; 320 audio.controls = true; 321 audio.loop = true; 322 document.body.appendChild(audio); 323 324 info(`all accumulated time should be zero`); 325 const audioChrome = SpecialPowers.wrap(audio); 326 await new Promise(r => audio.onloadeddata = r); 327 assertValueEqualTo(audioChrome, "totalVideoPlayTime", 0); 328 assertValueEqualTo(audioChrome, "totalAudioPlayTime", 0); 329 assertValueEqualTo(audioChrome, "mutedPlayTime", 0); 330 assertValueEqualTo(audioChrome, "audiblePlayTime", 0); 331 332 info(`start accumulating play time after media starts`); 333 await Promise.all([ 334 audio.play(), 335 once(audio, "moztotalplaytimestarted"), 336 ]); 337 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 338 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 339 assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); 340 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 341 342 audio.muted = true; 343 await once(audio, "mozmutedaudioplaytimestarted"); 344 345 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 346 await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); 347 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 348 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 349 350 audio.currentTime = 0.0; 351 await once(audio, "seeked"); 352 353 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 354 await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); 355 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 356 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 357 358 audio.muted = false; 359 await once(audio, "mozmutedeaudioplaytimepaused"); 360 361 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 362 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 363 assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); 364 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 365 366 audio.volume = 0.0; 367 await once(audio, "mozmutedaudioplaytimestarted"); 368 369 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 370 await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); 371 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 372 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 373 374 audio.volume = 1.0; 375 await once(audio, "mozmutedeaudioplaytimepaused"); 376 377 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 378 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 379 assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); 380 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 381 382 audio.muted = true; 383 await once(audio, "mozmutedaudioplaytimestarted"); 384 385 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 386 await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); 387 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 388 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 389 390 audio.currentTime = 0.0; 391 392 await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); 393 await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); 394 await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); 395 assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); 396 397 // The media has a video track, but it's being played back in an 398 // HTMLAudioElement, without video frame location. 399 await cleanUpMediaAndCheckTelemetry(audio, {hasVideo: false}); 400 }); 401 402 // Note that video suspended time is not always align with the invisible play 403 // time even if `media.suspend-background-video.delay-ms` is `0`, because not all 404 // invisible videos would be suspended under current strategy. 405 add_task(async function testDecodeSuspendedTime() { 406 const video = document.createElement('video'); 407 video.src = "gizmo.mp4"; 408 video.loop = true; 409 document.body.appendChild(video); 410 411 info(`start video should start accumulating timers`); 412 const videoChrome = SpecialPowers.wrap(video); 413 let rv = await Promise.all([ 414 onceWithTrueReturn(video, "moztotalplaytimestarted"), 415 video.play().then(_ => true, _ => false), 416 ]); 417 ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); 418 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 419 assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); 420 421 info(`make it invisible and force to suspend decoding`); 422 video.setVisible(false); 423 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 424 await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); 425 426 info(`make it visible and resume decoding`); 427 video.setVisible(true); 428 await once(video, "mozinvisibleplaytimepaused"); 429 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 430 assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); 431 await cleanUpMediaAndCheckTelemetry(video); 432 }); 433 434 add_task(async function reuseSameElementForPlayback() { 435 const video = document.createElement('video'); 436 video.src = "gizmo.mp4"; 437 document.body.appendChild(video); 438 439 info(`start accumulating play time after media starts`); 440 const videoChrome = SpecialPowers.wrap(video); 441 let rv = await Promise.all([ 442 onceWithTrueReturn(video, "moztotalplaytimestarted"), 443 video.play().then(_ => true, _ => false), 444 ]); 445 ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); 446 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 447 448 info(`reset its src and all accumulated value should be reset after then`); 449 // After setting its src to nothing, that would trigger a failed load and set 450 // the error. If the following step tries to set the new resource and `play()` 451 // , then they should be done after receving the `error` from that failed load 452 // first. 453 await Promise.all([ 454 once(video, "error"), 455 cleanUpMediaAndCheckTelemetry(video), 456 ]); 457 // video doesn't have a decoder, so the return value would be -1 (error). 458 assertValueEqualTo(videoChrome, "totalVideoPlayTime", -1); 459 assertValueEqualTo(videoChrome, "invisiblePlayTime", -1); 460 461 info(`resue same element, make it visible and start playback again`); 462 video.src = "gizmo.mp4"; 463 rv = await Promise.all([ 464 onceWithTrueReturn(video, "moztotalplaytimestarted"), 465 video.play().then(_ => true, _ => false), 466 ]); 467 ok(returnTrueWhenAllValuesAreTrue(rv), "video started"); 468 await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); 469 await cleanUpMediaAndCheckTelemetry(video); 470 }); 471 472 add_task(async function testNoReportedTelemetryResult() { 473 info(`No result for empty video`); 474 const video = document.createElement('video'); 475 assertAllProbeRelatedAttributesKeptUnchanged(video); 476 await assertNoReportedTelemetryResult(video); 477 478 info(`No result for video which hasn't started playing`); 479 video.src = "gizmo.mp4"; 480 document.body.appendChild(video); 481 ok(await once(video, "loadeddata").then(_ => true), "video loaded data"); 482 assertAllProbeRelatedAttributesKeptUnchanged(video); 483 await assertNoReportedTelemetryResult(video); 484 485 info(`No result for video with error`); 486 video.src = "filedoesnotexist.mp4"; 487 ok(await video.play().then(_ => false, _ => true), "video failed to play"); 488 ok(video.error != undefined, "video got error"); 489 assertAllProbeRelatedAttributesKeptUnchanged(video); 490 await assertNoReportedTelemetryResult(video); 491 }); 492 493 /** 494 * Following are helper functions 495 */ 496 async function cleanUpMediaAndCheckTelemetry(media, { reportExpected = true, hasVideo = true, hasAudio = true, hasVideoHDR = false } = {}) { 497 media.src = ""; 498 await checkReportedTelemetry(media, reportExpected, hasVideo, hasAudio, hasVideoHDR); 499 } 500 501 async function assertNoReportedTelemetryResult(media) { 502 await checkReportedTelemetry(media, false, true, true); 503 } 504 505 async function checkReportedTelemetry(media, reportExpected, hasVideo, hasAudio, hasVideoHDR) { 506 const reportResultPromise = once(media, "mozreportedtelemetry"); 507 info(`check telemetry result, reportExpected=${reportExpected}`); 508 if (reportExpected) { 509 await reportResultPromise; 510 } 511 for (const name of videoHistNames) { 512 try { 513 const hist = SpecialPowers.Services.telemetry.getHistogramById(name); 514 /** 515 * Histogram's snapshot looks like that 516 * { 517 * "bucket_count": X, 518 * "histogram_type": Y, 519 * "sum": Z, 520 * "range": [min, max], 521 * "values": { "value1" : "num1", "value2" : "num2", ...} 522 * } 523 */ 524 const entriesNums = Object.entries(hist.snapshot().values).length; 525 if (reportExpected && hasVideo) { 526 ok(entriesNums > 0, `Reported result for ${name}`); 527 } else { 528 ok(entriesNums == 0, `Reported nothing for ${name}`); 529 } 530 hist.clear(); 531 } catch (e) { 532 ok(false , `histogram '${name}' doesn't exist`); 533 } 534 } 535 // videoHDRHistNames are checked for total time, not for number of samples. 536 for (const name of videoHDRHistNames) { 537 try { 538 const hist = SpecialPowers.Services.telemetry.getHistogramById(name); 539 const totalTimeMS = hist.snapshot().sum; 540 if (reportExpected && hasVideoHDR) { 541 ok(totalTimeMS > 0, `Reported some time for ${name}`); 542 } else { 543 ok(totalTimeMS == 0, `Reported no time for ${name}`); 544 } 545 hist.clear(); 546 } catch (e) { 547 ok(false , `histogram '${name}' doesn't exist`); 548 } 549 } 550 for (const name of videoKeyedHistNames) { 551 try { 552 const hist = SpecialPowers.Services.telemetry.getKeyedHistogramById(name); 553 /** 554 * Keyed Histogram's snapshot looks like that 555 * { 556 * "Key1" : { 557 * "bucket_count": X, 558 * "histogram_type": Y, 559 * "sum": Z, 560 * "range": [min, max], 561 * "values": { "value1" : "num1", "value2" : "num2", ...} 562 * }, 563 * "Key2" : {...}, 564 * } 565 */ 566 const items = Object.entries(hist.snapshot()); 567 if (items.length) { 568 for (const [key, value] of items) { 569 const entriesNums = Object.entries(value.values).length; 570 ok(reportExpected && entriesNums > 0, `Reported ${key} for ${name}`); 571 } 572 } else if (reportExpected) { 573 ok(!hasVideo, `No video telemetry reported but no video track in the media`); 574 } else { 575 ok(true, `No video telemetry expected, none reported`); 576 } 577 // Avoid to pollute next test task. 578 hist.clear(); 579 } catch (e) { 580 ok(false , `keyed histogram '${name}' doesn't exist`); 581 } 582 } 583 584 // In any case, the combined probe MEDIA_PLAY_TIME_MS should be reported, if 585 // expected 586 { 587 const hist = 588 SpecialPowers.Services.telemetry.getKeyedHistogramById("MEDIA_PLAY_TIME_MS"); 589 const items = Object.entries(hist.snapshot()); 590 if (items.length) { 591 for (const item of items) { 592 ok(item[0].includes("V") != -1 || !hasVideo, "Video time is reported if video was present"); 593 } 594 hist.clear(); 595 } else { 596 ok(!reportExpected, "MEDIA_PLAY_TIME_MS should always be reported if a report is expected"); 597 } 598 } 599 600 for (const name of audioKeyedHistNames) { 601 try { 602 const hist = SpecialPowers.Services.telemetry.getKeyedHistogramById(name); 603 const items = Object.entries(hist.snapshot()); 604 if (items.length) { 605 for (const [key, value] of items) { 606 const entriesNums = Object.entries(value.values).length; 607 ok(reportExpected && entriesNums > 0, `Reported ${key} for ${name}`); 608 } 609 } else { 610 ok(!reportExpected || !hasAudio, `No audio telemetry expected, none reported`); 611 } 612 // Avoid to pollute next test task. 613 hist.clear(); 614 } catch (e) { 615 ok(false , `keyed histogram '${name}' doesn't exist`); 616 } 617 } 618 } 619 620 function once(target, name) { 621 return new Promise(r => target.addEventListener(name, r, { once: true })); 622 } 623 624 function onceWithTrueReturn(target, name) { 625 return once(target, name).then(_ => true); 626 } 627 628 function returnTrueWhenAllValuesAreTrue(arr) { 629 for (let val of arr) { 630 if (!val) { 631 return false; 632 } 633 } 634 return true; 635 } 636 637 // Block the main thread for a number of milliseconds 638 function blockMainThread(durationMS) { 639 const start = Date.now(); 640 while (Date.now() - start < durationMS) { /* spin */ } 641 } 642 643 // Allows comparing two values from the system clocks that are not gathered 644 // atomically. Allow up to 1ms of fuzzing when lhs and rhs are seconds. 645 function timeFuzzyEquals(lhs, rhs, str) { 646 ok(Math.abs(lhs - rhs) < 1e-3, str); 647 } 648 649 function assertAttributeDefined(mediaChrome, checkType) { 650 ok(mediaChrome[checkType] != undefined, `${checkType} exists`); 651 } 652 653 function assertValueEqualTo(mediaChrome, checkType, expectedValue) { 654 assertAttributeDefined(mediaChrome, checkType); 655 is(mediaChrome[checkType], expectedValue, `${checkType} equals to ${expectedValue}`); 656 } 657 658 async function assertValueConstantlyIncreases(mediaChrome, checkType) { 659 assertAttributeDefined(mediaChrome, checkType); 660 const valueSnapshot = mediaChrome[checkType]; 661 // 30ms is long enough to have a low-resolution system clock tick, but short 662 // enough to not slow the test down. 663 blockMainThread(30); 664 const current = mediaChrome[checkType]; 665 ok(current > valueSnapshot, `${checkType} keeps increasing (${current} > ${valueSnapshot})`); 666 } 667 668 function assertValueKeptUnchanged(mediaChrome, checkType) { 669 assertAttributeDefined(mediaChrome, checkType); 670 const valueSnapshot = mediaChrome[checkType]; 671 // 30ms is long enough to have a low-resolution system clock tick, but short 672 // enough to not slow the test down. 673 blockMainThread(30); 674 const newValue = mediaChrome[checkType]; 675 timeFuzzyEquals(newValue, valueSnapshot, `${checkType} keeps unchanged (${newValue} vs. ${valueSnapshot})`); 676 } 677 678 function assertAllProbeRelatedAttributesKeptUnchanged(video) { 679 const videoChrome = SpecialPowers.wrap(video); 680 assertValueKeptUnchanged(videoChrome, "totalVideoPlayTime"); 681 assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); 682 } 683 684 </script> 685 </head> 686 <body> 687 </body> 688 </html>