summaryrefslogtreecommitdiffstats
path: root/dom/media/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/media/test
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/test')
-rw-r--r--dom/media/test/.eslintrc.js5
-rw-r--r--dom/media/test/16bit_wave_extrametadata.wavbin0 -> 97814 bytes
-rw-r--r--dom/media/test/16bit_wave_extrametadata.wav^headers^1
-rw-r--r--dom/media/test/320x240.ogvbin0 -> 28942 bytes
-rw-r--r--dom/media/test/320x240.ogv^headers^1
-rw-r--r--dom/media/test/448636.ogvbin0 -> 7799 bytes
-rw-r--r--dom/media/test/448636.ogv^headers^1
-rw-r--r--dom/media/test/A4.ogvbin0 -> 94372 bytes
-rw-r--r--dom/media/test/A4.ogv^headers^1
-rw-r--r--dom/media/test/VID_0001.oggbin0 -> 633435 bytes
-rw-r--r--dom/media/test/VID_0001.ogg^headers^1
-rw-r--r--dom/media/test/allowed.sjs56
-rw-r--r--dom/media/test/ambisonics.mp4bin0 -> 1053904 bytes
-rw-r--r--dom/media/test/ambisonics.mp4^headers^1
-rw-r--r--dom/media/test/audio-gaps-short.oggbin0 -> 5233 bytes
-rw-r--r--dom/media/test/audio-gaps-short.ogg^headers^1
-rw-r--r--dom/media/test/audio-gaps.oggbin0 -> 12306 bytes
-rw-r--r--dom/media/test/audio-gaps.ogg^headers^1
-rw-r--r--dom/media/test/audio-overhang.oggbin0 -> 45463 bytes
-rw-r--r--dom/media/test/audio-overhang.ogg^headers^1
-rw-r--r--dom/media/test/audio.wavbin0 -> 1422 bytes
-rw-r--r--dom/media/test/audio.wav^headers^1
-rw-r--r--dom/media/test/av1.mp4bin0 -> 13089 bytes
-rw-r--r--dom/media/test/av1.mp4^headers^1
-rw-r--r--dom/media/test/background_video.js224
-rw-r--r--dom/media/test/badtags.oggbin0 -> 5033 bytes
-rw-r--r--dom/media/test/badtags.ogg^headers^1
-rw-r--r--dom/media/test/bear-640x360-a_frag-cenc-key_rotation.mp4bin0 -> 80372 bytes
-rw-r--r--dom/media/test/bear-640x360-v_frag-cenc-key_rotation.mp4bin0 -> 280361 bytes
-rw-r--r--dom/media/test/beta-phrasebook.oggbin0 -> 47411 bytes
-rw-r--r--dom/media/test/beta-phrasebook.ogg^headers^1
-rw-r--r--dom/media/test/big-buck-bunny-cenc-avc3-1.m4sbin0 -> 60041 bytes
-rw-r--r--dom/media/test/big-buck-bunny-cenc-avc3-1.m4s^headers^1
-rw-r--r--dom/media/test/big-buck-bunny-cenc-avc3-init.mp4bin0 -> 819 bytes
-rw-r--r--dom/media/test/big-buck-bunny-cenc-avc3-init.mp4^headers^1
-rw-r--r--dom/media/test/big-short.wavbin0 -> 12366 bytes
-rw-r--r--dom/media/test/big-short.wav^headers^1
-rw-r--r--dom/media/test/big.wavbin0 -> 102444 bytes
-rw-r--r--dom/media/test/big.wav^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-audio-key1.xml28
-rw-r--r--dom/media/test/bipbop-cenc-audio-key2.xml28
-rw-r--r--dom/media/test/bipbop-cenc-audio1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop-cenc-audio1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-audio2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop-cenc-audio2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-audio3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop-cenc-audio3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-audioinit.mp4bin0 -> 1000 bytes
-rw-r--r--dom/media/test/bipbop-cenc-audioinit.mp4^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-video-10s.mp4bin0 -> 299914 bytes
-rw-r--r--dom/media/test/bipbop-cenc-video-10s.mp4^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-video-key1.xml28
-rw-r--r--dom/media/test/bipbop-cenc-video-key2.xml28
-rw-r--r--dom/media/test/bipbop-cenc-video1.m4sbin0 -> 25211 bytes
-rw-r--r--dom/media/test/bipbop-cenc-video1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-video2.m4sbin0 -> 22934 bytes
-rw-r--r--dom/media/test/bipbop-cenc-video2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop-cenc-videoinit.mp4bin0 -> 1058 bytes
-rw-r--r--dom/media/test/bipbop-cenc-videoinit.mp4^headers^1
-rw-r--r--dom/media/test/bipbop-cenc.sh29
-rw-r--r--dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4bin0 -> 8675 bytes
-rw-r--r--dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^1
-rw-r--r--dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4bin0 -> 278040 bytes
-rw-r--r--dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^1
-rw-r--r--dom/media/test/bipbop-frag-cenc.xml57
-rw-r--r--dom/media/test/bipbop-lateaudio.mp4bin0 -> 70404 bytes
-rw-r--r--dom/media/test/bipbop-lateaudio.mp4^headers^1
-rw-r--r--dom/media/test/bipbop-no-edts.mp4bin0 -> 285681 bytes
-rw-r--r--dom/media/test/bipbop.mp4bin0 -> 285765 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4sbin0 -> 37646 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4bin0 -> 1086 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4sbin0 -> 37646 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4bin0 -> 1086 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_225w_175kbps.mp4bin0 -> 38713 bytes
-rw-r--r--dom/media/test/bipbop_225w_175kbps.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4bin0 -> 874 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4bin0 -> 874 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4sbin0 -> 25211 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4sbin0 -> 22938 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4bin0 -> 932 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4sbin0 -> 25211 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4sbin0 -> 22938 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4bin0 -> 932 bytes
-rw-r--r--dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300_215kbps.mp4bin0 -> 48393 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4sbin0 -> 25211 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4sbin0 -> 22938 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4bin0 -> 1094 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4sbin0 -> 25211 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4sbin0 -> 22938 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4bin0 -> 1094 bytes
-rw-r--r--dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_300wp_227kbps.mp4bin0 -> 48355 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4sbin0 -> 53149 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4bin0 -> 1088 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4sbin0 -> 53149 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4bin0 -> 1088 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-clearkey-audio.webmbin0 -> 7553 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webmbin0 -> 44671 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webmbin0 -> 46030 bytes
-rw-r--r--dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^1
-rw-r--r--dom/media/test/bipbop_360w_253kbps.mp4bin0 -> 54218 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4bin0 -> 874 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4bin0 -> 874 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4sbin0 -> 68025 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4sbin0 -> 66457 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4bin0 -> 932 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4sbin0 -> 68025 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4sbin0 -> 66457 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4bin0 -> 932 bytes
-rw-r--r--dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_624kbps.mp4bin0 -> 133264 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4bin0 -> 874 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4bin0 -> 874 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4sbin0 -> 101203 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4sbin0 -> 99366 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4bin0 -> 932 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4sbin0 -> 101203 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4sbin0 -> 99366 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4bin0 -> 932 bytes
-rw-r--r--dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480_959kbps.mp4bin0 -> 199351 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4sbin0 -> 101203 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4sbin0 -> 99366 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4bin0 -> 1094 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4sbin0 -> 101203 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4sbin0 -> 99366 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4bin0 -> 1094 bytes
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_1001kbps.mp4bin0 -> 199911 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4sbin0 -> 921 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4sbin0 -> 565 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4sbin0 -> 977 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4sbin0 -> 389 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4bin0 -> 1020 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4sbin0 -> 68025 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4sbin0 -> 66457 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4bin0 -> 1094 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4sbin0 -> 68025 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4sbin0 -> 66457 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4bin0 -> 1094 bytes
-rw-r--r--dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^1
-rw-r--r--dom/media/test/bipbop_480wp_663kbps.mp4bin0 -> 133824 bytes
-rw-r--r--dom/media/test/black100x100-aspect3to2.ogvbin0 -> 3428 bytes
-rw-r--r--dom/media/test/black100x100-aspect3to2.ogv^headers^1
-rw-r--r--dom/media/test/bogus.duh45
-rw-r--r--dom/media/test/bogus.ogv45
-rw-r--r--dom/media/test/bogus.ogv^headers^1
-rw-r--r--dom/media/test/bogus.wav45
-rw-r--r--dom/media/test/bogus.wav^headers^1
-rw-r--r--dom/media/test/browser/browser.ini7
-rw-r--r--dom/media/test/browser/browser_tab_visibility_and_play_time.js120
-rw-r--r--dom/media/test/browser/file_media.html9
-rw-r--r--dom/media/test/bug1066943.webmbin0 -> 18442 bytes
-rw-r--r--dom/media/test/bug1066943.webm^headers^1
-rw-r--r--dom/media/test/bug1301226-odd.wavbin0 -> 244 bytes
-rw-r--r--dom/media/test/bug1301226-odd.wav^headers^1
-rw-r--r--dom/media/test/bug1301226.wavbin0 -> 240 bytes
-rw-r--r--dom/media/test/bug1301226.wav^headers^1
-rw-r--r--dom/media/test/bug1377278.webmbin0 -> 215594 bytes
-rw-r--r--dom/media/test/bug1377278.webm^headers^1
-rw-r--r--dom/media/test/bug461281.oggbin0 -> 16521 bytes
-rw-r--r--dom/media/test/bug461281.ogg^headers^1
-rw-r--r--dom/media/test/bug482461-theora.ogvbin0 -> 280904 bytes
-rw-r--r--dom/media/test/bug482461-theora.ogv^headers^1
-rw-r--r--dom/media/test/bug482461.ogvbin0 -> 305785 bytes
-rw-r--r--dom/media/test/bug482461.ogv^headers^1
-rw-r--r--dom/media/test/bug495129.ogvbin0 -> 122207 bytes
-rw-r--r--dom/media/test/bug495129.ogv^headers^1
-rw-r--r--dom/media/test/bug495794.oggbin0 -> 4837 bytes
-rw-r--r--dom/media/test/bug495794.ogg^headers^1
-rw-r--r--dom/media/test/bug498380.ogvbin0 -> 65535 bytes
-rw-r--r--dom/media/test/bug498380.ogv^headers^1
-rw-r--r--dom/media/test/bug498855-1.ogvbin0 -> 20480 bytes
-rw-r--r--dom/media/test/bug498855-1.ogv^headers^1
-rw-r--r--dom/media/test/bug498855-2.ogvbin0 -> 20480 bytes
-rw-r--r--dom/media/test/bug498855-2.ogv^headers^1
-rw-r--r--dom/media/test/bug498855-3.ogvbin0 -> 20480 bytes
-rw-r--r--dom/media/test/bug498855-3.ogv^headers^1
-rw-r--r--dom/media/test/bug499519.ogvbin0 -> 20480 bytes
-rw-r--r--dom/media/test/bug499519.ogv^headers^1
-rw-r--r--dom/media/test/bug500311.ogvbin0 -> 55834 bytes
-rw-r--r--dom/media/test/bug500311.ogv^headers^1
-rw-r--r--dom/media/test/bug501279.oggbin0 -> 2361 bytes
-rw-r--r--dom/media/test/bug501279.ogg^headers^1
-rw-r--r--dom/media/test/bug504613.ogvbin0 -> 35000 bytes
-rw-r--r--dom/media/test/bug504613.ogv^headers^1
-rw-r--r--dom/media/test/bug504644.ogvbin0 -> 131114 bytes
-rw-r--r--dom/media/test/bug504644.ogv^headers^1
-rw-r--r--dom/media/test/bug504843.ogvbin0 -> 65536 bytes
-rw-r--r--dom/media/test/bug504843.ogv^headers^1
-rw-r--r--dom/media/test/bug506094.ogvbin0 -> 8195 bytes
-rw-r--r--dom/media/test/bug506094.ogv^headers^1
-rw-r--r--dom/media/test/bug516323.indexed.ogvbin0 -> 162193 bytes
-rw-r--r--dom/media/test/bug516323.indexed.ogv^headers^1
-rw-r--r--dom/media/test/bug516323.ogvbin0 -> 161789 bytes
-rw-r--r--dom/media/test/bug516323.ogv^headers^1
-rw-r--r--dom/media/test/bug520493.oggbin0 -> 3901 bytes
-rw-r--r--dom/media/test/bug520493.ogg^headers^1
-rw-r--r--dom/media/test/bug520500.oggbin0 -> 21978 bytes
-rw-r--r--dom/media/test/bug520500.ogg^headers^1
-rw-r--r--dom/media/test/bug520908.ogvbin0 -> 28942 bytes
-rw-r--r--dom/media/test/bug520908.ogv^headers^1
-rw-r--r--dom/media/test/bug523816.ogvbin0 -> 40585 bytes
-rw-r--r--dom/media/test/bug523816.ogv^headers^1
-rw-r--r--dom/media/test/bug533822.oggbin0 -> 35010 bytes
-rw-r--r--dom/media/test/bug533822.ogg^headers^1
-rw-r--r--dom/media/test/bug556821.ogvbin0 -> 196608 bytes
-rw-r--r--dom/media/test/bug556821.ogv^headers^1
-rw-r--r--dom/media/test/bug557094.ogvbin0 -> 76966 bytes
-rw-r--r--dom/media/test/bug557094.ogv^headers^1
-rw-r--r--dom/media/test/bug603918.webmbin0 -> 103227 bytes
-rw-r--r--dom/media/test/bug603918.webm^headers^1
-rw-r--r--dom/media/test/bug604067.webmbin0 -> 103227 bytes
-rw-r--r--dom/media/test/bug604067.webm^headers^1
-rw-r--r--dom/media/test/bunny.webmbin0 -> 195455 bytes
-rw-r--r--dom/media/test/can_play_type_dash.js27
-rw-r--r--dom/media/test/can_play_type_ogg.js72
-rw-r--r--dom/media/test/can_play_type_wave.js30
-rw-r--r--dom/media/test/can_play_type_webm.js39
-rw-r--r--dom/media/test/cancellable_request.sjs153
-rw-r--r--dom/media/test/chain.oggbin0 -> 63610 bytes
-rw-r--r--dom/media/test/chain.ogg^headers^1
-rw-r--r--dom/media/test/chain.ogvbin0 -> 45463 bytes
-rw-r--r--dom/media/test/chain.ogv^headers^1
-rw-r--r--dom/media/test/chain.opusbin0 -> 50101 bytes
-rw-r--r--dom/media/test/chain.opus^headers^1
-rw-r--r--dom/media/test/chained-audio-video.oggbin0 -> 92552 bytes
-rw-r--r--dom/media/test/chained-audio-video.ogg^headers^1
-rw-r--r--dom/media/test/chained-video.ogvbin0 -> 57906 bytes
-rw-r--r--dom/media/test/chained-video.ogv^headers^1
-rw-r--r--dom/media/test/chrome/chrome.ini6
-rw-r--r--dom/media/test/chrome/test_accumulated_play_time.html355
-rw-r--r--dom/media/test/chromeHelper.js23
-rw-r--r--dom/media/test/cloneElementVisually_helpers.js232
-rw-r--r--dom/media/test/contentType.sjs77
-rw-r--r--dom/media/test/crashtests/0-timescale.html14
-rw-r--r--dom/media/test/crashtests/0-timescale.mp4bin0 -> 14718 bytes
-rw-r--r--dom/media/test/crashtests/1012609.html9
-rw-r--r--dom/media/test/crashtests/1015662.html4
-rw-r--r--dom/media/test/crashtests/1028458.html23
-rw-r--r--dom/media/test/crashtests/1041466.html21
-rw-r--r--dom/media/test/crashtests/1045650.html18
-rw-r--r--dom/media/test/crashtests/1080986.html3
-rw-r--r--dom/media/test/crashtests/1080986.wavbin0 -> 592 bytes
-rw-r--r--dom/media/test/crashtests/1122218.html24
-rw-r--r--dom/media/test/crashtests/1127188.html3
-rw-r--r--dom/media/test/crashtests/1157994.html21
-rw-r--r--dom/media/test/crashtests/1158427.html21
-rw-r--r--dom/media/test/crashtests/1180881.html8
-rw-r--r--dom/media/test/crashtests/1180881.webmbin0 -> 524 bytes
-rw-r--r--dom/media/test/crashtests/1185176.html24
-rw-r--r--dom/media/test/crashtests/1185192.html18
-rw-r--r--dom/media/test/crashtests/1197935.html8
-rw-r--r--dom/media/test/crashtests/1197935.mp4bin0 -> 1806042 bytes
-rw-r--r--dom/media/test/crashtests/1223670.html23
-rw-r--r--dom/media/test/crashtests/1236639.html9
-rw-r--r--dom/media/test/crashtests/1236639.mp3bin0 -> 1080 bytes
-rw-r--r--dom/media/test/crashtests/1257700.html8
-rw-r--r--dom/media/test/crashtests/1257700.webmbin0 -> 59264 bytes
-rw-r--r--dom/media/test/crashtests/1267263.html19
-rw-r--r--dom/media/test/crashtests/1270303.html8
-rw-r--r--dom/media/test/crashtests/1270303.webmbin0 -> 5822 bytes
-rw-r--r--dom/media/test/crashtests/1291702.html72
-rw-r--r--dom/media/test/crashtests/1368490.html30
-rw-r--r--dom/media/test/crashtests/1378826.html46
-rw-r--r--dom/media/test/crashtests/1384248.html10
-rw-r--r--dom/media/test/crashtests/1388372.html13
-rw-r--r--dom/media/test/crashtests/1389304.html32
-rw-r--r--dom/media/test/crashtests/1389304.mp4bin0 -> 198320 bytes
-rw-r--r--dom/media/test/crashtests/1393272.webmbin0 -> 6781 bytes
-rw-r--r--dom/media/test/crashtests/1411322.html18
-rw-r--r--dom/media/test/crashtests/1450845.html34
-rw-r--r--dom/media/test/crashtests/1489160.html10
-rw-r--r--dom/media/test/crashtests/1494073.html19
-rw-r--r--dom/media/test/crashtests/1526044.html19
-rw-r--r--dom/media/test/crashtests/1538727.html14
-rw-r--r--dom/media/test/crashtests/1545133.html34
-rw-r--r--dom/media/test/crashtests/1547784.html33
-rw-r--r--dom/media/test/crashtests/1547899.html20
-rw-r--r--dom/media/test/crashtests/1560215.html20
-rw-r--r--dom/media/test/crashtests/1569645.html23
-rw-r--r--dom/media/test/crashtests/1575271.html25
-rw-r--r--dom/media/test/crashtests/1577184.html15
-rw-r--r--dom/media/test/crashtests/1587248.html23
-rw-r--r--dom/media/test/crashtests/1594466.html22
-rw-r--r--dom/media/test/crashtests/1601385.html12
-rw-r--r--dom/media/test/crashtests/1601422.html20
-rw-r--r--dom/media/test/crashtests/1604941.html22
-rw-r--r--dom/media/test/crashtests/1608286.html50
-rw-r--r--dom/media/test/crashtests/1673525.html15
-rw-r--r--dom/media/test/crashtests/459439-1.html36
-rw-r--r--dom/media/test/crashtests/466607-1.html14
-rw-r--r--dom/media/test/crashtests/466945-1.html25
-rw-r--r--dom/media/test/crashtests/468763-1.html1
-rw-r--r--dom/media/test/crashtests/474744-1.html15
-rw-r--r--dom/media/test/crashtests/481136-1.html3
-rw-r--r--dom/media/test/crashtests/492286-1.xhtml1
-rw-r--r--dom/media/test/crashtests/493915-1.html18
-rw-r--r--dom/media/test/crashtests/495794-1.html8
-rw-r--r--dom/media/test/crashtests/495794-1.oggbin0 -> 4837 bytes
-rw-r--r--dom/media/test/crashtests/497734-1.xhtml21
-rw-r--r--dom/media/test/crashtests/497734-2.html17
-rw-r--r--dom/media/test/crashtests/576612-1.html15
-rw-r--r--dom/media/test/crashtests/691096-1.html31
-rw-r--r--dom/media/test/crashtests/752784-1.html15
-rw-r--r--dom/media/test/crashtests/789075-1.html20
-rw-r--r--dom/media/test/crashtests/789075.webmbin0 -> 12294 bytes
-rw-r--r--dom/media/test/crashtests/795892-1.html23
-rw-r--r--dom/media/test/crashtests/844563.html5
-rw-r--r--dom/media/test/crashtests/846612.html8
-rw-r--r--dom/media/test/crashtests/852838.html11
-rw-r--r--dom/media/test/crashtests/865004.html19
-rw-r--r--dom/media/test/crashtests/865537-1.html13
-rw-r--r--dom/media/test/crashtests/865550.html22
-rw-r--r--dom/media/test/crashtests/868504.html14
-rw-r--r--dom/media/test/crashtests/874869.html15
-rw-r--r--dom/media/test/crashtests/874915.html24
-rw-r--r--dom/media/test/crashtests/874934.html23
-rw-r--r--dom/media/test/crashtests/874952.html11
-rw-r--r--dom/media/test/crashtests/875144.html81
-rw-r--r--dom/media/test/crashtests/875596.html12
-rw-r--r--dom/media/test/crashtests/875911.html3
-rw-r--r--dom/media/test/crashtests/876024-1.html5
-rw-r--r--dom/media/test/crashtests/876024-2.html17
-rw-r--r--dom/media/test/crashtests/876118.html16
-rw-r--r--dom/media/test/crashtests/876207.html30
-rw-r--r--dom/media/test/crashtests/876215.html14
-rw-r--r--dom/media/test/crashtests/876249.html27
-rw-r--r--dom/media/test/crashtests/876252.html23
-rw-r--r--dom/media/test/crashtests/876834.html4
-rw-r--r--dom/media/test/crashtests/877527.html37
-rw-r--r--dom/media/test/crashtests/877820.html4
-rw-r--r--dom/media/test/crashtests/878014.html31
-rw-r--r--dom/media/test/crashtests/878328.html5
-rw-r--r--dom/media/test/crashtests/878407.html11
-rw-r--r--dom/media/test/crashtests/878478.html30
-rw-r--r--dom/media/test/crashtests/880129.html9
-rw-r--r--dom/media/test/crashtests/880202.html33
-rw-r--r--dom/media/test/crashtests/880342-1.html208
-rw-r--r--dom/media/test/crashtests/880342-2.html8
-rw-r--r--dom/media/test/crashtests/880384.html8
-rw-r--r--dom/media/test/crashtests/880404.html6
-rw-r--r--dom/media/test/crashtests/880724.html13
-rw-r--r--dom/media/test/crashtests/881775.html25
-rw-r--r--dom/media/test/crashtests/882956.html15
-rw-r--r--dom/media/test/crashtests/884459.html12
-rw-r--r--dom/media/test/crashtests/889042.html4
-rw-r--r--dom/media/test/crashtests/907986-1.html17
-rw-r--r--dom/media/test/crashtests/907986-2.html17
-rw-r--r--dom/media/test/crashtests/907986-3.html17
-rw-r--r--dom/media/test/crashtests/907986-4.html15
-rw-r--r--dom/media/test/crashtests/910171-1.html17
-rw-r--r--dom/media/test/crashtests/920987.html6
-rw-r--r--dom/media/test/crashtests/925619-1.html14
-rw-r--r--dom/media/test/crashtests/925619-2.html15
-rw-r--r--dom/media/test/crashtests/926619.html24
-rw-r--r--dom/media/test/crashtests/933151.html16
-rw-r--r--dom/media/test/crashtests/933156.html23
-rw-r--r--dom/media/test/crashtests/944851.html17
-rw-r--r--dom/media/test/crashtests/952756.html19
-rw-r--r--dom/media/test/crashtests/986901.html16
-rw-r--r--dom/media/test/crashtests/990794.html22
-rw-r--r--dom/media/test/crashtests/995289.html9
-rw-r--r--dom/media/test/crashtests/analyser-channels-1.html16
-rw-r--r--dom/media/test/crashtests/audiocontext-after-unload-1.html27
-rw-r--r--dom/media/test/crashtests/audiocontext-double-suspend.html5
-rw-r--r--dom/media/test/crashtests/audioworkletnode-after-unload-1.html27
-rw-r--r--dom/media/test/crashtests/buffer-source-duration-1.html14
-rw-r--r--dom/media/test/crashtests/buffer-source-ended-1.html16
-rw-r--r--dom/media/test/crashtests/buffer-source-resampling-start-1.html16
-rw-r--r--dom/media/test/crashtests/buffer-source-slow-resampling-1.html34
-rw-r--r--dom/media/test/crashtests/channel-count-in-metadata-different-than-in-content.mp4bin0 -> 13651 bytes
-rw-r--r--dom/media/test/crashtests/convolver-memory-report-1.html25
-rw-r--r--dom/media/test/crashtests/copyFromChannel-2.html16
-rw-r--r--dom/media/test/crashtests/cors.webmbin0 -> 215529 bytes
-rw-r--r--dom/media/test/crashtests/cors.webm^headers^1
-rw-r--r--dom/media/test/crashtests/crashtests.list142
-rw-r--r--dom/media/test/crashtests/disconnect-wrong-destination.html13
-rw-r--r--dom/media/test/crashtests/doppler-1.html23
-rw-r--r--dom/media/test/crashtests/empty-buffer-source.html14
-rw-r--r--dom/media/test/crashtests/empty-samples.webm0
-rw-r--r--dom/media/test/crashtests/encrypted-track-with-bad-sample-description-index.mp4bin0 -> 198320 bytes
-rw-r--r--dom/media/test/crashtests/encrypted-track-with-sample-missing-cenc-aux.mp4bin0 -> 152132 bytes
-rw-r--r--dom/media/test/crashtests/encrypted-track-without-tenc.mp4bin0 -> 152132 bytes
-rw-r--r--dom/media/test/crashtests/media-element-source-seek-1.html27
-rw-r--r--dom/media/test/crashtests/mp4_box_emptyrange.mp4bin0 -> 918 bytes
-rw-r--r--dom/media/test/crashtests/offline-buffer-source-ended-1.html15
-rw-r--r--dom/media/test/crashtests/oscillator-ended-1.html15
-rw-r--r--dom/media/test/crashtests/oscillator-ended-2.html15
-rw-r--r--dom/media/test/crashtests/sound.oggbin0 -> 2603 bytes
-rw-r--r--dom/media/test/crashtests/track-with-zero-dimensions.mp4bin0 -> 11817 bytes
-rw-r--r--dom/media/test/crashtests/video-crash.webmbin0 -> 58482 bytes
-rw-r--r--dom/media/test/crashtests/video-replay-after-audio-end.html43
-rw-r--r--dom/media/test/dash/dash-manifest-garbled-webm.mpd35
-rw-r--r--dom/media/test/dash/dash-manifest-garbled.mpd1
-rw-r--r--dom/media/test/dash/dash-manifest-sjs.mpd35
-rw-r--r--dom/media/test/dash/dash-manifest.mpd35
-rw-r--r--dom/media/test/dash/dash-webm-audio-128k.webmbin0 -> 41946 bytes
-rw-r--r--dom/media/test/dash/dash-webm-video-320x180.webmbin0 -> 35123 bytes
-rw-r--r--dom/media/test/dash/dash-webm-video-428x240.webmbin0 -> 50206 bytes
-rw-r--r--dom/media/test/dash/garbled.webm1
-rw-r--r--dom/media/test/dash_detect_stream_switch.sjs114
-rw-r--r--dom/media/test/detodos-recorder-test.opusbin0 -> 1507 bytes
-rw-r--r--dom/media/test/detodos-recorder-test.opus^headers^1
-rw-r--r--dom/media/test/detodos-short.opusbin0 -> 648 bytes
-rw-r--r--dom/media/test/detodos-short.opus^headers^1
-rw-r--r--dom/media/test/detodos-short.webmbin0 -> 1085 bytes
-rw-r--r--dom/media/test/detodos-short.webm^headers^1
-rw-r--r--dom/media/test/detodos.opusbin0 -> 6270 bytes
-rw-r--r--dom/media/test/detodos.opus^headers^1
-rw-r--r--dom/media/test/detodos.webmbin0 -> 11701 bytes
-rw-r--r--dom/media/test/detodos.webm^headers^1
-rw-r--r--dom/media/test/dirac.oggbin0 -> 106338 bytes
-rw-r--r--dom/media/test/dirac.ogg^headers^1
-rw-r--r--dom/media/test/dynamic_resource.sjs48
-rw-r--r--dom/media/test/eme.js496
-rw-r--r--dom/media/test/empty_size.mp3bin0 -> 90368 bytes
-rw-r--r--dom/media/test/file_access_controls.html160
-rw-r--r--dom/media/test/file_eme_createMediaKeys.html47
-rw-r--r--dom/media/test/flac-noheader-s16.flacbin0 -> 242826 bytes
-rw-r--r--dom/media/test/flac-noheader-s16.flac^headers^1
-rw-r--r--dom/media/test/flac-s24.flacbin0 -> 980951 bytes
-rw-r--r--dom/media/test/flac-s24.flac^headers^1
-rw-r--r--dom/media/test/flac-sample-cenc.mp4bin0 -> 336823 bytes
-rw-r--r--dom/media/test/flac-sample-cenc.mp4^headers^1
-rw-r--r--dom/media/test/flac-sample.mp4bin0 -> 876556 bytes
-rw-r--r--dom/media/test/flac-sample.mp4^headers^1
-rw-r--r--dom/media/test/fragment_noplay.js19
-rw-r--r--dom/media/test/fragment_play.js92
-rw-r--r--dom/media/test/gUM_support.js106
-rw-r--r--dom/media/test/gizmo-frag.mp4bin0 -> 152132 bytes
-rw-r--r--dom/media/test/gizmo-noaudio.mp4bin0 -> 342980 bytes
-rw-r--r--dom/media/test/gizmo-noaudio.mp4^headers^1
-rw-r--r--dom/media/test/gizmo-noaudio.webmbin0 -> 112663 bytes
-rw-r--r--dom/media/test/gizmo-noaudio.webm^headers^1
-rw-r--r--dom/media/test/gizmo-short.mp4bin0 -> 29905 bytes
-rw-r--r--dom/media/test/gizmo-short.mp4^headers^1
-rw-r--r--dom/media/test/gizmo.mp4bin0 -> 455255 bytes
-rw-r--r--dom/media/test/gizmo.mp4^headers^1
-rw-r--r--dom/media/test/gizmo.webmbin0 -> 159035 bytes
-rw-r--r--dom/media/test/gizmo.webm^headers^1
-rw-r--r--dom/media/test/gzipped_mp4.sjs27
-rw-r--r--dom/media/test/hls/400x300_prog_index.m3u810
-rw-r--r--dom/media/test/hls/400x300_prog_index_5s.m3u88
-rw-r--r--dom/media/test/hls/400x300_seg0.tsbin0 -> 291588 bytes
-rw-r--r--dom/media/test/hls/400x300_seg0_5s.tsbin0 -> 168636 bytes
-rw-r--r--dom/media/test/hls/400x300_seg1.tsbin0 -> 288204 bytes
-rw-r--r--dom/media/test/hls/416x243_prog_index_5s.m3u88
-rw-r--r--dom/media/test/hls/416x243_seg0_5s.tsbin0 -> 197400 bytes
-rw-r--r--dom/media/test/hls/640x480_prog_index.m3u810
-rw-r--r--dom/media/test/hls/640x480_seg0.tsbin0 -> 814228 bytes
-rw-r--r--dom/media/test/hls/640x480_seg1.tsbin0 -> 796368 bytes
-rw-r--r--dom/media/test/hls/960x720_prog_index.m3u810
-rw-r--r--dom/media/test/hls/960x720_seg0.tsbin0 -> 1878120 bytes
-rw-r--r--dom/media/test/hls/960x720_seg1.tsbin0 -> 1839392 bytes
-rw-r--r--dom/media/test/hls/bipbop_16x9_single.m3u85
-rw-r--r--dom/media/test/hls/bipbop_4x3_single.m3u84
-rw-r--r--dom/media/test/hls/bipbop_4x3_variant.m3u810
-rw-r--r--dom/media/test/huge-id3.mp3bin0 -> 141774 bytes
-rw-r--r--dom/media/test/huge-id3.mp3^headers^1
-rw-r--r--dom/media/test/id3tags.mp3bin0 -> 3530 bytes
-rw-r--r--dom/media/test/id3tags.mp3^headers^1
-rw-r--r--dom/media/test/invalid-cmap-s0c0.opusbin0 -> 6835 bytes
-rw-r--r--dom/media/test/invalid-cmap-s0c0.opus^headers^1
-rw-r--r--dom/media/test/invalid-cmap-s0c2.opusbin0 -> 6834 bytes
-rw-r--r--dom/media/test/invalid-cmap-s0c2.opus^headers^1
-rw-r--r--dom/media/test/invalid-cmap-s1c2.opusbin0 -> 6848 bytes
-rw-r--r--dom/media/test/invalid-cmap-s1c2.opus^headers^1
-rw-r--r--dom/media/test/invalid-cmap-short.opusbin0 -> 6854 bytes
-rw-r--r--dom/media/test/invalid-cmap-short.opus^headers^1
-rw-r--r--dom/media/test/invalid-discard_on_multi_blocks.webmbin0 -> 19636 bytes
-rw-r--r--dom/media/test/invalid-discard_on_multi_blocks.webm^headers^1
-rw-r--r--dom/media/test/invalid-excess_discard.webmbin0 -> 18442 bytes
-rw-r--r--dom/media/test/invalid-excess_discard.webm^headers^1
-rw-r--r--dom/media/test/invalid-excess_neg_discard.webmbin0 -> 18442 bytes
-rw-r--r--dom/media/test/invalid-excess_neg_discard.webm^headers^1
-rw-r--r--dom/media/test/invalid-m0c0.opusbin0 -> 2471 bytes
-rw-r--r--dom/media/test/invalid-m0c0.opus^headers^1
-rw-r--r--dom/media/test/invalid-m0c3.opusbin0 -> 2471 bytes
-rw-r--r--dom/media/test/invalid-m0c3.opus^headers^1
-rw-r--r--dom/media/test/invalid-m1c0.opusbin0 -> 6836 bytes
-rw-r--r--dom/media/test/invalid-m1c0.opus^headers^1
-rw-r--r--dom/media/test/invalid-m1c9.opusbin0 -> 6836 bytes
-rw-r--r--dom/media/test/invalid-m1c9.opus^headers^1
-rw-r--r--dom/media/test/invalid-m2c0.opusbin0 -> 2471 bytes
-rw-r--r--dom/media/test/invalid-m2c0.opus^headers^1
-rw-r--r--dom/media/test/invalid-m2c1.opusbin0 -> 2455 bytes
-rw-r--r--dom/media/test/invalid-m2c1.opus^headers^1
-rw-r--r--dom/media/test/invalid-neg_discard.webmbin0 -> 18442 bytes
-rw-r--r--dom/media/test/invalid-neg_discard.webm^headers^1
-rw-r--r--dom/media/test/invalid-preskip.webmbin0 -> 7251 bytes
-rw-r--r--dom/media/test/invalid-preskip.webm^headers^1
-rw-r--r--dom/media/test/make-headers.sh18
-rw-r--r--dom/media/test/manifest.js2266
-rw-r--r--dom/media/test/midflight-redirect.sjs78
-rw-r--r--dom/media/test/mochitest.ini1172
-rw-r--r--dom/media/test/multi_id3v2.mp3bin0 -> 5039737 bytes
-rw-r--r--dom/media/test/multiple-bos-more-header-fileds.oggbin0 -> 27527 bytes
-rw-r--r--dom/media/test/multiple-bos-more-header-fileds.ogg^headers^1
-rw-r--r--dom/media/test/multiple-bos.oggbin0 -> 33045 bytes
-rw-r--r--dom/media/test/multiple-bos.ogg^headers^1
-rw-r--r--dom/media/test/no-cues.webmbin0 -> 220609 bytes
-rw-r--r--dom/media/test/no-cues.webm^headers^1
-rw-r--r--dom/media/test/notags.mp3bin0 -> 2506 bytes
-rw-r--r--dom/media/test/notags.mp3^headers^1
-rw-r--r--dom/media/test/opus-mapping2.mp4bin0 -> 308048 bytes
-rw-r--r--dom/media/test/opus-mapping2.mp4^headers^1
-rw-r--r--dom/media/test/opus-mapping2.webmbin0 -> 309387 bytes
-rw-r--r--dom/media/test/opus-mapping2.webm^headers^1
-rw-r--r--dom/media/test/opus-sample-cenc.mp4bin0 -> 21958 bytes
-rw-r--r--dom/media/test/opus-sample-cenc.mp4^headers^1
-rw-r--r--dom/media/test/opus-sample.mp4bin0 -> 105690 bytes
-rw-r--r--dom/media/test/opus-sample.mp4^headers^1
-rw-r--r--dom/media/test/owl-funnier-id3.mp3bin0 -> 69603 bytes
-rw-r--r--dom/media/test/owl-funnier-id3.mp3^headers^1
-rw-r--r--dom/media/test/owl-funny-id3.mp3bin0 -> 71696 bytes
-rw-r--r--dom/media/test/owl-funny-id3.mp3^headers^1
-rw-r--r--dom/media/test/owl-short.mp3bin0 -> 11016 bytes
-rw-r--r--dom/media/test/owl-short.mp3^headers^1
-rw-r--r--dom/media/test/owl.mp3bin0 -> 67430 bytes
-rw-r--r--dom/media/test/owl.mp3^headers^1
-rw-r--r--dom/media/test/pixel_aspect_ratio.mp4bin0 -> 1806042 bytes
-rw-r--r--dom/media/test/play_promise.js3
-rw-r--r--dom/media/test/poster-test.jpgbin0 -> 58493 bytes
-rw-r--r--dom/media/test/r11025_msadpcm_c1.wavbin0 -> 5978 bytes
-rw-r--r--dom/media/test/r11025_msadpcm_c1.wav^headers^1
-rw-r--r--dom/media/test/r11025_s16_c1-short.wavbin0 -> 8270 bytes
-rw-r--r--dom/media/test/r11025_s16_c1-short.wav^headers^1
-rw-r--r--dom/media/test/r11025_s16_c1.wavbin0 -> 22094 bytes
-rw-r--r--dom/media/test/r11025_s16_c1.wav^headers^1
-rw-r--r--dom/media/test/r11025_s16_c1_trailing.wavbin0 -> 22095 bytes
-rw-r--r--dom/media/test/r11025_s16_c1_trailing.wav^headers^1
-rw-r--r--dom/media/test/r11025_u8_c1.wavbin0 -> 11069 bytes
-rw-r--r--dom/media/test/r11025_u8_c1.wav^headers^1
-rw-r--r--dom/media/test/r11025_u8_c1_trunc.wavbin0 -> 20000 bytes
-rw-r--r--dom/media/test/r11025_u8_c1_trunc.wav^headers^1
-rw-r--r--dom/media/test/r16000_u8_c1_list.wavbin0 -> 68318 bytes
-rw-r--r--dom/media/test/r16000_u8_c1_list.wav^headers^1
-rw-r--r--dom/media/test/reactivate_helper.html57
-rw-r--r--dom/media/test/red-46x48.mp4bin0 -> 1548 bytes
-rw-r--r--dom/media/test/red-46x48.mp4^headers^1
-rw-r--r--dom/media/test/red-48x46.mp4bin0 -> 1548 bytes
-rw-r--r--dom/media/test/red-48x46.mp4^headers^1
-rw-r--r--dom/media/test/redirect.sjs26
-rw-r--r--dom/media/test/referer.sjs45
-rw-r--r--dom/media/test/reftest/av1hdr2020.mp4bin0 -> 109327 bytes
-rw-r--r--dom/media/test/reftest/av1hdr2020.pngbin0 -> 4162799 bytes
-rw-r--r--dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html4
-rw-r--r--dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html19
-rw-r--r--dom/media/test/reftest/generateREF.html99
-rw-r--r--dom/media/test/reftest/gizmo.mp4.55thframe-ref.html7
-rw-r--r--dom/media/test/reftest/gizmo.mp4.seek.html36
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-720-90-ref.html4
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-720-90-video.html19
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-720-ref.html4
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-720-video.html19
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-720.video.html19
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-90-ref.html4
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-90-video.html19
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-ref.html4
-rw-r--r--dom/media/test/reftest/image-10bits-rendering-video.html19
-rw-r--r--dom/media/test/reftest/reftest.list8
-rw-r--r--dom/media/test/reftest/short.mp4.firstframe-ref.html4
-rw-r--r--dom/media/test/reftest/short.mp4.firstframe.html19
-rw-r--r--dom/media/test/reftest/short.mp4.lastframe-ref.html4
-rw-r--r--dom/media/test/reftest/short.mp4.lastframe.html42
-rw-r--r--dom/media/test/reftest/vp9hdr2020.pngbin0 -> 5083456 bytes
-rw-r--r--dom/media/test/reftest/vp9hdr2020.webmbin0 -> 108855 bytes
-rw-r--r--dom/media/test/resolution-change.webmbin0 -> 7166 bytes
-rw-r--r--dom/media/test/resolution-change.webm^headers^1
-rw-r--r--dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4bin0 -> 122703 bytes
-rw-r--r--dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^1
-rw-r--r--dom/media/test/sample-fisbone-skeleton4.ogvbin0 -> 8747 bytes
-rw-r--r--dom/media/test/sample-fisbone-skeleton4.ogv^headers^1
-rw-r--r--dom/media/test/sample-fisbone-wrong-header.ogvbin0 -> 8703 bytes
-rw-r--r--dom/media/test/sample-fisbone-wrong-header.ogv^headers^1
-rw-r--r--dom/media/test/sample.3g2bin0 -> 28561 bytes
-rw-r--r--dom/media/test/sample.3gpbin0 -> 28561 bytes
-rw-r--r--dom/media/test/seek-short.ogvbin0 -> 79921 bytes
-rw-r--r--dom/media/test/seek-short.ogv^headers^1
-rw-r--r--dom/media/test/seek-short.webmbin0 -> 19267 bytes
-rw-r--r--dom/media/test/seek-short.webm^headers^1
-rw-r--r--dom/media/test/seek.ogvbin0 -> 285310 bytes
-rw-r--r--dom/media/test/seek.ogv^headers^1
-rw-r--r--dom/media/test/seek.webmbin0 -> 215529 bytes
-rw-r--r--dom/media/test/seek.webm^headers^1
-rw-r--r--dom/media/test/seekLies.sjs23
-rw-r--r--dom/media/test/seek_support.js61
-rw-r--r--dom/media/test/seek_with_sound.oggbin0 -> 299507 bytes
-rw-r--r--dom/media/test/seek_with_sound.ogg^headers^1
-rw-r--r--dom/media/test/short-aac-encrypted-audio.mp4bin0 -> 5267 bytes
-rw-r--r--dom/media/test/short-aac-encrypted-audio.mp4^headers^1
-rw-r--r--dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4bin0 -> 9261 bytes
-rw-r--r--dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4^headers^1
-rw-r--r--dom/media/test/short-cenc-pssh-in-moof.mp4bin0 -> 14860 bytes
-rw-r--r--dom/media/test/short-cenc.mp4bin0 -> 14860 bytes
-rw-r--r--dom/media/test/short-cenc.xml37
-rw-r--r--dom/media/test/short-video.ogvbin0 -> 16049 bytes
-rw-r--r--dom/media/test/short-video.ogv^headers^1
-rw-r--r--dom/media/test/short-vp9-encrypted-video.mp4bin0 -> 6727 bytes
-rw-r--r--dom/media/test/short-vp9-encrypted-video.mp4^headers^1
-rw-r--r--dom/media/test/short.mp4bin0 -> 13708 bytes
-rw-r--r--dom/media/test/short.mp4.gzbin0 -> 6708 bytes
-rw-r--r--dom/media/test/short.mp4^headers^1
-rw-r--r--dom/media/test/sine.webmbin0 -> 17510 bytes
-rw-r--r--dom/media/test/sine.webm^headers^1
-rw-r--r--dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webmbin0 -> 30362 bytes
-rw-r--r--dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm^headers^1
-rw-r--r--dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webmbin0 -> 46703 bytes
-rw-r--r--dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm^headers^1
-rw-r--r--dom/media/test/small-shot-mp3.mp4bin0 -> 7491 bytes
-rw-r--r--dom/media/test/small-shot-mp3.mp4^headers^1
-rw-r--r--dom/media/test/small-shot.flacbin0 -> 16430 bytes
-rw-r--r--dom/media/test/small-shot.m4abin0 -> 2710 bytes
-rw-r--r--dom/media/test/small-shot.mp3bin0 -> 6825 bytes
-rw-r--r--dom/media/test/small-shot.mp3^headers^1
-rw-r--r--dom/media/test/small-shot.oggbin0 -> 6416 bytes
-rw-r--r--dom/media/test/small-shot.ogg^headers^1
-rw-r--r--dom/media/test/sound.oggbin0 -> 2603 bytes
-rw-r--r--dom/media/test/sound.ogg^headers^1
-rw-r--r--dom/media/test/spacestorm-1000Hz-100ms.oggbin0 -> 3270 bytes
-rw-r--r--dom/media/test/spacestorm-1000Hz-100ms.ogg^headers^1
-rw-r--r--dom/media/test/split.webmbin0 -> 105755 bytes
-rw-r--r--dom/media/test/split.webm^headers^1
-rw-r--r--dom/media/test/street.mp4bin0 -> 1505751 bytes
-rw-r--r--dom/media/test/street.mp4^headers^1
-rw-r--r--dom/media/test/test-1-mono.opusbin0 -> 4086 bytes
-rw-r--r--dom/media/test/test-1-mono.opus^headers^1
-rw-r--r--dom/media/test/test-2-stereo.opusbin0 -> 24973 bytes
-rw-r--r--dom/media/test/test-2-stereo.opus^headers^1
-rw-r--r--dom/media/test/test-3-LCR.opusbin0 -> 39471 bytes
-rw-r--r--dom/media/test/test-3-LCR.opus^headers^1
-rw-r--r--dom/media/test/test-4-quad.opusbin0 -> 129906 bytes
-rw-r--r--dom/media/test/test-4-quad.opus^headers^1
-rw-r--r--dom/media/test/test-5-5.0.opusbin0 -> 164935 bytes
-rw-r--r--dom/media/test/test-5-5.0.opus^headers^1
-rw-r--r--dom/media/test/test-6-5.1.opusbin0 -> 288195 bytes
-rw-r--r--dom/media/test/test-6-5.1.opus^headers^1
-rw-r--r--dom/media/test/test-7-6.1.opusbin0 -> 401668 bytes
-rw-r--r--dom/media/test/test-7-6.1.opus^headers^1
-rw-r--r--dom/media/test/test-8-7.1.opusbin0 -> 543119 bytes
-rw-r--r--dom/media/test/test-8-7.1.opus^headers^1
-rw-r--r--dom/media/test/test-stereo-phase-inversion-180.opusbin0 -> 14011 bytes
-rw-r--r--dom/media/test/test-stereo-phase-inversion-180.opus^headers^1
-rw-r--r--dom/media/test/test_VideoPlaybackQuality.html61
-rw-r--r--dom/media/test/test_VideoPlaybackQuality_disabled.html37
-rw-r--r--dom/media/test/test_access_control.html54
-rw-r--r--dom/media/test/test_arraybuffer.html83
-rw-r--r--dom/media/test/test_aspectratio_mp4.html46
-rw-r--r--dom/media/test/test_audio1.html33
-rw-r--r--dom/media/test/test_audio2.html33
-rw-r--r--dom/media/test/test_audioDocumentTitle.html56
-rw-r--r--dom/media/test/test_background_video_cancel_suspend_taint.html70
-rw-r--r--dom/media/test/test_background_video_cancel_suspend_visible.html69
-rw-r--r--dom/media/test/test_background_video_drawimage_with_suspended_video.html76
-rw-r--r--dom/media/test/test_background_video_ended_event.html48
-rw-r--r--dom/media/test/test_background_video_no_suspend_disabled.html36
-rw-r--r--dom/media/test/test_background_video_no_suspend_not_in_tree.html56
-rw-r--r--dom/media/test/test_background_video_no_suspend_short_vid.html38
-rw-r--r--dom/media/test/test_background_video_resume_after_end_show_last_frame.html131
-rw-r--r--dom/media/test/test_background_video_resume_looping_video_without_audio.html81
-rw-r--r--dom/media/test/test_background_video_suspend.html81
-rw-r--r--dom/media/test/test_background_video_suspend_ends.html55
-rw-r--r--dom/media/test/test_background_video_tainted_by_capturestream.html46
-rw-r--r--dom/media/test/test_background_video_tainted_by_createimagebitmap.html42
-rw-r--r--dom/media/test/test_background_video_tainted_by_drawimage.html58
-rw-r--r--dom/media/test/test_buffered.html117
-rw-r--r--dom/media/test/test_bug1113600.html50
-rw-r--r--dom/media/test/test_bug1242338.html66
-rw-r--r--dom/media/test/test_bug1248229.html35
-rw-r--r--dom/media/test/test_bug1431810_opus_downmix_to_mono.html139
-rw-r--r--dom/media/test/test_bug1512958.html74
-rw-r--r--dom/media/test/test_bug1553262.html31
-rw-r--r--dom/media/test/test_bug448534.html71
-rw-r--r--dom/media/test/test_bug463162.xhtml78
-rw-r--r--dom/media/test/test_bug465498.html83
-rw-r--r--dom/media/test/test_bug495145.html95
-rw-r--r--dom/media/test/test_bug495300.html63
-rw-r--r--dom/media/test/test_bug654550.html84
-rw-r--r--dom/media/test/test_bug686942.html68
-rw-r--r--dom/media/test/test_bug726904.html56
-rw-r--r--dom/media/test/test_bug874897.html68
-rw-r--r--dom/media/test/test_bug879717.html130
-rw-r--r--dom/media/test/test_bug895305.html42
-rw-r--r--dom/media/test/test_bug919265.html30
-rw-r--r--dom/media/test/test_can_play_type.html40
-rw-r--r--dom/media/test/test_can_play_type_mpeg.html168
-rw-r--r--dom/media/test/test_can_play_type_no_ogg.html42
-rw-r--r--dom/media/test/test_can_play_type_ogg.html37
-rw-r--r--dom/media/test/test_can_play_type_wave.html30
-rw-r--r--dom/media/test/test_can_play_type_webm.html39
-rw-r--r--dom/media/test/test_chaining.html92
-rw-r--r--dom/media/test/test_cloneElementVisually_ended_video.html48
-rw-r--r--dom/media/test/test_cloneElementVisually_mediastream.html70
-rw-r--r--dom/media/test/test_cloneElementVisually_mediastream_multitrack.html88
-rw-r--r--dom/media/test/test_cloneElementVisually_no_suspend.html90
-rw-r--r--dom/media/test/test_cloneElementVisually_paused.html45
-rw-r--r--dom/media/test/test_cloneElementVisually_poster.html52
-rw-r--r--dom/media/test/test_cloneElementVisually_resource_change.html67
-rw-r--r--dom/media/test/test_clone_media_element.html54
-rw-r--r--dom/media/test/test_closing_connections.html58
-rw-r--r--dom/media/test/test_constants.html228
-rw-r--r--dom/media/test/test_controls.html33
-rw-r--r--dom/media/test/test_cueless_webm_seek-1.html136
-rw-r--r--dom/media/test/test_cueless_webm_seek-2.html126
-rw-r--r--dom/media/test/test_cueless_webm_seek-3.html120
-rw-r--r--dom/media/test/test_currentTime.html19
-rw-r--r--dom/media/test/test_decode_error.html66
-rw-r--r--dom/media/test/test_decode_error_crossorigin.html54
-rw-r--r--dom/media/test/test_decoder_disable.html78
-rw-r--r--dom/media/test/test_defaultMuted.html54
-rw-r--r--dom/media/test/test_delay_load.html108
-rw-r--r--dom/media/test/test_duration_after_error.html54
-rw-r--r--dom/media/test/test_eme_autoplay.html119
-rw-r--r--dom/media/test/test_eme_canvas_blocked.html60
-rw-r--r--dom/media/test/test_eme_createMediaKeys_iframes.html201
-rw-r--r--dom/media/test/test_eme_detach_media_keys.html63
-rw-r--r--dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html145
-rw-r--r--dom/media/test/test_eme_getstatusforpolicy.html57
-rw-r--r--dom/media/test/test_eme_initDataTypes.html133
-rw-r--r--dom/media/test/test_eme_missing_pssh.html92
-rw-r--r--dom/media/test/test_eme_non_mse_fails.html100
-rw-r--r--dom/media/test/test_eme_playback.html193
-rw-r--r--dom/media/test/test_eme_pssh_in_moof.html141
-rw-r--r--dom/media/test/test_eme_requestKeySystemAccess.html480
-rw-r--r--dom/media/test/test_eme_requestMediaKeySystemAccess_with_app_approval.html209
-rw-r--r--dom/media/test/test_eme_request_notifications.html83
-rw-r--r--dom/media/test/test_eme_sample_groups_playback.html130
-rw-r--r--dom/media/test/test_eme_session_callable_value.html35
-rw-r--r--dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html38
-rw-r--r--dom/media/test/test_eme_stream_capture_blocked_case1.html64
-rw-r--r--dom/media/test/test_eme_stream_capture_blocked_case2.html57
-rw-r--r--dom/media/test/test_eme_stream_capture_blocked_case3.html55
-rw-r--r--dom/media/test/test_eme_unsetMediaKeys_then_capture.html113
-rw-r--r--dom/media/test/test_eme_waitingforkey.html83
-rw-r--r--dom/media/test/test_empty_resource.html58
-rw-r--r--dom/media/test/test_error_in_video_document.html59
-rw-r--r--dom/media/test/test_error_on_404.html84
-rw-r--r--dom/media/test/test_fastSeek-forwards.html77
-rw-r--r--dom/media/test/test_fastSeek.html88
-rw-r--r--dom/media/test/test_fragment_noplay.html127
-rw-r--r--dom/media/test/test_fragment_play.html91
-rw-r--r--dom/media/test/test_hls_player_independency.html53
-rw-r--r--dom/media/test/test_imagecapture.html128
-rw-r--r--dom/media/test/test_info_leak.html175
-rw-r--r--dom/media/test/test_invalid_reject.html58
-rw-r--r--dom/media/test/test_invalid_reject_play.html44
-rw-r--r--dom/media/test/test_invalid_seek.html32
-rw-r--r--dom/media/test/test_load.html217
-rw-r--r--dom/media/test/test_load_candidates.html84
-rw-r--r--dom/media/test/test_load_same_resource.html106
-rw-r--r--dom/media/test/test_load_source.html76
-rw-r--r--dom/media/test/test_load_source_empty_type.html36
-rw-r--r--dom/media/test/test_loop.html57
-rw-r--r--dom/media/test/test_looping_eventsOrder.html52
-rw-r--r--dom/media/test/test_media_selection.html142
-rw-r--r--dom/media/test/test_media_sniffer.html67
-rw-r--r--dom/media/test/test_mediacapabilities_resistfingerprinting.html69
-rw-r--r--dom/media/test/test_mediarecorder_avoid_recursion.html61
-rw-r--r--dom/media/test/test_mediarecorder_bitrate.html127
-rw-r--r--dom/media/test/test_mediarecorder_creation.html45
-rw-r--r--dom/media/test/test_mediarecorder_creation_fail.html50
-rw-r--r--dom/media/test/test_mediarecorder_fires_start_event_once_when_erroring.html45
-rw-r--r--dom/media/test/test_mediarecorder_onerror_pause.html107
-rw-r--r--dom/media/test/test_mediarecorder_pause_resume_video.html130
-rw-r--r--dom/media/test/test_mediarecorder_playback_can_repeat.html87
-rw-r--r--dom/media/test/test_mediarecorder_principals.html132
-rw-r--r--dom/media/test/test_mediarecorder_record_4ch_audiocontext.html76
-rw-r--r--dom/media/test/test_mediarecorder_record_addtracked_stream.html184
-rw-r--r--dom/media/test/test_mediarecorder_record_audiocontext.html65
-rw-r--r--dom/media/test/test_mediarecorder_record_audiocontext_mlk.html24
-rw-r--r--dom/media/test/test_mediarecorder_record_audionode.html135
-rw-r--r--dom/media/test/test_mediarecorder_record_canvas_captureStream.html75
-rw-r--r--dom/media/test/test_mediarecorder_record_changing_video_resolution.html174
-rw-r--r--dom/media/test/test_mediarecorder_record_downsize_resolution.html148
-rw-r--r--dom/media/test/test_mediarecorder_record_getdata_afterstart.html81
-rw-r--r--dom/media/test/test_mediarecorder_record_gum_video_timeslice.html94
-rw-r--r--dom/media/test/test_mediarecorder_record_gum_video_timeslice_mixed.html100
-rw-r--r--dom/media/test/test_mediarecorder_record_immediate_stop.html115
-rw-r--r--dom/media/test/test_mediarecorder_record_no_timeslice.html106
-rw-r--r--dom/media/test/test_mediarecorder_record_session.html75
-rw-r--r--dom/media/test/test_mediarecorder_record_startstopstart.html75
-rw-r--r--dom/media/test/test_mediarecorder_record_timeslice.html105
-rw-r--r--dom/media/test/test_mediarecorder_record_upsize_resolution.html148
-rw-r--r--dom/media/test/test_mediarecorder_reload_crash.html29
-rw-r--r--dom/media/test/test_mediarecorder_state_event_order.html83
-rw-r--r--dom/media/test/test_mediarecorder_state_transition.html280
-rw-r--r--dom/media/test/test_mediarecorder_webm_support.html56
-rw-r--r--dom/media/test/test_mediatrack_consuming_mediaresource.html198
-rw-r--r--dom/media/test/test_mediatrack_consuming_mediastream.html146
-rw-r--r--dom/media/test/test_mediatrack_events.html135
-rw-r--r--dom/media/test/test_mediatrack_parsing_ogg.html72
-rw-r--r--dom/media/test/test_mediatrack_replay_from_end.html160
-rw-r--r--dom/media/test/test_metadata.html82
-rw-r--r--dom/media/test/test_midflight_redirect_blocked.html96
-rw-r--r--dom/media/test/test_mixed_principals.html83
-rw-r--r--dom/media/test/test_mozHasAudio.html42
-rw-r--r--dom/media/test/test_mp3_with_multiple_ID3v2.html30
-rw-r--r--dom/media/test/test_multiple_mediastreamtracks.html47
-rw-r--r--dom/media/test/test_networkState.html47
-rw-r--r--dom/media/test/test_new_audio.html48
-rw-r--r--dom/media/test/test_no_load_event.html53
-rw-r--r--dom/media/test/test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html46
-rw-r--r--dom/media/test/test_paused.html21
-rw-r--r--dom/media/test/test_paused_after_ended.html53
-rw-r--r--dom/media/test/test_play_events.html61
-rw-r--r--dom/media/test/test_play_events_2.html60
-rw-r--r--dom/media/test/test_play_promise_1.html42
-rw-r--r--dom/media/test/test_play_promise_10.html40
-rw-r--r--dom/media/test/test_play_promise_11.html40
-rw-r--r--dom/media/test/test_play_promise_12.html45
-rw-r--r--dom/media/test/test_play_promise_13.html49
-rw-r--r--dom/media/test/test_play_promise_14.html56
-rw-r--r--dom/media/test/test_play_promise_15.html51
-rw-r--r--dom/media/test/test_play_promise_16.html47
-rw-r--r--dom/media/test/test_play_promise_17.html43
-rw-r--r--dom/media/test/test_play_promise_18.html46
-rw-r--r--dom/media/test/test_play_promise_2.html43
-rw-r--r--dom/media/test/test_play_promise_3.html47
-rw-r--r--dom/media/test/test_play_promise_4.html41
-rw-r--r--dom/media/test/test_play_promise_5.html44
-rw-r--r--dom/media/test/test_play_promise_6.html45
-rw-r--r--dom/media/test/test_play_promise_7.html47
-rw-r--r--dom/media/test/test_play_promise_8.html47
-rw-r--r--dom/media/test/test_play_promise_9.html44
-rw-r--r--dom/media/test/test_play_twice.html95
-rw-r--r--dom/media/test/test_playback.html108
-rw-r--r--dom/media/test/test_playback_errors.html48
-rw-r--r--dom/media/test/test_playback_hls.html91
-rw-r--r--dom/media/test/test_playback_rate.html175
-rw-r--r--dom/media/test/test_playback_rate_playpause.html66
-rw-r--r--dom/media/test/test_playback_reactivate.html77
-rw-r--r--dom/media/test/test_played.html245
-rw-r--r--dom/media/test/test_preload_actions.html582
-rw-r--r--dom/media/test/test_preload_attribute.html44
-rw-r--r--dom/media/test/test_preload_suspend.html112
-rw-r--r--dom/media/test/test_preserve_playbackrate_after_ui_play.html60
-rw-r--r--dom/media/test/test_progress.html52
-rw-r--r--dom/media/test/test_reactivate.html64
-rw-r--r--dom/media/test/test_readyState.html51
-rw-r--r--dom/media/test/test_referer.html88
-rw-r--r--dom/media/test/test_replay_metadata.html120
-rw-r--r--dom/media/test/test_reset_events_async.html58
-rw-r--r--dom/media/test/test_reset_src.html99
-rw-r--r--dom/media/test/test_resolution_change.html52
-rw-r--r--dom/media/test/test_resume.html47
-rw-r--r--dom/media/test/test_seamless_looping.html185
-rw-r--r--dom/media/test/test_seek-1.html84
-rw-r--r--dom/media/test/test_seek-10.html56
-rw-r--r--dom/media/test/test_seek-11.html76
-rw-r--r--dom/media/test/test_seek-12.html59
-rw-r--r--dom/media/test/test_seek-13.html72
-rw-r--r--dom/media/test/test_seek-14.html43
-rw-r--r--dom/media/test/test_seek-2.html76
-rw-r--r--dom/media/test/test_seek-3.html68
-rw-r--r--dom/media/test/test_seek-4.html70
-rw-r--r--dom/media/test/test_seek-5.html69
-rw-r--r--dom/media/test/test_seek-6.html64
-rw-r--r--dom/media/test/test_seek-7.html59
-rw-r--r--dom/media/test/test_seek-8.html42
-rw-r--r--dom/media/test/test_seek-9.html41
-rw-r--r--dom/media/test/test_seekLies.html28
-rw-r--r--dom/media/test/test_seekToNextFrame.html95
-rw-r--r--dom/media/test/test_seek_duration.html55
-rw-r--r--dom/media/test/test_seek_negative.html77
-rw-r--r--dom/media/test/test_seek_nosrc.html58
-rw-r--r--dom/media/test/test_seek_out_of_range.html48
-rw-r--r--dom/media/test/test_seek_promise_bug1344357.html35
-rw-r--r--dom/media/test/test_seekable1.html66
-rw-r--r--dom/media/test/test_source.html91
-rw-r--r--dom/media/test/test_source_null.html33
-rw-r--r--dom/media/test/test_source_write.html40
-rw-r--r--dom/media/test/test_standalone.html61
-rw-r--r--dom/media/test/test_streams_capture_origin.html90
-rw-r--r--dom/media/test/test_streams_element_capture.html120
-rw-r--r--dom/media/test/test_streams_element_capture_mediatrack.html100
-rw-r--r--dom/media/test/test_streams_element_capture_playback.html47
-rw-r--r--dom/media/test/test_streams_element_capture_reset.html174
-rw-r--r--dom/media/test/test_streams_element_capture_twice.html79
-rw-r--r--dom/media/test/test_streams_firstframe.html67
-rw-r--r--dom/media/test/test_streams_gc.html44
-rw-r--r--dom/media/test/test_streams_individual_pause.html77
-rw-r--r--dom/media/test/test_streams_srcObject.html60
-rw-r--r--dom/media/test/test_streams_tracks.html66
-rw-r--r--dom/media/test/test_suspend_media_by_inactive_docshell.html67
-rw-r--r--dom/media/test/test_temporary_file_blob_video_plays.html75
-rw-r--r--dom/media/test/test_timeupdate_small_files.html88
-rw-r--r--dom/media/test/test_unseekable.html101
-rw-r--r--dom/media/test/test_videoDocumentTitle.html57
-rw-r--r--dom/media/test/test_videoPlaybackQuality_totalFrames.html46
-rw-r--r--dom/media/test/test_video_dimensions.html96
-rw-r--r--dom/media/test/test_video_gzip_encoding.html25
-rw-r--r--dom/media/test/test_video_in_audio_element.html68
-rw-r--r--dom/media/test/test_video_stats_resistfingerprinting.html87
-rw-r--r--dom/media/test/test_video_to_canvas.html68
-rw-r--r--dom/media/test/test_volume.html41
-rw-r--r--dom/media/test/test_vp9_superframes.html31
-rw-r--r--dom/media/test/test_wav_ended1.html43
-rw-r--r--dom/media/test/test_wav_ended2.html62
-rw-r--r--dom/media/test/variable-channel.oggbin0 -> 27749 bytes
-rw-r--r--dom/media/test/variable-channel.ogg^headers^1
-rw-r--r--dom/media/test/variable-channel.opusbin0 -> 46597 bytes
-rw-r--r--dom/media/test/variable-channel.opus^headers^1
-rw-r--r--dom/media/test/variable-preskip.opusbin0 -> 17660 bytes
-rw-r--r--dom/media/test/variable-preskip.opus^headers^1
-rw-r--r--dom/media/test/variable-samplerate.oggbin0 -> 22325 bytes
-rw-r--r--dom/media/test/variable-samplerate.ogg^headers^1
-rw-r--r--dom/media/test/variable-samplerate.opusbin0 -> 28111 bytes
-rw-r--r--dom/media/test/variable-samplerate.opus^headers^1
-rw-r--r--dom/media/test/vbr-head.mp3bin0 -> 4474 bytes
-rw-r--r--dom/media/test/vbr-head.mp3^headers^1
-rw-r--r--dom/media/test/vbr.mp3bin0 -> 300553 bytes
-rw-r--r--dom/media/test/vbr.mp3^headers^1
-rw-r--r--dom/media/test/very-short.mp3bin0 -> 625 bytes
-rw-r--r--dom/media/test/video-overhang.oggbin0 -> 301831 bytes
-rw-r--r--dom/media/test/video-overhang.ogg^headers^1
-rw-r--r--dom/media/test/vp9-short.webmbin0 -> 3107 bytes
-rw-r--r--dom/media/test/vp9-short.webm^headers^1
-rw-r--r--dom/media/test/vp9-superframes.webmbin0 -> 173187 bytes
-rw-r--r--dom/media/test/vp9-superframes.webm^headers^1
-rw-r--r--dom/media/test/vp9.webmbin0 -> 97465 bytes
-rw-r--r--dom/media/test/vp9.webm^headers^1
-rw-r--r--dom/media/test/vp9cake-short.webmbin0 -> 25155 bytes
-rw-r--r--dom/media/test/vp9cake-short.webm^headers^1
-rw-r--r--dom/media/test/vp9cake.webmbin0 -> 141743 bytes
-rw-r--r--dom/media/test/vp9cake.webm^headers^1
-rw-r--r--dom/media/test/wave_metadata.wavbin0 -> 42706 bytes
-rw-r--r--dom/media/test/wave_metadata.wav^headers^1
-rw-r--r--dom/media/test/wave_metadata_bad_len.wavbin0 -> 42706 bytes
-rw-r--r--dom/media/test/wave_metadata_bad_len.wav^headers^1
-rw-r--r--dom/media/test/wave_metadata_bad_no_null.wavbin0 -> 42706 bytes
-rw-r--r--dom/media/test/wave_metadata_bad_no_null.wav^headers^1
-rw-r--r--dom/media/test/wave_metadata_bad_utf8.wavbin0 -> 42704 bytes
-rw-r--r--dom/media/test/wave_metadata_bad_utf8.wav^headers^1
-rw-r--r--dom/media/test/wave_metadata_unknown_tag.wavbin0 -> 42706 bytes
-rw-r--r--dom/media/test/wave_metadata_unknown_tag.wav^headers^1
-rw-r--r--dom/media/test/wave_metadata_utf8.wavbin0 -> 42704 bytes
-rw-r--r--dom/media/test/wave_metadata_utf8.wav^headers^1
-rw-r--r--dom/media/test/wavedata_alaw.wavbin0 -> 11067 bytes
-rw-r--r--dom/media/test/wavedata_alaw.wav^headers^1
-rw-r--r--dom/media/test/wavedata_float.wavbin0 -> 176458 bytes
-rw-r--r--dom/media/test/wavedata_float.wav^headers^1
-rw-r--r--dom/media/test/wavedata_s16.wavbin0 -> 22062 bytes
-rw-r--r--dom/media/test/wavedata_s16.wav^headers^1
-rw-r--r--dom/media/test/wavedata_s24.wavbin0 -> 33071 bytes
-rw-r--r--dom/media/test/wavedata_s24.wav^headers^1
-rw-r--r--dom/media/test/wavedata_u8.wavbin0 -> 11037 bytes
-rw-r--r--dom/media/test/wavedata_u8.wav^headers^1
-rw-r--r--dom/media/test/wavedata_ulaw.wavbin0 -> 11067 bytes
-rw-r--r--dom/media/test/wavedata_ulaw.wav^headers^1
1140 files changed, 30988 insertions, 0 deletions
diff --git a/dom/media/test/.eslintrc.js b/dom/media/test/.eslintrc.js
new file mode 100644
index 0000000000..845ed3f013
--- /dev/null
+++ b/dom/media/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/mochitest-test"],
+};
diff --git a/dom/media/test/16bit_wave_extrametadata.wav b/dom/media/test/16bit_wave_extrametadata.wav
new file mode 100644
index 0000000000..443ec73a3d
--- /dev/null
+++ b/dom/media/test/16bit_wave_extrametadata.wav
Binary files differ
diff --git a/dom/media/test/16bit_wave_extrametadata.wav^headers^ b/dom/media/test/16bit_wave_extrametadata.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/16bit_wave_extrametadata.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/320x240.ogv b/dom/media/test/320x240.ogv
new file mode 100644
index 0000000000..093158432a
--- /dev/null
+++ b/dom/media/test/320x240.ogv
Binary files differ
diff --git a/dom/media/test/320x240.ogv^headers^ b/dom/media/test/320x240.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/320x240.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/448636.ogv b/dom/media/test/448636.ogv
new file mode 100644
index 0000000000..628df924f8
--- /dev/null
+++ b/dom/media/test/448636.ogv
Binary files differ
diff --git a/dom/media/test/448636.ogv^headers^ b/dom/media/test/448636.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/448636.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/A4.ogv b/dom/media/test/A4.ogv
new file mode 100644
index 0000000000..de99616ece
--- /dev/null
+++ b/dom/media/test/A4.ogv
Binary files differ
diff --git a/dom/media/test/A4.ogv^headers^ b/dom/media/test/A4.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/A4.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/VID_0001.ogg b/dom/media/test/VID_0001.ogg
new file mode 100644
index 0000000000..0068b9af85
--- /dev/null
+++ b/dom/media/test/VID_0001.ogg
Binary files differ
diff --git a/dom/media/test/VID_0001.ogg^headers^ b/dom/media/test/VID_0001.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/VID_0001.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/allowed.sjs b/dom/media/test/allowed.sjs
new file mode 100644
index 0000000000..176a1a24a9
--- /dev/null
+++ b/dom/media/test/allowed.sjs
@@ -0,0 +1,56 @@
+function parseQuery(request, key) {
+ var params = request.queryString.split('&');
+ for (var j = 0; j < params.length; ++j) {
+ var p = params[j];
+ if (p == key)
+ return true;
+ if (p.indexOf(key + "=") == 0)
+ return p.substring(key.length + 1);
+ if (p.indexOf("=") < 0 && key == "")
+ return p;
+ }
+ return false;
+}
+
+var types = {
+ js: "text/javascript",
+ m4s: "video/mp4",
+ mp4: "video/mp4",
+ ogg: "video/ogg",
+ ogv: "video/ogg",
+ oga: "audio/ogg",
+ webm: "video/webm",
+ wav: "audio/x-wav"
+};
+
+// Return file with name as per the query string with access control
+// allow headers.
+function handleRequest(request, response)
+{
+ var resource = parseQuery(request, "");
+
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+ var paths = "tests/dom/media/test/" + resource;
+ var split = paths.split("/");
+ for(var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+ dump("file=" + file + "\n");
+ bis.setInputStream(fis);
+ var bytes = bis.readBytes(bis.available());
+ response.setStatusLine(request.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "bytes 0-" + (bytes.length - 1) + "/" + bytes.length);
+ response.setHeader("Content-Length", ""+bytes.length, false);
+ var ext = resource.substring(resource.lastIndexOf(".")+1);
+ response.setHeader("Content-Type", types[ext], false);
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.write(bytes, bytes.length);
+ bis.close();
+}
diff --git a/dom/media/test/ambisonics.mp4 b/dom/media/test/ambisonics.mp4
new file mode 100644
index 0000000000..4f5bcdfd26
--- /dev/null
+++ b/dom/media/test/ambisonics.mp4
Binary files differ
diff --git a/dom/media/test/ambisonics.mp4^headers^ b/dom/media/test/ambisonics.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/ambisonics.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/audio-gaps-short.ogg b/dom/media/test/audio-gaps-short.ogg
new file mode 100644
index 0000000000..e01a24bfda
--- /dev/null
+++ b/dom/media/test/audio-gaps-short.ogg
Binary files differ
diff --git a/dom/media/test/audio-gaps-short.ogg^headers^ b/dom/media/test/audio-gaps-short.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/audio-gaps-short.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/audio-gaps.ogg b/dom/media/test/audio-gaps.ogg
new file mode 100644
index 0000000000..ce96748ccd
--- /dev/null
+++ b/dom/media/test/audio-gaps.ogg
Binary files differ
diff --git a/dom/media/test/audio-gaps.ogg^headers^ b/dom/media/test/audio-gaps.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/audio-gaps.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/audio-overhang.ogg b/dom/media/test/audio-overhang.ogg
new file mode 100644
index 0000000000..c07986e7a1
--- /dev/null
+++ b/dom/media/test/audio-overhang.ogg
Binary files differ
diff --git a/dom/media/test/audio-overhang.ogg^headers^ b/dom/media/test/audio-overhang.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/audio-overhang.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/audio.wav b/dom/media/test/audio.wav
new file mode 100644
index 0000000000..c6fd5cb869
--- /dev/null
+++ b/dom/media/test/audio.wav
Binary files differ
diff --git a/dom/media/test/audio.wav^headers^ b/dom/media/test/audio.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/audio.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/av1.mp4 b/dom/media/test/av1.mp4
new file mode 100644
index 0000000000..28de929a29
--- /dev/null
+++ b/dom/media/test/av1.mp4
Binary files differ
diff --git a/dom/media/test/av1.mp4^headers^ b/dom/media/test/av1.mp4^headers^
new file mode 100644
index 0000000000..2567dc2fe5
--- /dev/null
+++ b/dom/media/test/av1.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store \ No newline at end of file
diff --git a/dom/media/test/background_video.js b/dom/media/test/background_video.js
new file mode 100644
index 0000000000..508f8fd89a
--- /dev/null
+++ b/dom/media/test/background_video.js
@@ -0,0 +1,224 @@
+/* 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 file expects manager to be defined in the global scope.
+/* global manager */
+/* import-globals-from manifest.js */
+
+"use strict";
+
+function startTest(test) {
+ info(test.desc);
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({ set: test.prefs }, () => {
+ manager.runTests(test.tests, test.runTest);
+ });
+}
+
+function nextVideoEnded(video) {
+ return nextEvent(video, "ended");
+}
+
+function nextVideoPlaying(video) {
+ return nextEvent(video, "playing");
+}
+
+function nextVideoResumes(video) {
+ return nextEvent(video, "mozexitvideosuspend");
+}
+
+function nextVideoSuspends(video) {
+ return nextEvent(video, "mozentervideosuspend");
+}
+
+/**
+ * @param {string} url video src.
+ * @returns {HTMLMediaElement} The created video element.
+ */
+function appendVideoToDoc(url, token, width, height) {
+ // Default size of (160, 120) is used by other media tests.
+ if (width === undefined) {
+ width = 160;
+ }
+ if (height === undefined) {
+ height = (3 * width) / 4;
+ }
+
+ let v = document.createElement("video");
+ v.token = token;
+ v.width = width;
+ v.height = height;
+ v.src = url;
+ document.body.appendChild(v);
+ return v;
+}
+
+function appendVideoToDocWithoutLoad(token, width, height) {
+ // Default size of (160, 120) is used by other media tests.
+ if (width === undefined) {
+ width = 160;
+ }
+ if (height === undefined) {
+ height = (3 * width) / 4;
+ }
+
+ let v = document.createElement("video");
+ v.token = token;
+ document.body.appendChild(v);
+ v.width = width;
+ v.height = height;
+ return v;
+}
+
+function loadAndWaitUntilLoadedmetadata(video, url, preloadType = "metadata") {
+ return new Promise((resolve, reject) => {
+ video.preload = preloadType;
+ video.addEventListener(
+ "loadedmetadata",
+ () => {
+ resolve();
+ },
+ true
+ );
+ video.src = url;
+ });
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element with under test.
+ * @returns {Promise} Promise that is resolved when video 'visibilitychanged' event fires.
+ */
+function waitUntilVisible(video) {
+ let videoChrome = SpecialPowers.wrap(video);
+ if (videoChrome.isInViewPort) {
+ return Promise.resolve();
+ }
+
+ return new Promise(resolve => {
+ videoChrome.addEventListener("visibilitychanged", () => {
+ if (videoChrome.isInViewPort) {
+ ok(true, `${video.token} is visible.`);
+ videoChrome.removeEventListener("visibilitychanged", this);
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video 'playing' event fires.
+ */
+function waitUntilPlaying(video) {
+ var p = once(video, "playing", () => {
+ ok(true, `${video.token} played.`);
+ });
+ Log(video.token, "Start playing");
+ video.play();
+ return p;
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise which is resolved when video 'ended' event fires.
+ */
+function waitUntilEnded(video) {
+ Log(video.token, "Waiting for ended");
+ if (video.ended) {
+ ok(true, video.token + " already ended");
+ return Promise.resolve();
+ }
+
+ return once(video, "ended", () => {
+ ok(true, `${video.token} ended`);
+ });
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video decode starts
+ * suspend timer.
+ */
+function testSuspendTimerStartedWhenHidden(video) {
+ var p = once(video, "mozstartvideosuspendtimer").then(() => {
+ ok(true, `${video.token} suspend begins`);
+ });
+ Log(video.token, "Set Hidden");
+ video.setVisible(false);
+ return p;
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video decode suspends.
+ */
+function testVideoSuspendsWhenHidden(video) {
+ let p = once(video, "mozentervideosuspend").then(() => {
+ ok(true, `${video.token} suspends`);
+ });
+ Log(video.token, "Set hidden");
+ video.setVisible(false);
+ return p;
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video decode resumes.
+ */
+function testVideoResumesWhenShown(video) {
+ var p = once(video, "mozexitvideosuspend").then(() => {
+ ok(true, `${video.token} resumes`);
+ });
+ Log(video.token, "Set visible");
+ video.setVisible(true);
+ return p;
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video decode resumes.
+ */
+function testVideoOnlySeekCompletedWhenShown(video) {
+ var p = once(video, "mozvideoonlyseekcompleted").then(() => {
+ ok(true, `${video.token} resumes`);
+ });
+ Log(video.token, "Set visible");
+ video.setVisible(true);
+ return p;
+}
+
+/**
+ * @param {HTMLVideoElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved if video ends and rejects if video suspends.
+ */
+function checkVideoDoesntSuspend(video) {
+ let p = Promise.race([
+ waitUntilEnded(video).then(() => {
+ ok(true, `${video.token} ended before decode was suspended`);
+ }),
+ once(video, "mozentervideosuspend", () => {
+ Promise.reject(new Error(`${video.token} suspended`));
+ }),
+ ]);
+ Log(video.token, "Set hidden.");
+ video.setVisible(false);
+ return p;
+}
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @param {number} time video current time to wait til.
+ * @returns {Promise} Promise that is resolved once currentTime passes time.
+ */
+function waitTil(video, time) {
+ Log(video.token, `Waiting for time to reach ${time}s`);
+ return new Promise(resolve => {
+ video.addEventListener("timeupdate", function timeUpdateEvent() {
+ if (video.currentTime > time) {
+ video.removeEventListener(name, timeUpdateEvent);
+ resolve();
+ }
+ });
+ });
+}
diff --git a/dom/media/test/badtags.ogg b/dom/media/test/badtags.ogg
new file mode 100644
index 0000000000..12d8358730
--- /dev/null
+++ b/dom/media/test/badtags.ogg
Binary files differ
diff --git a/dom/media/test/badtags.ogg^headers^ b/dom/media/test/badtags.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/badtags.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bear-640x360-a_frag-cenc-key_rotation.mp4 b/dom/media/test/bear-640x360-a_frag-cenc-key_rotation.mp4
new file mode 100644
index 0000000000..dc4f197ffa
--- /dev/null
+++ b/dom/media/test/bear-640x360-a_frag-cenc-key_rotation.mp4
Binary files differ
diff --git a/dom/media/test/bear-640x360-v_frag-cenc-key_rotation.mp4 b/dom/media/test/bear-640x360-v_frag-cenc-key_rotation.mp4
new file mode 100644
index 0000000000..916c64e9ee
--- /dev/null
+++ b/dom/media/test/bear-640x360-v_frag-cenc-key_rotation.mp4
Binary files differ
diff --git a/dom/media/test/beta-phrasebook.ogg b/dom/media/test/beta-phrasebook.ogg
new file mode 100644
index 0000000000..7e6ef77ec4
--- /dev/null
+++ b/dom/media/test/beta-phrasebook.ogg
Binary files differ
diff --git a/dom/media/test/beta-phrasebook.ogg^headers^ b/dom/media/test/beta-phrasebook.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/beta-phrasebook.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s
new file mode 100644
index 0000000000..266ec4c100
--- /dev/null
+++ b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s
Binary files differ
diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s^headers^ b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/big-buck-bunny-cenc-avc3-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4 b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4
new file mode 100644
index 0000000000..7aeb3eca8a
--- /dev/null
+++ b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4
Binary files differ
diff --git a/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4^headers^ b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/big-buck-bunny-cenc-avc3-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/big-short.wav b/dom/media/test/big-short.wav
new file mode 100644
index 0000000000..c850e5fd14
--- /dev/null
+++ b/dom/media/test/big-short.wav
Binary files differ
diff --git a/dom/media/test/big-short.wav^headers^ b/dom/media/test/big-short.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/big-short.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/big.wav b/dom/media/test/big.wav
new file mode 100644
index 0000000000..5f66bc1f02
--- /dev/null
+++ b/dom/media/test/big.wav
Binary files differ
diff --git a/dom/media/test/big.wav^headers^ b/dom/media/test/big.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/big.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-audio-key1.xml b/dom/media/test/bipbop-cenc-audio-key1.xml
new file mode 100644
index 0000000000..a1672eecef
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio-key1.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
+ generate the encrypted files, run bipbop-cenc.sh
+-->
+
+<GPACDRM type="CENC AES-CTR">
+
+ <DRMInfo type="pssh" version="1">
+ <!--
+ SystemID specified in
+ https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+ -->
+ <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
+ <!-- Number of KeyIDs = 1 -->
+ <BS bits="32" value="1" />
+ <!-- KeyID -->
+ <BS ID128="0x7e571d047e571d047e571d047e571d21" />
+ </DRMInfo>
+
+ <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d047e571d047e571d047e571d21"
+ value="0x7e5744447e5744447e5744447e574421" />
+ </CrypTrack>
+
+</GPACDRM>
diff --git a/dom/media/test/bipbop-cenc-audio-key2.xml b/dom/media/test/bipbop-cenc-audio-key2.xml
new file mode 100644
index 0000000000..b706609052
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio-key2.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
+ generate the encrypted files, run bipbop-cenc.sh
+-->
+
+<GPACDRM type="CENC AES-CTR">
+
+ <DRMInfo type="pssh" version="1">
+ <!--
+ SystemID specified in
+ https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+ -->
+ <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
+ <!-- Number of KeyIDs = 1 -->
+ <BS bits="32" value="1" />
+ <!-- KeyID -->
+ <BS ID128="0x7e571d047e571d047e571d047e571d22" />
+ </DRMInfo>
+
+ <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d047e571d047e571d047e571d22"
+ value="0x7e5744447e5744447e5744447e574422" />
+ </CrypTrack>
+
+</GPACDRM>
diff --git a/dom/media/test/bipbop-cenc-audio1.m4s b/dom/media/test/bipbop-cenc-audio1.m4s
new file mode 100644
index 0000000000..63cfd66f7e
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-audio1.m4s^headers^ b/dom/media/test/bipbop-cenc-audio1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-audio2.m4s b/dom/media/test/bipbop-cenc-audio2.m4s
new file mode 100644
index 0000000000..04a6cb6ff9
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-audio2.m4s^headers^ b/dom/media/test/bipbop-cenc-audio2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-audio3.m4s b/dom/media/test/bipbop-cenc-audio3.m4s
new file mode 100644
index 0000000000..ad0cd72f90
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-audio3.m4s^headers^ b/dom/media/test/bipbop-cenc-audio3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audio3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-audioinit.mp4 b/dom/media/test/bipbop-cenc-audioinit.mp4
new file mode 100644
index 0000000000..b827aa49aa
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audioinit.mp4
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-audioinit.mp4^headers^ b/dom/media/test/bipbop-cenc-audioinit.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-audioinit.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-video-10s.mp4 b/dom/media/test/bipbop-cenc-video-10s.mp4
new file mode 100644
index 0000000000..abbe4561fd
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video-10s.mp4
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-video-10s.mp4^headers^ b/dom/media/test/bipbop-cenc-video-10s.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video-10s.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-video-key1.xml b/dom/media/test/bipbop-cenc-video-key1.xml
new file mode 100644
index 0000000000..f0d9878fa2
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video-key1.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
+ generate the encrypted files, run bipbop-cenc.sh
+-->
+
+<GPACDRM type="CENC AES-CTR">
+
+ <DRMInfo type="pssh" version="1">
+ <!--
+ SystemID specified in
+ https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+ -->
+ <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
+ <!-- Number of KeyIDs = 1 -->
+ <BS bits="32" value="1" />
+ <!-- KeyID -->
+ <BS ID128="0x7e571d037e571d037e571d037e571d11" />
+ </DRMInfo>
+
+ <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d037e571d037e571d037e571d11"
+ value="0x7e5733337e5733337e5733337e573311" />
+ </CrypTrack>
+
+</GPACDRM>
diff --git a/dom/media/test/bipbop-cenc-video-key2.xml b/dom/media/test/bipbop-cenc-video-key2.xml
new file mode 100644
index 0000000000..1f320e6336
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video-key2.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ This XML file describes the encryption applied to |bipbop_<res>-cenc*|. To
+ generate the encrypted files, run bipbop-cenc.sh
+-->
+
+<GPACDRM type="CENC AES-CTR">
+
+ <DRMInfo type="pssh" version="1">
+ <!--
+ SystemID specified in
+ https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+ -->
+ <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
+ <!-- Number of KeyIDs = 1 -->
+ <BS bits="32" value="1" />
+ <!-- KeyID -->
+ <BS ID128="0x7e571d037e571d037e571d037e571d12" />
+ </DRMInfo>
+
+ <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d037e571d037e571d037e571d12"
+ value="0x7e5733337e5733337e5733337e573312" />
+ </CrypTrack>
+
+</GPACDRM>
diff --git a/dom/media/test/bipbop-cenc-video1.m4s b/dom/media/test/bipbop-cenc-video1.m4s
new file mode 100644
index 0000000000..755013c11c
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-video1.m4s^headers^ b/dom/media/test/bipbop-cenc-video1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-video2.m4s b/dom/media/test/bipbop-cenc-video2.m4s
new file mode 100644
index 0000000000..c884bd95fc
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-video2.m4s^headers^ b/dom/media/test/bipbop-cenc-video2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-video2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc-videoinit.mp4 b/dom/media/test/bipbop-cenc-videoinit.mp4
new file mode 100644
index 0000000000..aa87d0bbe6
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-videoinit.mp4
Binary files differ
diff --git a/dom/media/test/bipbop-cenc-videoinit.mp4^headers^ b/dom/media/test/bipbop-cenc-videoinit.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-cenc-videoinit.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-cenc.sh b/dom/media/test/bipbop-cenc.sh
new file mode 100644
index 0000000000..a00c38ae80
--- /dev/null
+++ b/dom/media/test/bipbop-cenc.sh
@@ -0,0 +1,29 @@
+mkdir work.tmp
+
+for r in 225w_175kbps 300_215kbps 300wp_227kbps 360w_253kbps 480_624kbps 480wp_663kbps 480_959kbps 480wp_1001kbps
+do
+ for k in 1 2
+ do
+ # Encrypt bipbop_<res>.mp4 with the keys specified in this file,
+ # and output to |bipbop_<res>-cenc-{video,audio}.mp4|
+ MP4Box -crypt bipbop-cenc-audio-key${k}.xml -rem 1 -out work.tmp/bipbop_${r}-cenc-audio-key${k}.mp4 bipbop_${r}.mp4
+ MP4Box -crypt bipbop-cenc-video-key${k}.xml -rem 2 -out work.tmp/bipbop_${r}-cenc-video-key${k}.mp4 bipbop_${r}.mp4
+
+ # Fragment |bipbop_<res>-cenc-*.mp4| into 500ms segments:
+ MP4Box -dash 500 -rap -segment-name work.tmp/bipbop_${r}-cenc-audio-key${k}- -subsegs-per-sidx 5 work.tmp/bipbop_${r}-cenc-audio-key${k}.mp4
+ MP4Box -dash 500 -rap -segment-name work.tmp/bipbop_${r}-cenc-video-key${k}- -subsegs-per-sidx 5 work.tmp/bipbop_${r}-cenc-video-key${k}.mp4
+
+ # The above command will generate a set of fragments |bipbop_<res>-cenc-{video,audio}-*.m4s
+ # and |bipbop_<res>-cenc-{video,audio}-init.mp4| containing just the init segment.
+
+ # Remove unneeded mpd files.
+ rm bipbop_${r}-cenc-{audio,video}-key${k}_dash.mpd
+ done
+done
+
+# Only keep the first 4 audio & 2 video segments:
+cp work.tmp/*-init[.]mp4 ./
+cp work.tmp/*audio*-[1234][.]m4s ./
+cp work.tmp/*video*-[12][.]m4s ./
+
+rm -Rf work.tmp
diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4 b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4
new file mode 100644
index 0000000000..5e5e30c255
--- /dev/null
+++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4
Binary files differ
diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^
new file mode 100644
index 0000000000..12a01c4a22
--- /dev/null
+++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4 b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4
new file mode 100644
index 0000000000..447c657475
--- /dev/null
+++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4
Binary files differ
diff --git a/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^
new file mode 100644
index 0000000000..12a01c4a22
--- /dev/null
+++ b/dom/media/test/bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-frag-cenc.xml b/dom/media/test/bipbop-frag-cenc.xml
new file mode 100644
index 0000000000..6f6a4d90a9
--- /dev/null
+++ b/dom/media/test/bipbop-frag-cenc.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ This XML file describes the encryption applied to |bipbop-cenc*|. To
+ generate the bipbop-cenc files, run the following commands:
+
+ Encrypt bipbop-no-edts.mp4 with the keys specified in this file,
+ and output to |bipbop-cenc-{video,audio}.mp4|
+ MP4Box -crypt bipbop-frag-cenc.xml -rem 2 -out bipbop-cenc-video.mp4 bipbop-no-edts.mp4
+ MP4Box -crypt bipbop-frag-cenc.xml -rem 1 -out bipbop-cenc-audio.mp4 bipbop-no-edts.mp4
+
+ Fragment |bipbop-cenc-*.mp4| into 500ms segments:
+ MP4Box -dash 500 -rap -segment-name bipbop-cenc-video -subsegs-per-sidx 5 bipbop-cenc-video.mp4
+ MP4Box -dash 500 -rap -segment-name bipbop-cenc-audio -subsegs-per-sidx 5 bipbop-cenc-audio.mp4
+
+ The above command will generate a set of fragments in |bipbop-cenc-{video,audio}*.m4s
+ and |bipbop-cenc-{video,audio}init.mp4| containing just the init segment.
+
+ To cut down the duration, we throw out all but the first 3 audio & 2 video segments:
+ rm bipbop-cenc-audio{[^123],[123][^.]}.m4s
+ rm bipbop-cenc-video{[^12],[12][^.]}.m4s
+
+ MP4Box will also have generated some *.mpd files we don't need:
+ rm bipbop-cenc-*.mpd
+
+ Delete intermediate encrypted files:
+ rm bipbop-cenc-{audio,video}.mp4
+-->
+
+<GPACDRM type="CENC AES-CTR">
+
+ <DRMInfo type="pssh" version="1">
+ <!--
+ SystemID specified in
+ https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+ -->
+ <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
+ <!-- Number of KeyIDs = 2 -->
+ <BS bits="32" value="2" />
+ <!-- KeyID -->
+ <BS ID128="0x7e571d037e571d037e571d037e571d03" />
+ <BS ID128="0x7e571d047e571d047e571d047e571d04" />
+ </DRMInfo>
+
+ <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d037e571d037e571d037e571d03"
+ value="0x7e5733337e5733337e5733337e573333" />
+ </CrypTrack>
+
+ <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d047e571d047e571d047e571d04"
+ value="0x7e5744447e5744447e5744447e574444" />
+ </CrypTrack>
+
+</GPACDRM>
diff --git a/dom/media/test/bipbop-lateaudio.mp4 b/dom/media/test/bipbop-lateaudio.mp4
new file mode 100644
index 0000000000..5b4cc57095
--- /dev/null
+++ b/dom/media/test/bipbop-lateaudio.mp4
Binary files differ
diff --git a/dom/media/test/bipbop-lateaudio.mp4^headers^ b/dom/media/test/bipbop-lateaudio.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop-lateaudio.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop-no-edts.mp4 b/dom/media/test/bipbop-no-edts.mp4
new file mode 100644
index 0000000000..63435887df
--- /dev/null
+++ b/dom/media/test/bipbop-no-edts.mp4
Binary files differ
diff --git a/dom/media/test/bipbop.mp4 b/dom/media/test/bipbop.mp4
new file mode 100644
index 0000000000..017d658f31
--- /dev/null
+++ b/dom/media/test/bipbop.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..40c3a7bb98
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..986e5fb186
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..547950e516
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..3214f131d4
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..08713078d9
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..0b13fed5f0
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_225w_175kbps.mp4 b/dom/media/test/bipbop_225w_175kbps.mp4
new file mode 100644
index 0000000000..abe37b9f9d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_225w_175kbps.mp4^headers^ b/dom/media/test/bipbop_225w_175kbps.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..21f3863274
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..bc741cdf86
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..9c6818d06f
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s
new file mode 100644
index 0000000000..f327aaa573
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..543f18c24b
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..f850ceaf0a
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s
new file mode 100644
index 0000000000..a28a106daf
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..a05a879970
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300_215kbps.mp4 b/dom/media/test/bipbop_300_215kbps.mp4
new file mode 100644
index 0000000000..084d477430
--- /dev/null
+++ b/dom/media/test/bipbop_300_215kbps.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..40c3a7bb98
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..986e5fb186
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..9c6818d06f
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s
new file mode 100644
index 0000000000..ce2e64eb33
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..8592a5b0a3
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..f850ceaf0a
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s
new file mode 100644
index 0000000000..d07ce9753e
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..9d2fa23dd4
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_300wp_227kbps.mp4 b/dom/media/test/bipbop_300wp_227kbps.mp4
new file mode 100644
index 0000000000..1499355313
--- /dev/null
+++ b/dom/media/test/bipbop_300wp_227kbps.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..40c3a7bb98
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..986e5fb186
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..a571d47cfb
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..42dbfec1ed
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..9e4224cac8
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..21763ecbdd
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm
new file mode 100644
index 0000000000..4be8f340c3
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^ b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-audio.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm
new file mode 100644
index 0000000000..56cf4c483c
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm
new file mode 100644
index 0000000000..9f411d0e34
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm
Binary files differ
diff --git a/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_360w_253kbps.mp4 b/dom/media/test/bipbop_360w_253kbps.mp4
new file mode 100644
index 0000000000..6c796f4e1f
--- /dev/null
+++ b/dom/media/test/bipbop_360w_253kbps.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..e626fa4564
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..d7cbb2b6b0
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..805f4bbf3f
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s
new file mode 100644
index 0000000000..5bf9994733
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..77c7daba5a
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..c5127beec9
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s
new file mode 100644
index 0000000000..b0ff51f74a
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..cfa099c043
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_624kbps.mp4 b/dom/media/test/bipbop_480_624kbps.mp4
new file mode 100644
index 0000000000..27928b85f4
--- /dev/null
+++ b/dom/media/test/bipbop_480_624kbps.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..c9106aad99
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..888b20ab63
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..796ad13670
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s
new file mode 100644
index 0000000000..d02be53198
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..6e0c60f986
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..06778e6f2b
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s
new file mode 100644
index 0000000000..4c1c603e8d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..f4a98eca97
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480_959kbps.mp4 b/dom/media/test/bipbop_480_959kbps.mp4
new file mode 100644
index 0000000000..4a9f2ee823
--- /dev/null
+++ b/dom/media/test/bipbop_480_959kbps.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..416bc7a7ca
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..73d542cfe0
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..796ad13670
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s
new file mode 100644
index 0000000000..80824e9ffc
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..5db21d091b
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..06778e6f2b
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s
new file mode 100644
index 0000000000..38a081187a
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..bc8bddf505
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_1001kbps.mp4 b/dom/media/test/bipbop_480wp_1001kbps.mp4
new file mode 100644
index 0000000000..600376cf83
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_1001kbps.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s
new file mode 100644
index 0000000000..e2bd754c7e
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s
new file mode 100644
index 0000000000..347835feee
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s
new file mode 100644
index 0000000000..64b0da69a0
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s
new file mode 100644
index 0000000000..864f4248af
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4
new file mode 100644
index 0000000000..416bc7a7ca
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s
new file mode 100644
index 0000000000..a8896e069a
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s
new file mode 100644
index 0000000000..0f0a35ce79
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s
new file mode 100644
index 0000000000..fece52ff42
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s
new file mode 100644
index 0000000000..70e61e3d5f
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4
new file mode 100644
index 0000000000..73d542cfe0
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s
new file mode 100644
index 0000000000..805f4bbf3f
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s
new file mode 100644
index 0000000000..0a40d1cb73
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4
new file mode 100644
index 0000000000..5db21d091b
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s
new file mode 100644
index 0000000000..c5127beec9
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s
new file mode 100644
index 0000000000..3f344022a4
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4 b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4
new file mode 100644
index 0000000000..bc8bddf505
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4
Binary files differ
diff --git a/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bipbop_480wp_663kbps.mp4 b/dom/media/test/bipbop_480wp_663kbps.mp4
new file mode 100644
index 0000000000..3cc1da69d2
--- /dev/null
+++ b/dom/media/test/bipbop_480wp_663kbps.mp4
Binary files differ
diff --git a/dom/media/test/black100x100-aspect3to2.ogv b/dom/media/test/black100x100-aspect3to2.ogv
new file mode 100644
index 0000000000..81fe51ffb3
--- /dev/null
+++ b/dom/media/test/black100x100-aspect3to2.ogv
Binary files differ
diff --git a/dom/media/test/black100x100-aspect3to2.ogv^headers^ b/dom/media/test/black100x100-aspect3to2.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/black100x100-aspect3to2.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bogus.duh b/dom/media/test/bogus.duh
new file mode 100644
index 0000000000..528ae275d0
--- /dev/null
+++ b/dom/media/test/bogus.duh
@@ -0,0 +1,45 @@
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
diff --git a/dom/media/test/bogus.ogv b/dom/media/test/bogus.ogv
new file mode 100644
index 0000000000..528ae275d0
--- /dev/null
+++ b/dom/media/test/bogus.ogv
@@ -0,0 +1,45 @@
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
diff --git a/dom/media/test/bogus.ogv^headers^ b/dom/media/test/bogus.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bogus.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bogus.wav b/dom/media/test/bogus.wav
new file mode 100644
index 0000000000..528ae275d0
--- /dev/null
+++ b/dom/media/test/bogus.wav
@@ -0,0 +1,45 @@
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
+bogus bogus bogus
diff --git a/dom/media/test/bogus.wav^headers^ b/dom/media/test/bogus.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bogus.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/browser/browser.ini b/dom/media/test/browser/browser.ini
new file mode 100644
index 0000000000..d828f4b6f9
--- /dev/null
+++ b/dom/media/test/browser/browser.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = mediacontrol
+support-files =
+ file_media.html
+ ../gizmo.mp4
+
+[browser_tab_visibility_and_play_time.js]
diff --git a/dom/media/test/browser/browser_tab_visibility_and_play_time.js b/dom/media/test/browser/browser_tab_visibility_and_play_time.js
new file mode 100644
index 0000000000..4eb956b9ea
--- /dev/null
+++ b/dom/media/test/browser/browser_tab_visibility_and_play_time.js
@@ -0,0 +1,120 @@
+/**
+ * This test is used to ensure that invisible play time would be accumulated
+ * when tab is in background. However, this test won't directly check the
+ * reported telemetry result, because we can't check the snapshot histogram in
+ * the content process.
+ * The actual probe checking happens in `test_accumulated_play_time.html`.
+ */
+"use strict";
+
+const PAGE_URL =
+ "https://example.com/browser/dom/media/test/browser/file_media.html";
+
+add_task(async function testChangingTabVisibilityAffectsInvisiblePlayTime() {
+ const originalTab = gBrowser.selectedTab;
+ const mediaTab = await openMediaTab(PAGE_URL);
+
+ info(`measuring play time when tab is in foreground`);
+ await startMedia({
+ mediaTab,
+ shouldAccumulateTime: true,
+ shouldAccumulateInvisibleTime: false,
+ });
+ await pauseMedia(mediaTab);
+
+ info(`measuring play time when tab is in foreground`);
+ await BrowserTestUtils.switchTab(window.gBrowser, originalTab);
+ await startMedia({
+ mediaTab,
+ shouldAccumulateTime: true,
+ shouldAccumulateInvisibleTime: true,
+ });
+ await pauseMedia(mediaTab);
+
+ BrowserTestUtils.removeTab(mediaTab);
+});
+
+/**
+ * Following are helper functions.
+ */
+async function openMediaTab(url) {
+ info(`open tab for media playback`);
+ const tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url);
+ info(`add content helper functions and variables`);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
+ content.assertAttributeDefined = (videoChrome, checkType) => {
+ ok(videoChrome[checkType] != undefined, `${checkType} exists`);
+ };
+ content.assertValueEqualTo = (videoChrome, checkType, expectedValue) => {
+ content.assertAttributeDefined(videoChrome, checkType);
+ is(
+ videoChrome[checkType],
+ expectedValue,
+ `${checkType} equals to ${expectedValue}`
+ );
+ };
+ content.assertValueConstantlyIncreases = (videoChrome, checkType) => {
+ content.assertAttributeDefined(videoChrome, checkType);
+ const valueSnapshot = videoChrome[checkType];
+ ok(
+ videoChrome[checkType] > valueSnapshot,
+ `${checkType} keeps increasing`
+ );
+ };
+ content.assertValueKeptUnchanged = (videoChrome, checkType) => {
+ content.assertAttributeDefined(videoChrome, checkType);
+ const valueSnapshot = videoChrome[checkType];
+ ok(
+ videoChrome[checkType] == valueSnapshot,
+ `${checkType} keeps unchanged`
+ );
+ };
+ });
+ return tab;
+}
+
+function startMedia({
+ mediaTab,
+ shouldAccumulateTime,
+ shouldAccumulateInvisibleTime,
+}) {
+ return SpecialPowers.spawn(
+ mediaTab.linkedBrowser,
+ [shouldAccumulateTime, shouldAccumulateInvisibleTime],
+ async (accumulateTime, accumulateInvisibleTime) => {
+ const video = content.document.getElementById("video");
+ ok(
+ await video.play().then(
+ () => true,
+ () => false
+ ),
+ "video started playing"
+ );
+ const videoChrome = SpecialPowers.wrap(video);
+ if (accumulateTime) {
+ content.assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+ } else {
+ content.assertValueKeptUnchanged(videoChrome, "totalPlayTime");
+ }
+ if (accumulateInvisibleTime) {
+ content.assertValueConstantlyIncreases(
+ videoChrome,
+ "invisiblePlayTime"
+ );
+ } else {
+ content.assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ }
+ }
+ );
+}
+
+function pauseMedia(tab) {
+ return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
+ const video = content.document.getElementById("video");
+ video.pause();
+ ok(true, "video paused");
+ const videoChrome = SpecialPowers.wrap(video);
+ content.assertValueKeptUnchanged(videoChrome, "totalPlayTime");
+ content.assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ });
+}
diff --git a/dom/media/test/browser/file_media.html b/dom/media/test/browser/file_media.html
new file mode 100644
index 0000000000..498c2eaad6
--- /dev/null
+++ b/dom/media/test/browser/file_media.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Non-Autoplay page</title>
+</head>
+<body>
+<video id="video" src="gizmo.mp4" loop></video>
+</body>
+</html>
diff --git a/dom/media/test/bug1066943.webm b/dom/media/test/bug1066943.webm
new file mode 100644
index 0000000000..64a24ec898
--- /dev/null
+++ b/dom/media/test/bug1066943.webm
Binary files differ
diff --git a/dom/media/test/bug1066943.webm^headers^ b/dom/media/test/bug1066943.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug1066943.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug1301226-odd.wav b/dom/media/test/bug1301226-odd.wav
new file mode 100644
index 0000000000..dd2df4e4dd
--- /dev/null
+++ b/dom/media/test/bug1301226-odd.wav
Binary files differ
diff --git a/dom/media/test/bug1301226-odd.wav^headers^ b/dom/media/test/bug1301226-odd.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug1301226-odd.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug1301226.wav b/dom/media/test/bug1301226.wav
new file mode 100644
index 0000000000..0128486f07
--- /dev/null
+++ b/dom/media/test/bug1301226.wav
Binary files differ
diff --git a/dom/media/test/bug1301226.wav^headers^ b/dom/media/test/bug1301226.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug1301226.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug1377278.webm b/dom/media/test/bug1377278.webm
new file mode 100644
index 0000000000..802019f39f
--- /dev/null
+++ b/dom/media/test/bug1377278.webm
Binary files differ
diff --git a/dom/media/test/bug1377278.webm^headers^ b/dom/media/test/bug1377278.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug1377278.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug461281.ogg b/dom/media/test/bug461281.ogg
new file mode 100644
index 0000000000..d7f6a0ccf4
--- /dev/null
+++ b/dom/media/test/bug461281.ogg
Binary files differ
diff --git a/dom/media/test/bug461281.ogg^headers^ b/dom/media/test/bug461281.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug461281.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug482461-theora.ogv b/dom/media/test/bug482461-theora.ogv
new file mode 100644
index 0000000000..941b8d8efd
--- /dev/null
+++ b/dom/media/test/bug482461-theora.ogv
Binary files differ
diff --git a/dom/media/test/bug482461-theora.ogv^headers^ b/dom/media/test/bug482461-theora.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug482461-theora.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug482461.ogv b/dom/media/test/bug482461.ogv
new file mode 100644
index 0000000000..6cf6aed330
--- /dev/null
+++ b/dom/media/test/bug482461.ogv
Binary files differ
diff --git a/dom/media/test/bug482461.ogv^headers^ b/dom/media/test/bug482461.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug482461.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug495129.ogv b/dom/media/test/bug495129.ogv
new file mode 100644
index 0000000000..44eb9296f5
--- /dev/null
+++ b/dom/media/test/bug495129.ogv
Binary files differ
diff --git a/dom/media/test/bug495129.ogv^headers^ b/dom/media/test/bug495129.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug495129.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug495794.ogg b/dom/media/test/bug495794.ogg
new file mode 100644
index 0000000000..1c19a64061
--- /dev/null
+++ b/dom/media/test/bug495794.ogg
Binary files differ
diff --git a/dom/media/test/bug495794.ogg^headers^ b/dom/media/test/bug495794.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug495794.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug498380.ogv b/dom/media/test/bug498380.ogv
new file mode 100644
index 0000000000..1179ecb70a
--- /dev/null
+++ b/dom/media/test/bug498380.ogv
Binary files differ
diff --git a/dom/media/test/bug498380.ogv^headers^ b/dom/media/test/bug498380.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug498380.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug498855-1.ogv b/dom/media/test/bug498855-1.ogv
new file mode 100644
index 0000000000..95a524da4c
--- /dev/null
+++ b/dom/media/test/bug498855-1.ogv
Binary files differ
diff --git a/dom/media/test/bug498855-1.ogv^headers^ b/dom/media/test/bug498855-1.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug498855-1.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug498855-2.ogv b/dom/media/test/bug498855-2.ogv
new file mode 100644
index 0000000000..795a308ae1
--- /dev/null
+++ b/dom/media/test/bug498855-2.ogv
Binary files differ
diff --git a/dom/media/test/bug498855-2.ogv^headers^ b/dom/media/test/bug498855-2.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug498855-2.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug498855-3.ogv b/dom/media/test/bug498855-3.ogv
new file mode 100644
index 0000000000..714858dfed
--- /dev/null
+++ b/dom/media/test/bug498855-3.ogv
Binary files differ
diff --git a/dom/media/test/bug498855-3.ogv^headers^ b/dom/media/test/bug498855-3.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug498855-3.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug499519.ogv b/dom/media/test/bug499519.ogv
new file mode 100644
index 0000000000..62c0922d36
--- /dev/null
+++ b/dom/media/test/bug499519.ogv
Binary files differ
diff --git a/dom/media/test/bug499519.ogv^headers^ b/dom/media/test/bug499519.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug499519.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug500311.ogv b/dom/media/test/bug500311.ogv
new file mode 100644
index 0000000000..2cf27ef1ee
--- /dev/null
+++ b/dom/media/test/bug500311.ogv
Binary files differ
diff --git a/dom/media/test/bug500311.ogv^headers^ b/dom/media/test/bug500311.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug500311.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug501279.ogg b/dom/media/test/bug501279.ogg
new file mode 100644
index 0000000000..e266f07ee8
--- /dev/null
+++ b/dom/media/test/bug501279.ogg
Binary files differ
diff --git a/dom/media/test/bug501279.ogg^headers^ b/dom/media/test/bug501279.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug501279.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug504613.ogv b/dom/media/test/bug504613.ogv
new file mode 100644
index 0000000000..5c7fd015e9
--- /dev/null
+++ b/dom/media/test/bug504613.ogv
Binary files differ
diff --git a/dom/media/test/bug504613.ogv^headers^ b/dom/media/test/bug504613.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug504613.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug504644.ogv b/dom/media/test/bug504644.ogv
new file mode 100644
index 0000000000..46fb4a876b
--- /dev/null
+++ b/dom/media/test/bug504644.ogv
Binary files differ
diff --git a/dom/media/test/bug504644.ogv^headers^ b/dom/media/test/bug504644.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug504644.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug504843.ogv b/dom/media/test/bug504843.ogv
new file mode 100644
index 0000000000..94b4750865
--- /dev/null
+++ b/dom/media/test/bug504843.ogv
Binary files differ
diff --git a/dom/media/test/bug504843.ogv^headers^ b/dom/media/test/bug504843.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug504843.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug506094.ogv b/dom/media/test/bug506094.ogv
new file mode 100644
index 0000000000..142b7b9ad1
--- /dev/null
+++ b/dom/media/test/bug506094.ogv
Binary files differ
diff --git a/dom/media/test/bug506094.ogv^headers^ b/dom/media/test/bug506094.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug506094.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug516323.indexed.ogv b/dom/media/test/bug516323.indexed.ogv
new file mode 100644
index 0000000000..7bd76eeccc
--- /dev/null
+++ b/dom/media/test/bug516323.indexed.ogv
Binary files differ
diff --git a/dom/media/test/bug516323.indexed.ogv^headers^ b/dom/media/test/bug516323.indexed.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug516323.indexed.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug516323.ogv b/dom/media/test/bug516323.ogv
new file mode 100644
index 0000000000..8f2f38b983
--- /dev/null
+++ b/dom/media/test/bug516323.ogv
Binary files differ
diff --git a/dom/media/test/bug516323.ogv^headers^ b/dom/media/test/bug516323.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug516323.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug520493.ogg b/dom/media/test/bug520493.ogg
new file mode 100644
index 0000000000..6eb23198f4
--- /dev/null
+++ b/dom/media/test/bug520493.ogg
Binary files differ
diff --git a/dom/media/test/bug520493.ogg^headers^ b/dom/media/test/bug520493.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug520493.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug520500.ogg b/dom/media/test/bug520500.ogg
new file mode 100644
index 0000000000..b91d3dd97d
--- /dev/null
+++ b/dom/media/test/bug520500.ogg
Binary files differ
diff --git a/dom/media/test/bug520500.ogg^headers^ b/dom/media/test/bug520500.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug520500.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug520908.ogv b/dom/media/test/bug520908.ogv
new file mode 100644
index 0000000000..093158432a
--- /dev/null
+++ b/dom/media/test/bug520908.ogv
Binary files differ
diff --git a/dom/media/test/bug520908.ogv^headers^ b/dom/media/test/bug520908.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug520908.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug523816.ogv b/dom/media/test/bug523816.ogv
new file mode 100644
index 0000000000..ca9a31b6da
--- /dev/null
+++ b/dom/media/test/bug523816.ogv
Binary files differ
diff --git a/dom/media/test/bug523816.ogv^headers^ b/dom/media/test/bug523816.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug523816.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug533822.ogg b/dom/media/test/bug533822.ogg
new file mode 100644
index 0000000000..a8e506910e
--- /dev/null
+++ b/dom/media/test/bug533822.ogg
Binary files differ
diff --git a/dom/media/test/bug533822.ogg^headers^ b/dom/media/test/bug533822.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug533822.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug556821.ogv b/dom/media/test/bug556821.ogv
new file mode 100644
index 0000000000..8d76fee45e
--- /dev/null
+++ b/dom/media/test/bug556821.ogv
Binary files differ
diff --git a/dom/media/test/bug556821.ogv^headers^ b/dom/media/test/bug556821.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug556821.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug557094.ogv b/dom/media/test/bug557094.ogv
new file mode 100644
index 0000000000..b4fc0799a6
--- /dev/null
+++ b/dom/media/test/bug557094.ogv
Binary files differ
diff --git a/dom/media/test/bug557094.ogv^headers^ b/dom/media/test/bug557094.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug557094.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug603918.webm b/dom/media/test/bug603918.webm
new file mode 100644
index 0000000000..c430e97f40
--- /dev/null
+++ b/dom/media/test/bug603918.webm
Binary files differ
diff --git a/dom/media/test/bug603918.webm^headers^ b/dom/media/test/bug603918.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug603918.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bug604067.webm b/dom/media/test/bug604067.webm
new file mode 100644
index 0000000000..86bdfdc91f
--- /dev/null
+++ b/dom/media/test/bug604067.webm
Binary files differ
diff --git a/dom/media/test/bug604067.webm^headers^ b/dom/media/test/bug604067.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/bug604067.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/bunny.webm b/dom/media/test/bunny.webm
new file mode 100644
index 0000000000..823439d1c5
--- /dev/null
+++ b/dom/media/test/bunny.webm
Binary files differ
diff --git a/dom/media/test/can_play_type_dash.js b/dom/media/test/can_play_type_dash.js
new file mode 100644
index 0000000000..b4760545db
--- /dev/null
+++ b/dom/media/test/can_play_type_dash.js
@@ -0,0 +1,27 @@
+function check_dash(v, enabled) {
+ function check(type, expected) {
+ is(v.canPlayType(type), enabled ? expected : "", type);
+ }
+
+ // DASH types
+ check("application/dash+xml", "probably");
+
+ // Supported Webm codecs
+ check("application/dash+xml; codecs=vorbis", "probably");
+ check("application/dash+xml; codecs=vorbis", "probably");
+ check("application/dash+xml; codecs=vorbis,vp8", "probably");
+ check("application/dash+xml; codecs=vorbis,vp8.0", "probably");
+ check('application/dash+xml; codecs="vorbis,vp8"', "probably");
+ check('application/dash+xml; codecs="vorbis,vp8.0"', "probably");
+ check('application/dash+xml; codecs="vp8, vorbis"', "probably");
+ check('application/dash+xml; codecs="vp8.0, vorbis"', "probably");
+ check("application/dash+xml; codecs=vp8", "probably");
+ check("application/dash+xml; codecs=vp8.0", "probably");
+
+ // Unsupported codecs
+ check("application/dash+xml; codecs=xyz", "");
+ check("application/dash+xml; codecs=xyz,vorbis", "");
+ check("application/dash+xml; codecs=vorbis,xyz", "");
+ check("application/dash+xml; codecs=xyz,vp8.0", "");
+ check("application/dash+xml; codecs=vp8.0,xyz", "");
+}
diff --git a/dom/media/test/can_play_type_ogg.js b/dom/media/test/can_play_type_ogg.js
new file mode 100644
index 0000000000..c03f8b2d3e
--- /dev/null
+++ b/dom/media/test/can_play_type_ogg.js
@@ -0,0 +1,72 @@
+function check_ogg(v, enabled, finish) {
+ function check(type, expected) {
+ is(v.canPlayType(type), enabled ? expected : "", type);
+ }
+
+ function basic_test() {
+ return new Promise(function(resolve, reject) {
+ // Ogg types
+ check("video/ogg", "maybe");
+ check("audio/ogg", "maybe");
+ check("application/ogg", "maybe");
+
+ // Supported Ogg codecs
+ check("audio/ogg; codecs=vorbis", "probably");
+ check("video/ogg; codecs=vorbis", "probably");
+ check("video/ogg; codecs=vorbis,theora", "probably");
+ check('video/ogg; codecs="vorbis, theora"', "probably");
+ check("video/ogg; codecs=theora", "probably");
+
+ resolve();
+ });
+ }
+
+ // Verify Opus support
+ function verify_opus_support() {
+ return new Promise(function(resolve, reject) {
+ var OpusEnabled = SpecialPowers.getBoolPref(
+ "media.opus.enabled",
+ undefined
+ );
+ if (OpusEnabled != undefined) {
+ resolve();
+ } else {
+ console.log(
+ "media.opus.enabled pref not found; skipping Opus validation"
+ );
+ reject();
+ }
+ });
+ }
+
+ function opus_enable() {
+ return SpecialPowers.pushPrefEnv({
+ set: [["media.opus.enabled", true]],
+ }).then(function() {
+ check("audio/ogg; codecs=opus", "probably");
+ });
+ }
+
+ function opus_disable() {
+ return SpecialPowers.pushPrefEnv({
+ set: [["media.opus.enabled", false]],
+ }).then(function() {
+ check("audio/ogg; codecs=opus", "");
+ });
+ }
+
+ function unspported_ogg() {
+ // Unsupported Ogg codecs
+ check("video/ogg; codecs=xyz", "");
+ check("video/ogg; codecs=xyz,vorbis", "");
+ check("video/ogg; codecs=vorbis,xyz", "");
+
+ finish.call();
+ }
+
+ basic_test()
+ .then(verify_opus_support)
+ .then(opus_enable)
+ .then(opus_disable)
+ .then(unspported_ogg, unspported_ogg);
+}
diff --git a/dom/media/test/can_play_type_wave.js b/dom/media/test/can_play_type_wave.js
new file mode 100644
index 0000000000..a5e087aa40
--- /dev/null
+++ b/dom/media/test/can_play_type_wave.js
@@ -0,0 +1,30 @@
+function check_wave(v, enabled) {
+ function check(type, expected) {
+ is(v.canPlayType(type), enabled ? expected : "", type);
+ }
+
+ // Wave types
+ check("audio/wave", "maybe");
+ check("audio/wav", "maybe");
+ check("audio/x-wav", "maybe");
+ check("audio/x-pn-wav", "maybe");
+
+ // Supported Wave codecs
+ check("audio/wave; codecs=1", "probably");
+ check("audio/wave; codecs=3", "probably");
+ check("audio/wave; codecs=6", "probably");
+ check("audio/wave; codecs=7", "probably");
+ // "no codecs" should be supported, I guess
+ check("audio/wave; codecs=", "maybe");
+ check('audio/wave; codecs=""', "maybe");
+
+ // Unsupported Wave codecs
+ check("audio/wave; codecs=0", "");
+ check("audio/wave; codecs=2", "");
+ check("audio/wave; codecs=xyz,1", "");
+ check("audio/wave; codecs=1,xyz", "");
+ check('audio/wave; codecs="xyz, 1"', "");
+ // empty codec names
+ check("audio/wave; codecs=,", "");
+ check('audio/wave; codecs="0, 1,"', "");
+}
diff --git a/dom/media/test/can_play_type_webm.js b/dom/media/test/can_play_type_webm.js
new file mode 100644
index 0000000000..ee57da66c6
--- /dev/null
+++ b/dom/media/test/can_play_type_webm.js
@@ -0,0 +1,39 @@
+async function check_webm(v, enabled) {
+ function check(type, expected) {
+ is(
+ v.canPlayType(type),
+ enabled ? expected : "",
+ type + "='" + expected + "'"
+ );
+ }
+
+ // WebM types
+ check("video/webm", "maybe");
+ check("audio/webm", "maybe");
+
+ var video = ["vp8", "vp8.0", "vp9", "vp9.0"];
+ var audio = ["vorbis", "opus"];
+
+ audio.forEach(function(acodec) {
+ check("audio/webm; codecs=" + acodec, "probably");
+ check("video/webm; codecs=" + acodec, "probably");
+ });
+ video.forEach(function(vcodec) {
+ check("video/webm; codecs=" + vcodec, "probably");
+ audio.forEach(function(acodec) {
+ check('video/webm; codecs="' + vcodec + ", " + acodec + '"', "probably");
+ check('video/webm; codecs="' + acodec + ", " + vcodec + '"', "probably");
+ });
+ });
+
+ // Unsupported WebM codecs
+ check("video/webm; codecs=xyz", "");
+ check("video/webm; codecs=xyz,vorbis", "");
+ check("video/webm; codecs=vorbis,xyz", "");
+
+ await SpecialPowers.pushPrefEnv({ set: [["media.av1.enabled", true]] });
+ check('video/webm; codecs="av1"', "probably");
+
+ await SpecialPowers.pushPrefEnv({ set: [["media.av1.enabled", false]] });
+ check('video/webm; codecs="av1"', "");
+}
diff --git a/dom/media/test/cancellable_request.sjs b/dom/media/test/cancellable_request.sjs
new file mode 100644
index 0000000000..9a4a12076e
--- /dev/null
+++ b/dom/media/test/cancellable_request.sjs
@@ -0,0 +1,153 @@
+function parseQuery(request, key) {
+ var params = request.queryString.split('&');
+ for (var j = 0; j < params.length; ++j) {
+ var p = params[j];
+ if (p == key)
+ return true;
+ if (p.indexOf(key + "=") == 0)
+ return p.substring(key.length + 1);
+ if (p.indexOf("=") < 0 && key == "")
+ return p;
+ }
+ return false;
+}
+
+function push32BE(array, input) {
+ array.push(String.fromCharCode((input >> 24) & 0xff));
+ array.push(String.fromCharCode((input >> 16) & 0xff));
+ array.push(String.fromCharCode((input >> 8) & 0xff));
+ array.push(String.fromCharCode((input) & 0xff));
+}
+
+function push32LE(array, input) {
+ array.push(String.fromCharCode((input) & 0xff));
+ array.push(String.fromCharCode((input >> 8) & 0xff));
+ array.push(String.fromCharCode((input >> 16) & 0xff));
+ array.push(String.fromCharCode((input >> 24) & 0xff));
+}
+
+function push16LE(array, input) {
+ array.push(String.fromCharCode((input) & 0xff));
+ array.push(String.fromCharCode((input >> 8) & 0xff));
+}
+
+function buildWave(samples, sample_rate) {
+ const RIFF_MAGIC = 0x52494646;
+ const WAVE_MAGIC = 0x57415645;
+ const FRMT_MAGIC = 0x666d7420;
+ const DATA_MAGIC = 0x64617461;
+ const RIFF_SIZE = 44;
+
+ var header = [];
+ push32BE(header, RIFF_MAGIC);
+ push32LE(header, RIFF_SIZE + samples.length * 2);
+ push32BE(header, WAVE_MAGIC);
+ push32BE(header, FRMT_MAGIC);
+ push32LE(header, 16);
+ push16LE(header, 1);
+ push16LE(header, 1);
+ push32LE(header, sample_rate);
+ push32LE(header, sample_rate);
+ push16LE(header, 2);
+ push16LE(header, 16);
+ push32BE(header, DATA_MAGIC);
+ push32LE(header, samples.length * 2);
+ for (var i = 0; i < samples.length; ++i) {
+ push16LE(header, samples[i], 2);
+ }
+ return header;
+}
+
+const CC = Components.Constructor;
+const Timer = CC("@mozilla.org/timer;1", "nsITimer", "initWithCallback");
+const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+
+function poll(f) {
+ if (f()) {
+ return;
+ }
+ new Timer(function() { poll(f); }, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+function handleRequest(request, response)
+{
+ var cancel = parseQuery(request, "cancelkey");
+ if (cancel) {
+ setState(cancel[1], "cancelled");
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write("Cancel approved!");
+ return;
+ }
+
+ var samples = [];
+ for (var i = 0; i < 1000000; ++i) {
+ samples.push(0);
+ }
+ var bytes = buildWave(samples, 44100).join("");
+
+ var key = parseQuery(request, "key");
+ response.setHeader("Content-Type", "audio/x-wav");
+ response.setHeader("Content-Length", ""+bytes.length, false);
+
+ var out = new BinaryOutputStream(response.bodyOutputStream);
+
+ var start = 0, end = bytes.length - 1;
+ if (request.hasHeader("Range"))
+ {
+ var rangeMatch = request.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
+
+ if (rangeMatch[1] !== undefined)
+ start = parseInt(rangeMatch[1], 10);
+
+ if (rangeMatch[2] !== undefined)
+ end = parseInt(rangeMatch[2], 10);
+
+ // No start given, so the end is really the count of bytes from the
+ // end of the file.
+ if (start === undefined)
+ {
+ start = Math.max(0, bytes.length - end);
+ end = bytes.length - 1;
+ }
+
+ // start and end are inclusive
+ if (end === undefined || end >= bytes.length)
+ end = bytes.length - 1;
+
+ if (end < start)
+ {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ start = 0;
+ end = bytes.length - 1;
+ }
+ else
+ {
+ response.setStatusLine(request.httpVersion, 206, "Partial Content");
+ var contentRange = "bytes " + start + "-" + end + "/" + bytes.length;
+ response.setHeader("Content-Range", contentRange);
+ }
+ }
+
+ if (start > 0) {
+ // Send all requested data
+ out.write(bytes.slice(start, end + 1), end + 1 - start);
+ return;
+ }
+
+ // Write the first 1.2M of the Wave file. We know the cache size is set to
+ // 100K so this will fill the cache and and cause a "suspend" event on
+ // the loading element.
+ out.write(bytes, 1200000);
+
+ response.processAsync();
+ // Now wait for the message to cancel this response
+ poll(function() {
+ if (getState(key[1]) != "cancelled") {
+ return false;
+ }
+ response.finish();
+ return true;
+ });
+}
diff --git a/dom/media/test/chain.ogg b/dom/media/test/chain.ogg
new file mode 100644
index 0000000000..3535b280f4
--- /dev/null
+++ b/dom/media/test/chain.ogg
Binary files differ
diff --git a/dom/media/test/chain.ogg^headers^ b/dom/media/test/chain.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/chain.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/chain.ogv b/dom/media/test/chain.ogv
new file mode 100644
index 0000000000..3e684b64a5
--- /dev/null
+++ b/dom/media/test/chain.ogv
Binary files differ
diff --git a/dom/media/test/chain.ogv^headers^ b/dom/media/test/chain.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/chain.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/chain.opus b/dom/media/test/chain.opus
new file mode 100644
index 0000000000..9fa67f94c3
--- /dev/null
+++ b/dom/media/test/chain.opus
Binary files differ
diff --git a/dom/media/test/chain.opus^headers^ b/dom/media/test/chain.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/chain.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/chained-audio-video.ogg b/dom/media/test/chained-audio-video.ogg
new file mode 100644
index 0000000000..adda68bb47
--- /dev/null
+++ b/dom/media/test/chained-audio-video.ogg
Binary files differ
diff --git a/dom/media/test/chained-audio-video.ogg^headers^ b/dom/media/test/chained-audio-video.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/chained-audio-video.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/chained-video.ogv b/dom/media/test/chained-video.ogv
new file mode 100644
index 0000000000..a6288ef6c9
--- /dev/null
+++ b/dom/media/test/chained-video.ogv
Binary files differ
diff --git a/dom/media/test/chained-video.ogv^headers^ b/dom/media/test/chained-video.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/chained-video.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/chrome/chrome.ini b/dom/media/test/chrome/chrome.ini
new file mode 100644
index 0000000000..429c3a093c
--- /dev/null
+++ b/dom/media/test/chrome/chrome.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = media
+support-files =
+ ../gizmo.mp4
+
+[test_accumulated_play_time.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..509ebd5a07
--- /dev/null
+++ b/dom/media/test/chrome/test_accumulated_play_time.html
@@ -0,0 +1,355 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test Video Play Time Related Permenant 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_HIDDEN_PLAY_TIME_MS
+ * - VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE
+ * - VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE
+ */
+const histNames = ["VIDEO_PLAY_TIME_MS", "VIDEO_HIDDEN_PLAY_TIME_MS"];
+const keyedHistNames = ["VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE", "VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE"];
+
+add_task(async function setTestPref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.testing-only-events", true],
+ ["media.test.video-suspend", true],
+ ["media.suspend-bkgnd-video.enabled", true],
+ ["media.suspend-bkgnd-video.delay-ms", 0]]});
+});
+
+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, "totalPlayTime", 0);
+ assertValueEqualTo(videoChrome, "invisiblePlayTime", 0);
+ assertValueEqualTo(videoChrome, "videoDecodeSuspendedTime", 0);
+
+ info(`start accumulating play time after media starts`);
+ video.autoplay = true;
+ await Promise.all([
+ once(video, "playing"),
+ once(video, "moztotalplaytimestarted"),
+ ]);
+ assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime");
+
+ info(`should not accumulate time for paused video`);
+ video.pause();
+ await once(video, "moztotalplaytimepaused");
+ assertValueKeptUnchanged(videoChrome, "totalPlayTime");
+
+ 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");
+ assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+ await cleanUpMediaAndCheckTelemetry(video);
+});
+
+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");
+ 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");
+ 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") {
+ // If we set only `display` the video would still be hidden, so setting
+ // width and height to make it visible.
+ video.style.display = "unset";
+ video.style.width = "300px";
+ video.style.height = "150px";
+ } else {
+ ok(false, "undefined reason");
+ }
+ await once(video, "mozinvisibleplaytimepaused");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ await cleanUpMediaAndCheckTelemetry(video);
+ }
+});
+
+// Note that video suspended time is not always align with the invisible play
+// time even if `media.suspend-bkgnd-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";
+ 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");
+ assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime");
+
+ info(`make it invisible and force to suspend decoding`);
+ video.setVisible(false);
+ await once(video, "mozvideodecodesuspendedstarted");
+ assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+ assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime");
+ assertValueConstantlyIncreases(videoChrome, "videoDecodeSuspendedTime");
+
+ info(`make it visible and resume decoding`);
+ video.setVisible(true);
+ await Promise.all([
+ once(video, "mozinvisibleplaytimepaused"),
+ once(video, "mozvideodecodesuspendedpaused"),
+ ]);
+ assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+ 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");
+ assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+
+ 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, "totalPlayTime", -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");
+ assertValueConstantlyIncreases(videoChrome, "totalPlayTime");
+ 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);
+
+ info(`No result for playing an audio`);
+ const audio = document.createElement('audio');
+ audio.src = "gizmo.mp4";
+ document.body.appendChild(audio);
+ ok(await audio.play().then(_ => true, _ => false), "audio started playing");
+ audio.pause();
+ await assertNoReportedTelemetryResult(audio);
+});
+
+/**
+ * Following are helper functions
+ */
+async function cleanUpMediaAndCheckTelemetry(media, { shouldReport = true } = {}) {
+ media.src = "";
+ await checkReportedTelemetry(media, shouldReport);
+}
+
+async function assertNoReportedTelemetryResult(media) {
+ await checkReportedTelemetry(media, false);
+}
+
+async function checkReportedTelemetry(media, shouldReport) {
+ const reportResultPromise = once(media, "mozreportedtelemetry");
+ info(`check telemetry result, shouldReport=${shouldReport}`);
+ if (shouldReport) {
+ await reportResultPromise;
+ }
+ for (const name of histNames) {
+ 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 (shouldReport) {
+ 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`);
+ }
+ }
+ for (const name of keyedHistNames) {
+ 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 > 0) {
+ for (const [key, value] of items) {
+ const entriesNums = Object.entries(value.values).length;
+ ok(shouldReport && entriesNums > 0, `Reported ${key} for ${name}`);
+ }
+ } else {
+ ok(!shouldReport, `Reported nothing for ${name}`);
+ }
+ // 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;
+}
+
+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}`);
+}
+
+function assertValueConstantlyIncreases(mediaChrome, checkType) {
+ assertAttributeDefined(mediaChrome, checkType);
+ const valueSnapshot = mediaChrome[checkType];
+ ok(mediaChrome[checkType] > valueSnapshot, `${checkType} keeps increasing`);
+}
+
+function assertValueKeptUnchanged(mediaChrome, checkType) {
+ assertAttributeDefined(mediaChrome, checkType);
+ const valueSnapshot = mediaChrome[checkType];
+ ok(mediaChrome[checkType] == valueSnapshot, `${checkType} keeps unchanged`);
+}
+
+function assertAllProbeRelatedAttributesKeptUnchanged(video) {
+ const videoChrome = SpecialPowers.wrap(video);
+ assertValueKeptUnchanged(videoChrome, "totalPlayTime");
+ assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");
+ assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime");
+}
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/media/test/chromeHelper.js b/dom/media/test/chromeHelper.js
new file mode 100644
index 0000000000..ff83660fe2
--- /dev/null
+++ b/dom/media/test/chromeHelper.js
@@ -0,0 +1,23 @@
+/* -*- Mode: javascript; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+"use strict";
+
+// eslint-disable-next-line mozilla/use-services
+const dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(
+ Ci.nsIProperties
+);
+
+addMessageListener("media-test:getcwd", () => {
+ let cwd;
+ try {
+ cwd = dirSvc.get("CurWorkD", Ci.nsIFile).path;
+ } finally {
+ sendAsyncMessage("media-test:cwd", cwd);
+ }
+});
diff --git a/dom/media/test/cloneElementVisually_helpers.js b/dom/media/test/cloneElementVisually_helpers.js
new file mode 100644
index 0000000000..ab81aad9ed
--- /dev/null
+++ b/dom/media/test/cloneElementVisually_helpers.js
@@ -0,0 +1,232 @@
+const TEST_VIDEO_1 =
+ "http://mochi.test:8888/tests/dom/media/test/bipbop_225w_175kbps.mp4";
+const TEST_VIDEO_2 =
+ "http://mochi.test:8888/tests/dom/media/test/pixel_aspect_ratio.mp4";
+const LONG_VIDEO = "http://mochi.test:8888/tests/dom/media/test/gizmo.mp4";
+
+/**
+ * Ensure that the original <video> is prepped and ready to play
+ * before starting any other tests.
+ */
+async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.test.video-suspend", true],
+ ["media.suspend-bkgnd-video.enabled", true],
+ ["media.suspend-bkgnd-video.delay-ms", 500],
+ ["media.dormant-on-pause-timeout-ms", 0],
+ ["media.cloneElementVisually.testing", true],
+ ],
+ });
+
+ let originalVideo = document.getElementById("original");
+ await setVideoSrc(originalVideo, TEST_VIDEO_1);
+}
+
+/**
+ * Given a canvas, as well as a width and height of something to be
+ * blitted onto that canvas, makes any necessary adjustments to the
+ * canvas to work with the current display resolution.
+ *
+ * @param canvas (<canvas> element)
+ * The canvas to be adjusted.
+ * @param width (int)
+ * The width of the image to be blitted.
+ * @param height (int)
+ * The height of the image to be blitted.
+ * @return CanvasRenderingContext2D (SpecialPowers wrapper)
+ */
+function getWrappedScaledCanvasContext(canvas, width, height) {
+ let devRatio = window.devicePixelRatio || 1;
+ let ctx = SpecialPowers.wrap(canvas.getContext("2d"));
+ let backingRatio = ctx.webkitBackingStorePixelRatio || 1;
+
+ let ratio = 1;
+ ratio = devRatio / backingRatio;
+ canvas.width = ratio * width;
+ canvas.height = ratio * height;
+ canvas.style.width = width + "px";
+ canvas.style.height = height + "px";
+ ctx.scale(ratio, ratio);
+
+ return ctx;
+}
+
+/**
+ * Given a <video> element in the DOM, figures out its location, and captures
+ * a snapshot of what it's currently presenting.
+ *
+ * @param video (<video> element)
+ * @return ImageData (SpecialPowers wrapper)
+ */
+function captureFrameImageData(video) {
+ // Flush layout first, just in case the decoder has recently
+ // updated the dimensions of the video.
+ let rect = video.getBoundingClientRect();
+
+ let width = video.videoWidth;
+ let height = video.videoHeight;
+
+ let canvas = document.createElement("canvas");
+ let ctx = getWrappedScaledCanvasContext(canvas, width, height);
+ ctx.drawWindow(window, rect.left, rect.top, width, height, "rgb(0,0,0)");
+
+ return ctx.getImageData(0, 0, width, height);
+}
+
+/**
+ * Given two <video> elements, captures snapshots of what they're currently
+ * displaying, and asserts that they're identical.
+ *
+ * @param video1 (<video> element)
+ * A video element to compare against.
+ * @param video2 (<video> element)
+ * A video to compare with video1.
+ * @return Promise
+ * @resolves
+ * Resolves as true if the two videos match.
+ */
+async function assertVideosMatch(video1, video2) {
+ let video1Frame = captureFrameImageData(video1);
+ let video2Frame = captureFrameImageData(video2);
+
+ let left = document.getElementById("left");
+ let leftCtx = getWrappedScaledCanvasContext(
+ left,
+ video1Frame.width,
+ video1Frame.height
+ );
+ leftCtx.putImageData(video1Frame, 0, 0);
+
+ let right = document.getElementById("right");
+ let rightCtx = getWrappedScaledCanvasContext(
+ right,
+ video2Frame.width,
+ video2Frame.height
+ );
+ rightCtx.putImageData(video2Frame, 0, 0);
+
+ if (video1Frame.data.length != video2Frame.data.length) {
+ return false;
+ }
+
+ let leftDataURL = left.toDataURL();
+ let rightDataURL = right.toDataURL();
+
+ if (leftDataURL != rightDataURL) {
+ dump("Left frame: " + leftDataURL + "\n\n");
+ dump("Right frame: " + rightDataURL + "\n\n");
+
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Testing helper function that constructs a node clone of a video,
+ * injects it into the DOM, and then runs an async testing function.
+ * This also does the work of removing the clone before resolving.
+ *
+ * @param video (<video> element)
+ * The video to clone the node from.
+ * @param asyncFn (async function)
+ * A test function that will be passed the new clone as its
+ * only argument.
+ * @return Promise
+ * @resolves
+ * When the asyncFn resolves and the clone has been removed
+ * from the DOM.
+ */
+async function withNewClone(video, asyncFn) {
+ let clone = video.cloneNode();
+ clone.id = "clone";
+ clone.src = "";
+ let content = document.getElementById("content");
+ content.appendChild(clone);
+
+ try {
+ await asyncFn(clone);
+ } finally {
+ clone.remove();
+ }
+}
+
+/**
+ * Sets the src on a video and waits until its ready.
+ *
+ * @param video (<video> element)
+ * The video to set the src on.
+ * @param src (string)
+ * The URL to set as the source on a video.
+ * @return Promise
+ * @resolves
+ * When the video fires the "canplay" event.
+ */
+async function setVideoSrc(video, src) {
+ let promiseReady = waitForEventOnce(video, "canplay");
+ video.src = src;
+ await promiseReady;
+}
+
+/**
+ * Returns a Promise once target emits a particular event
+ * once.
+ *
+ * @param target (DOM node)
+ * The target to monitor for the event.
+ * @param event (string)
+ * The name of the event to wait for.
+ * @return Promise
+ * @resolves
+ * When the event fires, and resolves to the event.
+ */
+function waitForEventOnce(target, event) {
+ return new Promise(resolve => {
+ target.addEventListener(event, resolve, { once: true });
+ });
+}
+
+/**
+ * Polls the video debug data as a hacky way of knowing when
+ * when the decoders have shut down.
+ *
+ * @param video (<video> element)
+ * @return Promise
+ * @resolves
+ * When the decoder has shut down.
+ */
+async function waitForShutdownDecoder(video) {
+ await SimpleTest.promiseWaitForCondition(async () => {
+ let readerData = SpecialPowers.wrap(video).mozDebugReaderData;
+ return readerData.includes(": shutdown");
+ }, "Video decoder should eventually shut down.");
+}
+
+/**
+ * Ensures that both hiding and pausing the video causes the
+ * video to suspend and make dormant its decoders, respectively.
+ *
+ * @param video (<video element)
+ */
+async function ensureVideoSuspendable(video) {
+ video = SpecialPowers.wrap(video);
+
+ ok(!video.hasSuspendTaint(), "Should be suspendable");
+
+ // First, we'll simulate putting the video in the background by
+ // making it invisible.
+ let suspendPromise = waitForEventOnce(video, "mozentervideosuspend");
+ video.setVisible(false);
+ await suspendPromise;
+ ok(true, "Suspended after the video was made invisible.");
+ video.setVisible(true);
+
+ ok(!video.hasSuspendTaint(), "Should still be suspendable.");
+
+ // Next, we'll pause the video.
+ await video.pause();
+ await waitForShutdownDecoder(video);
+ ok(true, "Shutdown decoder after the video was paused.");
+ await video.play();
+}
diff --git a/dom/media/test/contentType.sjs b/dom/media/test/contentType.sjs
new file mode 100644
index 0000000000..a7a0caf669
--- /dev/null
+++ b/dom/media/test/contentType.sjs
@@ -0,0 +1,77 @@
+// Parse the query string, and give us the value for a certain key, or false if
+// it does not exist.
+function parseQuery(request, key) {
+ var params = request.queryString.split('?')[0].split('&');
+ for (var j = 0; j < params.length; ++j) {
+ var p = params[j];
+ if (p == key)
+ return true;
+ if (p.indexOf(key + "=") == 0)
+ return p.substring(key.length + 1);
+ if (p.indexOf("=") < 0 && key == "")
+ return p;
+ }
+ return false;
+}
+
+function handleRequest(request, response) {
+ try {
+ // Get the filename to send back.
+ var filename = parseQuery(request, "file");
+
+ const CC = Components.Constructor;
+ const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+ var paths = "tests/dom/media/test/" + filename;
+ dump(paths + '\n');
+ var split = paths.split("/");
+ for(var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+
+ // handle range requests
+ var partialstart = 0,
+ partialend = file.fileSize - 1;
+ if (request.hasHeader("Range")) {
+ var range = request.getHeader("Range");
+ var parts = range.replace(/bytes=/, "").split("-");
+ var partialstart = parts[0];
+ var partialend = parts[1];
+ if (!partialend.length) {
+ partialend = file.fileSize - 1;
+ }
+ response.setStatusLine(request.httpVersion, 206, "Partial Content");
+ var contentRange = "bytes " + partialstart + "-" + partialend + "/" + file.fileSize;
+ response.setHeader("Content-Range", contentRange);
+ }
+
+ fis.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, partialstart);
+ bis.setInputStream(fis);
+
+ var sendContentType = parseQuery(request, "nomime");
+ if (sendContentType == false) {
+ var contentType = parseQuery(request, "type");
+ if (contentType == false) {
+ // This should not happen.
+ dump("No type specified without having \'nomime\' in parameters.");
+ return;
+ }
+ response.setHeader("Content-Type", contentType, false);
+ }
+ response.setHeader("Content-Length", ""+bis.available(), false);
+
+ var bytes = bis.readBytes(bis.available());
+ response.write(bytes, bytes.length);
+ } catch (e) {
+ dump ("ERROR : " + e + "\n");
+ }
+}
diff --git a/dom/media/test/crashtests/0-timescale.html b/dom/media/test/crashtests/0-timescale.html
new file mode 100644
index 0000000000..db845096dd
--- /dev/null
+++ b/dom/media/test/crashtests/0-timescale.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<!-- This video file has a timescale of 0 in the mdhd atom -->
+<video src="0-timescale.mp4"
+ autoplay
+ onerror="document.documentElement.className=undefined"
+ onloadedmetadata="this.src=''; document.documentElement.className=undefined">
+<!-- Note we reset 'src' to release decoder resources and cubeb streams to prevent OOM or OpenCubeb() failures. -->
+</video>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/0-timescale.mp4 b/dom/media/test/crashtests/0-timescale.mp4
new file mode 100644
index 0000000000..32df5dc5a5
--- /dev/null
+++ b/dom/media/test/crashtests/0-timescale.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/1012609.html b/dom/media/test/crashtests/1012609.html
new file mode 100644
index 0000000000..1dad783a07
--- /dev/null
+++ b/dom/media/test/crashtests/1012609.html
@@ -0,0 +1,9 @@
+<script>
+try{var r0=new AudioContext();}catch(e){}
+try{var r32=r0.createOscillator();}catch(e){}
+try{var r58=r0.createPeriodicWave(new Float32Array(1997),new Float32Array(1997));}catch(e){}
+try{r32.start(0);}catch(e){}
+try{r32.setPeriodicWave(r58);}catch(e){}
+try{r32.frequency.value=-1;}catch(e){}
+</script>
+
diff --git a/dom/media/test/crashtests/1015662.html b/dom/media/test/crashtests/1015662.html
new file mode 100644
index 0000000000..20407c807d
--- /dev/null
+++ b/dom/media/test/crashtests/1015662.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+<video><track src="javascript:5"></track></video>
+</body>
diff --git a/dom/media/test/crashtests/1028458.html b/dom/media/test/crashtests/1028458.html
new file mode 100644
index 0000000000..bb01e3343a
--- /dev/null
+++ b/dom/media/test/crashtests/1028458.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+<audio id="testAudio" controls></audio>
+<script type="text/javascript">
+navigator.mozGetUserMedia({audio: true, fake: true}, function(stream) {
+ stream.getAudioTracks()[0].enabled = false;
+ var testAudio = document.getElementById('testAudio');
+ // Wait some time for good measure
+ var eventReceived = 0;
+ testAudio.addEventListener("timeupdate", function() {
+ if (++eventReceived == 3) {
+ document.querySelector("html").className = "";
+ }
+ })
+ testAudio.srcObject = stream;
+ testAudio.play();
+ }, function(err) {
+ // Don't go orange if we can't get an audio input stream,
+ // as this is not what we are trying to test and can happen on Windows.
+ document.querySelector("html").className = "";
+ });
+</script>
+
+</html>
diff --git a/dom/media/test/crashtests/1041466.html b/dom/media/test/crashtests/1041466.html
new file mode 100644
index 0000000000..0f064d186c
--- /dev/null
+++ b/dom/media/test/crashtests/1041466.html
@@ -0,0 +1,21 @@
+<html>
+<script>
+try{var Context1= new (window.webkitAudioContext || window.AudioContext)()}catch(e){}
+try{var Delay0=Context1.createDelay();}catch(e){}
+try{var ScriptProcessor0=Context1.createScriptProcessor(512,26,7);}catch(e){}
+try{var ChannelSplitter0=Context1.createChannelSplitter(91);}catch(e){}
+try{var Gain1=Context1.createGain();}catch(e){}
+try{var WaveShaper0=Context1.createWaveShaper();}catch(e){}
+try{var Analyser1=Context1.createAnalyser();}catch(e){}
+try{Gain1.connect(Delay0);}catch(e){}
+try{Analyser1.connect(BiquadFilter0);}catch(e){}
+try{Gain1.connect(Context1.destination);}catch(e){}
+try{WaveShaper0.connect(ScriptProcessor0);}catch(e){}
+try{ChannelSplitter0.connect(BiquadFilter1);}catch(e){}
+try{Delay0.connect(Gain1);}catch(e){}
+try{ScriptProcessor0.connect(Context1.destination);}catch(e){}
+try{WaveShaper0.connect(Gain1);}catch(e){}
+try{WaveShaper0.connect(ChannelSplitter0);}catch(e){}
+try{ScriptProcessor0.connect(WaveShaper0);}catch(e){}
+</script>
+</html>
diff --git a/dom/media/test/crashtests/1045650.html b/dom/media/test/crashtests/1045650.html
new file mode 100644
index 0000000000..32acd2ce75
--- /dev/null
+++ b/dom/media/test/crashtests/1045650.html
@@ -0,0 +1,18 @@
+<html><body><script>
+
+var r0=new AudioContext();
+
+var splitter=r0.createChannelSplitter();
+var delay=r0.createDelay();
+var scriptp=r0.createScriptProcessor();
+var cmerger=r0.createChannelMerger();
+var gain=r0.createGain();
+
+splitter.connect(delay,2);
+delay.connect(scriptp);
+scriptp.connect(cmerger);
+cmerger.connect(splitter);
+gain.connect(gain);
+gain.connect(cmerger);
+
+</script></body></html>
diff --git a/dom/media/test/crashtests/1080986.html b/dom/media/test/crashtests/1080986.html
new file mode 100644
index 0000000000..1de3075169
--- /dev/null
+++ b/dom/media/test/crashtests/1080986.html
@@ -0,0 +1,3 @@
+<html>
+<audio autoplay src="1080986.wav"></audio>
+</html>
diff --git a/dom/media/test/crashtests/1080986.wav b/dom/media/test/crashtests/1080986.wav
new file mode 100644
index 0000000000..b96c59b7ec
--- /dev/null
+++ b/dom/media/test/crashtests/1080986.wav
Binary files differ
diff --git a/dom/media/test/crashtests/1122218.html b/dom/media/test/crashtests/1122218.html
new file mode 100644
index 0000000000..984487dd21
--- /dev/null
+++ b/dom/media/test/crashtests/1122218.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<script>
+function boom() {
+ var r0=new AudioContext();
+
+ var cm=r0.createChannelMerger(20);
+
+ var o1=r0.createOscillator();
+ var o2=r0.createOscillator();
+
+ var pw=r0.createPeriodicWave(new Float32Array(4),new Float32Array(4));
+ o2.setPeriodicWave(pw);
+
+ o1.connect(cm);
+ cm.connect(o2.frequency);
+
+ o1.start();
+ o2.start(0.476);
+}
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/1127188.html b/dom/media/test/crashtests/1127188.html
new file mode 100644
index 0000000000..650f07dd47
--- /dev/null
+++ b/dom/media/test/crashtests/1127188.html
@@ -0,0 +1,3 @@
+<script>
+ (new AudioContext).close();
+</script>
diff --git a/dom/media/test/crashtests/1157994.html b/dom/media/test/crashtests/1157994.html
new file mode 100644
index 0000000000..2e3001302a
--- /dev/null
+++ b/dom/media/test/crashtests/1157994.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var ac = new AudioContext();
+ // first "suspended" -> "running" transition
+ ac.onstatechange = function() {
+ ac.onstatechange = null;
+ ac.suspend();
+ ac.close();
+ }
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
+
diff --git a/dom/media/test/crashtests/1158427.html b/dom/media/test/crashtests/1158427.html
new file mode 100644
index 0000000000..b544a942a1
--- /dev/null
+++ b/dom/media/test/crashtests/1158427.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ dump("before capture\n");
+ document.createElement("audio").mozCaptureStreamUntilEnded();
+ dump("before context\n");
+ new window.AudioContext();
+ dump("before gc\n");
+ SpecialPowers.forceCC();
+ dump("after gc\n");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
+
diff --git a/dom/media/test/crashtests/1180881.html b/dom/media/test/crashtests/1180881.html
new file mode 100644
index 0000000000..d5bf2f5642
--- /dev/null
+++ b/dom/media/test/crashtests/1180881.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<video src="1180881.webm" autoplay></video>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1180881.webm b/dom/media/test/crashtests/1180881.webm
new file mode 100644
index 0000000000..2fb2be7a7f
--- /dev/null
+++ b/dom/media/test/crashtests/1180881.webm
Binary files differ
diff --git a/dom/media/test/crashtests/1185176.html b/dom/media/test/crashtests/1185176.html
new file mode 100644
index 0000000000..d5e9b68a29
--- /dev/null
+++ b/dom/media/test/crashtests/1185176.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var ac = new window.AudioContext();
+ var oscillator = ac.createOscillator();
+ oscillator.start(0);
+ oscillator.stop(0.1);
+ setTimeout(function() {
+ oscillator.channelCount = 1;
+ oscillator.channelCountMode = "explicit";
+ oscillator.channelInterpretation = "speakers";
+ document.documentElement.removeAttribute("class");
+ }, 1000);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
+
diff --git a/dom/media/test/crashtests/1185192.html b/dom/media/test/crashtests/1185192.html
new file mode 100644
index 0000000000..46fa311aa2
--- /dev/null
+++ b/dom/media/test/crashtests/1185192.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ new Audio().mozCaptureStreamUntilEnded();
+ var ac = new window.AudioContext();
+ ac.resume();
+ ac.close();
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
+
diff --git a/dom/media/test/crashtests/1197935.html b/dom/media/test/crashtests/1197935.html
new file mode 100644
index 0000000000..dd8ad0382d
--- /dev/null
+++ b/dom/media/test/crashtests/1197935.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<video src="1197935.mp4" autoplay></video>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1197935.mp4 b/dom/media/test/crashtests/1197935.mp4
new file mode 100644
index 0000000000..f00de75627
--- /dev/null
+++ b/dom/media/test/crashtests/1197935.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/1223670.html b/dom/media/test/crashtests/1223670.html
new file mode 100644
index 0000000000..94cad43e23
--- /dev/null
+++ b/dom/media/test/crashtests/1223670.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+
+ var ac = new window.AudioContext("publicnotification");
+
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ var htmlAudio = new Audio();
+ var stream = htmlAudio.mozCaptureStreamUntilEnded();
+ ac.createMediaStreamSource(stream);
+ }, 0);
+}
+
+</script>
+</head>
+<body onload="boom();">
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1236639.html b/dom/media/test/crashtests/1236639.html
new file mode 100644
index 0000000000..5c4634a4d4
--- /dev/null
+++ b/dom/media/test/crashtests/1236639.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Bug 1236639: Crash on audio playback due to invalid XING headers</title>
+</head>
+<body>
+<audio autoplay src="1236639.mp3"></audio>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1236639.mp3 b/dom/media/test/crashtests/1236639.mp3
new file mode 100644
index 0000000000..8ef96e8cc7
--- /dev/null
+++ b/dom/media/test/crashtests/1236639.mp3
Binary files differ
diff --git a/dom/media/test/crashtests/1257700.html b/dom/media/test/crashtests/1257700.html
new file mode 100644
index 0000000000..5377b17da5
--- /dev/null
+++ b/dom/media/test/crashtests/1257700.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<video src="1257700.webm" autoplay></video>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1257700.webm b/dom/media/test/crashtests/1257700.webm
new file mode 100644
index 0000000000..63e53c8c0a
--- /dev/null
+++ b/dom/media/test/crashtests/1257700.webm
Binary files differ
diff --git a/dom/media/test/crashtests/1267263.html b/dom/media/test/crashtests/1267263.html
new file mode 100644
index 0000000000..a4d0e621cd
--- /dev/null
+++ b/dom/media/test/crashtests/1267263.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ vid.setMediaKeys(null);
+ vid.fastSeek(111);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+ <video id="vid" src="../../../../layout/reftests/webm-video/frames.webm"></video>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1270303.html b/dom/media/test/crashtests/1270303.html
new file mode 100644
index 0000000000..23608bb2b8
--- /dev/null
+++ b/dom/media/test/crashtests/1270303.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<video src="1270303.webm" autoplay></video>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1270303.webm b/dom/media/test/crashtests/1270303.webm
new file mode 100644
index 0000000000..c9b0003d6b
--- /dev/null
+++ b/dom/media/test/crashtests/1270303.webm
Binary files differ
diff --git a/dom/media/test/crashtests/1291702.html b/dom/media/test/crashtests/1291702.html
new file mode 100644
index 0000000000..facc52af7b
--- /dev/null
+++ b/dom/media/test/crashtests/1291702.html
@@ -0,0 +1,72 @@
+<script>
+Logger={}; Logger.JSError=function(e){};
+try { o0 = new Audio("media/audio/mono-uncompressed-8bit-44100hz.wav") } catch(e) { Logger.JSError(e); }
+try { o1 = o0.mozCaptureStreamUntilEnded() } catch(e) { Logger.JSError(e); }
+try { o2 = new window.AudioContext(); } catch(e) { Logger.JSError(e); }
+try { o3 = o2.createBufferSource(); } catch(e) { Logger.JSError(e); }
+try { o5 = o2.createChannelMerger(3); } catch(e) { Logger.JSError(e); }
+try { o3.start(0) } catch(e) { Logger.JSError(e); }
+try { o2.listener.setPosition(0.0417049336344248, 0.9932504594310304, 32) } catch(e) { Logger.JSError(e); }
+try { o5.disconnect(0) } catch(e) { Logger.JSError(e); }
+try { o0.mozGetMetadata() } catch(e) { Logger.JSError(e); }
+try { o3.channelCount = 1; } catch(e) { Logger.JSError(e); }
+try { o3.buffer = function anonymous() {
+var buffer = o2.createBuffer(1, 512, o2.sampleRate);for(var c=0;c<1;c++) {var data = buffer.getChannelData(c);for(var i=0;i<512;i++) {data[i] = i % 512}}return buffer;
+}(); } catch(e) { Logger.JSError(e); }
+try { o3.loop = false; } catch(e) { Logger.JSError(e); }
+try { o0.mozPreservesPitch = false; } catch(e) { Logger.JSError(e); }
+try { o2.destination.channelCount = 1; } catch(e) { Logger.JSError(e); }
+try { o3.loopStart = 8; } catch(e) { Logger.JSError(e); }
+try { o3.playbackRate.value = 0.46271130895770884; } catch(e) { Logger.JSError(e); }
+try { o2.listener.setVelocity(0.34781960219792546, 4, 2048) } catch(e) { Logger.JSError(e); }
+try { o5.connect(o5, 0, 0) } catch(e) { Logger.JSError(e); }
+try { o3.loopStart = -0.24696638021780326; } catch(e) { Logger.JSError(e); }
+try { o0.mozSetup(1, 44100) } catch(e) { Logger.JSError(e); }
+try { o3.buffer.copyToChannel(function anonymous() {
+var buffer=new Float32Array(256);for(var i=0;i<256;i++){buffer[i]=i / 256}return buffer;
+}(), 1, 2048, 64) } catch(e) { Logger.JSError(e); }
+try { o3.loop = false; } catch(e) { Logger.JSError(e); }
+try { setInterval(function anonymous() {
+try { o0.pause() } catch(e) { Logger.JSError(e); }
+}, 12.902067779658143) } catch(e) { Logger.JSError(e); }
+try { o2.listener.setPosition(256, 256, 16) } catch(e) { Logger.JSError(e); }
+try { o3.playbackRate.setValueCurveAtTime(function anonymous() {
+var buffer=new Float32Array(4);for(var i=0;i<4;i++){buffer[i]=i / 4}return buffer;
+}(), 2, 0.40792575814014437) } catch(e) { Logger.JSError(e); }
+try { o3.playbackRate.value = 0.4997270553139334; } catch(e) { Logger.JSError(e); }
+try { o0.loop = true; } catch(e) { Logger.JSError(e); }
+try { o3.loopStart = 4; } catch(e) { Logger.JSError(e); }
+try { setInterval(function anonymous() {
+try { o3.buffer = function anonymous() {
+var buffer = o2.createBuffer(1, 1, o2.sampleRate);for(var c=0;c<1;c++) {var data = buffer.getChannelData(c);for(var i=0;i<1;i++) {data[i] = Math.sin(Math.sin(i))}}return buffer;
+}() } catch(e) { Logger.JSError(e); }
+}, 54.32078602859342) } catch(e) { Logger.JSError(e); }
+try { o3.connect(o2.destination); } catch(e) { Logger.JSError(e); }
+try { o3.channelCountMode = 'max'; } catch(e) { Logger.JSError(e); }
+try { setInterval(function anonymous() {
+try { o3.channelCount = 1; } catch(e) { Logger.JSError(e); }
+}, 55.448587039802966) } catch(e) { Logger.JSError(e); }
+try { o3.playbackRate.cancelScheduledValues(0.7190983131805198) } catch(e) { Logger.JSError(e); }
+try { o3.playbackRate.cancelScheduledValues(16) } catch(e) { Logger.JSError(e); }
+try { o3.connect(o5, 0, 2) } catch(e) { Logger.JSError(e); }
+try { o3.loopEnd = 0.5864771678080962; } catch(e) { Logger.JSError(e); }
+try { o0.playbackRate = 0.2781783298771312; } catch(e) { Logger.JSError(e); }
+try { o0.loop = false; } catch(e) { Logger.JSError(e); }
+try { setInterval(function anonymous() {
+try { o5.disconnect(0) } catch(e) { Logger.JSError(e); }
+}, 29.75776777646425) } catch(e) { Logger.JSError(e); }
+try { o3.playbackRate.setValueCurveAtTime(function anonymous() {
+var buffer=new Float32Array(8);for(var i=0;i<8;i++){buffer[i]=8 % 8}return buffer;
+}(), 0.4972710112336257, 64) } catch(e) { Logger.JSError(e); }
+try { setInterval(function anonymous() {
+try { o0.controls = false; } catch(e) { Logger.JSError(e); }
+}, 22.550249570567694) } catch(e) { Logger.JSError(e); }
+try { o2.listener.setOrientation(0.6531494410366634, 64, 0.5120918081402992, -64, 0.32912433155093446, 256) } catch(e) { Logger.JSError(e); }
+try { o3.loop = true; } catch(e) { Logger.JSError(e); }
+try { o3.connect(o5, 0, 0) } catch(e) { Logger.JSError(e); }
+try { o3.buffer = function anonymous() {
+var buffer = o2.createBuffer(1, 2048, 48000);for(var c=0;c<1;c++) {var data = buffer.getChannelData(c);for(var i=0;i<2048;i++) {data[i] = Math.sin(Math.sin(2048 * 0.2519529190035427))}}return buffer;
+}(); } catch(e) { Logger.JSError(e); }
+try { o3.disconnect(0) } catch(e) { Logger.JSError(e); }
+</script>
+
diff --git a/dom/media/test/crashtests/1368490.html b/dom/media/test/crashtests/1368490.html
new file mode 100644
index 0000000000..8a2d9f9674
--- /dev/null
+++ b/dom/media/test/crashtests/1368490.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title> Bug 1368490 : Crash if media recorder source stream reduces number of channels. </title>
+</head>
+<meta charset="utf-8">
+<script type="text/javascript">
+
+function boom() {
+ let audioContext = new window.AudioContext();
+ let oscillator = audioContext.createOscillator();
+ let dst = audioContext.createMediaStreamDestination();
+ oscillator.channelCount = 4;
+ dst.channelCount = 4;
+ oscillator.connect(dst, 0, 0);
+ oscillator.start();
+ mediaRec = new MediaRecorder(dst.stream);
+
+ mediaRec.start(100);
+ setTimeout(() => {
+ dst.channelCount = 1;
+ setTimeout(() => {
+ mediaRec.stop();
+ document.documentElement.removeAttribute("class");
+ }, 100);
+ }, 100);
+}
+</script>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/1378826.html b/dom/media/test/crashtests/1378826.html
new file mode 100644
index 0000000000..e1913cd0f5
--- /dev/null
+++ b/dom/media/test/crashtests/1378826.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<title>Bug 1378826 : Removing last video track from recorder stream crashes.</title>
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<script type="text/javascript">
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+function boom() {
+ let canvas = document.getElementById("canvas");
+ let ctx = canvas.getContext('2d');
+ ctx.fillRect(10, 10, 100, 100);
+ let stream = canvas.captureStream();
+ let rec = new MediaRecorder(stream);
+ // At the time of fixing this bug onstop would fire, but this may change in
+ // future. As such defensively listen for onerror too to prevent this test
+ // timing out.
+ let stoppedPromise = new Promise(y => (rec.onstop = y,
+ rec.onerror = e => y));
+ rec.onstart = () => {
+ // Remove the video track from the stream we're recording
+ stream.removeTrack(stream.getTracks()[0]);
+ // Recorder should stop or error in response to the above
+ return stoppedPromise
+ .then(() => {
+ // Little wait to help get bad writes if they're going to happen
+ wait(100)
+ .then(() => {
+ // Didn't crash, finish
+ document.documentElement.removeAttribute("class");
+ });
+ });
+ };
+ rec.start();
+}
+
+window.onload = boom;
+
+</script>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1384248.html b/dom/media/test/crashtests/1384248.html
new file mode 100644
index 0000000000..5d9c60edda
--- /dev/null
+++ b/dom/media/test/crashtests/1384248.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <script>
+ try { o1 = document.createElement('audio') } catch(e) { }
+ try { o2 = document.implementation.createDocument('', '', null).adoptNode(o1); } catch(e) { };
+ try { o3 = new AudioContext('alarm') } catch(e) { }
+ try { o3.createMediaElementSource(o1) } catch(e) { }
+ </script>
+ </head>
+</html>
diff --git a/dom/media/test/crashtests/1388372.html b/dom/media/test/crashtests/1388372.html
new file mode 100644
index 0000000000..977ebddf53
--- /dev/null
+++ b/dom/media/test/crashtests/1388372.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+navigator.mediaDevices.getUserMedia({audio: {
+ echoCancellation: false,
+ noiseSuppression: false,
+ autoGainControl: false
+ }, fake: true
+}).then((stream) => {
+ document.documentElement.removeAttribute("class");
+})
+</script>
+</html>
diff --git a/dom/media/test/crashtests/1389304.html b/dom/media/test/crashtests/1389304.html
new file mode 100644
index 0000000000..df419c51d7
--- /dev/null
+++ b/dom/media/test/crashtests/1389304.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: Negative duration.</title>
+</head>
+<body>
+
+<video id="v" controls src="1389304.mp4">
+</video>
+<p id="msg"></p>
+
+<script type="text/javascript">
+
+function log(x) {
+ msg.innerHTML = x + "<br>";
+}
+
+v.play();
+v.onended = function() {
+ log("endded!");
+ let seekable = v.seekable;
+ for (let i = 0; i < seekable.length; ++i) {
+ let start = seekable.start(i);
+ let end = seekable.end(i);
+ log(`[${i}]: start=${start} end=${end}`);
+ }
+}
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1389304.mp4 b/dom/media/test/crashtests/1389304.mp4
new file mode 100644
index 0000000000..25cd746972
--- /dev/null
+++ b/dom/media/test/crashtests/1389304.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/1393272.webm b/dom/media/test/crashtests/1393272.webm
new file mode 100644
index 0000000000..1f1cade6dc
--- /dev/null
+++ b/dom/media/test/crashtests/1393272.webm
Binary files differ
diff --git a/dom/media/test/crashtests/1411322.html b/dom/media/test/crashtests/1411322.html
new file mode 100644
index 0000000000..772b68f0cc
--- /dev/null
+++ b/dom/media/test/crashtests/1411322.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1411322: Shutdown after getting memory reports from MediaRecorder</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<audio id="audio"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+let recorder = new MediaRecorder(audio.mozCaptureStream());
+recorder.start();
+SpecialPowers.getMemoryReports();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1450845.html b/dom/media/test/crashtests/1450845.html
new file mode 100644
index 0000000000..451d116e83
--- /dev/null
+++ b/dom/media/test/crashtests/1450845.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>Bug 1450845: Avoid seek to next frame when already seeking</title>
+ <script>
+ async function boom() {
+ let video = document.getElementById('video');
+
+ // Internally play causes a seek, make sure we don't crash during this
+ video.play();
+ try {
+ await document.getElementById('video').seekToNextFrame();
+ } catch (e) {
+ // We don't mind if the promise was rejected so long as we don't crash
+ }
+ // Didn't crash
+
+ // Stop playback and cause a seek to 0
+ video.pause();
+ video.currentTime = 0;
+ try {
+ await document.getElementById('video').seekToNextFrame();
+ } finally {
+ // Didn't crash
+ document.documentElement.removeAttribute("class");
+ }
+ }
+ window.addEventListener('load', boom)
+ </script>
+</head>
+<body>
+ <video id='video' src='data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBQoWBAhhTgGcBAAAAAAAB6BFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEwTbuMU6uEHFO7a1OsggHL7AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEUqTYCNTGF2ZjU3LjI5LjEwMVdBjUxhdmY1Ny4yOS4xMDFzpJBAb17Yv2oNAF1ZEESuco33RImIQFCAAAAAAAAWVK5rAQAAAAAAADyuAQAAAAAAADPXgQFzxYEBnIEAIrWcg3VuZIaFVl9WUDmDgQEj44OEAfygVeABAAAAAAAAB7CCAUC6gfAfQ7Z1AQAAAAAAAEfngQCjqYEAAICCSYNCABPwDvYAOCQcGFQAAFBh9jAAABML7AAATEnjdRwIJ+gAo5eBACEAhgBAkpwATEAABCasAABekcXgAB'>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1489160.html b/dom/media/test/crashtests/1489160.html
new file mode 100644
index 0000000000..c4f643700c
--- /dev/null
+++ b/dom/media/test/crashtests/1489160.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<script>
+audio = new AudioContext()
+audio.close()
+audio.close()
+</script>
+</head>
+</html>
+
diff --git a/dom/media/test/crashtests/1494073.html b/dom/media/test/crashtests/1494073.html
new file mode 100644
index 0000000000..41e7a36554
--- /dev/null
+++ b/dom/media/test/crashtests/1494073.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>Bug 1494073: Setting playback rate too high</title>
+ <script>
+ function finish() {
+ document.documentElement.removeAttribute("class");
+ }
+ var src="data:audio/wav;base64,UklGRqsTAABXQVZFZm10IBAAAAABAAMA8FUAANABAQADAAgAZGF0YYcTAAAUCnYAPm6YALYJSgA4IO8A9UuWALiiZQAYWc4AnMNoAKcc9gBmWLcAtL0NAAGU0QDbSgMA777tAEkfrQD14E4A2nb/AERxxAD5c+0AmplbADUo0AC14DQAXWgRAGsl4wB0oGIAkVlkAOWmTwCm/fEAZqErAEHpqgAMXjkA5AlGAMsOOwAUQAsABvRfAJyVUwDTKM8AtIMYAKh6lQDPkq0A9JtqAKiudAD2I3wAHTsPAOy36ACLtz8ATQELAJCXQgDpxksA4BzzABSR7gD7vMEAgo/6AKovngCZNLoA0A5UALhiFABNVRQAjXIuABmhFACj8/sAt+bQAP71dgCbdGoAXuf5AN5zVAACgggAlHiqAGTPswC0x5oAsgxDAEimygCYlMUAS3r2AKFS0gCZzCYAnXPZAEU8cgAf2gQADqF2ACCY+ABSVIEAW17CANxZWQCk0s0AIF1kALYn7gC9U3MAQqRFAHSUegC1UR8Au99GADrbaQCQQksAOQ6kALUmMgAC0YQAS9rjAMNL9QAQKkcAayg7AOjHXgBNZxcAzIUrAAgq+ACVJPMAV5s2APTVsgAsxX4AMP9cAInlqADcltMAv10FAAzJAgDiHQ4AbsW4ACDTowC897AAx1A8AGw3JwBe6msAf/jJAL1PbwCYYRkAscdyACi8+QCXCEwABWnAAOz4QQC0W60AcToLAH4/+wBion0AzzBXAJCuHgAsv+gAvDmdAA1LaAC04xgA/KU6AAurkwCbpwkAzkTeAA9+SABQqJoAPFHNAK96FgDxMNkAaPelAOG5YgCJsS8AtUp8ALhn8gD7sIYAxoR+AMWFHgA3aRIA00/wABwXJwCmQVQAxU7xAGnkjABvBaUAMz8UAL7qjQBokUEAx8eOAAd/ogDKKqcAsx8EAEpW/AADoDAA/D1+AKVqMwDnkmYA/fOAAGpcfwAlm3UA5+t8AM5tMwCdW3cA2UzjABHxWQBKIx4A7bf3AC+RkgARtBAA27gWADcb/ADjO68AS+g0AMutgwAzRawAtbgLAGjfnwCGINIAowqrAFsDmACIyhgAHfz+AByCiABBwdYASnWAANadowA7KFYADTx1ALG1WAAm4csAti5iACQMZgAeg7MAtrnPAMRmsAAsONAAKR6XAEu5TQCrqMYAd0hcABAU8gCLBFEA2C0MAJ7nZwAETAYA6i53APxR2gDRwBsAf392AH/csgAidosA3MaAAKgCrQBwid4A4xUfAHpqnACYWcQAHfIoAO67cQBlKVUAfR6pAEvQyQDFcWoA82ivAGsVXAA5gcQA8YCIAH9igAC6EzIAvrJEACTfdgBM21wAE6W+ABkqTwAL0+wA85kAALdRfQDbetYAT9RxALUxoQDScSoACRY8AIOzSwAUQS4A6yG1ALDLIwCf5vUAUvQTAK8r+QC5zVoAckBEABQfYwBFzoQAEUyXANlojQBGhjMA0WNjAI0qOQC7TIkAMbboAAp5igBw640ArVL3ADsU7QBoR/cAemWCAChwZQDuILoApIhRAOXapADr/IAApX7IAKvnxABvSHgAV1k1ABqwegD4s6IAT4m1AH/wPgBEnc0AwuIVAO5lzgDsHIAA8O8QAPZZeAC68JcAMzVvAAVKUwBGXv0AaOoBACuF6QD9uBQAwGpOAEiF1wCq/1kAXLJ4AGP4xgC8uzMAHgMmAF3WXwCssiUAya68AP7FUwAkZqYAqnfsAJkuUgC+08cANBobALJvTgAtK7oAaIIoALfiPgAW5qAAO1YyAM9+owCFIMMAbL82ABfR7wBJrDgASc3DAB8q7AAi/80AxleNALQ+3wARtIIA3D9uAFWiEwBtshQAoPHFAITU8gA1S4EAB8w6ANZxHgBhLBEAx70EADf8KgADzHIAQ4ONAEyVRgAXxdIAoS0hAHkIhwBUeFgAOP1YAM6KtgAJaJ0AuaUnADdSkQDNaVIAJkoIAFVlkAARLYwAc2jBAEHoeQBMnFIAQS8hAKUu2ABZafMAQRGbAElVMgAdHRgA9ImMAF4/NADLcZMAiaXbAB0rpQB1RTEAGqsMAGYPWgAB4nkAEk/2AOg7lQC5bI8A7x5cALOosgDQV7YA8mwTACI31wCjuIAAegycABfkWwBSqBQApdm/AIqmCgBEp3QANDopAHSFjgDOIKgABWL8AOppagBvyq0AXyT+AGvkZwDaq1MA8J0CAO0qCADs3gcAj/T/AFHxnwBnObIAAHr0AGNgYQCyAeoAerH5ANuJsQAZnSwAqPWRABmF5gBKI5IAkQpVAOQq0gCdZxQArSpTAFC4aACZFzgA2vozAKKrjwADOGYAxejeANjGkgAogccAnJtJAHa0iQDNsMQAvkg0AAxqhQABL88A0+rRAGam/AAZaiwAakBeAIkbewAlIpIAMQjsAHKsxAAVPRIAH5HeABuShQBm4ocAtOdpAIvy0QDAdbAAnRZ2AFNL6QCFcQYACIehACvVNgChty8Ah5A8AFc4iwBQwRwAv3PeAO+FjABRt0kAMfNNAMz2/QDaFEEAzLRaAJ5hlQBihAoAP/CjAPH4bAC/fxwAPapOAO3BCQDgg2gA7IyRABgp/AAO+QgAXrMFAHPiZwDc83IAmMzHALPPaAAlyrkAAk6uAD33pwBo0iAAC56eAOolMACgdmQAKdJeAA0UPgBtua8AIH66AJMoxwCYv7UA4JcKAN5c/wDgWDwAcWbUANvmLwD73kIAu/vCAFUiCQB5CJIAxOlxADKN8AB4H8EAh8gnAC8h8QCdBUgAX7cyADXejABFCWYAm7v7AOENigCsVMYAY8UeABEpaADYLmMAOWcMANk9hAANhM8AAQHaAMfmVwB2p2AA5MbYAOw44gCKrqQADZOHAM1RlwBhnvAAGkToADueDQDmbJ0AFYYTAGE6UgDjbEgAeJhCAOOGgAC0s6QANh6iAMu3kQAMMBQAl7YOANsIXwCkd9oA8mqMANf38wCxC7EAv7gjAKa0HwCgqV4ApgI/AIe1oQBaGE8AcxcBAObgYQBMMWwA1G0gAF+J9ABkAl8A2buoAIlKWAA/ZykAl6S0ABtBMQAkyOEAzFtyAHIMdgCCfMgAG9m4ABOX7wBfi/wAeg74AELcSAC7RgEAP1V0AD/FBwABALUAdGX+APlznwBqD1MAfTHIALA+HgDRP/QAaraOANCTrgBWaVUANUQjAFVulwBFlpkAsU3sAN86OACsleQAcFHoANanugC10xgAjD4TAA0g3gDcmx4A/eNDAIsxFAC6N5UAJ7fDAFRwZQD/+pAALV5yACfwHwBzltQA9CAuAD0nQQDbjogAKLenAHydkQDwD+IABvePAF9zWAAvQBwA4Ae3AJk+hQCB0ugAHXjgAOBbKQC4/ksAP4CtAAELZgDZ6kEAO2TIAPH6ZAC80CwAd9CIANrJ3QBbk7QAcT0AAE58HwDa9QMAOYjoABoHGQArjcwADSF4AFNcRgCnkjIAsG/cAC7iCwCh3qEAramDAAYS3gBO0jcA+2p2ADpCZwATvIsAa0FuABWrbwD+0jUA+PYEAPXTnQCnvXQAYtYoAKfT2wCPsNUA0bqlAG1+sQBnnqEA7hvxABQYewBmeO4Ackj6AJhuRADlYpAA9O9TAPSSeACW+ZAAa0nLAErmuAABaxcA+WJsAAJrYgDjOEkAwldNAC3lLwDy81wAoYZzADYpmwC509wAtFJdAIFkwgDiIEsAkyBvAMmSIgDb/sgAjTw2AJzz6QA+0Z8AwXaTAD/3lwB0Kg8A63iFAFe95wARFK8AFOY5AMAmfwB+Rj4AVleaAJNp0wBDKCUAh08qAEy8BwAX0CUAs+xgABUglAD3+TAA8jt9AM83uwBUVaEAvErrACRYKgB8iGkAb5fUAP71dwDEJIoAx7wfAAIduQALb3kAjm+cABeJCADKk10AdfclABorYAC0WOgA167pAGsx/QC8htoA+7auAATccACYfLsAszNLADwo8ACOsr0AdJmfABl+xgD7MQ4Ax2x6AMpK4gBV7FkA0g/QAFmDRQA01bkAFKhtAH9PEwCEoukAV2UiAC2B+wCuwRsA560TAK92ZAABWWcAovTVAPS/6AC+1C8AXCkSAM2NegBYrWQAmozZAE2CIgBAgY4A9YmBAKY0DgD3ZLAAOgXaAOOGSwDofs8AHlvXAN204wCjH2YAlRVHAA2cxwBDjN8AwumLAC5MEwAKK9YAyELrANJYkACbtlYAKfF2AGpW8ACRmC0AWHqZAB1CzwAGY9sA0N9+AOS4igAHJNkACm+yAA+sXwBKUUIAaipZAJuECwDrgV0AFMJpAAKMjgAtdggAJYIXAHDtegAgFIoASxGoALACPgA2KPEASXlaAGIX7QAjLsYA62u0ABm8HQB5dUsATxadAK6qQwD8VEkAd1ARAFOFzADC26QAfe2GANRbdwC7nK8AEwy4AB2XrgDAHIsAZSA6AEo5AgAM8GUAth6kAINTsACbAcEAxoCEAIZGPAD4GlwA3aV9AK7CDgCjga4ArWf0AHD1VQBd2TgA+eGcABMlUACIulIAOzg4AHP9zACB2VIAkyv0ALSeowCib4MA3fyrAMP7+gBVtSMAzpdQAMFI1AATJq8ALcZ1AGvp0gC/4oMAH3/0AFPf7wB5xTwAHeDZAALL8gDOEuQA+iN+AMs3AgCP3lIAs+6FABBzswBwlFkAxPuuAEg6pgCvKoIAU9EoAGAPigBqilYACnYGADJYkQCkihkA7FTKAISADQCODaoAYE04ALH1iwAUi7MAzURTAORgwQCts3wAc78IAFkTXwB+A5UAJiRXAJFADwAjr7MAdjo6ANNvLACcXagA09XGAOzjAwC0GJ0AV3hVAI1RkwAuDVgAtcfaABkNCQAljBgABQNSABEgDQB5kmwAIre6AFs6+wAnad4AT4iAAMSvawA7Fn8AlLC9AOscDABRf54ATp8wAMzgnwD29YQASW0XAEj2NwDw2U4A9NokAMTdYQBw+5UAgxCpAEwmpwCyDPwANRUYALHKyQB5mGsA1yJ8AJqjDQCaHDEAMtJrAEvLIQCDVUoAwGn3AETZ3wCLIwEAeJNzAMkj8AAUk2wAe+YVAOVR8wDCzpcAu4jkACv7dAA3vOgApBWtAO+tkAD8tu8Ac7noACsS7QAXfvMA7mjTACJc9QCea+gAx2jFALywtACyxdUA7r2wANGY2wD4JvYAg5VOANb0ggDtytkA8WGyAHMOtwA0vqAAwO5NAJSJDAAUaRUAoDvEAIfetwA9PqoA1jC3AA61XADx70MA7fFUAPQtRgBpiV0AP2XDAKNQvgCyW6UAJ9esADvTdQBm6f4AF1lDADTKvgCXNQQA6SFtAEuSVQBsXjUAmGbSANERuABhSpUAD2OJAGLtFABRMFUA0IDAAPc3TwCtToMASSGaAMDpVwCjm40At6keAG6chAAgkRAAz7GGAG8lHQA7qgwA8BucAMnEEQDEC+4A9g4MALMwnwAEFpMAqzf0AK8BoAA6L6sA0l0OACWT/gCVqp8AfumWABzjDQCtsygAHG0JAHebagCmi2EAztDRALUodwCq1+sAEcGyAGmDQQAVnXMAxMu8APC5wACRds0AzdJ4AEUdzwD/LLAAUdAQADW6IQBIUcUAXGxOAHdGZAARzd8AA7Y5ADE7cABegAcA0eDuAMBHYwA6skcARPfCAEYMxwB2/g8APBr5AFNusABVe1kAw4+OAOxwgwB7LiwAOltxAI0eAwALkk4AG9EZAESUzgD9fdQA4YOFAJVkHACkj4MAsWJxABeFJABHocwATeLwAG+OuQDdhzIAabHNAPogKwCTMKQAkanLAAsZLQDFv6YAXjQFAK1LdwDK4XsAcM7eAGCurgA7dq8AgD8dAHK87QDpwi8AwH3WAM/BcgBW7sAAyUeVAGw91wCHzM8Aa5fYABuR9QBhOk8Ay+OSAGYDiwAHCJAAYjifABxdQABItm0Ame2tAE/4XABIC6sA0aPxAAwi2ACitbQAtsWpAA+jPwB2lCgAqaATAMx3JADF2o4A6fhSABNVmwC6giQAYfj6AD0dkwBkmecAU5sTANWMgwD7D+cAp9QWAI6+QQAkZvcAkvGvAMaNOgAtsuEAF+LgADEdxgDAc9MA7+36APqe/wD41wQAIIDKAHsx1gATEKgAnWdaAFgB0wAyMAEAxTsPADTrDACBAuMALov9APMo1wB1wawAlCj7AP3EbAD1lDQA3xUvAAPdwABvPXQAwPeDAGxwkQAaAUEAbJQ2ACGgEQBn8XAAuu81AKaQhgAzfIQAkp8jADQ4TQBSSaIALjCgAJuAjQBP8AsAHL5SAKmH7ACyx5cAP/VjAG71zgCuL+IAV2JLAN+yFgA7QvgAG0HdAFO6AwCx+AwArlawAB83cABUNtcAe9LbACB2gAAZVTUA4DRYAEFV4wByz2QAWUgkALXS9ACCaEYAxjjiAI0qiAArChIAr7twAPvaOwDhyfkAEoYVAIM5KABarEsAol47AIBNdQDfMHQAN8GNADM6XwBbJFMAMAEbALILpgCxRIsA3mxlAGqpZACd9SkA9uwTABtT4ABUZDIA3cxyAGnKygA3zW4Age3TANrX0QBc2KEAAab2AEQksQDabWAAdmSYAOBdUwDsLkgAOa4fAPi45QAjr5gAZvEs"
+ var audio=new Audio(src)
+ audio.onended = finish;
+ audio.onerror = finish;
+ try{audio.play()}catch(e){}
+ try{audio.preload=1}catch(e){}
+ try{audio.muted=1}catch(e){}
+ try{audio.playbackRate=567312.2079031984}catch(e){}
+ </script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/1526044.html b/dom/media/test/crashtests/1526044.html
new file mode 100644
index 0000000000..1914dfc2f5
--- /dev/null
+++ b/dom/media/test/crashtests/1526044.html
@@ -0,0 +1,19 @@
+<script>
+ function start () {
+ try { o1 = new AudioContext({}) } catch (e) { }
+ try { o2 = new DynamicsCompressorNode(o1, {}) } catch (e) { }
+ try { o3 = o2.attack } catch (e) { }
+ try { o4 = new XMLHttpRequest({mozAnon: true, mozSystem: true}) } catch (e) { }
+ try { o4.open('GET', '', false) } catch (e) { }
+ try { o4.send() } catch (e) { }
+ for (let i = 0; i < 20; i++) {
+ try { o5 = o1.createGain() } catch (e) { }
+ try { o1.suspend().then(function () { }) } catch (e) { }
+ try { o5.connect(o3) } catch (e) { }
+ }
+ setTimeout('location.reload()', 200)
+ }
+
+ window.addEventListener('load', start)
+</script>
+
diff --git a/dom/media/test/crashtests/1538727.html b/dom/media/test/crashtests/1538727.html
new file mode 100644
index 0000000000..6371d54383
--- /dev/null
+++ b/dom/media/test/crashtests/1538727.html
@@ -0,0 +1,14 @@
+<script>
+const canvas = document.createElement('canvas')
+const context = canvas.getContext('2d', {})
+const xhr = new XMLHttpRequest({})
+const stream = canvas.captureStream(new Float64Array([1593177632.1689904])[0])
+recorder = new MediaRecorder(stream)
+recorder.start(100)
+xhr.open('G', '', false)
+xhr.send()
+recorder.stop()
+tracks = stream.getVideoTracks()
+track = tracks[(1051736525 % tracks.length)]
+stream.removeTrack(track)
+</script>
diff --git a/dom/media/test/crashtests/1545133.html b/dom/media/test/crashtests/1545133.html
new file mode 100644
index 0000000000..fb7039aae3
--- /dev/null
+++ b/dom/media/test/crashtests/1545133.html
@@ -0,0 +1,34 @@
+<html class="reftest-wait">
+<head>
+<script>
+const xhr = new XMLHttpRequest()
+
+async function boom () {
+ await new Promise(r => setTimeout(r, 100))
+
+ SpecialPowers.forceCC()
+ SpecialPowers.forceCC()
+ SpecialPowers.forceCC()
+
+ document.documentElement.removeAttribute("class")
+}
+
+function start () {
+ const context = new AudioContext({})
+ const filter = new BiquadFilterNode(context, {})
+ const destination = context.createMediaStreamDestination()
+ const processor = context.createScriptProcessor(8192, 8, 8)
+ processor.connect(filter.Q)
+ processor.disconnect()
+ xhr.open('G', '', false)
+ xhr.send()
+ context.createMediaStreamSource(destination.stream)
+ processor.connect(filter.Q)
+ context.close()
+ context.addEventListener('statechange', boom, true)
+}
+
+document.addEventListener('DOMContentLoaded', start)
+</script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/1547784.html b/dom/media/test/crashtests/1547784.html
new file mode 100644
index 0000000000..ee270491f1
--- /dev/null
+++ b/dom/media/test/crashtests/1547784.html
@@ -0,0 +1,33 @@
+<html class="reftest-wait">
+<head>
+ <script>
+ const doc = new Document();
+ const video = document.createElementNS('http://www.w3.org/1999/xhtml', 'video');
+ const source = new MediaSource();
+
+ navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{ '': [{ '': '' }] }])
+ .then(keySystemAccess => {
+ return keySystemAccess.createMediaKeys();
+ }).then(_ => {
+ video.src = URL.createObjectURL(source);
+ source.addEventListener('sourceopen', () => {
+ doc.adoptNode(video);
+ });
+ });
+
+ navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{ '': [{ '': '' }] }])
+ .then(keySystemAccess => {
+ return keySystemAccess.createMediaKeys();
+ }).then(mediaKeys => {
+ return video.setMediaKeys(mediaKeys);
+ }).then(() => {
+ video.src = URL.createObjectURL(source);
+ document.documentElement.removeAttribute("class");
+ }).catch(e => {
+ // Catch JS errors caused by raciness in the test. So long as we're
+ // not crashing we're good.
+ document.documentElement.removeAttribute("class");
+ });
+ </script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/1547899.html b/dom/media/test/crashtests/1547899.html
new file mode 100644
index 0000000000..4ffd90c565
--- /dev/null
+++ b/dom/media/test/crashtests/1547899.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <script>
+ function start () {
+ const video = document.getElementById('id_0')
+ const stream_1 = new MediaStream()
+ const stream_2 = video.mozCaptureStreamUntilEnded()
+ const track = stream_2.getTracks()[0]
+
+ video.srcObject = stream_1
+ stream_1.addTrack(track)
+ }
+
+ window.addEventListener('load', start)
+ </script>
+</head>
+<body>
+<video id="id_0" src="data:audio/mpeg;base64,ZQr/+1DEAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAsAAAnKABcXFxcXFxcXFy4uLi4uLi4uLkVFRUVFRUVFRV1dXV1dXV1dXXR0dHR0dHR0dIuLi4uLi4uLi6KioqKioqKiorq6urq6urq6utHR0dHR0dHR0ejo6Ojo6Ojo6P///////////wAAADlMQU1FMy45OHIBpQAAAAAuHQAAFEAkBElCAABAAAAJyuGI2MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//tQxAAABmQjXHSRAAH6IfB3OVIDAADLlmjIxWKxWTyIABgmGydGjRox4f8Tg+D4OAgc/BwEDn+UBD+qwH/+8Tny7///KO4IYDgcDgcDgcDgcCgQAAAKKBUFc/nnP30a5O0zyYzMNMkzKlPva2GXgMIBbwDQEAYDQNXGLT2EIgEhoFnQNDb4BAPD9wTFYGJBWJw/8AUWgMBQgIEg4KmAKGP/wRG0csCIWDaQxcFhYvv/8WYMUpEIkXhvnRef//kmsuLPH5OvSoAFKxrJIm26DP/7UsQ">
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1560215.html b/dom/media/test/crashtests/1560215.html
new file mode 100644
index 0000000000..1b76e218d7
--- /dev/null
+++ b/dom/media/test/crashtests/1560215.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<head>
+ <script>
+ async function start () {
+ const canvas = document.createElement('canvas')
+ const context = canvas.getContext('2d')
+ context.fillStyle = "red"
+ context.fillRect(0, 0, 1, 1)
+ const recorder = new MediaRecorder(
+ canvas.captureStream(), { videoBitsPerSecond: 16 })
+ recorder.start(100)
+ await new Promise(r => recorder.onstart = r)
+ recorder.pause()
+ document.documentElement.removeAttribute("class")
+ }
+
+ window.addEventListener('load', start)
+ </script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/1569645.html b/dom/media/test/crashtests/1569645.html
new file mode 100644
index 0000000000..b1f1247f26
--- /dev/null
+++ b/dom/media/test/crashtests/1569645.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <script>
+ function start () {
+ const canvas = document.getElementById("c")
+ canvas.getContext("2d")
+ const video = canvas.captureStream()
+ const ac = new AudioContext()
+ const dest = ac.createMediaStreamDestination()
+ const recorder = new MediaRecorder(
+ new MediaStream([...video.getTracks(), ...dest.stream.getTracks()]), {
+ 'mimeType': 'audio/ogg'
+ })
+ recorder.start()
+ }
+
+ window.addEventListener('load', start)
+ </script>
+</head>
+<body>
+<canvas id="c"></canvas>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1575271.html b/dom/media/test/crashtests/1575271.html
new file mode 100644
index 0000000000..ceda986553
--- /dev/null
+++ b/dom/media/test/crashtests/1575271.html
@@ -0,0 +1,25 @@
+<html class="reftest-wait">
+<head>
+<script>
+ async function start () {
+ const canvas = document.createElement("canvas")
+ const context = canvas.getContext("2d")
+ context.fillStyle = "blue"
+ context.fillRect(0, 0, canvas.width, canvas.height)
+ const stream = canvas.captureStream()
+ const track = stream.getTracks()[0]
+ const recorder = new MediaRecorder(stream)
+ recorder.start()
+ await new Promise(r => recorder.onstart = r)
+ recorder.pause()
+ stream.removeTrack(track)
+ recorder.resume()
+ await new Promise(r => recorder.onstop = r)
+ document.documentElement.removeAttribute("class")
+ }
+
+ window.addEventListener('load', start)
+</script>
+</head>
+</html>
+
diff --git a/dom/media/test/crashtests/1577184.html b/dom/media/test/crashtests/1577184.html
new file mode 100644
index 0000000000..a38c4a1265
--- /dev/null
+++ b/dom/media/test/crashtests/1577184.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<script>
+function start () {
+ const frame = document.createElementNS('http://www.w3.org/1999/xhtml', 'frame')
+ document.documentElement.appendChild(frame)
+ frame.contentWindow.eval('window.top.context=new AudioContext()')
+ document.documentElement.innerHTML = ''
+ context.createMediaElementSource(new Audio(''))
+}
+
+document.addEventListener('DOMContentLoaded', start)
+</script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/1587248.html b/dom/media/test/crashtests/1587248.html
new file mode 100644
index 0000000000..10c555e2b1
--- /dev/null
+++ b/dom/media/test/crashtests/1587248.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+<head>
+<script>
+function start () {
+ const audio = document.getElementById('id_4')
+ const doc = new Document()
+ const stream = new MediaStream()
+ const track = document.createElementNS('http://www.w3.org/1999/xhtml', 'track')
+ audio.srcObject = stream
+ track.textContent = '�'
+ setTimeout(() => {
+ track.replaceChild(audio, track.childNodes[0])
+ audio.play().then(function (arg4) { })
+ document.documentElement.removeAttribute("class")
+ }, 157)
+ doc.adoptNode(audio)
+}
+
+document.addEventListener('DOMContentLoaded', start)
+</script>
+</head>
+<audio class="" id="id_4" itemscope></audio>
+</html>
diff --git a/dom/media/test/crashtests/1594466.html b/dom/media/test/crashtests/1594466.html
new file mode 100644
index 0000000000..276c5fe3d1
--- /dev/null
+++ b/dom/media/test/crashtests/1594466.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<script>
+function start() {
+ const ac = new AudioContext();
+ const {stream: audioStream} = ac.createMediaStreamDestination();
+ const [audioTrack] = audioStream.getTracks();
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+ const [videoTrack] = canvas.captureStream().getTracks();
+
+ const rec = new MediaRecorder(new MediaStream([audioTrack, videoTrack]), {
+ mimeType: 'video/webm; codecs="vp8, opus"'
+ });
+ rec.start();
+}
+
+document.addEventListener('DOMContentLoaded', start)
+</script>
+</head>
+</html>
+
diff --git a/dom/media/test/crashtests/1601385.html b/dom/media/test/crashtests/1601385.html
new file mode 100644
index 0000000000..7192809076
--- /dev/null
+++ b/dom/media/test/crashtests/1601385.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script>
+const video = document.createElement("video");
+video.srcObject = new MediaStream();
+video.mozCaptureStreamUntilEnded();
+video.src = "sound.ogg";
+video.srcObject = undefined;
+</script>
+</head>
+</html>
+
diff --git a/dom/media/test/crashtests/1601422.html b/dom/media/test/crashtests/1601422.html
new file mode 100644
index 0000000000..9ff3c1a07a
--- /dev/null
+++ b/dom/media/test/crashtests/1601422.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<head>
+<script>
+(async _ => {
+ try {
+ const video = document.createElement('video')
+ video.preload = 'metadata'
+ video.src = 'sound.ogg'
+ await new Promise(r => video.onloadedmetadata = r)
+ const stream_1 = video.mozCaptureStreamUntilEnded()
+ video.src = ''
+ const stream_2 = video.mozCaptureStreamUntilEnded()
+ } finally {
+ document.documentElement.removeAttribute("class")
+ }
+})()
+</script>
+</head>
+</html>
+
diff --git a/dom/media/test/crashtests/1604941.html b/dom/media/test/crashtests/1604941.html
new file mode 100644
index 0000000000..5a9265ea1b
--- /dev/null
+++ b/dom/media/test/crashtests/1604941.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+async function boom()
+{
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["media.cubeb.force_null_context", true],
+ ]});
+ new Audio().mozCaptureStreamUntilEnded();
+ var ac = new window.AudioContext();
+ ac.resume();
+ ac.close();
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
+
diff --git a/dom/media/test/crashtests/1608286.html b/dom/media/test/crashtests/1608286.html
new file mode 100644
index 0000000000..9f52605be6
--- /dev/null
+++ b/dom/media/test/crashtests/1608286.html
@@ -0,0 +1,50 @@
+<html class="reftest-wait">
+<head>
+ <script>
+ function test() {
+ function checkResolve(value) {
+ // Let the test timeout and fail
+ throw new Error("This promise should not resolve");
+ }
+
+ function checkReject(reason) {
+ if (reason.message !== "Browsing context is no longer available") {
+ // Let the test timeout and fail
+ throw new Error("Unexpected rejected promise reason");
+ }
+ // Otherwise, successfully rejected a request not attached to a
+ // window without crashing
+ }
+
+ var i = document.querySelector("iframe");
+ var nav = i.contentWindow.navigator;
+ i.remove();
+
+ // First, check with valid args
+ nav.requestMediaKeySystemAccess(
+ "com.widevine.alpha",
+ [{
+ initDataTypes: ["webm"],
+ videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }]
+ }]
+ ).then(
+ checkResolve,
+ (reason) => {
+ checkReject(reason);
+
+ // Then, check with invalid args
+ nav.requestMediaKeySystemAccess("", []).then(
+ checkResolve,
+ (reason) => {
+ checkReject(reason);
+ document.documentElement.removeAttribute("class");
+ }
+ );
+ });
+ }
+ </script>
+</head>
+<body onload="test()">
+ <iframe></iframe>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/1673525.html b/dom/media/test/crashtests/1673525.html
new file mode 100644
index 0000000000..51de202999
--- /dev/null
+++ b/dom/media/test/crashtests/1673525.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const audio = document.getElementById("audio");
+ audio.autoplay = true;
+ audio.mozCaptureStream();
+ })
+ </script>
+</head>
+<body>
+ <!-- The data URL is crafted from a fuzzed mp3 so the base64 can be decoded back to an mp3 as needed -->
+ <audio id="audio" src="data:audio/mpeg;base64,//tQxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAALAAAJygAXFxcXFxcXFxcuLi4uLi4uLi5FRUVFRUVFRUVdXV1dXV1dXV10dHR0dHR0dHSLi4uLi4uLi4uioqKioqKioqK6urq6urq6urrR0dHR0dHR0dHo6Ojo6Ojo6Oj///////////8AAAA5TEFNRTMuOThyAaUAAAAALh0AABRAJARJQgAAQAAACcrhiNjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/7UMQAAAZkI1x0kQAB+iHwdzlSAwAAy5ZoyMVisVk8iAAYJhsnRo0aMeH/E4Pg+DgIHPwcBA5/lAQ/qsB//vE58u///yjuCGA4HA4HA4HA4HAoEAAACigVBXP55z99GuTtM8mMzDTJMypT72thl4DCAW8A0BAGA0DVxi09hCIBIaBZ0DQ2+AQDw/cExWBiQVicP/AFFoDAUICBIOCpgChj/8ERtHLAiFg2kMXBYWL7//FmDFKRCJF4b50Xn//5JrLizx+Tr0qABSsaySJtugz/+1LEBIALgQmLvLUAMXkorjzSjuWd/+FbPOduga79Dhef0OFYNhp+FEPDPRyRvOEERl/Uwbv9F/UuQN5yt9WPM5xwqlB4+hzmfmoYmcceWPD8s3WA8FhYEPkhR0SgMHw="></audio>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/459439-1.html b/dom/media/test/crashtests/459439-1.html
new file mode 100644
index 0000000000..7bb0131d51
--- /dev/null
+++ b/dom/media/test/crashtests/459439-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+var i = 0;
+function boom()
+{
+ var div = document.getElementById("div");
+ var audio = document.getElementById("audio");
+
+ audio.onload = null;
+
+ div.textContent = "FAIL";
+ audio.src += "";
+ div.textContent = "PASS?";
+
+ ++i;
+
+ setTimeout(done, 1);
+}
+
+function done()
+{
+ // Note we reset 'src' to release decoder resources and cubeb streams to
+ // prevent OOM or OpenCubeb() failures.
+ var audio = document.getElementById("audio");
+ audio.src = "";
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body>
+<audio id="audio" autoplay src="sound.ogg" oncanplaythrough="setTimeout(boom, 1);"></audio>
+<div id="div"></div>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/466607-1.html b/dom/media/test/crashtests/466607-1.html
new file mode 100644
index 0000000000..e154223efe
--- /dev/null
+++ b/dom/media/test/crashtests/466607-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.appendChild(document.createElementNS("bar", "audio"));
+ document.body.appendChild(document.createElementNS("bar", "video"));
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/466945-1.html b/dom/media/test/crashtests/466945-1.html
new file mode 100644
index 0000000000..ac1ba29e36
--- /dev/null
+++ b/dom/media/test/crashtests/466945-1.html
@@ -0,0 +1,25 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var s;
+
+function boom()
+{
+ s = document.createElement("span");
+ s.innerHTML = "<video src='data:text/html,' autoplay='autoplay'><\/video>";
+ document.body.appendChild(document.createElement("iframe"));
+ setTimeout(boom2, 0);
+}
+
+function boom2()
+{
+ s.innerHTML = "";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);"></body>
+</html>
diff --git a/dom/media/test/crashtests/468763-1.html b/dom/media/test/crashtests/468763-1.html
new file mode 100644
index 0000000000..d21e5bc388
--- /dev/null
+++ b/dom/media/test/crashtests/468763-1.html
@@ -0,0 +1 @@
+<html><head></head><body><video src="nosuchprotocol:"></video></body></html> \ No newline at end of file
diff --git a/dom/media/test/crashtests/474744-1.html b/dom/media/test/crashtests/474744-1.html
new file mode 100644
index 0000000000..8a7c70cf05
--- /dev/null
+++ b/dom/media/test/crashtests/474744-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.innerHTML += "<video src='aim:yaz'><\/video>";
+}
+
+</script>
+</head>
+<body onload="boom();">
+</body>
+</html>
diff --git a/dom/media/test/crashtests/481136-1.html b/dom/media/test/crashtests/481136-1.html
new file mode 100644
index 0000000000..bd264058d9
--- /dev/null
+++ b/dom/media/test/crashtests/481136-1.html
@@ -0,0 +1,3 @@
+<html>
+<div><object data='sound.ogg'></div>
+</html>
diff --git a/dom/media/test/crashtests/492286-1.xhtml b/dom/media/test/crashtests/492286-1.xhtml
new file mode 100644
index 0000000000..627ac38723
--- /dev/null
+++ b/dom/media/test/crashtests/492286-1.xhtml
@@ -0,0 +1 @@
+<source xmlns="http://www.w3.org/1999/xhtml"/> \ No newline at end of file
diff --git a/dom/media/test/crashtests/493915-1.html b/dom/media/test/crashtests/493915-1.html
new file mode 100644
index 0000000000..2a6ae9bd6c
--- /dev/null
+++ b/dom/media/test/crashtests/493915-1.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ s = document.createElement("span");
+ a = document.createElement("audio");
+ a['src'] = "javascript:4";
+ a['loopend'] = 3;
+ s.appendChild(a);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/495794-1.html b/dom/media/test/crashtests/495794-1.html
new file mode 100644
index 0000000000..2db69206a1
--- /dev/null
+++ b/dom/media/test/crashtests/495794-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <body>
+ <audio src="495794-1.ogg" autoplay onended="this.src=''; document.documentElement.className=undefined"></audio>
+ <!-- Note we reset 'src' to release decoder resources and cubeb streams to prevent OOM or OpenCubeb() failures. -->
+ </body>
+</html>
+
diff --git a/dom/media/test/crashtests/495794-1.ogg b/dom/media/test/crashtests/495794-1.ogg
new file mode 100644
index 0000000000..1c19a64061
--- /dev/null
+++ b/dom/media/test/crashtests/495794-1.ogg
Binary files differ
diff --git a/dom/media/test/crashtests/497734-1.xhtml b/dom/media/test/crashtests/497734-1.xhtml
new file mode 100644
index 0000000000..6df055da39
--- /dev/null
+++ b/dom/media/test/crashtests/497734-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+
+ div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ div.appendChild(document.getElementById("v"));
+ document.body.appendChild(div);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<video id="v"><source></source></video>
+
+</body>
+</html>
diff --git a/dom/media/test/crashtests/497734-2.html b/dom/media/test/crashtests/497734-2.html
new file mode 100644
index 0000000000..990ac4af46
--- /dev/null
+++ b/dom/media/test/crashtests/497734-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<script>
+
+function boom()
+{
+ audio1 = document.createElement("audio");
+ (audio1).appendChild(document.createElement("source"));
+ (audio1).appendChild(document.createElement("source"));
+ setTimeout(function() {
+ audio2 = document.createElement("audio");
+ audio2.appendChild(audio1);
+ }, 100);
+}
+
+</script>
+<body onload="boom();"></body>
diff --git a/dom/media/test/crashtests/576612-1.html b/dom/media/test/crashtests/576612-1.html
new file mode 100644
index 0000000000..04f993e780
--- /dev/null
+++ b/dom/media/test/crashtests/576612-1.html
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+
+ var v = document.getElementById("v");
+ v.src = "data:text/plain,_";
+ document.documentElement.appendChild(v);
+
+}
+</script>
+</head>
+<body onload="boom();"><video id="v" src="data:video/ogg;codecs=&quot;theora,vorbis&quot;,1"></video></body>
+</html>
diff --git a/dom/media/test/crashtests/691096-1.html b/dom/media/test/crashtests/691096-1.html
new file mode 100644
index 0000000000..3c3ebfc12a
--- /dev/null
+++ b/dom/media/test/crashtests/691096-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+var ITERATIONS = 200;
+
+function stoptest (evt)
+{
+ if (evt) {
+ // Note we reset 'src' to release decoder resources and cubeb streams to
+ // prevent OOM or OpenCubeb() failures.
+ evt.target.src = "";
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+function boom()
+{
+ for (var i = 0; i < ITERATIONS; ++i) {
+ a = document.createElementNS("http://www.w3.org/1999/xhtml", "audio");
+ a.addEventListener("loadedmetadata", stoptest);
+ a.setAttributeNS(null, "autoplay", "");
+ a.setAttributeNS(null, "src", "sound.ogg");
+ }
+ setTimeout(stoptest, 250);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/752784-1.html b/dom/media/test/crashtests/752784-1.html
new file mode 100644
index 0000000000..4644eeb89a
--- /dev/null
+++ b/dom/media/test/crashtests/752784-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ document.getElementById("a").mozCaptureStream();
+}
+</script>
+</head>
+
+<body onload="boom();">
+<audio id="a" src="sound.ogg">
+</body>
+</html>
diff --git a/dom/media/test/crashtests/789075-1.html b/dom/media/test/crashtests/789075-1.html
new file mode 100644
index 0000000000..6cd673fb75
--- /dev/null
+++ b/dom/media/test/crashtests/789075-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<video src="789075.webm" preload="metadata" id="v">
+</video>
+<!-- Note we reset 'src' to release decoder resources and cubeb streams to prevent OOM or OpenCubeb() failures. -->
+<script type="application/javascript">
+ var video = document.getElementById("v");
+ video.onloadeddata = function () {
+ video.play();
+ };
+ video.onended = function () {
+ video.src="";
+ document.documentElement.className = undefined;
+ };
+</script>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/789075.webm b/dom/media/test/crashtests/789075.webm
new file mode 100644
index 0000000000..602e53fca2
--- /dev/null
+++ b/dom/media/test/crashtests/789075.webm
Binary files differ
diff --git a/dom/media/test/crashtests/795892-1.html b/dom/media/test/crashtests/795892-1.html
new file mode 100644
index 0000000000..d73cea7f2e
--- /dev/null
+++ b/dom/media/test/crashtests/795892-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ var a = document.getElementById("a");
+ a.play();
+ a.onplaying = function () {
+ a.onplaying = null;
+ // Note we reset 'src' to release decoder resources and cubeb streams to
+ // prevent OOM or OpenCubeb() failures.
+ a.src = "";
+ document.documentElement.className = "";
+ }
+}
+</script>
+</head>
+
+<body>
+<video id="a" src="cors.webm" crossorigin preload="metadata" onloadedmetadata="boom();">
+</body>
+</html>
diff --git a/dom/media/test/crashtests/844563.html b/dom/media/test/crashtests/844563.html
new file mode 100644
index 0000000000..7f9365511f
--- /dev/null
+++ b/dom/media/test/crashtests/844563.html
@@ -0,0 +1,5 @@
+<script>
+var a = document.createElementNS("http://www.w3.org/1999/xhtml", "audio");
+a.mozPreservesPitch = a;
+</script>
+
diff --git a/dom/media/test/crashtests/846612.html b/dom/media/test/crashtests/846612.html
new file mode 100644
index 0000000000..070c375381
--- /dev/null
+++ b/dom/media/test/crashtests/846612.html
@@ -0,0 +1,8 @@
+<script>
+document.addEventListener("DOMContentLoaded", function() {
+ var a = document.querySelector("audio");
+ a.playbackRate = 2;
+ a.play();
+});
+</script>
+<audio src="495794-1.ogg"></audio>
diff --git a/dom/media/test/crashtests/852838.html b/dom/media/test/crashtests/852838.html
new file mode 100644
index 0000000000..0bea29351b
--- /dev/null
+++ b/dom/media/test/crashtests/852838.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+ var o0 = new window.AudioContext();
+ var o1 = o0.createBuffer(536870912, 1, 8192);
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/media/test/crashtests/865004.html b/dom/media/test/crashtests/865004.html
new file mode 100644
index 0000000000..4da39071c4
--- /dev/null
+++ b/dom/media/test/crashtests/865004.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var ac = new AudioContext();
+ for (var j = 0; j < 200; ++j) {
+ ac.createScriptProcessor(undefined);
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/865537-1.html b/dom/media/test/crashtests/865537-1.html
new file mode 100644
index 0000000000..4065eb3608
--- /dev/null
+++ b/dom/media/test/crashtests/865537-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="doTest()">
+<base href="../unknown">
+<div id="test3"></div>
+<video id="test4"><source src="white.webm"></video>
+<script>
+function doTest() {
+ test3.appendChild(test4);
+}
+</script>
+</body>
+</html>
diff --git a/dom/media/test/crashtests/865550.html b/dom/media/test/crashtests/865550.html
new file mode 100644
index 0000000000..b8626e8d67
--- /dev/null
+++ b/dom/media/test/crashtests/865550.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+ <head>
+ <script>
+ var i = 0;
+ var interval;
+ function crash() {
+ var o0 = new AudioContext();
+ o1 = o0.createBufferSource();
+ ++i;
+ if (i == 2000) {
+ document.documentElement.removeAttribute("class");
+ clearInterval(interval);
+ }
+ }
+ function start() {
+ interval = setInterval("crash()", 0)
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ </body>
+</html>
diff --git a/dom/media/test/crashtests/868504.html b/dom/media/test/crashtests/868504.html
new file mode 100644
index 0000000000..94090c1c09
--- /dev/null
+++ b/dom/media/test/crashtests/868504.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ new AudioContext().createBufferSource().playbackRate.linearRampToValueAtTime(0, -1);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/874869.html b/dom/media/test/crashtests/874869.html
new file mode 100644
index 0000000000..1fe3dbd2fc
--- /dev/null
+++ b/dom/media/test/crashtests/874869.html
@@ -0,0 +1,15 @@
+<script>
+var Context0= new AudioContext()
+var Panner0=Context0.createPanner();
+var BufferSource3=Context0.createBufferSource();
+Panner0.channelCount=0;
+BufferSource3.connect(Panner0);
+BufferSource3.buffer=function(){
+ var length=1;
+ var Buffer=Context0.createBuffer(1,length,'44200');
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = 255;};
+ return Buffer;
+}();
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/874915.html b/dom/media/test/crashtests/874915.html
new file mode 100644
index 0000000000..59218b3da3
--- /dev/null
+++ b/dom/media/test/crashtests/874915.html
@@ -0,0 +1,24 @@
+<script>
+var Context0= new AudioContext()
+var BufferSource6=Context0.createBufferSource();
+
+setInterval(function(){
+BufferSource6.buffer=function(){
+ var length=11283;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(624))};
+ return Buffer;
+}();
+},0)
+
+BufferSource6.start(0.15831333969254047,0.23571860056836158,0.529235512483865);
+
+BufferSource6.buffer=function(){
+ var length=48517;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(365))};
+ return Buffer;
+}();
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/874934.html b/dom/media/test/crashtests/874934.html
new file mode 100644
index 0000000000..350ce28101
--- /dev/null
+++ b/dom/media/test/crashtests/874934.html
@@ -0,0 +1,23 @@
+<script>
+var Context0= new AudioContext()
+var BufferSource0=Context0.createBufferSource();
+BufferSource0.start(0.01932738965842873,0.33345631847623736,0.3893404237460345);
+BufferSource0.buffer=function(){
+ var length=35887;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(0.39765272522345185))};
+ return Buffer;
+}();
+
+BufferSource0.buffer=function(){
+ var length=15952;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(85))};
+ return Buffer;
+}();
+
+BufferSource0.playbackRate.value=20.401213286832185;
+
+</script>
diff --git a/dom/media/test/crashtests/874952.html b/dom/media/test/crashtests/874952.html
new file mode 100644
index 0000000000..a9a398fdb2
--- /dev/null
+++ b/dom/media/test/crashtests/874952.html
@@ -0,0 +1,11 @@
+<script>
+var Context0= new AudioContext()
+var ChannelSplitter0=Context0.createChannelSplitter();
+var BiquadFilter0=Context0.createBiquadFilter();
+var WaveShaper0=Context0.createWaveShaper();
+
+ChannelSplitter0.connect(BiquadFilter0,3,0);
+ChannelSplitter0.connect(WaveShaper0);
+BiquadFilter0.disconnect();
+WaveShaper0.connect(ChannelSplitter0);
+</script>
diff --git a/dom/media/test/crashtests/875144.html b/dom/media/test/crashtests/875144.html
new file mode 100644
index 0000000000..bf5d0d0861
--- /dev/null
+++ b/dom/media/test/crashtests/875144.html
@@ -0,0 +1,81 @@
+<script>
+Logger = {}
+Logger.error = function(e) {}
+Logger.comment = function(e) {}
+
+try { o0 = document.createElement('audio'); } catch(e) { Logger.error(Logger.comment(e)); }
+try { (document.body || document.documentElement).appendChild(o0); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o1 = new AudioContext(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o2 = o1.createGain(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3 = o1.createBufferSource(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.buffer = function() { o4 = o1.createBuffer(1, 3, 52970);
+o5 = o4.getChannelData(0);
+for(var i=0; i<3; ++i) {
+o5[i] = Math.sin(i * 63);
+}
+return o4;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.buffer = function() { o6 = o1.createBuffer(1, 15, 41218);
+o7 = o6.getChannelData(0);
+for(var i=0; i<15; ++i) {
+o7[i] = Math.sin(i * 0);
+}
+return o6;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.buffer = function() { o8 = o1.createBuffer(1, 0, 49074);
+o9 = o8.getChannelData(0);
+for(var i=0; i<0; ++i) {
+o9[i] = Math.sin(i * 0);
+}
+return o8;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.buffer = function() { o10 = o1.createBuffer(1, 31, 86527);
+o11 = o10.getChannelData(0);
+for(var i=0; i<31; ++i) {
+o11[i] = Math.sin(i * 127);
+}
+return o10;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.stop(-1) } catch(e) { Logger.error(Logger.comment(e)); }
+/* [Exception... "An attempt was made to use an object that is not, or is no longer, usable" code: "11" nsresult: "0x8053000b (InvalidStateError)" location: "file:///Users/cdiehl/dev/projects/peach/Peach/Utilities/JS/undefined.js Line: 602"] */
+try { o3.channelCountMode = 'explicit'; } catch(e) { Logger.error(Logger.comment(e)); }
+try { o12 = o1.createBiquadFilter(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.buffer = function() { o13 = o1.createBuffer(1, 63, 28347);
+o14 = o13.getChannelData(0);
+for(var i=0; i<63; ++i) {
+o14[i] = Math.sin(i * 15);
+}
+return o13;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o12.channelCount = 1; } catch(e) { Logger.error(Logger.comment(e)); }
+try { o12.connect(GainNode, 65536, 0) } catch(e) { Logger.error(Logger.comment(e)); }
+/* TypeError: Value does not implement interface AudioNode. */
+try { o3.buffer = function() { o15 = o1.createBuffer(1, 1, 72540);
+o16 = o15.getChannelData(0);
+for(var i=0; i<1; ++i) {
+o16[i] = Math.sin(i * 7);
+}
+return o15;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o12.getFrequencyResponse(new Float32Array(7), new Float32Array(127), new Float32Array(7)) } catch(e) { Logger.error(Logger.comment(e)); }
+try { o12.getFrequencyResponse(new Float32Array(15), new Float32Array(127), new Float32Array(7)) } catch(e) { Logger.error(Logger.comment(e)); }
+try { o17 = document.createElement('audio'); } catch(e) { Logger.error(Logger.comment(e)); }
+try { (document.body || document.documentElement).appendChild(o0); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.buffer = function() { o18 = o1.createBuffer(1, 7, 91261);
+o19 = o18.getChannelData(0);
+for(var i=0; i<7; ++i) {
+o19[i] = Math.sin(i * 7);
+}
+return o18;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o12.getFrequencyResponse(new Float32Array(31), new Float32Array(31), new Float32Array(127)) } catch(e) { Logger.error(Logger.comment(e)); }
+try { o20 = o1.createChannelSplitter(1, 2, 4, 16, 32); } catch(e) { Logger.error(Logger.comment(e)); }
+try { o12.channelCountMode = 'explicit'; } catch(e) { Logger.error(Logger.comment(e)); }
+try { o3.buffer = function() { o21 = o1.createBuffer(1, 0, 14451);
+o22 = o21.getChannelData(0);
+for(var i=0; i<0; ++i) {
+o22[i] = Math.sin(i * 63);
+}
+return o21;
+}(); } catch(e) { Logger.error(Logger.comment(e)); }
+</script>
diff --git a/dom/media/test/crashtests/875596.html b/dom/media/test/crashtests/875596.html
new file mode 100644
index 0000000000..7bac09dab7
--- /dev/null
+++ b/dom/media/test/crashtests/875596.html
@@ -0,0 +1,12 @@
+<script>
+try { o1 = new AudioContext(); } catch(e) { }
+try { o6 = o1.createGain(); } catch(e) { }
+try { o8 = o1.createBufferSource(); } catch(e) { }
+try { o6.gain.setValueCurveAtTime(new Float32Array(7), 0, 0) } catch(e) { }
+try { o8.connect(o6, 0, 0) } catch(e) { }
+try { o8.buffer = function() {
+ o19 = o1.createBuffer(1, 1, 76309);
+ for(var i=0; i<1; ++i) { }
+ return o19;
+}(); } catch(e) { }
+</script>
diff --git a/dom/media/test/crashtests/875911.html b/dom/media/test/crashtests/875911.html
new file mode 100644
index 0000000000..fbc52642b4
--- /dev/null
+++ b/dom/media/test/crashtests/875911.html
@@ -0,0 +1,3 @@
+<script>
+ new OfflineAudioContext(1, 10, 48000);
+</script>
diff --git a/dom/media/test/crashtests/876024-1.html b/dom/media/test/crashtests/876024-1.html
new file mode 100644
index 0000000000..5502d8e42d
--- /dev/null
+++ b/dom/media/test/crashtests/876024-1.html
@@ -0,0 +1,5 @@
+<script>
+o1 = new window.AudioContext(2, 8, 44100);
+o4 = o1.createBiquadFilter();
+o4.detune.setValueAtTime(0.0843, 1.7976931348623157e+308);
+</script>
diff --git a/dom/media/test/crashtests/876024-2.html b/dom/media/test/crashtests/876024-2.html
new file mode 100644
index 0000000000..4b9beb7453
--- /dev/null
+++ b/dom/media/test/crashtests/876024-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var bufferSource = new AudioContext().createScriptProcessor().context.createBufferSource();
+ bufferSource.start(0, 0, 0);
+ bufferSource.stop(562949953421313);
+}
+
+</script></head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/876118.html b/dom/media/test/crashtests/876118.html
new file mode 100644
index 0000000000..bc0630350a
--- /dev/null
+++ b/dom/media/test/crashtests/876118.html
@@ -0,0 +1,16 @@
+<script>
+var Context0= new AudioContext()
+var BufferSource7=Context0.createBufferSource();
+
+BufferSource7.connect(Context0.destination);
+BufferSource7.buffer=function(){
+ var length=7;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(-1))};
+ return Buffer;
+}();
+
+
+Context0.destination.channelCountMode="explicit";
+Context0.destination.channelCount=519910189000000;
+</script>
diff --git a/dom/media/test/crashtests/876207.html b/dom/media/test/crashtests/876207.html
new file mode 100644
index 0000000000..7bfcb096b2
--- /dev/null
+++ b/dom/media/test/crashtests/876207.html
@@ -0,0 +1,30 @@
+<script>
+try { o1 = new window.OfflineAudioContext(1, 10, 44100); } catch(e) { }
+try { o2 = o1.createChannelSplitter(1); } catch(e) { }
+try { o3 = o1.createAnalyser(); } catch(e) { }
+try { o4 = o1.createWaveShaper(); } catch(e) { }
+try { o5 = o1.createChannelSplitter(6); } catch(e) { }
+try { o6 = o1.createAnalyser(); } catch(e) { }
+try { o7 = o1.createBufferSource(); } catch(e) { }
+try { o7.connect(o1.destination); } catch(e) { }
+try { o7.buffer = function() {
+o8 = o1.createBuffer(1, 3, o1.sampleRate);
+o9 = o8.getChannelData(0);
+for(var i = 0; i < 3; ++i) {
+o9[i] = Math.sin(i * 127);
+}
+return o8;
+}()
+; } catch(e) { }
+try { o7.playbackRate.cancelScheduledValues(0) } catch(e) { }
+try { o7.channelInterpretation = 'speakers'; } catch(e) { }
+try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { }
+try { o5.connect(o1.destination, 1, 1) } catch(e) { }
+try { o1.listener.setOrientation(0, 1073741824, 1073741824, 4194304, 1, 0) } catch(e) { }
+try { o4.curve = new Float32Array(127); } catch(e) { }
+try { o6.getByteFrequencyData(new Uint8Array(12)) } catch(e) { }
+try { o6.disconnect() } catch(e) { }
+try { o1.destination.channelCount = 33554432; } catch(e) { }
+try { o7.playbackRate.setTargetAtTime(0, 8388608, 1) } catch(e) { }
+try { o6.getByteTimeDomainData(new Uint8Array(12)) } catch(e) { }
+</script>
diff --git a/dom/media/test/crashtests/876215.html b/dom/media/test/crashtests/876215.html
new file mode 100644
index 0000000000..07135e3628
--- /dev/null
+++ b/dom/media/test/crashtests/876215.html
@@ -0,0 +1,14 @@
+<script>
+try { o1 = new window.OfflineAudioContext(1, 10, 44100); } catch(e) { }
+try { o6 = o1.createScriptProcessor(0, 0, 2); } catch(e) { }
+try { o8 = o1.createBufferSource(); } catch(e) { }
+try { o8.connect(o1.destination); } catch(e) { }
+try { o8.connect(o6) } catch(e) { }
+try { o3.disconnect() } catch(e) { }
+try { o8.buffer = function() {
+o21 = o1.createBuffer(1, 3, o1.sampleRate);
+o22 = o21.getChannelData(0);
+return o21;
+}()
+; } catch(e) { }
+</script>
diff --git a/dom/media/test/crashtests/876249.html b/dom/media/test/crashtests/876249.html
new file mode 100644
index 0000000000..6f72b40bc1
--- /dev/null
+++ b/dom/media/test/crashtests/876249.html
@@ -0,0 +1,27 @@
+<script>
+var Context0= new AudioContext()
+var BufferSource0=Context0.createBufferSource();
+var BiquadFilter0=Context0.createBiquadFilter();
+BufferSource0.start(0,0.6167310480959713,0.7142638498917222);
+BiquadFilter0.connect(Context0.destination);
+BufferSource0.buffer=function(){
+ var length=86333;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(-1))};
+ return Buffer;
+}();
+
+BufferSource0.connect(BiquadFilter0);
+
+BufferSource0.buffer=function(){
+ var length=21989;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(0))};
+ return Buffer;
+}();
+
+BufferSource0.stop(0.04184641852043569);
+
+</script>
diff --git a/dom/media/test/crashtests/876252.html b/dom/media/test/crashtests/876252.html
new file mode 100644
index 0000000000..cb9cb63ed3
--- /dev/null
+++ b/dom/media/test/crashtests/876252.html
@@ -0,0 +1,23 @@
+<script>
+var Context0= new AudioContext()
+var BufferSource4=Context0.createBufferSource();
+BufferSource4.start(0.05386466556228697,0.397192713804543,0.48810303467325866);
+
+BufferSource4.buffer=function(){
+ var length=109076;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(370))};
+ return Buffer;
+}();
+
+BufferSource4.buffer=function(){
+ var length=19339;
+ var Buffer=Context0.createBuffer(1,length,53362);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(-0.16235407581552863))};
+ return Buffer;
+}();
+
+BufferSource4.stop(0.46482366253621876);
+</script>
diff --git a/dom/media/test/crashtests/876834.html b/dom/media/test/crashtests/876834.html
new file mode 100644
index 0000000000..f4ca6ee558
--- /dev/null
+++ b/dom/media/test/crashtests/876834.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+new OfflineAudioContext(0, 0, 3229622);
+</script>
diff --git a/dom/media/test/crashtests/877527.html b/dom/media/test/crashtests/877527.html
new file mode 100644
index 0000000000..c639d501b7
--- /dev/null
+++ b/dom/media/test/crashtests/877527.html
@@ -0,0 +1,37 @@
+<script>
+try { o1 = new window.AudioContext(2, 5, 44100); } catch(e) { }
+try { o2 = o1.createChannelMerger(1); } catch(e) { }
+try { o3 = o1.createDelay(10); } catch(e) { }
+try { o4 = o1.createBuffer(2, 2048, 8000); } catch(e) { }
+try { o5 = o1.createPanner(); } catch(e) { }
+try { o6 = o1.createBufferSource(); } catch(e) { }
+try { o7 = (function() {
+var buf = o1.createBuffer(1, 50000, o1.sampleRate);
+for(var j=0; j<1; ++j) {
+for(var i=0; i<50000; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (9.8));}
+}
+return buf;
+})(); } catch(e) { }
+try { o6.buffer = o7; } catch(e) { }
+try { o6.connect(o5); } catch(e) { }
+try { o5.connect(o1.destination); } catch(e) { }
+try { o1.listener.speedOfSound = 0.0000019073486328125; } catch(e) { }
+try { o6.loop = true; } catch(e) { }
+try { o8 = (function() {
+var buf = o1.createBuffer(2, 1000, o1.sampleRate);
+for(var j=0; j<2; ++j) {
+for(var i=0; i<1000; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (1));}
+}
+return buf;
+})(); } catch(e) { }
+try { o6.buffer = o7; } catch(e) { }
+try { o6.connect(o5); } catch(e) { }
+try { o5.connect(o1.destination); } catch(e) { }
+try { o6.loopEnd = 1.4901161193847656e-8; } catch(e) { }
+try { o6.connect(o1.destination); } catch(e) { }
+try { o6.buffer = o8; } catch(e) { }
+try { o5.setPosition(0.36, o1.destination.context.destination.channelCountMode, o1.destination.context.destination.channelInterpretation) } catch(e) { }
+try { o2.channelCountMode = 'explicit'; } catch(e) { }
+try { o1.listener.speedOfSound = 4; } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+</script>
diff --git a/dom/media/test/crashtests/877820.html b/dom/media/test/crashtests/877820.html
new file mode 100644
index 0000000000..6d65c1e9d9
--- /dev/null
+++ b/dom/media/test/crashtests/877820.html
@@ -0,0 +1,4 @@
+<script>
+o1 = new window.OfflineAudioContext(2, 5, 0.39);
+window.location.reload();
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/878014.html b/dom/media/test/crashtests/878014.html
new file mode 100644
index 0000000000..4a0ca6dce2
--- /dev/null
+++ b/dom/media/test/crashtests/878014.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+try { o2 = new window.AudioContext(1, 15, 44100); } catch(e) { }
+try { o5 = o2.createDelay(0.1); } catch(e) { }
+try { o6 = o2.createPanner(); } catch(e) { }
+try { o8 = o2.createBufferSource(); } catch(e) { }
+try { o9 = (function() {
+var buf = o2.createBuffer(1, 12134, o2.sampleRate);
+for(var j=0; j<1; ++j) {
+for(var i=0; i<12134; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (-127));}
+}
+return buf;
+})(); } catch(e) { }
+try { o8.buffer = o9; } catch(e) { }
+try { o8.connect(o6); } catch(e) { }
+try { o6.connect(o5, 0, 0) } catch(e) { }
+try { o8.buffer = (function() {
+var buf = o2.createBuffer(1, 5409, o2.sampleRate);
+for(var j=0; j<1; ++j) {
+for(var i=0; i<5409; ++i) { buf.getChannelData(j)[i] = Math.sin(i * (-1));}
+}
+return buf;
+})(); } catch(e) { }
+setTimeout(function() { try { o5.delayTime.setValueAtTime(0.78, 121560862.56366833); } catch(e) { } setTimeout(done, 0); },128)
+try { o5.delayTime.value = 0.4283; } catch(e) { }
+
+function done() {
+ document.documentElement.removeAttribute("class");
+}
+</script>
diff --git a/dom/media/test/crashtests/878328.html b/dom/media/test/crashtests/878328.html
new file mode 100644
index 0000000000..ec7b39871b
--- /dev/null
+++ b/dom/media/test/crashtests/878328.html
@@ -0,0 +1,5 @@
+<script>
+o1 = new window.AudioContext(2, 7, 44100);
+o5 = o1.createDelay(1);
+o5.delayTime.setValueCurveAtTime(new Float32Array(3), 1.7976931348623157e+308, 0.64);
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/878407.html b/dom/media/test/crashtests/878407.html
new file mode 100644
index 0000000000..ae2918dc85
--- /dev/null
+++ b/dom/media/test/crashtests/878407.html
@@ -0,0 +1,11 @@
+<script>
+o1 = new window.AudioContext();
+o4 = o1.createDelay(0.4468);
+o6 = o1.createPanner();
+o7 = (function() {var buf = o1.createBuffer(1, 1000, o1.sampleRate);for(var j=0; j<1; ++j) {for(var i=0; i<1000; ++i) {buf.getChannelData(j)[i] = Math.sin(i * (-15));}}return buf;})();
+o8 = o1.createBufferSource();
+o8.buffer = o7;
+o8.connect(o6);
+o6.connect(o4)
+o4.delayTime.setValueAtTime(0.6019893103749466289898, 0.26)
+</script>
diff --git a/dom/media/test/crashtests/878478.html b/dom/media/test/crashtests/878478.html
new file mode 100644
index 0000000000..89a47ddb55
--- /dev/null
+++ b/dom/media/test/crashtests/878478.html
@@ -0,0 +1,30 @@
+<script>
+var Context0= new AudioContext()
+var BufferSource0=Context0.createBufferSource();
+
+BufferSource0.loop=true;
+
+BufferSource0.buffer=function(){
+ var channels=3;
+ var length=97252;
+ var Buffer=Context0.createBuffer(channels,length,Context0.sampleRate);
+ for(var y=0;y<channels;y++){
+ var bufferData= Buffer.getChannelData(y);
+ for (var i = 0; i < length; ++i) { bufferData[i] = 1;}
+ };
+ return Buffer;
+}();
+
+BufferSource0.playbackRate.cancelScheduledValues(0.4);
+
+BufferSource0.loopEnd=5e-324;
+
+BufferSource0.buffer=function(){
+ var length=26590;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = 1};
+ return Buffer;
+}();
+
+</script>
diff --git a/dom/media/test/crashtests/880129.html b/dom/media/test/crashtests/880129.html
new file mode 100644
index 0000000000..775e9d80ba
--- /dev/null
+++ b/dom/media/test/crashtests/880129.html
@@ -0,0 +1,9 @@
+<script>
+try { o1 = new window.AudioContext(3, 2, 44100); } catch(e) { }
+try { o6 = o1.createBufferSource(); } catch(e) { }
+try { o15 = o1.createAnalyser(); } catch(e) { }
+try { o15.fftSize = 32; } catch(e) { }
+try { o6.connect(o15,0,0) } catch(e) { }
+try { o27 = o1.createPanner(); } catch(e) { }
+try { o6.buffer = function(){var buffer = o1.createBuffer(2, 1148, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1148; i++) {buffer.getChannelData(c)[i] = 0;}}return buffer;}() } catch(e) { }
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/880202.html b/dom/media/test/crashtests/880202.html
new file mode 100644
index 0000000000..dc0fade9db
--- /dev/null
+++ b/dom/media/test/crashtests/880202.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var Context0= new window.OfflineAudioContext(15,12119,44100)
+var BufferSource1=Context0.createBufferSource();
+var Gain0=Context0.createGain();
+var Panner0=Context0.createPanner();
+Context0.listener.setPosition(29,135,158);
+
+BufferSource1.connect(Gain0);
+
+BufferSource1.buffer=function(){
+ var channels=3;
+ var length=53325;
+ var Buffer=Context0.createBuffer(channels,length,Context0.sampleRate);
+ for(var y=0;y<channels;y++){
+ var bufferData= Buffer.getChannelData(y);
+ for (var i = 0; i < length; ++i) {
+ bufferData[i] = i*(270);
+ }
+ };
+ return Buffer;
+}();
+
+Gain0.connect(Panner0);
+Panner0.panningModel="equalpower";
+
+
+setTimeout(function(){
+document.documentElement.removeAttribute("class");
+},500)
+
+</script>
diff --git a/dom/media/test/crashtests/880342-1.html b/dom/media/test/crashtests/880342-1.html
new file mode 100644
index 0000000000..7d1aa0c3e3
--- /dev/null
+++ b/dom/media/test/crashtests/880342-1.html
@@ -0,0 +1,208 @@
+<script>
+try { o1 = new window.AudioContext(2, 10, 1019159); } catch(e) { }
+try { o2 = o1.createBufferSource(); } catch(e) { }
+try { o3 = o1.createConvolver(); } catch(e) { }
+try { o4 = o1.createChannelMerger(2); } catch(e) { }
+try { o5 = o1.createAnalyser(); } catch(e) { }
+try { o6 = o1.createPanner(); } catch(e) { }
+try { o7 = o1.createDynamicsCompressor(); } catch(e) { }
+try { o8 = o1.createWaveShaper(); } catch(e) { }
+try { o9 = o1.createChannelSplitter(6); } catch(e) { }
+try { o10 = o1.createScriptProcessor(8192, 2, 3); } catch(e) { }
+try { o1.listener.setPosition(-144115188075855870, 0, -5e-324) } catch(e) { }
+try { o3.buffer = function(){var buffer = o1.createBuffer(2, 741, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<741; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}(); } catch(e) { }
+try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { }
+try { o3.connect(o5,0,0) } catch(e) { }
+try { o9.channelCountMode = 'max'; } catch(e) { }
+try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { }
+try { o7.channelInterpretation = 'speakers'; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(2, 1887, 63346);for(var c=0; c<2; c++) {for(var i=0; i<1887; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.playbackRate.exponentialRampToValueAtTime(3.819464020334534, 32) } catch(e) { }
+try { o5.connect(o9,2,0) } catch(e) { }
+try { o9.channelCountMode = 'explicit'; } catch(e) { }
+try { o1.listener.speedOfSound = 72057594037927940; } catch(e) { }
+try { o1.destination.channelCountMode = 'explicit'; } catch(e) { }
+try { o7.threshold.value = 0.0646435404346891590021684237399313133209944; } catch(e) { }
+try { o7.connect(o10,0,0) } catch(e) { }
+try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { }
+try { o3.connect(o4,0,0) } catch(e) { }
+try { o7.release.value = 23.030355486273447; } catch(e) { }
+try { o9.connect(o9,4,0) } catch(e) { }
+try { o6.channelCountMode = 'max'; } catch(e) { }
+try { o7.threshold.value = 0.6395867363641939418172910336579661816358566284179687500; } catch(e) { }
+try { o2.connect(o9,3,0) } catch(e) { }
+try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { }
+try { o7.connect(o10,0,0) } catch(e) { }
+try { o5.connect(o5,0,0) } catch(e) { }
+try { o2.playbackRate.value = 5e-324; } catch(e) { }
+try { o4.channelInterpretation = 'discrete'; } catch(e) { }
+try { o2.playbackRate.value = 1.2211628339508704; } catch(e) { }
+try { o2.channelCountMode = 'clamped-max'; } catch(e) { }
+try { o1.listener.dopplerFactor = 32; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(22, 1811, 21177);for(var c=0; c<22; c++) {for(var i=0; i<1811; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o5.channelCountMode = 'max'; } catch(e) { }
+try { o1.listener.speedOfSound = 1024; } catch(e) { }
+try { o2.playbackRate.value = 9.999999999999998e-91; } catch(e) { }
+try { o1.listener.speedOfSound = 16; } catch(e) { }
+try { o8.curve = new Float32Array(256); } catch(e) { }
+try { o1.listener.setVelocity(0, 128, 4) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(23, 483, o1.sampleRate);for(var c=0; c<23; c++) {for(var i=0; i<483; i++) {buffer.getChannelData(c)[i] = 0.026;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 945, 43803);for(var c=0; c<3; c++) {for(var i=0; i<945; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(19, 997, 23781);for(var c=0; c<19; c++) {for(var i=0; i<997; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.listener.speedOfSound = -8; } catch(e) { }
+try { o3.channelCountMode = 'explicit'; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 1740, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<1740; i++) {buffer.getChannelData(c)[i] = 8;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1057, 30602);for(var c=0; c<1; c++) {for(var i=0; i<1057; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 78, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<78; i++) {buffer.getChannelData(c)[i] = 10;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.listener.setPosition(9.999999999999995e-275, 0.0001, 2) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(1, 863, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<863; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o8.channelCount = 3; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(2, 1821, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1821; i++) {buffer.getChannelData(c)[i] = 0.3655;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o6.connect(o6,0,0) } catch(e) { }
+try { o1.listener.setOrientation(128, -0.479527496, 128, 1, 1, -16) } catch(e) { }
+try { o4.connect(o8,0,0) } catch(e) { }
+try { o7.reduction.exponentialRampToValueAtTime(1.7976931348623157e+308, 2048) } catch(e) { }
+try { o1.listener.dopplerFactor = 0.5754; } catch(e) { }
+try { o7.release.value = 0; } catch(e) { }
+try { o5.getFloatFrequencyData(new Float32Array(2048)) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(2, 120, 42419);for(var c=0; c<2; c++) {for(var i=0; i<120; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o3.buffer = function(){var buffer = o1.createBuffer(1, 1620, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<1620; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}(); } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(1, 823, 77239);for(var c=0; c<1; c++) {for(var i=0; i<823; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { }
+try { o1.listener.setPosition(2048, 0.000522899209, -4503599627370495) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 84028, 75483);for(var c=0; c<3; c++) {for(var i=0; i<84028; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.playbackRate.setTargetAtTime(5e-324, 9.999999999999998e-104, 0) } catch(e) { }
+try { o7.attack.value = 9.999999999999994e-236; } catch(e) { }
+try { o1.listener.dopplerFactor = 1024; } catch(e) { }
+try { o3.connect(o7,0,0) } catch(e) { }
+try { o2.playbackRate.linearRampToValueAtTime(0, 0) } catch(e) { }
+try { o4.connect(o10,0,0) } catch(e) { }
+try { o1.listener.setVelocity(0.682, 32, 1e-76) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 201, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<201; i++) {buffer.getChannelData(c)[i] = 4;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.connect(o6,0,0) } catch(e) { }
+try { o5.getByteFrequencyData(new Uint8Array(32)) } catch(e) { }
+try { o3.connect(o6,0,0) } catch(e) { }
+try { o7.channelCount = 1; } catch(e) { }
+try { o2.playbackRate.setValueCurveAtTime(new Float32Array(512), 8, 1) } catch(e) { }
+try { o3.connect(o4,0,1) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 1996, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<1996; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.listener.dopplerFactor = 2; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(2, 983, 60517);for(var c=0; c<2; c++) {for(var i=0; i<983; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o6.coneInnerAngle = 360; } catch(e) { }
+try { o8.curve = new Float32Array(512); } catch(e) { }
+try { o1.listener.setPosition(32, 16, 9.999999999999995e-280) } catch(e) { }
+try { o6.connect(o6,0,0) } catch(e) { }
+try { o1.listener.setPosition(2097151, 512, 5e-324) } catch(e) { }
+try { o7.channelCountMode = 'clamped-max'; } catch(e) { }
+try { o1.destination.channelCountMode = 'max'; } catch(e) { }
+try { o1.listener.setPosition(0, 1000000, 64) } catch(e) { }
+try { o4.connect(o5,0,0) } catch(e) { }
+try { o9.connect(o4,0,1) } catch(e) { }
+try { o7.attack.setValueCurveAtTime(new Float32Array(8), 256, 2048) } catch(e) { }
+try { o9.channelCountMode = 'explicit'; } catch(e) { }
+try { o1.listener.dopplerFactor = 5e-324; } catch(e) { }
+try { o4.connect(o10,0,0) } catch(e) { }
+try { o7.ratio.cancelScheduledValues(1) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1590, 9733);for(var c=0; c<1; c++) {for(var i=0; i<1590; i++) {buffer.getChannelData(c)[i] = 2;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.destination.channelCount = 2; } catch(e) { }
+try { o1.destination.channelInterpretation = 'speakers'; } catch(e) { }
+try { o5.channelCountMode = 'max'; } catch(e) { }
+try { o9.channelCountMode = 'clamped-max'; } catch(e) { }
+try { o7.attack.setTargetAtTime(0.11499421161482907549622467513472656719386577606201171875000, 1, 1.7976931348623157e+308) } catch(e) { }
+try { o2.playbackRate.value = 1; } catch(e) { }
+try { o3.normalize = false; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(2, 1866, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1866; i++) {buffer.getChannelData(c)[i] = 0.174908422905697580329587026426452212035655975341797;}}return buffer;}(); } catch(e) { }
+try { o2.playbackRate.value = 1.0; } catch(e) { }
+try { o7.threshold.value = 100; } catch(e) { }
+try { o1.listener.setVelocity(961.4441892370145, 9.999999999999999e-157, 1) } catch(e) { }
+try { o5.getByteFrequencyData(new Uint8Array(2)) } catch(e) { }
+try { o6.connect(o10,0,0) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 1176, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<1176; i++) {buffer.getChannelData(c)[i] = 9.753993292300242;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.playbackRate.linearRampToValueAtTime(0.8687, 32) } catch(e) { }
+try { o5.channelCountMode = 'explicit'; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(13, 703, 12723);for(var c=0; c<13; c++) {for(var i=0; i<703; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.loopEnd = 1.7976931348623157e+308; } catch(e) { }
+try { o3.connect(o7,0,0) } catch(e) { }
+try { o1.destination.channelCountMode = 'clamped-max'; } catch(e) { }
+try { o1.destination.channelInterpretation = 'discrete'; } catch(e) { }
+try { o2.connect(o10,0,0) } catch(e) { }
+try { o3.normalize = false; } catch(e) { }
+try { /* switch-case did not return anything. */ } catch(e) { }
+try { o10.connect(o8,0,0) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 132, 88507);for(var c=0; c<3; c++) {for(var i=0; i<132; i++) {buffer.getChannelData(c)[i] = 1.7976931348623157e+308;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.loopStart = 1.7976931348623157e+308; } catch(e) { }
+try { o1.listener.dopplerFactor = 1024; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(1, 40, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<40; i++) {buffer.getChannelData(c)[i] = 1.3785770335346594;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o4.connect(o9,5,0) } catch(e) { }
+try { o8.curve = new Float32Array(16); } catch(e) { }
+try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { }
+try { o6.channelCountMode = 'explicit'; } catch(e) { }
+try { o2.playbackRate.value = 0; } catch(e) { }
+try { o4.channelCount = 3; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 220, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<220; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.destination.channelInterpretation = 'discrete'; } catch(e) { }
+try { o1.listener.setPosition(70368744177664, 2048, 1.7976931348623157e+308) } catch(e) { }
+try { o2.playbackRate.setValueCurveAtTime(new Float32Array(128), 1.7976931348623157e+308, 0.75) } catch(e) { }
+try { o5.fftSize = 118; } catch(e) { }
+try { o5.minDecibels = 1; } catch(e) { }
+try { o7.release.value = 5e-324; } catch(e) { }
+try { o2.channelCount = 3; } catch(e) { }
+try { o2.channelCountMode = 'clamped-max'; } catch(e) { }
+try { o3.normalize = false; } catch(e) { }
+try { o1.listener.speedOfSound = 2; } catch(e) { }
+try { o2.loopStart = 32; } catch(e) { }
+try { o3.channelCountMode = 'max'; } catch(e) { }
+try { o1.listener.setPosition(0.37976588317653304, -274877906943, 64) } catch(e) { }
+try { o8.curve = new Float32Array(4); } catch(e) { }
+try { o1.listener.setVelocity(16, -1024, 0) } catch(e) { }
+try { o5.minDecibels = 10000; } catch(e) { }
+try { o2.playbackRate.value = 5e-324; } catch(e) { }
+try { o3.connect(o9,1,0) } catch(e) { }
+try { o10.onaudioprocess = function(e) { /* onEvent */ }; } catch(e) { }
+try { o5.connect(o8,0,0) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(3, 554, o1.sampleRate);for(var c=0; c<3; c++) {for(var i=0; i<554; i++) {buffer.getChannelData(c)[i] = 0.000001;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o7.release.value = 0; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1289, 29583);for(var c=0; c<1; c++) {for(var i=0; i<1289; i++) {buffer.getChannelData(c)[i] = 5e-324;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.listener.setOrientation(1024, 64, 16, 1024, 1, 68719476735) } catch(e) { }
+try { o1.listener.setOrientation(512, 10000000, 274877906944, 9.999999999999998e-113, -8, 3.6191134923595274) } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(1, 1453, 15258);for(var c=0; c<1; c++) {for(var i=0; i<1453; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o8.channelInterpretation = 'discrete'; } catch(e) { }
+try { o5.channelInterpretation = 'discrete'; } catch(e) { }
+try { o7.connect(o9,4,0) } catch(e) { }
+try { o8.connect(o10,0,0) } catch(e) { }
+try { o1.listener.setPosition(18014398509481984, 16, 0.505129302503804056279079759406158700585365295410156250000) } catch(e) { }
+try { o10.connect(o9,5,0) } catch(e) { }
+try { o2.loop = false; } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(11, 1673, o1.sampleRate);for(var c=0; c<11; c++) {for(var i=0; i<1673; i++) {buffer.getChannelData(c)[i] = 0;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(2, 577, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<577; i++) {buffer.getChannelData(c)[i] = 0.5611;}}return buffer;}() } catch(e) { }
+try { o2.connect(o1.destination); } catch(e) { }
+try { o1.listener.setOrientation(64, 2049, 5e-324, 0.1777, 2, 7) } catch(e) { }
+try { o5.maxDecibels = 128; } catch(e) { }
+try { o2.start(0) } catch(e) { }
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/880342-2.html b/dom/media/test/crashtests/880342-2.html
new file mode 100644
index 0000000000..a8ef88ae70
--- /dev/null
+++ b/dom/media/test/crashtests/880342-2.html
@@ -0,0 +1,8 @@
+<script>
+try { o1 = new window.AudioContext(2, 10, 1019159); } catch(e) { }
+try { o2 = o1.createBufferSource(); } catch(e) { }
+try { o3 = o1.createConvolver(); } catch(e) { }
+try { o3.buffer = function(){var buffer = o1.createBuffer(2, 741, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<741; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}(); } catch(e) { }
+try { o2.buffer = function(){var buffer = o1.createBuffer(2, 120, 42419);for(var c=0; c<2; c++) {for(var i=0; i<120; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}() } catch(e) { }
+try { o3.buffer = function(){var buffer = o1.createBuffer(1, 1620, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<1620; i++) {buffer.getChannelData(c)[i] = -1;}}return buffer;}(); } catch(e) { }
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/880384.html b/dom/media/test/crashtests/880384.html
new file mode 100644
index 0000000000..798fbf133d
--- /dev/null
+++ b/dom/media/test/crashtests/880384.html
@@ -0,0 +1,8 @@
+<script>
+o1 = new window.AudioContext(3, 256, 44100);
+o2 = o1.createBufferSource();
+o3 = o1.createConvolver();
+o3.normalize = false;
+o3.buffer = function(){var buffer = o1.createBuffer(2, 1051, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1051; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}();
+o3.buffer = function(){var buffer = o1.createBuffer(2, 1112, o1.sampleRate);for(var c=0; c<2; c++) {for(var i=0; i<1112; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}();
+</script>
diff --git a/dom/media/test/crashtests/880404.html b/dom/media/test/crashtests/880404.html
new file mode 100644
index 0000000000..bf34932388
--- /dev/null
+++ b/dom/media/test/crashtests/880404.html
@@ -0,0 +1,6 @@
+<script>
+o1 = new window.OfflineAudioContext(2, 32, 44100);
+o12 = o1.createConvolver();
+o12.buffer = function(){var buffer = o1.createBuffer(1, 78, o1.sampleRate);for(var c=0; c<1; c++) {for(var i=0; i<78; i++) {buffer.getChannelData(c)[i] = 1;}}return buffer;}();
+o1.startRendering();
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/880724.html b/dom/media/test/crashtests/880724.html
new file mode 100644
index 0000000000..b57b5f9964
--- /dev/null
+++ b/dom/media/test/crashtests/880724.html
@@ -0,0 +1,13 @@
+<script>
+o1 = new window.AudioContext();
+o5 = o1.createConvolver();
+o5.buffer = function() {
+ var buffer = o1.createBuffer(2, 3, 33120);
+ for(var c=0; c<2; c++) {
+ for(var i=0; i<3; i++) {
+ buffer.getChannelData(c)[i] = -1;
+ }
+ }
+ return buffer;
+}();
+</script> \ No newline at end of file
diff --git a/dom/media/test/crashtests/881775.html b/dom/media/test/crashtests/881775.html
new file mode 100644
index 0000000000..d55c45d17e
--- /dev/null
+++ b/dom/media/test/crashtests/881775.html
@@ -0,0 +1,25 @@
+<script>
+o1 = new window.AudioContext(2, 16, 44100);
+o2 = o1.createBufferSource();
+o12 = o1.createBiquadFilter();
+o1.destination.channelCountMode = 'max';
+o2.buffer = function(){
+ var buffer = o1.createBuffer(2, 326, 77632);
+ for(var c=0; c<2; c++) {
+ for(var i=0; i<326; i++) {
+ buffer.getChannelData(c)[i] = -1;
+ }
+ }
+ return buffer;
+}();
+o2.connect(o1.destination);
+o2.buffer = function(){
+ var buffer = o1.createBuffer(3, 405, o1.sampleRate);
+ for(var c=0; c<3; c++) {
+ for(var i=0; i<405; i++) {
+ buffer.getChannelData(c)[i] = 1;
+ }
+ }
+ return buffer;
+}();
+</script>
diff --git a/dom/media/test/crashtests/882956.html b/dom/media/test/crashtests/882956.html
new file mode 100644
index 0000000000..ae7b441f99
--- /dev/null
+++ b/dom/media/test/crashtests/882956.html
@@ -0,0 +1,15 @@
+<script>
+o1 = new window.AudioContext(1, 2048, 44100);
+o2 = o1.createBufferSource();
+o1.destination.channelCountMode = 'max';
+o2.connect(o1.destination);
+o2.buffer = function(){
+ var buffer = o1.createBuffer(30, 442, 94933);
+ for(var c=0; c<30; c++) {
+ for(var i=0; i<442; i++) {
+ buffer.getChannelData(c)[i] = 1;
+ }
+ }
+ return buffer;
+}();
+</script>
diff --git a/dom/media/test/crashtests/884459.html b/dom/media/test/crashtests/884459.html
new file mode 100644
index 0000000000..e321d569f2
--- /dev/null
+++ b/dom/media/test/crashtests/884459.html
@@ -0,0 +1,12 @@
+<script>
+var Context0= new window.OfflineAudioContext(14,191531,44100)
+var BufferSource1=Context0.createBufferSource();
+
+setInterval(function(){
+BufferSource1.playbackRate.setTargetAtTime(0xC8F461D3EE6B2,(Context0.currentTime+0.0677539280615747),0.826130285160616);
+BufferSource1.playbackRate.setValueAtTime(35467.63924283907536607336193,0);
+},1)
+
+Context0.startRendering();
+
+</script>
diff --git a/dom/media/test/crashtests/889042.html b/dom/media/test/crashtests/889042.html
new file mode 100644
index 0000000000..9f74979c58
--- /dev/null
+++ b/dom/media/test/crashtests/889042.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ (new AudioContext()).createConvolver().buffer = null;
+</script>
diff --git a/dom/media/test/crashtests/907986-1.html b/dom/media/test/crashtests/907986-1.html
new file mode 100644
index 0000000000..320b8eadd0
--- /dev/null
+++ b/dom/media/test/crashtests/907986-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.OfflineAudioContext(1, 100, 48000);
+context.oncomplete = function(e) {
+ document.documentElement.removeAttribute("class");
+};
+// zero front vector
+context.listener.setOrientation(0, 0, 0, 6.311749985202524e+307, 0, 0);
+var panner = context.createPanner();
+panner.setPosition(6.311749985202524e+307, 4, 6.311749985202524e+307);
+panner.connect(context.destination);
+var source = context.createOscillator();
+source.connect(panner);
+source.start(0);
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/907986-2.html b/dom/media/test/crashtests/907986-2.html
new file mode 100644
index 0000000000..e0626ba2c2
--- /dev/null
+++ b/dom/media/test/crashtests/907986-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.OfflineAudioContext(1, 100, 48000);
+context.oncomplete = function(e) {
+ document.documentElement.removeAttribute("class");
+};
+// zero up vector
+context.listener.setOrientation(0, 6.311749985202524e+307, 0, 0, 0, 0);
+var panner = context.createPanner();
+panner.setPosition(1, 2, 3);
+panner.connect(context.destination);
+var source = context.createOscillator();
+source.connect(panner);
+source.start(0);
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/907986-3.html b/dom/media/test/crashtests/907986-3.html
new file mode 100644
index 0000000000..75b756c363
--- /dev/null
+++ b/dom/media/test/crashtests/907986-3.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.OfflineAudioContext(1, 100, 48000);
+context.oncomplete = function(e) {
+ document.documentElement.removeAttribute("class");
+};
+// linearly dependent
+context.listener.setOrientation(0, 0, -6.311749985202524e+307, 0, 0, 6.311749985202524e+307);
+var panner = context.createPanner();
+panner.setPosition(1, 2, 3);
+panner.connect(context.destination);
+var source = context.createOscillator();
+source.connect(panner);
+source.start(0);
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/907986-4.html b/dom/media/test/crashtests/907986-4.html
new file mode 100644
index 0000000000..a73500efca
--- /dev/null
+++ b/dom/media/test/crashtests/907986-4.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.OfflineAudioContext(1, 100, 48000);
+context.oncomplete = function(e) {
+ document.documentElement.removeAttribute("class");
+};
+var panner = context.createPanner();
+panner.setPosition(0, 3, 0); // directly overhead
+panner.connect(context.destination);
+var source = context.createOscillator();
+source.connect(panner);
+source.start(0);
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/910171-1.html b/dom/media/test/crashtests/910171-1.html
new file mode 100644
index 0000000000..9f3ec831be
--- /dev/null
+++ b/dom/media/test/crashtests/910171-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.OfflineAudioContext(1, 4096, 48000);
+context.oncomplete = function(e) {
+ document.documentElement.removeAttribute("class");
+};
+var delay = context.createDelay();
+delay.connect(context.destination);
+delay.delayTime.value = 1.0;
+var buffer = context.createBuffer(1, 2048, context.sampleRate);
+var source = context.createBufferSource();
+source.buffer = buffer;
+source.connect(delay);
+source.start();
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/920987.html b/dom/media/test/crashtests/920987.html
new file mode 100644
index 0000000000..6e8b2992a6
--- /dev/null
+++ b/dom/media/test/crashtests/920987.html
@@ -0,0 +1,6 @@
+<script>
+var ctx = new AudioContext();
+var buffer = ctx.createBuffer(1, 1000, ctx.sampleRate);
+var array = new Float32Array(900);
+buffer.copyToChannel(array, 0, 0xfffffff8);
+</script>
diff --git a/dom/media/test/crashtests/925619-1.html b/dom/media/test/crashtests/925619-1.html
new file mode 100644
index 0000000000..146c531f9b
--- /dev/null
+++ b/dom/media/test/crashtests/925619-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+// 1024 > 89478.5 * 48000 - (1 << 32)
+var context = new window.OfflineAudioContext(1, 1024, 48000);
+context.oncomplete = function(e) {
+ document.documentElement.removeAttribute("class");
+};
+var buffer = context.createBuffer(1, 2048, context.sampleRate);
+var source = context.createBufferSource();
+source.buffer = buffer;
+source.start(89478.5); // 89478.5 is a little greater than 2^32 / 48000.
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/925619-2.html b/dom/media/test/crashtests/925619-2.html
new file mode 100644
index 0000000000..e734b8bcad
--- /dev/null
+++ b/dom/media/test/crashtests/925619-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.OfflineAudioContext(1, 2048, 48000);
+// 1024 > 89478.5 * 48000 - (1 << 32)
+var buffer = context.createBuffer(1, 1024, context.sampleRate);
+var source = context.createBufferSource();
+source.buffer = buffer;
+source.onended = function(e) {
+ document.documentElement.removeAttribute("class");
+};
+source.start(0);
+source.stop(89478.5); // 89478.5 is a little greater than 2^32 / 48000.
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/926619.html b/dom/media/test/crashtests/926619.html
new file mode 100644
index 0000000000..2ead02af4e
--- /dev/null
+++ b/dom/media/test/crashtests/926619.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var ac = new window.AudioContext();
+
+ var _ChannelMergerNode = ac.createChannelMerger(4);
+
+ var _MediaStreamAudioDestinationNode = ac.createMediaStreamDestination();
+ var _MediaStream = _MediaStreamAudioDestinationNode.stream;
+ var _MediaStreamAudioSourceNode = ac.createMediaStreamSource(_MediaStream);
+
+ _ChannelMergerNode.connect(_MediaStreamAudioDestinationNode, 0, 0);
+ _MediaStreamAudioSourceNode.connect(_ChannelMergerNode, 0, 0);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/933151.html b/dom/media/test/crashtests/933151.html
new file mode 100644
index 0000000000..3d45f7af38
--- /dev/null
+++ b/dom/media/test/crashtests/933151.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var ac = new window.AudioContext();
+ var buffer = ac.createBuffer(1, 24313, 47250);
+ buffer.copyFromChannel(buffer.getChannelData(0), '');
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/test/crashtests/933156.html b/dom/media/test/crashtests/933156.html
new file mode 100644
index 0000000000..b89445a43d
--- /dev/null
+++ b/dom/media/test/crashtests/933156.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+function boom()
+{
+ var ac = new window.AudioContext();
+
+ var panner = ac.createPanner();
+ panner.setPosition(8, 0.7643051305237005, 0.10292575673733972);
+
+ var oscillator = ac.createOscillator();
+ oscillator.connect(panner);
+ oscillator.start(0);
+
+ setTimeout(function() {
+ panner.panningModel = 'equalpower';
+ oscillator.stop(0);
+ document.documentElement.removeAttribute("class");
+ }, 0.5);
+}
+boom();
+</script>
+</html>
diff --git a/dom/media/test/crashtests/944851.html b/dom/media/test/crashtests/944851.html
new file mode 100644
index 0000000000..4f663accc4
--- /dev/null
+++ b/dom/media/test/crashtests/944851.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+var ac = new AudioContext(1, 1354, 44100);
+var shaper = ac.createWaveShaper();
+var biquad = ac.createBiquadFilter();
+shaper.connect(biquad.frequency);
+biquad.getFrequencyResponse(new Float32Array(55785),
+ new Float32Array(62876),
+ new Float32Array(45111));
+
+</script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/952756.html b/dom/media/test/crashtests/952756.html
new file mode 100644
index 0000000000..ffced2e400
--- /dev/null
+++ b/dom/media/test/crashtests/952756.html
@@ -0,0 +1,19 @@
+<script>
+var Context0= new AudioContext()
+var BufferSource0=Context0.createBufferSource();
+BufferSource0.buffer=function(){
+ var length=35887;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(0.39765272522345185))};
+ return Buffer;
+}();
+BufferSource0.start(0.01932738965842873,0.33345631847623736,0.3893404237460345);
+BufferSource0.buffer=function(){
+ var length=15952;
+ var Buffer=Context0.createBuffer(1,length,Context0.sampleRate);
+ var bufferData= Buffer.getChannelData(0);
+ for (var i = 0; i < length; ++i) { bufferData[i] = Math.sin(i*(85))};
+ return Buffer;
+}();
+</script>
diff --git a/dom/media/test/crashtests/986901.html b/dom/media/test/crashtests/986901.html
new file mode 100644
index 0000000000..343df2c0ed
--- /dev/null
+++ b/dom/media/test/crashtests/986901.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+ var ac = new window.AudioContext();
+ var delay1 = ac.createDelay(0.02);
+ var delay2 = ac.createDelay(0.002);
+ var source = ac.createOscillator();
+ source.start(0);
+ source.connect(delay1, 0, 0);
+ delay2.connect(delay1, 0, 0);
+ delay1.connect(delay2, 0, 0);
+</script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/990794.html b/dom/media/test/crashtests/990794.html
new file mode 100644
index 0000000000..8b40088b1a
--- /dev/null
+++ b/dom/media/test/crashtests/990794.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script>
+var ctx = new AudioContext();
+var source = ctx.createOscillator();
+source.start(0);
+
+function appendMerger(src) {
+ const inputCount = 18;
+
+ var merger = ctx.createChannelMerger(32);
+
+ for (var i = 0; i < inputCount; ++i) {
+ src.connect(merger, 0, i);
+ }
+
+ return merger;
+}
+
+for (var i = 0; i < 6; ++i) {
+ source = appendMerger(source);
+}
+</script>
diff --git a/dom/media/test/crashtests/995289.html b/dom/media/test/crashtests/995289.html
new file mode 100644
index 0000000000..c988f41fa8
--- /dev/null
+++ b/dom/media/test/crashtests/995289.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script>
+var r0=new AudioContext();
+var r5=r0.createOscillator();
+var r6=r0.createPeriodicWave(new Float32Array(1),new Float32Array(1));
+r5.frequency.value = 4294967295;
+r5.start(0);
+r5.setPeriodicWave(r6);
+</script>
diff --git a/dom/media/test/crashtests/analyser-channels-1.html b/dom/media/test/crashtests/analyser-channels-1.html
new file mode 100644
index 0000000000..2f3133cf13
--- /dev/null
+++ b/dom/media/test/crashtests/analyser-channels-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+ var context = new window.OfflineAudioContext(1, 256, 48000);
+ var analyser = context.createAnalyser();
+ analyser.channelCount = 2;
+ analyser.channelCountMode = "explicit";
+ analyser.fftSize = 32;
+ var source = context.createOscillator();
+ source.connect(analyser);
+ source.start(0);
+ context.startRendering().
+ then(function() {
+ document.documentElement.removeAttribute("class");
+ });
+</script>
diff --git a/dom/media/test/crashtests/audiocontext-after-unload-1.html b/dom/media/test/crashtests/audiocontext-after-unload-1.html
new file mode 100644
index 0000000000..9b4f1181d2
--- /dev/null
+++ b/dom/media/test/crashtests/audiocontext-after-unload-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<title>Test for bug 1646601</title>
+<script>
+document.addEventListener('DOMContentLoaded', async () => {
+ const frame = document.createElement('iframe');
+ document.body.appendChild(frame);
+ frame.srcdoc = '<html></html>';
+ await new Promise(resolve => frame.onload = resolve);
+ const subwin = frame.contentWindow;
+ const subcontext = subwin.AudioContext;
+ // Construct an AudioContext while the subdocument is fully active to start
+ // a MediaTrackGraph.
+ new subcontext();
+ // Unload the subdocument and wait for completion.
+ // This shuts down the MediaTrackGraph.
+ subwin.location.reload();
+ await new Promise(resolve => frame.onload = resolve);
+ // Test that a new AudioContext on the inactive subdocument does not attempt
+ // to use the shut-down MediaTrackGraph.
+ try { new subcontext() } catch {}
+ document.documentElement.removeAttribute('class');
+});
+</script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/audiocontext-double-suspend.html b/dom/media/test/crashtests/audiocontext-double-suspend.html
new file mode 100644
index 0000000000..98399549bd
--- /dev/null
+++ b/dom/media/test/crashtests/audiocontext-double-suspend.html
@@ -0,0 +1,5 @@
+<script>
+var ac = new AudioContext();
+ac.resume();
+ac.resume();
+</script>
diff --git a/dom/media/test/crashtests/audioworkletnode-after-unload-1.html b/dom/media/test/crashtests/audioworkletnode-after-unload-1.html
new file mode 100644
index 0000000000..7da8d1161a
--- /dev/null
+++ b/dom/media/test/crashtests/audioworkletnode-after-unload-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<title>Test for bug 1634200 and bug 1655544</title>
+<script>
+document.addEventListener('DOMContentLoaded', async () => {
+ const frame = document.createElement('iframe');
+ document.body.appendChild(frame);
+ frame.srcdoc = '<html></html>';
+ await new Promise(resolve => frame.onload = resolve);
+
+ const subwin = frame.contentWindow;
+ const ctx = new subwin.AudioContext();
+ const url = URL.createObjectURL(
+ new Blob([`registerProcessor("noop",
+ class extends AudioWorkletProcessor {})`]),
+ {type: "application/javascript"});
+ await ctx.audioWorklet.addModule(url);
+
+ frame.remove();
+ new subwin.AudioWorkletNode(ctx, 'noop')
+
+ document.documentElement.removeAttribute('class');
+});
+</script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/buffer-source-duration-1.html b/dom/media/test/crashtests/buffer-source-duration-1.html
new file mode 100644
index 0000000000..df8d7a37d5
--- /dev/null
+++ b/dom/media/test/crashtests/buffer-source-duration-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+const rate = 44100;
+var context = new window.OfflineAudioContext(1, 512, rate);
+var buffer = context.createBuffer(1, 128, rate);
+var source = context.createBufferSource();
+source.buffer = buffer;
+source.start(0, 0, 86400);
+context.startRendering().
+ then(function() {
+ document.documentElement.removeAttribute("class");
+ });
+</script>
diff --git a/dom/media/test/crashtests/buffer-source-ended-1.html b/dom/media/test/crashtests/buffer-source-ended-1.html
new file mode 100644
index 0000000000..de8546316c
--- /dev/null
+++ b/dom/media/test/crashtests/buffer-source-ended-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new AudioContext();
+
+var source = context.createBufferSource();
+source.buffer = context.createBuffer(1, 2.0 * context.sampleRate, context.sampleRate);
+source.onended = function(e) {
+ document.documentElement.removeAttribute("class");
+}
+source.start(0.0, 1.0);
+setTimeout(
+ function() {
+ source.buffer = context.createBuffer(1, 1, context.sampleRate);
+ }, 0);
+</script>
diff --git a/dom/media/test/crashtests/buffer-source-resampling-start-1.html b/dom/media/test/crashtests/buffer-source-resampling-start-1.html
new file mode 100644
index 0000000000..55db8591ed
--- /dev/null
+++ b/dom/media/test/crashtests/buffer-source-resampling-start-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+const rate = 44101; // not divisible by 2
+var context = new window.OfflineAudioContext(1, 512, rate);
+var buffer = context.createBuffer(1, 128, rate);
+buffer.getChannelData(0)[0] = 1.0;
+var source = context.createBufferSource();
+source.buffer = buffer;
+source.playbackRate.value = rate / (Math.pow(2, 30) * 1.0000001);
+source.start(512 / rate);
+context.startRendering().
+ then(function() {
+ document.documentElement.removeAttribute("class");
+ });
+</script>
diff --git a/dom/media/test/crashtests/buffer-source-slow-resampling-1.html b/dom/media/test/crashtests/buffer-source-slow-resampling-1.html
new file mode 100644
index 0000000000..5d8a50442b
--- /dev/null
+++ b/dom/media/test/crashtests/buffer-source-slow-resampling-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+const blockSize = 128;
+// The sample rate is a prime number so that the resampler is not expected to
+// simplify in/out fractions.
+const rate = 44101;
+var context = new window.OfflineAudioContext(1, 3 * blockSize, rate);
+// Non-zero buffer, so it can't be optimized away.
+var buffer = context.createBuffer(1, 128, rate);
+buffer.getChannelData(0)[0] = 1.0;
+var source = context.createBufferSource();
+source.buffer = buffer;
+source.loop = true;
+// Initialize the resampler with a slow input rate.
+// With the current (Mar 2017) implementation, very slow rates give the
+// resampler a very large denominator.
+source.playbackRate.setValueAtTime(rate / 0x7fffffff, 0.0);
+// Change to a moderate input rate.
+// With the current implementation, skip_frac_num increases by den_rate for
+// each output sample and so one block before the change in playback rate is
+// enough for high skip_frac_num at the time of the change.
+const changeBlock = 1;
+const changeBlockSeconds = changeBlock * blockSize / rate;
+// With the current speex_resampler_set_rate_frac() implementation, the
+// moderate resampler denominator is still large enough to trigger overflow of
+// 32-bit unsigned integer arithmetic.
+source.playbackRate.setValueAtTime(rate / (rate + 1), changeBlockSeconds);
+source.start(0);
+context.startRendering().
+ then(function() {
+ document.documentElement.removeAttribute("class");
+ });
+</script>
diff --git a/dom/media/test/crashtests/channel-count-in-metadata-different-than-in-content.mp4 b/dom/media/test/crashtests/channel-count-in-metadata-different-than-in-content.mp4
new file mode 100644
index 0000000000..92bf3722f2
--- /dev/null
+++ b/dom/media/test/crashtests/channel-count-in-metadata-different-than-in-content.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/convolver-memory-report-1.html b/dom/media/test/crashtests/convolver-memory-report-1.html
new file mode 100644
index 0000000000..a49a281d1c
--- /dev/null
+++ b/dom/media/test/crashtests/convolver-memory-report-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>Bug 1481745: Exercise ConvolverNode memory reporting</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script>
+let context = new AudioContext();
+let response = new AudioBuffer({length: 128,
+ sampleRate: context.sampleRate});
+response.getChannelData(0)[response.length - 1] = 1;
+let convolver = new ConvolverNode(context,
+ {disableNormalization: true,
+ buffer: response});
+convolver.connect(context.destination);
+let osc = new OscillatorNode(context);
+osc.connect(convolver);
+osc.start();
+osc.stop(128/context.sampleRate);
+osc.onended = (e) => {
+ SpecialPowers.getMemoryReports();
+ document.documentElement.removeAttribute("class");
+};
+ </script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/copyFromChannel-2.html b/dom/media/test/crashtests/copyFromChannel-2.html
new file mode 100644
index 0000000000..8d3d5a2124
--- /dev/null
+++ b/dom/media/test/crashtests/copyFromChannel-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Crashtest for bug 1548816</title>
+ <script>
+let cx = new OfflineAudioContext({numberOfChannels: 1,
+ length: 1, sampleRate: 44100});
+let buffer = new AudioBuffer({numberOfChannels: 13,
+ length: 22050, sampleRate: 44100});
+buffer.getChannelData(12)[0] = 1.0;
+let o2248 = new AudioBufferSourceNode(cx, {buffer: buffer});
+let array = new Float32Array(52428);
+buffer.copyFromChannel(array, 12);
+ </script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/cors.webm b/dom/media/test/crashtests/cors.webm
new file mode 100644
index 0000000000..72b0297233
--- /dev/null
+++ b/dom/media/test/crashtests/cors.webm
Binary files differ
diff --git a/dom/media/test/crashtests/cors.webm^headers^ b/dom/media/test/crashtests/cors.webm^headers^
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/dom/media/test/crashtests/cors.webm^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/dom/media/test/crashtests/crashtests.list b/dom/media/test/crashtests/crashtests.list
new file mode 100644
index 0000000000..f3e5683733
--- /dev/null
+++ b/dom/media/test/crashtests/crashtests.list
@@ -0,0 +1,142 @@
+load 0-timescale.html # bug 1229166
+skip-if(Android) pref(media.autoplay.default,0) load 459439-1.html # bug 888557
+load 466607-1.html
+load 466945-1.html
+load 468763-1.html
+load 474744-1.html
+HTTP load 481136-1.html # needs to be HTTP to recognize the ogg as an audio file?
+load 492286-1.xhtml
+load 493915-1.html
+pref(media.autoplay.default,0) load 495794-1.html
+load 497734-1.xhtml
+load 497734-2.html
+load 576612-1.html
+load 752784-1.html
+skip-if(Android) load 789075-1.html # bug 1374405 for android
+skip-if(Android&&AndroidVersion=='22') HTTP load 795892-1.html # bug 1358718
+load 844563.html
+load 846612.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 852838.html
+load 865004.html
+load 865537-1.html
+load 865550.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 868504.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 874869.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 874915.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 874934.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 874952.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 875144.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 875596.html
+load 875911.html
+load 876024-1.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 876024-2.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 876118.html
+load 876207.html
+load 876215.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 876249.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 876252.html
+load 876834.html
+load 877527.html
+load 877820.html
+load 878014.html
+load 878328.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 878407.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 878478.html
+load 880129.html
+load 880202.html
+load 880342-1.html
+load 880342-2.html
+load 880384.html
+load 880404.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 880724.html
+load 881775.html
+load 882956.html
+load 884459.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 889042.html
+load 907986-1.html
+load 907986-2.html
+load 907986-3.html
+load 907986-4.html
+load 910171-1.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 920987.html
+load 925619-1.html
+load 925619-2.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 926619.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 933151.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 933156.html
+load 944851.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 952756.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 986901.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 990794.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 995289.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1012609.html
+load 1015662.html
+skip-if(Android) test-pref(media.navigator.permission.disabled,true) load 1028458.html # bug 1048863
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1041466.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1045650.html
+load 1080986.html
+skip-if(Android&&AndroidVersion=='21') load 1180881.html # bug 1409365
+load 1197935.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1122218.html
+load 1127188.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1157994.html
+load 1158427.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1185176.html
+load 1185192.html
+skip-if(Android) load 1257700.html # bug 1575666
+load 1267263.html
+load 1270303.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1368490.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load 1291702.html
+load 1378826.html
+load 1384248.html
+load 1389304.html
+load 1393272.webm
+load 1411322.html
+load 1450845.html
+load 1489160.html
+load disconnect-wrong-destination.html
+load analyser-channels-1.html
+load audiocontext-after-unload-1.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load audiocontext-double-suspend.html
+skip-if(Android) load audioworkletnode-after-unload-1.html # Needs secure context
+load buffer-source-duration-1.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load buffer-source-ended-1.html
+load buffer-source-resampling-start-1.html
+load buffer-source-slow-resampling-1.html
+load convolver-memory-report-1.html
+load copyFromChannel-2.html
+load empty-buffer-source.html
+skip-if(verify&&isDebugBuild&&gtkWidget) HTTP load media-element-source-seek-1.html
+skip-if(verify&&isDebugBuild&&gtkWidget) load offline-buffer-source-ended-1.html
+load oscillator-ended-1.html
+load oscillator-ended-2.html
+skip-if(Android&&AndroidVersion=='22') load video-replay-after-audio-end.html # bug 1315125, bug 1358876
+# This needs to run at the end to avoid leaking busted state into other tests.
+skip-if(Android) load 691096-1.html # Bug 1365451
+load 1236639.html
+test-pref(media.navigator.permission.disabled,true) test-pref(media.devices.insecure.enabled,true) test-pref(media.getusermedia.insecure.enabled,true) load 1388372.html
+load 1494073.html
+skip-if(Android) load 1526044.html # Bug 1528391
+skip-if(Android&&AndroidVersion<21) load encrypted-track-with-bad-sample-description-index.mp4 # Bug 1533211, unkip after bug 1550912
+load encrypted-track-without-tenc.mp4 # Bug 1533215
+asserts-if(Android,0-1) load encrypted-track-with-sample-missing-cenc-aux.mp4 # Bug 1533625, bug 1588967
+load 1538727.html
+load empty-samples.webm # Bug 1540580
+test-pref(media.autoplay.block-webaudio,false) load 1545133.html
+load track-with-zero-dimensions.mp4 # Bug 1542539
+load 1560215.html
+skip-if(Android) load 1547784.html # Skip on Android as clearkey is not supported
+load 1547899.html
+load 1569645.html
+load 1575271.html
+load 1577184.html
+pref(media.autoplay.default,0) load 1587248.html
+load 1594466.html
+load 1601385.html
+load 1601422.html
+load 1604941.html
+pref(media.autoplay.default,0) load 1673525.html
+skip-if(!winWidget) load 1608286.html
+load channel-count-in-metadata-different-than-in-content.mp4 # Bug 1584959
+load mp4_box_emptyrange.mp4 # Bug 1667480
diff --git a/dom/media/test/crashtests/disconnect-wrong-destination.html b/dom/media/test/crashtests/disconnect-wrong-destination.html
new file mode 100644
index 0000000000..515ca3c877
--- /dev/null
+++ b/dom/media/test/crashtests/disconnect-wrong-destination.html
@@ -0,0 +1,13 @@
+<script>
+ var oc = new OfflineAudioContext(1, 1, 44100);
+ var splitter = oc.createChannelSplitter(2);
+ var merger0 = oc.createChannelMerger(2);
+ var merger1 = oc.createChannelMerger(2);
+ splitter.connect(merger0, 0);
+ splitter.connect(merger0, 1);
+ splitter.connect(merger1, 0);
+ splitter.connect(merger1, 1);
+
+ splitter.disconnect(merger0, 0);
+ splitter.disconnect(merger1, 1);
+</script>
diff --git a/dom/media/test/crashtests/doppler-1.html b/dom/media/test/crashtests/doppler-1.html
new file mode 100644
index 0000000000..2af3c8f460
--- /dev/null
+++ b/dom/media/test/crashtests/doppler-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.AudioContext();
+var source = context.createBufferSource();
+source.buffer = context.createBuffer(1, 1, context.sampleRate);
+source.onended =
+ function(e) {
+ setTimeout(
+ function() {
+ var panner = context.createPanner();
+ source.connect(panner);
+ panner.setVelocity(1.0, 0.0, 0.0);
+ setTimeout(
+ function() {
+ document.documentElement.removeAttribute("class");
+ },
+ 0);
+ },
+ 0);
+ };
+source.start(0);
+</script>
diff --git a/dom/media/test/crashtests/empty-buffer-source.html b/dom/media/test/crashtests/empty-buffer-source.html
new file mode 100644
index 0000000000..2ce48a9ec1
--- /dev/null
+++ b/dom/media/test/crashtests/empty-buffer-source.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>Bug 1636540: AudioBufferSourceNode with empty buffer</title>
+ <script>
+const offline = new OfflineAudioContext({length: 128, sampleRate: 16384});
+const buffer = new AudioBuffer({length: 1, sampleRate: 21725});
+const node = new AudioBufferSourceNode(offline, {buffer: buffer});
+node.start(5/offline.sampleRate);
+offline.startRendering().then(
+ () => document.documentElement.removeAttribute("class"));
+ </script>
+</head>
+</html>
diff --git a/dom/media/test/crashtests/empty-samples.webm b/dom/media/test/crashtests/empty-samples.webm
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/media/test/crashtests/empty-samples.webm
diff --git a/dom/media/test/crashtests/encrypted-track-with-bad-sample-description-index.mp4 b/dom/media/test/crashtests/encrypted-track-with-bad-sample-description-index.mp4
new file mode 100644
index 0000000000..32303f0357
--- /dev/null
+++ b/dom/media/test/crashtests/encrypted-track-with-bad-sample-description-index.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/encrypted-track-with-sample-missing-cenc-aux.mp4 b/dom/media/test/crashtests/encrypted-track-with-sample-missing-cenc-aux.mp4
new file mode 100644
index 0000000000..875c5dca76
--- /dev/null
+++ b/dom/media/test/crashtests/encrypted-track-with-sample-missing-cenc-aux.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/encrypted-track-without-tenc.mp4 b/dom/media/test/crashtests/encrypted-track-without-tenc.mp4
new file mode 100644
index 0000000000..188faebf1b
--- /dev/null
+++ b/dom/media/test/crashtests/encrypted-track-without-tenc.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/media-element-source-seek-1.html b/dom/media/test/crashtests/media-element-source-seek-1.html
new file mode 100644
index 0000000000..5c3aed5ae7
--- /dev/null
+++ b/dom/media/test/crashtests/media-element-source-seek-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var audioElement = document.createElement("audio");
+audioElement.autoplay = true;
+audioElement.src = "sound.ogg";
+audioElement.onplaying =
+ function() {
+ audioElement.onplaying = null;
+ setTimeout(
+ function() {
+ audioElement.onseeked =
+ function() {
+ // Note we reset 'src' to release decoder resources and cubeb
+ // streams to prevent OOM or OpenCubeb() failures.
+ audioElement.src = "";
+ document.documentElement.removeAttribute("class");
+ };
+ audioElement.currentTime = 0;
+ }, 100);
+ };
+
+var context = new window.AudioContext();
+var source = context.createMediaElementSource(audioElement);
+source.connect(context.destination);
+</script>
+</html>
diff --git a/dom/media/test/crashtests/mp4_box_emptyrange.mp4 b/dom/media/test/crashtests/mp4_box_emptyrange.mp4
new file mode 100644
index 0000000000..83057533a0
--- /dev/null
+++ b/dom/media/test/crashtests/mp4_box_emptyrange.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/offline-buffer-source-ended-1.html b/dom/media/test/crashtests/offline-buffer-source-ended-1.html
new file mode 100644
index 0000000000..0631021126
--- /dev/null
+++ b/dom/media/test/crashtests/offline-buffer-source-ended-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+var context = new window.OfflineAudioContext(1, 12001, 12000);
+
+var source = context.createBufferSource();
+source.buffer = context.createBuffer(1, 12000, context.sampleRate);
+source.onended = function(e) {
+ document.documentElement.removeAttribute("class");
+}
+source.connect(context.destination);
+source.start(0);
+
+context.startRendering();
+</script>
diff --git a/dom/media/test/crashtests/oscillator-ended-1.html b/dom/media/test/crashtests/oscillator-ended-1.html
new file mode 100644
index 0000000000..831111261c
--- /dev/null
+++ b/dom/media/test/crashtests/oscillator-ended-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+function createContext() {
+ var context = new window.AudioContext();
+ var source = context.createOscillator();
+ source.onended = function(e) {
+ document.documentElement.removeAttribute("class");
+ };
+ source.connect(context.destination);
+ source.start(0.49);
+ source.stop(0.5);
+}
+createContext();
+</script>
diff --git a/dom/media/test/crashtests/oscillator-ended-2.html b/dom/media/test/crashtests/oscillator-ended-2.html
new file mode 100644
index 0000000000..ee9b8cf300
--- /dev/null
+++ b/dom/media/test/crashtests/oscillator-ended-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+function createContext() {
+ var context = new window.AudioContext();
+ var source = context.createOscillator();
+ source.onended = function(e) {
+ document.documentElement.removeAttribute("class");
+ };
+ source.connect(context.destination);
+ source.start(60);
+ source.stop(0.5);
+}
+createContext();
+</script>
diff --git a/dom/media/test/crashtests/sound.ogg b/dom/media/test/crashtests/sound.ogg
new file mode 100644
index 0000000000..edda4e9128
--- /dev/null
+++ b/dom/media/test/crashtests/sound.ogg
Binary files differ
diff --git a/dom/media/test/crashtests/track-with-zero-dimensions.mp4 b/dom/media/test/crashtests/track-with-zero-dimensions.mp4
new file mode 100644
index 0000000000..3f4a1317f3
--- /dev/null
+++ b/dom/media/test/crashtests/track-with-zero-dimensions.mp4
Binary files differ
diff --git a/dom/media/test/crashtests/video-crash.webm b/dom/media/test/crashtests/video-crash.webm
new file mode 100644
index 0000000000..9532113d87
--- /dev/null
+++ b/dom/media/test/crashtests/video-crash.webm
Binary files differ
diff --git a/dom/media/test/crashtests/video-replay-after-audio-end.html b/dom/media/test/crashtests/video-replay-after-audio-end.html
new file mode 100644
index 0000000000..9ffd6078de
--- /dev/null
+++ b/dom/media/test/crashtests/video-replay-after-audio-end.html
@@ -0,0 +1,43 @@
+<html class="reftest-wait">
+<head>
+ <title> Bug 1242774 : video crashed if pause and play again after audio track ends </title>
+</head>
+<body>
+<script type="text/javascript">
+function assert(value, msg) {
+ if (!value) {
+ dump("### Error : " + msg + "\n");
+ }
+}
+
+var AUDIO_END_TIME = 4.5;
+var video = document.createElement('video');
+video.src = "video-crash.webm";
+video.play();
+
+video.ontimeupdate = function () {
+ assert(AUDIO_END_TIME < video.duration,
+ "AUDIO_END_TIME should be smaller than the duration!");
+
+ if (video.currentTime > AUDIO_END_TIME) {
+ dump("### Pause video during silent part.\n");
+ video.ontimeupdate = null;
+ video.pause();
+ }
+
+ video.onpause = function () {
+ video.onpause = null;
+ setTimeout(function() {
+ dump("### Re-play after pausing during silent part.\n");
+ video.play();
+ video.onended = function () {
+ video.onended = null;
+ dump("### Video is ended.\n");
+ document.documentElement.removeAttribute("class");
+ }
+ }, 1000);
+ }
+}
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/media/test/dash/dash-manifest-garbled-webm.mpd b/dom/media/test/dash/dash-manifest-garbled-webm.mpd
new file mode 100644
index 0000000000..aa78ded3ec
--- /dev/null
+++ b/dom/media/test/dash/dash-manifest-garbled-webm.mpd
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MPD
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="urn:mpeg:DASH:schema:MPD:2011"
+ xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011"
+ type="static"
+ mediaPresentationDuration="PT3.958S"
+ minBufferTime="PT1S"
+ profiles="urn:webm:dash:profile:webm-on-demand:2012">
+ <BaseURL>./</BaseURL>
+ <Period id="0" start="PT0S" duration="PT3.958S" >
+ <AdaptationSet id="0" mimeType="video/webm" codecs="vp8" lang="eng" subsegmentAlignment="true" subsegmentStartsWithSAP="1" bitstreamSwitching="true">
+ <Representation id="0" bandwidth="54207" width="320" height="180">
+ <BaseURL>garbled.webm</BaseURL>
+ <SegmentBase indexRange="35090-35123">
+ <Initialization range="0-228" />
+ </SegmentBase>
+ </Representation>
+ <Representation id="1" bandwidth="78006" width="428" height="240">
+ <BaseURL>dash-webm-video-428x240.webm</BaseURL>
+ <SegmentBase indexRange="50173-50206">
+ <Initialization range="0-228" />
+ </SegmentBase>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="1" mimeType="audio/webm" codecs="vorbis" lang="eng" audioSamplingRate="48000" subsegmentStartsWithSAP="1">
+ <Representation id="2" bandwidth="57264">
+ <BaseURL>dash-webm-audio-128k.webm</BaseURL>
+ <SegmentBase indexRange="41927-41946">
+ <Initialization range="0-4521" />
+ </SegmentBase>
+ </Representation>
+ </AdaptationSet>
+ </Period>
+</MPD>
diff --git a/dom/media/test/dash/dash-manifest-garbled.mpd b/dom/media/test/dash/dash-manifest-garbled.mpd
new file mode 100644
index 0000000000..ac8eadbddc
--- /dev/null
+++ b/dom/media/test/dash/dash-manifest-garbled.mpd
@@ -0,0 +1 @@
+PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxNUEQgbWVkaWFQcmVzZW50YXRpb25EdXJhdGlvbj0iUFQxOS41MVMiIG1pbkJ1ZmZlclRpbWU9IlBUMVMiIHByb2ZpbGVzPSJ1cm46d2VibTpkYXNoOnByb2ZpbGU6d2VibS1vbi1kZW1hbmQ6MjAxMiIgdHlwZT0ic3RhdGljIiB4bWxucz0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiPjxCYXNlVVJMPmh0dHA6Ly93d3cuZ29vZ2xlLmNvbTwvQmFzZVVSTD48UGVyaW9kIGR1cmF0aW9uPSJQVDE5LjUxUyIgaWQ9IjAiIHN0YXJ0PSJQVDBTIj48QWRhcHRhdGlvblNldCBhdWRpb1NhbXBsaW5nUmF0ZT0iNDgwMDAiIGNvZGVjcz0idm9yYmlzIiBpZD0iMSIgbGFuZz0iZW5nIiBtaW1lVHlwZT0iYXVkaW8vd2VibSIgc3Vic2VnbWVudFN0YXJ0c1dpdGhTQVA9IjEiPjxSZXByZXNlbnRhdGlvbiBiYW5kd2lkdGg9IjIwMTA5IiBpZD0iMiI+PEJhc2VVUkwvPjxTZWdtZW50QmFzZSBpbmRleFJhbmdlPSIzMTk3ODAtMzIwNjEyIj48SW5pdGlhbGl6YXRpb24gcmFuZ2U9IjAtMjA4NzAiLz48L1NlZ21lbnRCYXNlPjwvUmVwcmVzZW50YXRpb24+PC9BZGFwdGF0aW9uU2V0PjwvUGVyaW9kPjwvTVBEPg
diff --git a/dom/media/test/dash/dash-manifest-sjs.mpd b/dom/media/test/dash/dash-manifest-sjs.mpd
new file mode 100644
index 0000000000..c7ecba3c69
--- /dev/null
+++ b/dom/media/test/dash/dash-manifest-sjs.mpd
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MPD
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="urn:mpeg:DASH:schema:MPD:2011"
+ xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011"
+ type="static"
+ mediaPresentationDuration="PT3.958S"
+ minBufferTime="PT1S"
+ profiles="urn:webm:dash:profile:webm-on-demand:2012">
+ <BaseURL>./dash_detect_stream_switch.sjs?name=</BaseURL>
+ <Period id="0" start="PT0S" duration="PT3.958S" >
+ <AdaptationSet id="0" mimeType="video/webm" codecs="vp8" lang="eng" subsegmentAlignment="true" subsegmentStartsWithSAP="1" bitstreamSwitching="true">
+ <Representation id="0" bandwidth="54207" width="320" height="180">
+ <BaseURL>dash-webm-video-320x180.webm</BaseURL>
+ <SegmentBase indexRange="35090-35123">
+ <Initialization range="0-228" />
+ </SegmentBase>
+ </Representation>
+ <Representation id="1" bandwidth="78006" width="428" height="240">
+ <BaseURL>dash-webm-video-428x240.webm</BaseURL>
+ <SegmentBase indexRange="50173-50206">
+ <Initialization range="0-228" />
+ </SegmentBase>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="1" mimeType="audio/webm" codecs="vorbis" lang="eng" audioSamplingRate="48000" subsegmentStartsWithSAP="1">
+ <Representation id="2" bandwidth="57264">
+ <BaseURL>dash-webm-audio-128k.webm</BaseURL>
+ <SegmentBase indexRange="41927-41946">
+ <Initialization range="0-4521" />
+ </SegmentBase>
+ </Representation>
+ </AdaptationSet>
+ </Period>
+</MPD>
diff --git a/dom/media/test/dash/dash-manifest.mpd b/dom/media/test/dash/dash-manifest.mpd
new file mode 100644
index 0000000000..98c7a90480
--- /dev/null
+++ b/dom/media/test/dash/dash-manifest.mpd
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MPD
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="urn:mpeg:DASH:schema:MPD:2011"
+ xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011"
+ type="static"
+ mediaPresentationDuration="PT3.958S"
+ minBufferTime="PT1S"
+ profiles="urn:webm:dash:profile:webm-on-demand:2012">
+ <BaseURL>./</BaseURL>
+ <Period id="0" start="PT0S" duration="PT3.958S" >
+ <AdaptationSet id="0" mimeType="video/webm" codecs="vp8" lang="eng" subsegmentAlignment="true" subsegmentStartsWithSAP="1" bitstreamSwitching="true">
+ <Representation id="0" bandwidth="54207" width="320" height="180">
+ <BaseURL>dash-webm-video-320x180.webm</BaseURL>
+ <SegmentBase indexRange="35090-35123">
+ <Initialization range="0-228" />
+ </SegmentBase>
+ </Representation>
+ <Representation id="1" bandwidth="78006" width="428" height="240">
+ <BaseURL>dash-webm-video-428x240.webm</BaseURL>
+ <SegmentBase indexRange="50173-50206">
+ <Initialization range="0-228" />
+ </SegmentBase>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="1" mimeType="audio/webm" codecs="vorbis" lang="eng" audioSamplingRate="48000" subsegmentStartsWithSAP="1">
+ <Representation id="2" bandwidth="57264">
+ <BaseURL>dash-webm-audio-128k.webm</BaseURL>
+ <SegmentBase indexRange="41927-41946">
+ <Initialization range="0-4521" />
+ </SegmentBase>
+ </Representation>
+ </AdaptationSet>
+ </Period>
+</MPD>
diff --git a/dom/media/test/dash/dash-webm-audio-128k.webm b/dom/media/test/dash/dash-webm-audio-128k.webm
new file mode 100644
index 0000000000..f56c042053
--- /dev/null
+++ b/dom/media/test/dash/dash-webm-audio-128k.webm
Binary files differ
diff --git a/dom/media/test/dash/dash-webm-video-320x180.webm b/dom/media/test/dash/dash-webm-video-320x180.webm
new file mode 100644
index 0000000000..282e6a2cc3
--- /dev/null
+++ b/dom/media/test/dash/dash-webm-video-320x180.webm
Binary files differ
diff --git a/dom/media/test/dash/dash-webm-video-428x240.webm b/dom/media/test/dash/dash-webm-video-428x240.webm
new file mode 100644
index 0000000000..23f2c89616
--- /dev/null
+++ b/dom/media/test/dash/dash-webm-video-428x240.webm
Binary files differ
diff --git a/dom/media/test/dash/garbled.webm b/dom/media/test/dash/garbled.webm
new file mode 100644
index 0000000000..ac8eadbddc
--- /dev/null
+++ b/dom/media/test/dash/garbled.webm
@@ -0,0 +1 @@
+PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxNUEQgbWVkaWFQcmVzZW50YXRpb25EdXJhdGlvbj0iUFQxOS41MVMiIG1pbkJ1ZmZlclRpbWU9IlBUMVMiIHByb2ZpbGVzPSJ1cm46d2VibTpkYXNoOnByb2ZpbGU6d2VibS1vbi1kZW1hbmQ6MjAxMiIgdHlwZT0ic3RhdGljIiB4bWxucz0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0idXJuOm1wZWc6REFTSDpzY2hlbWE6TVBEOjIwMTEiPjxCYXNlVVJMPmh0dHA6Ly93d3cuZ29vZ2xlLmNvbTwvQmFzZVVSTD48UGVyaW9kIGR1cmF0aW9uPSJQVDE5LjUxUyIgaWQ9IjAiIHN0YXJ0PSJQVDBTIj48QWRhcHRhdGlvblNldCBhdWRpb1NhbXBsaW5nUmF0ZT0iNDgwMDAiIGNvZGVjcz0idm9yYmlzIiBpZD0iMSIgbGFuZz0iZW5nIiBtaW1lVHlwZT0iYXVkaW8vd2VibSIgc3Vic2VnbWVudFN0YXJ0c1dpdGhTQVA9IjEiPjxSZXByZXNlbnRhdGlvbiBiYW5kd2lkdGg9IjIwMTA5IiBpZD0iMiI+PEJhc2VVUkwvPjxTZWdtZW50QmFzZSBpbmRleFJhbmdlPSIzMTk3ODAtMzIwNjEyIj48SW5pdGlhbGl6YXRpb24gcmFuZ2U9IjAtMjA4NzAiLz48L1NlZ21lbnRCYXNlPjwvUmVwcmVzZW50YXRpb24+PC9BZGFwdGF0aW9uU2V0PjwvUGVyaW9kPjwvTVBEPg
diff --git a/dom/media/test/dash_detect_stream_switch.sjs b/dom/media/test/dash_detect_stream_switch.sjs
new file mode 100644
index 0000000000..a8abf6f2e5
--- /dev/null
+++ b/dom/media/test/dash_detect_stream_switch.sjs
@@ -0,0 +1,114 @@
+/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* dash_detect_stream_switch.sjs
+ *
+ * Parses requests for DASH manifests and ensures stream switching takes place
+ * by verifying the subsegments downloaded and the streams they belong to.
+ * If unexpected subsegments (byte ranges) are requested, the script will
+ * will respond with a 404.
+ */
+
+var DEBUG = false;
+
+function parseQuery(request, key) {
+ var params = request.queryString.split('&');
+ if (DEBUG) {
+ dump("DASH-SJS: request params = \"" + params + "\"\n");
+ }
+ for (var j = 0; j < params.length; ++j) {
+ var p = params[j];
+ if (p == key)
+ return true;
+ if (p.indexOf(key + "=") === 0)
+ return p.substring(key.length + 1);
+ if (p.indexOf("=") < 0 && key === "")
+ return p;
+ }
+ return false;
+}
+
+function handleRequest(request, response)
+{
+ try {
+ var name = parseQuery(request, "name");
+ var range = request.hasHeader("Range") ? request.getHeader("Range")
+ : undefined;
+
+ // Should not get request for 1st subsegment from 2nd stream, nor 2nd
+ // subsegment from 1st stream.
+ if (name == "dash-webm-video-320x180.webm" && range == "bytes=25514-32767" ||
+ name == "dash-webm-video-428x240.webm" && range == "bytes=228-35852")
+ {
+ throw "Should not request " + name + " with byte-range " + range;
+ } else {
+ var rangeSplit = range.split("=");
+ if (rangeSplit.length != 2) {
+ throw "DASH-SJS: ERROR: invalid number of tokens (" + rangeSplit.length +
+ ") delimited by \'=\' in \'Range\' header.";
+ }
+ var offsets = rangeSplit[1].split("-");
+ if (offsets.length != 2) {
+ throw "DASH-SJS: ERROR: invalid number of tokens (" + offsets.length +
+ ") delimited by \'-\' in \'Range\' header.";
+ }
+ var startOffset = parseInt(offsets[0]);
+ var endOffset = parseInt(offsets[1]);
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+
+ var paths = "tests/dom/media/test/" + name;
+ var split = paths.split("/");
+ for (var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+
+ fis.init(file, -1, -1, false);
+ // Exception: start offset should be within file bounds.
+ if (startOffset > file.fileSize) {
+ throw "Starting offset [" + startOffset + "] is after end of file [" +
+ file.fileSize + "].";
+ }
+ // End offset may be too large in the MPD. Real world HTTP servers just
+ // return what data they can; do the same here - reduce the end offset.
+ if (endOffset >= file.fileSize) {
+ if (DEBUG) {
+ dump("DASH-SJS: reducing endOffset [" + endOffset + "] to fileSize [" +
+ (file.fileSize-1) + "]\n");
+ }
+ endOffset = file.fileSize-1;
+ }
+ fis.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, startOffset);
+ bis.setInputStream(fis);
+
+ var byteLengthToRead = endOffset + 1 - startOffset;
+ var totalBytesExpected = byteLengthToRead + startOffset;
+ if (DEBUG) {
+ dump("DASH-SJS: byteLengthToRead = " + byteLengthToRead +
+ " byteLengthToRead+startOffset = " + totalBytesExpected +
+ " fileSize = " + file.fileSize + "\n");
+ }
+
+ var bytes = bis.readBytes(byteLengthToRead);
+ response.setStatusLine(request.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Length", ""+bytes.length, false);
+ response.setHeader("Content-Type", "application/dash+xml", false);
+ var contentRange = "bytes " + startOffset + "-" + endOffset + "/" +
+ file.fileSize;
+ response.setHeader("Content-Range", contentRange, false);
+ response.write(bytes, bytes.length);
+ bis.close();
+ }
+ } catch (e) {
+ dump ("DASH-SJS-ERROR: " + e + "\n");
+ response.setStatusLine(request.httpVersion, 404, "Not found");
+ }
+}
diff --git a/dom/media/test/detodos-recorder-test.opus b/dom/media/test/detodos-recorder-test.opus
new file mode 100644
index 0000000000..88b2eab0f8
--- /dev/null
+++ b/dom/media/test/detodos-recorder-test.opus
Binary files differ
diff --git a/dom/media/test/detodos-recorder-test.opus^headers^ b/dom/media/test/detodos-recorder-test.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/detodos-recorder-test.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/detodos-short.opus b/dom/media/test/detodos-short.opus
new file mode 100644
index 0000000000..8bda283fc5
--- /dev/null
+++ b/dom/media/test/detodos-short.opus
Binary files differ
diff --git a/dom/media/test/detodos-short.opus^headers^ b/dom/media/test/detodos-short.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/detodos-short.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/detodos-short.webm b/dom/media/test/detodos-short.webm
new file mode 100644
index 0000000000..45af2675a6
--- /dev/null
+++ b/dom/media/test/detodos-short.webm
Binary files differ
diff --git a/dom/media/test/detodos-short.webm^headers^ b/dom/media/test/detodos-short.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/detodos-short.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/detodos.opus b/dom/media/test/detodos.opus
new file mode 100644
index 0000000000..6c7ba88a66
--- /dev/null
+++ b/dom/media/test/detodos.opus
Binary files differ
diff --git a/dom/media/test/detodos.opus^headers^ b/dom/media/test/detodos.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/detodos.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/detodos.webm b/dom/media/test/detodos.webm
new file mode 100644
index 0000000000..39cfa7f537
--- /dev/null
+++ b/dom/media/test/detodos.webm
Binary files differ
diff --git a/dom/media/test/detodos.webm^headers^ b/dom/media/test/detodos.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/detodos.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/dirac.ogg b/dom/media/test/dirac.ogg
new file mode 100644
index 0000000000..2986cf1e80
--- /dev/null
+++ b/dom/media/test/dirac.ogg
Binary files differ
diff --git a/dom/media/test/dirac.ogg^headers^ b/dom/media/test/dirac.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/dirac.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/dynamic_resource.sjs b/dom/media/test/dynamic_resource.sjs
new file mode 100644
index 0000000000..7731612184
--- /dev/null
+++ b/dom/media/test/dynamic_resource.sjs
@@ -0,0 +1,48 @@
+function parseQuery(request, key) {
+ var params = request.queryString.split('&');
+ for (var j = 0; j < params.length; ++j) {
+ var p = params[j];
+ if (p == key)
+ return true;
+ if (p.indexOf(key + "=") == 0)
+ return p.substring(key.length + 1);
+ if (p.indexOf("=") < 0 && key == "")
+ return p;
+ }
+ return false;
+}
+
+// Return resource1 file content for the first request with a given key.
+// All subsequent requests return resource2. Both must be video/ogg.
+function handleRequest(request, response)
+{
+ var key = parseQuery(request, "key");
+ var resource1 = parseQuery(request, "res1");
+ var resource2 = parseQuery(request, "res2");
+
+ var resource = getState(key) == "2" ? resource2 : resource1;
+ setState(key, "2");
+
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+ var paths = "tests/dom/media/test/" + resource;
+ var split = paths.split("/");
+ for(var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+ dump("file=" + file + "\n");
+ bis.setInputStream(fis);
+ var bytes = bis.readBytes(bis.available());
+ response.setStatusLine(request.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", "bytes 0-" + (bytes.length - 1) + "/" + bytes.length);
+ response.setHeader("Content-Length", ""+bytes.length, false);
+ response.setHeader("Content-Type", "video/ogg", false);
+ response.write(bytes, bytes.length);
+ bis.close();
+}
diff --git a/dom/media/test/eme.js b/dom/media/test/eme.js
new file mode 100644
index 0000000000..fffe71607c
--- /dev/null
+++ b/dom/media/test/eme.js
@@ -0,0 +1,496 @@
+/* import-globals-from manifest.js */
+
+const CLEARKEY_KEYSYSTEM = "org.w3.clearkey";
+
+const gCencMediaKeySystemConfig = [
+ {
+ initDataTypes: ["cenc"],
+ videoCapabilities: [{ contentType: "video/mp4" }],
+ audioCapabilities: [{ contentType: "audio/mp4" }],
+ },
+];
+
+function bail(message) {
+ return function(err) {
+ if (err) {
+ message += "; " + String(err);
+ }
+ ok(false, message);
+ if (err) {
+ info(String(err));
+ }
+ SimpleTest.finish();
+ };
+}
+
+function ArrayBufferToString(arr) {
+ var str = "";
+ var view = new Uint8Array(arr);
+ for (var i = 0; i < view.length; i++) {
+ str += String.fromCharCode(view[i]);
+ }
+ return str;
+}
+
+function StringToArrayBuffer(str) {
+ var arr = new ArrayBuffer(str.length);
+ var view = new Uint8Array(arr);
+ for (var i = 0; i < str.length; i++) {
+ view[i] = str.charCodeAt(i);
+ }
+ return arr;
+}
+
+function StringToHex(str) {
+ var res = "";
+ for (var i = 0; i < str.length; ++i) {
+ res += ("0" + str.charCodeAt(i).toString(16)).slice(-2);
+ }
+ return res;
+}
+
+function Base64ToHex(str) {
+ var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
+ var res = "";
+ for (var i = 0; i < bin.length; i++) {
+ res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
+ }
+ return res;
+}
+
+function HexToBase64(hex) {
+ var bin = "";
+ for (var i = 0; i < hex.length; i += 2) {
+ bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+ }
+ return window
+ .btoa(bin)
+ .replace(/=/g, "")
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_");
+}
+
+function TimeRangesToString(trs) {
+ var l = trs.length;
+ if (l === 0) {
+ return "-";
+ }
+ var s = "";
+ var i = 0;
+ for (;;) {
+ s += trs.start(i) + "-" + trs.end(i);
+ if (++i === l) {
+ return s;
+ }
+ s += ",";
+ }
+}
+
+function SourceBufferToString(sb) {
+ return (
+ "SourceBuffer{" +
+ "AppendMode=" +
+ (sb.AppendMode || "-") +
+ ", updating=" +
+ (sb.updating ? "true" : "false") +
+ ", buffered=" +
+ TimeRangesToString(sb.buffered) +
+ ", audioTracks=" +
+ (sb.audioTracks ? sb.audioTracks.length : "-") +
+ ", videoTracks=" +
+ (sb.videoTracks ? sb.videoTracks.length : "-") +
+ "}"
+ );
+}
+
+function SourceBufferListToString(sbl) {
+ return "SourceBufferList[" + sbl.map(SourceBufferToString).join(", ") + "]";
+}
+
+function GenerateClearKeyLicense(licenseRequest, keyStore) {
+ var msgStr = ArrayBufferToString(licenseRequest);
+ var msg = JSON.parse(msgStr);
+
+ var keys = [];
+ for (var i = 0; i < msg.kids.length; i++) {
+ var id64 = msg.kids[i];
+ var idHex = Base64ToHex(msg.kids[i]).toLowerCase();
+ var key = keyStore[idHex];
+
+ if (key) {
+ keys.push({
+ kty: "oct",
+ kid: id64,
+ k: HexToBase64(key),
+ });
+ }
+ }
+
+ return new TextEncoder().encode(
+ JSON.stringify({
+ keys,
+ type: msg.type || "temporary",
+ })
+ );
+}
+
+function UpdateSessionFunc(test, token, sessionType, resolve, reject) {
+ return function(ev) {
+ var license = GenerateClearKeyLicense(ev.message, test.keys);
+ Log(
+ token,
+ "sending update message to CDM: " + new TextDecoder().decode(license)
+ );
+ ev.target
+ .update(license)
+ .then(function() {
+ Log(token, "MediaKeySession update ok!");
+ resolve(ev.target);
+ })
+ .catch(function(reason) {
+ reject(`${token} MediaKeySession update failed: ${reason}`);
+ });
+ };
+}
+
+function MaybeCrossOriginURI(test, uri) {
+ if (test.crossOrigin) {
+ return "https://example.com:443/tests/dom/media/test/allowed.sjs?" + uri;
+ }
+ return uri;
+}
+
+function AppendTrack(test, ms, track, token) {
+ return new Promise(function(resolve, reject) {
+ var sb;
+ var curFragment = 0;
+ var fragments = track.fragments;
+ var fragmentFile;
+
+ function addNextFragment() {
+ if (curFragment >= fragments.length) {
+ Log(token, track.name + ": end of track");
+ resolve();
+ return;
+ }
+
+ fragmentFile = MaybeCrossOriginURI(test, fragments[curFragment++]);
+
+ var req = new XMLHttpRequest();
+ req.open("GET", fragmentFile);
+ req.responseType = "arraybuffer";
+
+ req.addEventListener("load", function() {
+ Log(
+ token,
+ track.name + ": fetch of " + fragmentFile + " complete, appending"
+ );
+ sb.appendBuffer(new Uint8Array(req.response));
+ });
+
+ req.addEventListener("error", function() {
+ reject(`${token} - ${track.name}: error fetching ${fragmentFile}`);
+ });
+ req.addEventListener("abort", function() {
+ reject(`${token} - ${track.name}: aborted fetching ${fragmentFile}`);
+ });
+
+ Log(
+ token,
+ track.name +
+ ": addNextFragment() fetching next fragment " +
+ fragmentFile
+ );
+ req.send(null);
+ }
+
+ Log(token, track.name + ": addSourceBuffer(" + track.type + ")");
+ sb = ms.addSourceBuffer(track.type);
+ sb.addEventListener("updateend", function() {
+ Log(
+ token,
+ track.name +
+ ": updateend for " +
+ fragmentFile +
+ ", " +
+ SourceBufferToString(sb)
+ );
+ addNextFragment();
+ });
+
+ addNextFragment();
+ });
+}
+
+//Returns a promise that is resolved when the media element is ready to have
+//its play() function called; when it's loaded MSE fragments.
+function LoadTest(test, elem, token, endOfStream = true) {
+ if (!test.tracks) {
+ ok(false, token + " test does not have a tracks list");
+ return Promise.reject();
+ }
+
+ var ms = new MediaSource();
+ elem.src = URL.createObjectURL(ms);
+ elem.crossOrigin = test.crossOrigin || false;
+
+ return new Promise(function(resolve, reject) {
+ ms.addEventListener(
+ "sourceopen",
+ function() {
+ Log(token, "sourceopen");
+ Promise.all(
+ test.tracks.map(function(track) {
+ return AppendTrack(test, ms, track, token);
+ })
+ )
+ .then(function() {
+ Log(token, "Tracks loaded, calling MediaSource.endOfStream()");
+ if (endOfStream) {
+ ms.endOfStream();
+ }
+ resolve();
+ })
+ .catch(reject);
+ },
+ { once: true }
+ );
+ });
+}
+
+function EMEPromise() {
+ var self = this;
+ self.promise = new Promise(function(resolve, reject) {
+ self.resolve = resolve;
+ self.reject = reject;
+ });
+}
+
+/*
+ * Create a new MediaKeys object.
+ * Return a promise which will be resolved with a new MediaKeys object,
+ * or will be rejected with a string that describes the failure.
+ */
+function CreateMediaKeys(v, test, token) {
+ let p = new EMEPromise();
+
+ function streamType(type) {
+ var x = test.tracks.find(o => o.name == type);
+ return x ? x.type : undefined;
+ }
+
+ function onencrypted(ev) {
+ var options = { initDataTypes: [ev.initDataType] };
+ if (streamType("video")) {
+ options.videoCapabilities = [{ contentType: streamType("video") }];
+ }
+ if (streamType("audio")) {
+ options.audioCapabilities = [{ contentType: streamType("audio") }];
+ }
+ navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, [options]).then(
+ keySystemAccess => {
+ keySystemAccess
+ .createMediaKeys()
+ .then(p.resolve, () =>
+ p.reject(`${token} Failed to create MediaKeys object.`)
+ );
+ },
+ () => p.reject(`${token} Failed to request key system access.`)
+ );
+ }
+
+ v.addEventListener("encrypted", onencrypted, { once: true });
+ return p.promise;
+}
+
+/*
+ * Create a new MediaKeys object and provide it to the media element.
+ * Return a promise which will be resolved if succeeded, or will be rejected
+ * with a string that describes the failure.
+ */
+function CreateAndSetMediaKeys(v, test, token) {
+ let p = new EMEPromise();
+
+ CreateMediaKeys(v, test, token).then(mediaKeys => {
+ v.setMediaKeys(mediaKeys).then(p.resolve, () =>
+ p.reject(`${token} Failed to set MediaKeys on <video> element.`)
+ );
+ }, p.reject);
+
+ return p.promise;
+}
+
+/*
+ * Collect the init data from 'encrypted' events.
+ * Return a promise which will be resolved with the init data when collection
+ * is completed (specified by test.sessionCount).
+ */
+function LoadInitData(v, test, token) {
+ let p = new EMEPromise();
+ let initDataQueue = [];
+
+ // Call SimpleTest._originalSetTimeout() to bypass the flaky timeout checker.
+ let timer = SimpleTest._originalSetTimeout.call(
+ window,
+ () => {
+ p.reject(`${token} Timed out in waiting for the init data.`);
+ },
+ 60000
+ );
+
+ function onencrypted(ev) {
+ initDataQueue.push(ev);
+ Log(
+ token,
+ `got encrypted(${ev.initDataType}, ` +
+ `${StringToHex(ArrayBufferToString(ev.initData))}) event.`
+ );
+ if (test.sessionCount == initDataQueue.length) {
+ p.resolve(initDataQueue);
+ clearTimeout(timer);
+ }
+ }
+
+ v.addEventListener("encrypted", onencrypted);
+ return p.promise;
+}
+
+/*
+ * Generate a license request and update the session.
+ * Return a promsise which will be resolved with the updated session
+ * or rejected with a string that describes the failure.
+ */
+function MakeRequest(test, token, ev, session, sessionType) {
+ sessionType = sessionType || "temporary";
+ let p = new EMEPromise();
+ let str =
+ `session[${session.sessionId}].generateRequest(` +
+ `${ev.initDataType}, ${StringToHex(ArrayBufferToString(ev.initData))})`;
+
+ session.addEventListener(
+ "message",
+ UpdateSessionFunc(test, token, sessionType, p.resolve, p.reject)
+ );
+
+ Log(token, str);
+ session.generateRequest(ev.initDataType, ev.initData).catch(reason => {
+ // Reject the promise if generateRequest() failed.
+ // Otherwise it will be resolved in UpdateSessionFunc().
+ p.reject(`${token}: ${str} failed; ${reason}`);
+ });
+
+ return p.promise;
+}
+
+/*
+ * Process the init data by calling MakeRequest().
+ * Return a promise which will be resolved with the updated sessions
+ * when all init data are processed or rejected if any failure.
+ */
+function ProcessInitData(v, test, token, initData, sessionType) {
+ return Promise.all(
+ initData.map(ev => {
+ let session = v.mediaKeys.createSession(sessionType);
+ return MakeRequest(test, token, ev, session, sessionType);
+ })
+ );
+}
+
+/*
+ * Clean up the |v| element.
+ */
+function CleanUpMedia(v) {
+ v.setMediaKeys(null);
+ v.remove();
+ v.removeAttribute("src");
+ v.load();
+}
+
+/*
+ * Close all sessions and clean up the |v| element.
+ */
+function CloseSessions(v, sessions) {
+ return Promise.all(sessions.map(s => s.close())).then(CleanUpMedia(v));
+}
+
+/*
+ * Set up media keys and source buffers for the media element.
+ * Return a promise resolved when all key sessions are updated or rejected
+ * if any failure.
+ */
+function SetupEME(v, test, token) {
+ let p = new EMEPromise();
+
+ v.onerror = function() {
+ p.reject(`${token} got an error event.`);
+ };
+
+ Promise.all([
+ LoadInitData(v, test, token),
+ CreateAndSetMediaKeys(v, test, token),
+ LoadTest(test, v, token),
+ ])
+ .then(values => {
+ let initData = values[0];
+ return ProcessInitData(v, test, token, initData);
+ })
+ .then(p.resolve, p.reject);
+
+ return p.promise;
+}
+
+function SetupEMEPref(callback) {
+ var prefs = [
+ ["media.mediasource.enabled", true],
+ ["media.mediasource.webm.enabled", true],
+ ];
+
+ if (
+ SpecialPowers.Services.appinfo.name == "B2G" ||
+ !manifestVideo().canPlayType("video/mp4")
+ ) {
+ // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
+ prefs.push(["media.use-blank-decoder", true]);
+ }
+
+ SpecialPowers.pushPrefEnv({ set: prefs }, callback);
+}
+
+function fetchWithXHR(uri, onLoadFunction) {
+ var p = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", uri, true);
+ xhr.responseType = "arraybuffer";
+ xhr.addEventListener("load", function() {
+ is(
+ xhr.status,
+ 200,
+ "fetchWithXHR load uri='" + uri + "' status=" + xhr.status
+ );
+ resolve(xhr.response);
+ });
+ xhr.send();
+ });
+
+ if (onLoadFunction) {
+ p.then(onLoadFunction);
+ }
+
+ return p;
+}
+
+function once(target, name, cb) {
+ var p = new Promise(function(resolve, reject) {
+ target.addEventListener(
+ name,
+ function(arg) {
+ resolve(arg);
+ },
+ { once: true }
+ );
+ });
+ if (cb) {
+ p.then(cb);
+ }
+ return p;
+}
diff --git a/dom/media/test/empty_size.mp3 b/dom/media/test/empty_size.mp3
new file mode 100644
index 0000000000..0c208a2959
--- /dev/null
+++ b/dom/media/test/empty_size.mp3
Binary files differ
diff --git a/dom/media/test/file_access_controls.html b/dom/media/test/file_access_controls.html
new file mode 100644
index 0000000000..2f7bc360ed
--- /dev/null
+++ b/dom/media/test/file_access_controls.html
@@ -0,0 +1,160 @@
+<html>
+<head>
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body onload="setTimeout(load, 0);">
+<script>
+
+// Page URL: http://example.org/tests/dom/media/test/file_access_controls.html
+
+var gResource = getPlayableVideo(gSmallTests).name;
+
+var gTests = [
+ {
+ // Test 0
+ url: "redirect.sjs?domain=example.com&file="+ gResource,
+ result: "error",
+ description: "Won't load when redirected to different domain",
+ },{
+ // Test 1
+ url: "redirect.sjs?domain=example.com&allowed&file=" + gResource,
+ result: "loadeddata",
+ description: "Can load when redirected to different domain with allow-origin",
+ },{
+ // Test 2
+ url: "redirect.sjs?domain=test1.example.org&file=" + gResource,
+ result: "error",
+ description: "Won't load when redirected to subdomain",
+ },{
+ // Test 3
+ url: "redirect.sjs?domain=test1.example.org&allowed&file=" + gResource,
+ result: "loadeddata",
+ description: "Can load when redirected to subdomain with allow-origin",
+ },{
+ // Test 4
+ url: "redirect.sjs?domain=example.org&file=" + gResource,
+ result: "loadeddata",
+ description: "Can load when redirected to same domain",
+ },{
+ // Test 5
+ url: "http://example.org/tests/dom/media/test/" + gResource,
+ result: "loadeddata",
+ description: "Can load from same domain"
+ },{
+ // Test 6
+ url: "http://example.org:8000/tests/dom/media/test/" + gResource,
+ result: "error",
+ description: "Won't load from different port on same domain"
+ },{
+ // Test 7
+ url: "http://example.org:8000/tests/dom/media/test/allowed.sjs?" + gResource,
+ result: "loadeddata",
+ description: "Can load from different port on same domain with allow-origin",
+ },{
+ // Test 8
+ url: "http://example.com/tests/dom/media/test/" + gResource,
+ result: "error",
+ description: "Won't load cross domain",
+ },{
+ // Test 9
+ url: "http://example.com/tests/dom/media/test/allowed.sjs?" + gResource,
+ result: "loadeddata",
+ description: "Can load cross domain with allow-origin",
+ },{
+ // Test 10
+ url: "http://test1.example.org/tests/dom/media/test/allowed.sjs?" + gResource,
+ result: "loadeddata",
+ description: "Can load from subdomain with allow-origin",
+ },{
+ // Test 11
+ url: "http://test1.example.org/tests/dom/media/test/" + gResource,
+ result: "error",
+ description: "Won't load from subdomain",
+ }
+];
+
+var gTestNum = 0;
+var gVideo = null;
+var gTestedRemoved = false;
+
+function eventHandler(event) {
+ //dump((gTestNum - 1) + ": " + event.type + "\n");
+ var video = event.target;
+ opener.postMessage({"result": (event.type == video.expectedResult),
+ "message": video.testDescription + (gTestedRemoved ? " (element not in document)" : " (element in document)")},
+ "http://mochi.test:8888");
+ // Make sure any extra events cause an error
+ video.expectedResult = "<none>";
+ nextTest();
+}
+
+function createVideo() {
+ var v = document.createElement('video');
+ v.addEventListener('loadeddata', eventHandler);
+ v.addEventListener('error', eventHandler);
+ v.crossOrigin = 'anonymous';
+ return v;
+}
+
+function load() {
+ opener.postMessage({"result": (window.location.href == "http://example.org/tests/dom/media/test/file_access_controls.html"),
+ "message": "We must be on a example.org:80"},
+ "http://mochi.test:8888");
+
+ nextTest();
+}
+
+function nextTest() {
+ //dump("nextTest() called, gTestNum="+gTestNum+" gTestedRemoved="+gTestedRemoved+"\n");
+ if (gTestNum == gTests.length) {
+ //dump("gTestNum == gTests.length\n");
+ if (!gTestedRemoved) {
+ // Repeat all tests with element removed from doc, should get same result.
+ gTestedRemoved = true;
+ gTestNum = 0;
+ } else {
+ //dump("Exiting...\n");
+ // We're done, exit the test.
+ done();
+ window.close();
+ return;
+ }
+ }
+
+ if (gVideo) {
+ gVideo.remove();
+ gVideo.removeAttribute("src");
+ gVideo.load();
+ }
+
+ gVideo = null;
+ SpecialPowers.forceGC();
+
+ gVideo = createVideo();
+ gVideo.expectedResult = gTests[gTestNum].result;
+ gVideo.testDescription = gTests[gTestNum].description;
+ // Uniquify the resource URL to ensure that the resources loaded by earlier or subsequent tests
+ // don't overlap with the resources we load here, which are loaded with non-default preferences set.
+ // We also want to make sure that an HTTP fetch actually happens for each testcase.
+ var url = gTests[gTestNum].url;
+ var random = Math.floor(Math.random()*1000000000);
+ url += (url.search(/\?/) < 0 ? "?" : "&") + "rand=" + random;
+ gVideo.src = url;
+ //dump("Starting test " + gTestNum + " at " + gVideo.src + " expecting:" + gVideo.expectedResult + "\n");
+ if (!gTestedRemoved) {
+ document.body.appendChild(gVideo);
+ // Will cause load() to be invoked.
+ } else {
+ gVideo.load();
+ }
+ gTestNum++;
+}
+
+function done() {
+ opener.postMessage({"done": "true"}, "http://mochi.test:8888");
+}
+
+</script>
+</body>
+</html>
+
diff --git a/dom/media/test/file_eme_createMediaKeys.html b/dom/media/test/file_eme_createMediaKeys.html
new file mode 100644
index 0000000000..3ff782b1bf
--- /dev/null
+++ b/dom/media/test/file_eme_createMediaKeys.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Eme createMediaKeys helper page</title>
+<script>
+// This script waits for a message then attempts to requestMediaKeySystemAccess
+// then createMediaKeys. On success posts 'successCreatingMediaKeys' to the
+// source of the message, on failure posts 'failureCreatingMediaKeys' and a
+// description of the failure to the source of the message.
+
+async function createMediaKeys() {
+ const clearKeyOptions = [
+ {
+ initDataTypes: ["webm"],
+ videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }],
+ },
+ ];
+
+ let access = await navigator.requestMediaKeySystemAccess(
+ "org.w3.clearkey",
+ clearKeyOptions
+ );
+
+ return access.createMediaKeys();
+}
+function setupMessageListener() {
+ window.onmessage = async event => {
+ // We don't bother checking the message data since it should always be
+ // telling us to create media keys.
+ try {
+ let keys = await createMediaKeys();
+ if (!keys) {
+ event.source.postMessage("failureCreatingMediaKeys no keys", "*");
+ return;
+ }
+ event.source.postMessage("successCreatingMediaKeys", "*");
+ } catch (e) {
+ event.source.postMessage(`failureCreatingMediaKeys ${e}`, "*");
+ }
+ };
+}
+window.onload = setupMessageListener;
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/media/test/flac-noheader-s16.flac b/dom/media/test/flac-noheader-s16.flac
new file mode 100644
index 0000000000..01152142a9
--- /dev/null
+++ b/dom/media/test/flac-noheader-s16.flac
Binary files differ
diff --git a/dom/media/test/flac-noheader-s16.flac^headers^ b/dom/media/test/flac-noheader-s16.flac^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/flac-noheader-s16.flac^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/flac-s24.flac b/dom/media/test/flac-s24.flac
new file mode 100644
index 0000000000..1ba5e27a15
--- /dev/null
+++ b/dom/media/test/flac-s24.flac
Binary files differ
diff --git a/dom/media/test/flac-s24.flac^headers^ b/dom/media/test/flac-s24.flac^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/flac-s24.flac^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/flac-sample-cenc.mp4 b/dom/media/test/flac-sample-cenc.mp4
new file mode 100644
index 0000000000..c89190387a
--- /dev/null
+++ b/dom/media/test/flac-sample-cenc.mp4
Binary files differ
diff --git a/dom/media/test/flac-sample-cenc.mp4^headers^ b/dom/media/test/flac-sample-cenc.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/flac-sample-cenc.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/flac-sample.mp4 b/dom/media/test/flac-sample.mp4
new file mode 100644
index 0000000000..d39c94a7b2
--- /dev/null
+++ b/dom/media/test/flac-sample.mp4
Binary files differ
diff --git a/dom/media/test/flac-sample.mp4^headers^ b/dom/media/test/flac-sample.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/flac-sample.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/fragment_noplay.js b/dom/media/test/fragment_noplay.js
new file mode 100644
index 0000000000..247641763f
--- /dev/null
+++ b/dom/media/test/fragment_noplay.js
@@ -0,0 +1,19 @@
+function test_fragment_noplay(v, start, end, is, ok, finish) {
+ function onLoadedMetadata() {
+ var s = start == null ? 0 : start;
+ var e = end == null ? v.duration : end;
+ var a = s - 0.15;
+ var b = s + 0.15;
+ ok(
+ v.currentTime >= a && v.currentTime <= b,
+ "loadedmetadata currentTime is " + a + " < " + v.currentTime + " < " + b
+ );
+ ok(
+ v.mozFragmentEnd == e,
+ "mozFragmentEnd (" + v.mozFragmentEnd + ") == end Time (" + e + ")"
+ );
+ finish();
+ }
+
+ v.addEventListener("loadedmetadata", onLoadedMetadata);
+}
diff --git a/dom/media/test/fragment_play.js b/dom/media/test/fragment_play.js
new file mode 100644
index 0000000000..ec0fe7952a
--- /dev/null
+++ b/dom/media/test/fragment_play.js
@@ -0,0 +1,92 @@
+function test_fragment_play(v, start, end, is, ok, finish) {
+ var completed = false;
+ var loadedMetadataRaised = false;
+ var seekedRaised = false;
+ var pausedRaised = false;
+
+ function onLoadedMetadata() {
+ var s = start == null ? 0 : start;
+ var e = end == null ? v.duration : end;
+ ok(
+ v.currentTime == s,
+ "loadedmetadata currentTime is " + v.currentTime + " != " + s
+ );
+ ok(
+ v.mozFragmentEnd == e,
+ "mozFragmentEnd (" + v.mozFragmentEnd + ") == end Time (" + e + ")"
+ );
+ loadedMetadataRaised = true;
+ v.play();
+ }
+
+ function onSeeked() {
+ if (completed) {
+ return;
+ }
+
+ var s = start == null ? 0 : start;
+ ok(
+ v.currentTime - s < 0.1,
+ "seeked currentTime is " +
+ v.currentTime +
+ " != " +
+ s +
+ " (fuzzy compare +-0.1)"
+ );
+
+ seekedRaised = true;
+ }
+
+ function onTimeUpdate() {
+ if (completed) {
+ return;
+ }
+
+ v._lastTimeUpdate = v.currentTime;
+ }
+
+ function onPause() {
+ if (completed) {
+ return;
+ }
+
+ var e = end == null ? v.duration : end;
+ var a = e - 0.05;
+ var b = e + 0.05;
+ ok(
+ v.currentTime >= a && v.currentTime <= b,
+ "paused currentTime is " +
+ a +
+ " < " +
+ v.currentTime +
+ " < " +
+ b +
+ " ? " +
+ v._lastTimeUpdate
+ );
+ pausedRaised = true;
+ v.play();
+ }
+
+ function onEnded() {
+ if (completed) {
+ return;
+ }
+
+ completed = true;
+ ok(loadedMetadataRaised, "loadedmetadata event");
+ if (start) {
+ ok(seekedRaised, "seeked event");
+ }
+ if (end) {
+ ok(pausedRaised, "paused event: " + end + " " + v.duration);
+ }
+ finish();
+ }
+
+ v.addEventListener("ended", onEnded);
+ v.addEventListener("loadedmetadata", onLoadedMetadata);
+ v.addEventListener("seeked", onSeeked);
+ v.addEventListener("pause", onPause);
+ v.addEventListener("timeupdate", onTimeUpdate);
+}
diff --git a/dom/media/test/gUM_support.js b/dom/media/test/gUM_support.js
new file mode 100644
index 0000000000..2be885abf4
--- /dev/null
+++ b/dom/media/test/gUM_support.js
@@ -0,0 +1,106 @@
+// Support script for test that use getUserMedia. This allows explicit
+// configuration of prefs which affect gUM. See also
+// `testing/mochitest/runtests.py` for how the harness configures values.
+
+// Setup preconditions for tests using getUserMedia. This functions helps
+// manage different prefs that affect gUM calls in tests and makes explicit
+// the expected state before test runs.
+async function pushGetUserMediaTestPrefs({
+ fakeAudio = false,
+ fakeVideo = false,
+ loopbackAudio = false,
+ loopbackVideo = false,
+}) {
+ // Make sure we have sensical arguments
+ if (!fakeAudio && !loopbackAudio) {
+ throw new Error(
+ "pushGetUserMediaTestPrefs: Should have fake or loopback audio!"
+ );
+ } else if (fakeAudio && loopbackAudio) {
+ throw new Error(
+ "pushGetUserMediaTestPrefs: Should not have both fake and loopback audio!"
+ );
+ }
+ if (!fakeVideo && !loopbackVideo) {
+ throw new Error(
+ "pushGetUserMediaTestPrefs: Should have fake or loopback video!"
+ );
+ } else if (fakeVideo && loopbackVideo) {
+ throw new Error(
+ "pushGetUserMediaTestPrefs: Should not have both fake and loopback video!"
+ );
+ }
+
+ let testPrefs = [];
+ if (fakeAudio) {
+ // Unset the loopback device so it doesn't take precedence
+ testPrefs.push(["media.audio_loopback_dev", ""]);
+ // Setup fake streams pref
+ testPrefs.push(["media.navigator.streams.fake", true]);
+ }
+ if (loopbackAudio) {
+ // If audio loopback is requested we expect the test harness to have set
+ // the loopback device pref, make sure it's set
+ let audioLoopDev = SpecialPowers.getCharPref(
+ "media.audio_loopback_dev",
+ ""
+ );
+ if (!audioLoopDev) {
+ throw new Error(
+ "pushGetUserMediaTestPrefs: Loopback audio requested but " +
+ "media.audio_loopback_dev does not appear to be set!"
+ );
+ }
+ }
+ if (fakeVideo) {
+ // Unset the loopback device so it doesn't take precedence
+ testPrefs.push(["media.video_loopback_dev", ""]);
+ // Setup fake streams pref
+ testPrefs.push(["media.navigator.streams.fake", true]);
+ }
+ if (loopbackVideo) {
+ // If video loopback is requested we expect the test harness to have set
+ // the loopback device pref, make sure it's set
+ let videoLoopDev = SpecialPowers.getCharPref(
+ "media.video_loopback_dev",
+ ""
+ );
+ if (!videoLoopDev) {
+ throw new Error(
+ "pushGetUserMediaTestPrefs: Loopback video requested but " +
+ "media.video_loopback_dev does not appear to be set!"
+ );
+ }
+ }
+ if (loopbackAudio || loopbackVideo) {
+ // Prevent gUM permission prompt. Since loopback devices are considered
+ // real devices we need to set prefs so the gUM prompt isn't presented.
+ testPrefs.push(["media.navigator.permission.disabled", true]);
+ }
+ return SpecialPowers.pushPrefEnv({ set: testPrefs });
+}
+
+// Setup preconditions for tests using getUserMedia. This function will
+// configure prefs to select loopback device(s) if it can find loopback device
+// names already set in the prefs. If no loopback device name can be found then
+// prefs are setup such that a fake device is used.
+async function setupGetUserMediaTestPrefs() {
+ let prefRequests = {};
+ let audioLoopDev = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (audioLoopDev) {
+ prefRequests.fakeAudio = false;
+ prefRequests.loopbackAudio = true;
+ } else {
+ prefRequests.fakeAudio = true;
+ prefRequests.loopbackAudio = false;
+ }
+ let videoLoopDev = SpecialPowers.getCharPref("media.video_loopback_dev", "");
+ if (videoLoopDev) {
+ prefRequests.fakeVideo = false;
+ prefRequests.loopbackVideo = true;
+ } else {
+ prefRequests.fakeVideo = true;
+ prefRequests.loopbackVideo = false;
+ }
+ return pushGetUserMediaTestPrefs(prefRequests);
+}
diff --git a/dom/media/test/gizmo-frag.mp4 b/dom/media/test/gizmo-frag.mp4
new file mode 100644
index 0000000000..f6980663c2
--- /dev/null
+++ b/dom/media/test/gizmo-frag.mp4
Binary files differ
diff --git a/dom/media/test/gizmo-noaudio.mp4 b/dom/media/test/gizmo-noaudio.mp4
new file mode 100644
index 0000000000..24732a4064
--- /dev/null
+++ b/dom/media/test/gizmo-noaudio.mp4
Binary files differ
diff --git a/dom/media/test/gizmo-noaudio.mp4^headers^ b/dom/media/test/gizmo-noaudio.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/gizmo-noaudio.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/gizmo-noaudio.webm b/dom/media/test/gizmo-noaudio.webm
new file mode 100644
index 0000000000..9f412cb6e3
--- /dev/null
+++ b/dom/media/test/gizmo-noaudio.webm
Binary files differ
diff --git a/dom/media/test/gizmo-noaudio.webm^headers^ b/dom/media/test/gizmo-noaudio.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/gizmo-noaudio.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/gizmo-short.mp4 b/dom/media/test/gizmo-short.mp4
new file mode 100644
index 0000000000..f8caec741e
--- /dev/null
+++ b/dom/media/test/gizmo-short.mp4
Binary files differ
diff --git a/dom/media/test/gizmo-short.mp4^headers^ b/dom/media/test/gizmo-short.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/gizmo-short.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/gizmo.mp4 b/dom/media/test/gizmo.mp4
new file mode 100644
index 0000000000..87efad5ade
--- /dev/null
+++ b/dom/media/test/gizmo.mp4
Binary files differ
diff --git a/dom/media/test/gizmo.mp4^headers^ b/dom/media/test/gizmo.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/gizmo.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/gizmo.webm b/dom/media/test/gizmo.webm
new file mode 100644
index 0000000000..518531a93f
--- /dev/null
+++ b/dom/media/test/gizmo.webm
Binary files differ
diff --git a/dom/media/test/gizmo.webm^headers^ b/dom/media/test/gizmo.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/gizmo.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/gzipped_mp4.sjs b/dom/media/test/gzipped_mp4.sjs
new file mode 100644
index 0000000000..132b87e851
--- /dev/null
+++ b/dom/media/test/gzipped_mp4.sjs
@@ -0,0 +1,27 @@
+function getGzippedFileBytes()
+{
+ var file;
+ getObjectState("SERVER_ROOT", function(serverRoot) {
+ file = serverRoot.getFile("tests/dom/media/test/short.mp4.gz");
+ });
+ var fileInputStream =
+ Components.classes['@mozilla.org/network/file-input-stream;1']
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ var binaryInputStream =
+ Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ fileInputStream.init(file, -1, -1, 0);
+ binaryInputStream.setInputStream(fileInputStream);
+ return binaryInputStream.readBytes(binaryInputStream.available());
+}
+
+function handleRequest(request, response)
+{
+ var bytes = getGzippedFileBytes();
+ response.setHeader("Content-Length", String(bytes.length), false);
+ response.setHeader("Content-Type", "video/mp4", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(bytes, bytes.length);
+}
diff --git a/dom/media/test/hls/400x300_prog_index.m3u8 b/dom/media/test/hls/400x300_prog_index.m3u8
new file mode 100644
index 0000000000..3252eb178f
--- /dev/null
+++ b/dom/media/test/hls/400x300_prog_index.m3u8
@@ -0,0 +1,10 @@
+#EXTM3U
+#EXT-X-TARGETDURATION:10
+#EXT-X-VERSION:3
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXTINF:9.97667,
+400x300_seg0.ts
+#EXTINF:9.97667,
+400x300_seg1.ts
+#EXT-X-ENDLIST
diff --git a/dom/media/test/hls/400x300_prog_index_5s.m3u8 b/dom/media/test/hls/400x300_prog_index_5s.m3u8
new file mode 100644
index 0000000000..8e9d19f764
--- /dev/null
+++ b/dom/media/test/hls/400x300_prog_index_5s.m3u8
@@ -0,0 +1,8 @@
+#EXTM3U
+#EXT-X-TARGETDURATION:4.00
+#EXT-X-VERSION:3
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXTINF:4.00,
+400x300_seg0_5s.ts
+#EXT-X-ENDLIST
diff --git a/dom/media/test/hls/400x300_seg0.ts b/dom/media/test/hls/400x300_seg0.ts
new file mode 100644
index 0000000000..b17b0a88ff
--- /dev/null
+++ b/dom/media/test/hls/400x300_seg0.ts
Binary files differ
diff --git a/dom/media/test/hls/400x300_seg0_5s.ts b/dom/media/test/hls/400x300_seg0_5s.ts
new file mode 100644
index 0000000000..9504d5d889
--- /dev/null
+++ b/dom/media/test/hls/400x300_seg0_5s.ts
Binary files differ
diff --git a/dom/media/test/hls/400x300_seg1.ts b/dom/media/test/hls/400x300_seg1.ts
new file mode 100644
index 0000000000..d751091e68
--- /dev/null
+++ b/dom/media/test/hls/400x300_seg1.ts
Binary files differ
diff --git a/dom/media/test/hls/416x243_prog_index_5s.m3u8 b/dom/media/test/hls/416x243_prog_index_5s.m3u8
new file mode 100644
index 0000000000..ae518e7890
--- /dev/null
+++ b/dom/media/test/hls/416x243_prog_index_5s.m3u8
@@ -0,0 +1,8 @@
+#EXTM3U
+#EXT-X-TARGETDURATION:4.04
+#EXT-X-VERSION:4
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXTINF:4.04,
+416x243_seg0_5s.ts
+#EXT-X-ENDLIST
diff --git a/dom/media/test/hls/416x243_seg0_5s.ts b/dom/media/test/hls/416x243_seg0_5s.ts
new file mode 100644
index 0000000000..48e5473276
--- /dev/null
+++ b/dom/media/test/hls/416x243_seg0_5s.ts
Binary files differ
diff --git a/dom/media/test/hls/640x480_prog_index.m3u8 b/dom/media/test/hls/640x480_prog_index.m3u8
new file mode 100644
index 0000000000..ac1212e2e7
--- /dev/null
+++ b/dom/media/test/hls/640x480_prog_index.m3u8
@@ -0,0 +1,10 @@
+#EXTM3U
+#EXT-X-TARGETDURATION:10
+#EXT-X-VERSION:3
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXTINF:9.97667,
+640x480_seg0.ts
+#EXTINF:9.97667,
+640x480_seg1.ts
+#EXT-X-ENDLIST
diff --git a/dom/media/test/hls/640x480_seg0.ts b/dom/media/test/hls/640x480_seg0.ts
new file mode 100644
index 0000000000..9bf0f0454a
--- /dev/null
+++ b/dom/media/test/hls/640x480_seg0.ts
Binary files differ
diff --git a/dom/media/test/hls/640x480_seg1.ts b/dom/media/test/hls/640x480_seg1.ts
new file mode 100644
index 0000000000..c1ed938f44
--- /dev/null
+++ b/dom/media/test/hls/640x480_seg1.ts
Binary files differ
diff --git a/dom/media/test/hls/960x720_prog_index.m3u8 b/dom/media/test/hls/960x720_prog_index.m3u8
new file mode 100644
index 0000000000..8ff18b089c
--- /dev/null
+++ b/dom/media/test/hls/960x720_prog_index.m3u8
@@ -0,0 +1,10 @@
+#EXTM3U
+#EXT-X-TARGETDURATION:10
+#EXT-X-VERSION:3
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXTINF:9.97667,
+960x720_seg0.ts
+#EXTINF:9.97667,
+960x720_seg1.ts
+#EXT-X-ENDLIST
diff --git a/dom/media/test/hls/960x720_seg0.ts b/dom/media/test/hls/960x720_seg0.ts
new file mode 100644
index 0000000000..031bfe30d6
--- /dev/null
+++ b/dom/media/test/hls/960x720_seg0.ts
Binary files differ
diff --git a/dom/media/test/hls/960x720_seg1.ts b/dom/media/test/hls/960x720_seg1.ts
new file mode 100644
index 0000000000..63f15cb0cb
--- /dev/null
+++ b/dom/media/test/hls/960x720_seg1.ts
Binary files differ
diff --git a/dom/media/test/hls/bipbop_16x9_single.m3u8 b/dom/media/test/hls/bipbop_16x9_single.m3u8
new file mode 100644
index 0000000000..dce6a76c7b
--- /dev/null
+++ b/dom/media/test/hls/bipbop_16x9_single.m3u8
@@ -0,0 +1,5 @@
+#EXTM3U
+
+#EXT-X-STREAM-INF:BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d"
+416x243_prog_index_5s.m3u8
+
diff --git a/dom/media/test/hls/bipbop_4x3_single.m3u8 b/dom/media/test/hls/bipbop_4x3_single.m3u8
new file mode 100644
index 0000000000..8f354ff011
--- /dev/null
+++ b/dom/media/test/hls/bipbop_4x3_single.m3u8
@@ -0,0 +1,4 @@
+#EXTM3U
+
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=232370,CODECS="mp4a.40.2, avc1.4d4015"
+400x300_prog_index_5s.m3u8 \ No newline at end of file
diff --git a/dom/media/test/hls/bipbop_4x3_variant.m3u8 b/dom/media/test/hls/bipbop_4x3_variant.m3u8
new file mode 100644
index 0000000000..8a9a100dba
--- /dev/null
+++ b/dom/media/test/hls/bipbop_4x3_variant.m3u8
@@ -0,0 +1,10 @@
+#EXTM3U
+
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=232370,CODECS="mp4a.40.2, avc1.4d4015"
+400x300_prog_index.m3u8
+
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=649879,CODECS="mp4a.40.2, avc1.4d401e"
+640x480_prog_index.m3u8
+
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=649879,CODECS="mp4a.40.2, avc1.4d401e"
+960x720_prog_index.m3u8
diff --git a/dom/media/test/huge-id3.mp3 b/dom/media/test/huge-id3.mp3
new file mode 100644
index 0000000000..41cb93d805
--- /dev/null
+++ b/dom/media/test/huge-id3.mp3
Binary files differ
diff --git a/dom/media/test/huge-id3.mp3^headers^ b/dom/media/test/huge-id3.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/huge-id3.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/id3tags.mp3 b/dom/media/test/id3tags.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/dom/media/test/id3tags.mp3
Binary files differ
diff --git a/dom/media/test/id3tags.mp3^headers^ b/dom/media/test/id3tags.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/id3tags.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-cmap-s0c0.opus b/dom/media/test/invalid-cmap-s0c0.opus
new file mode 100644
index 0000000000..0b99587865
--- /dev/null
+++ b/dom/media/test/invalid-cmap-s0c0.opus
Binary files differ
diff --git a/dom/media/test/invalid-cmap-s0c0.opus^headers^ b/dom/media/test/invalid-cmap-s0c0.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-cmap-s0c0.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-cmap-s0c2.opus b/dom/media/test/invalid-cmap-s0c2.opus
new file mode 100644
index 0000000000..a921894fee
--- /dev/null
+++ b/dom/media/test/invalid-cmap-s0c2.opus
Binary files differ
diff --git a/dom/media/test/invalid-cmap-s0c2.opus^headers^ b/dom/media/test/invalid-cmap-s0c2.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-cmap-s0c2.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-cmap-s1c2.opus b/dom/media/test/invalid-cmap-s1c2.opus
new file mode 100644
index 0000000000..95a84f523c
--- /dev/null
+++ b/dom/media/test/invalid-cmap-s1c2.opus
Binary files differ
diff --git a/dom/media/test/invalid-cmap-s1c2.opus^headers^ b/dom/media/test/invalid-cmap-s1c2.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-cmap-s1c2.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-cmap-short.opus b/dom/media/test/invalid-cmap-short.opus
new file mode 100644
index 0000000000..fcd7eb506a
--- /dev/null
+++ b/dom/media/test/invalid-cmap-short.opus
Binary files differ
diff --git a/dom/media/test/invalid-cmap-short.opus^headers^ b/dom/media/test/invalid-cmap-short.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-cmap-short.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-discard_on_multi_blocks.webm b/dom/media/test/invalid-discard_on_multi_blocks.webm
new file mode 100644
index 0000000000..f39ab5bcb8
--- /dev/null
+++ b/dom/media/test/invalid-discard_on_multi_blocks.webm
Binary files differ
diff --git a/dom/media/test/invalid-discard_on_multi_blocks.webm^headers^ b/dom/media/test/invalid-discard_on_multi_blocks.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-discard_on_multi_blocks.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-excess_discard.webm b/dom/media/test/invalid-excess_discard.webm
new file mode 100644
index 0000000000..5b34aca1a7
--- /dev/null
+++ b/dom/media/test/invalid-excess_discard.webm
Binary files differ
diff --git a/dom/media/test/invalid-excess_discard.webm^headers^ b/dom/media/test/invalid-excess_discard.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-excess_discard.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-excess_neg_discard.webm b/dom/media/test/invalid-excess_neg_discard.webm
new file mode 100644
index 0000000000..2bfad6ed21
--- /dev/null
+++ b/dom/media/test/invalid-excess_neg_discard.webm
Binary files differ
diff --git a/dom/media/test/invalid-excess_neg_discard.webm^headers^ b/dom/media/test/invalid-excess_neg_discard.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-excess_neg_discard.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-m0c0.opus b/dom/media/test/invalid-m0c0.opus
new file mode 100644
index 0000000000..86555f60eb
--- /dev/null
+++ b/dom/media/test/invalid-m0c0.opus
Binary files differ
diff --git a/dom/media/test/invalid-m0c0.opus^headers^ b/dom/media/test/invalid-m0c0.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-m0c0.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-m0c3.opus b/dom/media/test/invalid-m0c3.opus
new file mode 100644
index 0000000000..2c681a8c03
--- /dev/null
+++ b/dom/media/test/invalid-m0c3.opus
Binary files differ
diff --git a/dom/media/test/invalid-m0c3.opus^headers^ b/dom/media/test/invalid-m0c3.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-m0c3.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-m1c0.opus b/dom/media/test/invalid-m1c0.opus
new file mode 100644
index 0000000000..c7728f79ee
--- /dev/null
+++ b/dom/media/test/invalid-m1c0.opus
Binary files differ
diff --git a/dom/media/test/invalid-m1c0.opus^headers^ b/dom/media/test/invalid-m1c0.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-m1c0.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-m1c9.opus b/dom/media/test/invalid-m1c9.opus
new file mode 100644
index 0000000000..1ef6e9f9cf
--- /dev/null
+++ b/dom/media/test/invalid-m1c9.opus
Binary files differ
diff --git a/dom/media/test/invalid-m1c9.opus^headers^ b/dom/media/test/invalid-m1c9.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-m1c9.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-m2c0.opus b/dom/media/test/invalid-m2c0.opus
new file mode 100644
index 0000000000..5c3f97e2ab
--- /dev/null
+++ b/dom/media/test/invalid-m2c0.opus
Binary files differ
diff --git a/dom/media/test/invalid-m2c0.opus^headers^ b/dom/media/test/invalid-m2c0.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-m2c0.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-m2c1.opus b/dom/media/test/invalid-m2c1.opus
new file mode 100644
index 0000000000..5ecb95ee25
--- /dev/null
+++ b/dom/media/test/invalid-m2c1.opus
Binary files differ
diff --git a/dom/media/test/invalid-m2c1.opus^headers^ b/dom/media/test/invalid-m2c1.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-m2c1.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-neg_discard.webm b/dom/media/test/invalid-neg_discard.webm
new file mode 100644
index 0000000000..3f665c0b59
--- /dev/null
+++ b/dom/media/test/invalid-neg_discard.webm
Binary files differ
diff --git a/dom/media/test/invalid-neg_discard.webm^headers^ b/dom/media/test/invalid-neg_discard.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-neg_discard.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/invalid-preskip.webm b/dom/media/test/invalid-preskip.webm
new file mode 100644
index 0000000000..99b4f2ca71
--- /dev/null
+++ b/dom/media/test/invalid-preskip.webm
Binary files differ
diff --git a/dom/media/test/invalid-preskip.webm^headers^ b/dom/media/test/invalid-preskip.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/invalid-preskip.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/make-headers.sh b/dom/media/test/make-headers.sh
new file mode 100644
index 0000000000..35d9bd90f8
--- /dev/null
+++ b/dom/media/test/make-headers.sh
@@ -0,0 +1,18 @@
+# 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/.
+
+# Script to generate ^header^ files for all media files we use.
+# This is to ensure that our media files are not cached by necko,
+# so that our detection as to whether the server supports byte range
+# requests is not interferred with by Necko's cache. See bug 977398
+# for details. Necko will fix this in bug 977314.
+
+FILES=(`ls *.ogg *.ogv *.webm *.mp3 *.opus *.mp4 *.m4s *.wav`)
+
+rm -f *.ogg^headers^ *.ogv^headers^ *.webm^headers^ *.mp3^headers^ *.opus^headers^ *.mp4^headers^ *.m4s^headers^ *.wav^headers^
+
+for i in "${FILES[@]}"
+do
+ echo "Cache-Control: no-store" >> $i^headers^
+done
diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js
new file mode 100644
index 0000000000..0d8d5b9300
--- /dev/null
+++ b/dom/media/test/manifest.js
@@ -0,0 +1,2266 @@
+// In each list of tests below, test file types that are not supported should
+// be ignored. To make sure tests respect that, we include a file of type
+// "bogus/duh" in each list.
+
+// Make sure to not touch navigator in here, since we want to push prefs that
+// will affect the APIs it exposes, but the set of exposed APIs is determined
+// when Navigator.prototype is created. So if we touch navigator before pushing
+// the prefs, the APIs it exposes will not take those prefs into account. We
+// work around this by using a navigator object from a different global for our
+// UA string testing.
+var gManifestNavigatorSource = document.documentElement.appendChild(
+ document.createElement("iframe")
+);
+gManifestNavigatorSource.style.display = "none";
+function manifestNavigator() {
+ return gManifestNavigatorSource.contentWindow.navigator;
+}
+
+// Similarly, use a <video> element from a different global for canPlayType or
+// other feature testing. If we used one from our global and did so before our
+// prefs are pushed, then we'd instantiate HTMLMediaElement.prototype before the
+// prefs are pushed and APIs we expect to be on that object would not be there.
+function manifestVideo() {
+ return gManifestNavigatorSource.contentDocument.createElement("video");
+}
+
+// Need to get the server url composed with ip:port instead of mochi.test.
+// Since we will provide the url to Exoplayer which cannot recognize the domain
+// name "mochi.test".
+let serverUrl = SpecialPowers.Services.prefs.getCharPref(
+ "media.hls.server.url"
+);
+var gHLSTests = [
+ {
+ name: serverUrl + "/bipbop_4x3_variant.m3u8",
+ type: "audio/x-mpegurl",
+ duration: 20.0,
+ },
+];
+
+// These are small test files, good for just seeing if something loads. We
+// really only need one test file per backend here.
+var gSmallTests = [
+ { name: "small-shot.ogg", type: "audio/ogg", duration: 0.276 },
+ { name: "small-shot.m4a", type: "audio/mp4", duration: 0.29 },
+ { name: "small-shot.mp3", type: "audio/mpeg", duration: 0.27 },
+ { name: "small-shot-mp3.mp4", type: "audio/mp4; codecs=mp3", duration: 0.34 },
+ { name: "small-shot.flac", type: "audio/flac", duration: 0.197 },
+ { name: "r11025_s16_c1-short.wav", type: "audio/x-wav", duration: 0.37 },
+ {
+ name: "320x240.ogv",
+ type: "video/ogg",
+ width: 320,
+ height: 240,
+ duration: 0.266,
+ contentDuration: 0.133,
+ },
+ {
+ name: "seek-short.webm",
+ type: "video/webm",
+ width: 320,
+ height: 240,
+ duration: 0.23,
+ },
+ {
+ name: "vp9-short.webm",
+ type: "video/webm",
+ width: 320,
+ height: 240,
+ duration: 0.2,
+ },
+ {
+ name: "detodos-short.opus",
+ type: "audio/ogg; codecs=opus",
+ duration: 0.22,
+ },
+ {
+ name: "gizmo-short.mp4",
+ type: "video/mp4",
+ width: 560,
+ height: 320,
+ duration: 0.27,
+ },
+ { name: "flac-s24.flac", type: "audio/flac", duration: 4.04 },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+var gFrameCountTests = [
+ { name: "bipbop.mp4", type: "video/mp4", totalFrameCount: 297 },
+ { name: "gizmo.mp4", type: "video/mp4", totalFrameCount: 166 },
+ { name: "seek-short.webm", type: "video/webm", totalFrameCount: 8 },
+ { name: "seek.webm", type: "video/webm", totalFrameCount: 120 },
+ { name: "320x240.ogv", type: "video/ogg", totalFrameCount: 8 },
+ { name: "av1.mp4", type: "video/mp4", totalFrameCount: 24 },
+];
+
+gSmallTests = gSmallTests.concat([
+ { name: "sample.3gp", type: "video/3gpp", duration: 4.933 },
+ { name: "sample.3g2", type: "video/3gpp2", duration: 4.933 },
+]);
+
+// Used by test_bug654550.html, for videoStats preference
+var gVideoTests = [
+ {
+ name: "320x240.ogv",
+ type: "video/ogg",
+ width: 320,
+ height: 240,
+ duration: 0.266,
+ },
+ {
+ name: "seek-short.webm",
+ type: "video/webm",
+ width: 320,
+ height: 240,
+ duration: 0.23,
+ },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+// Temp hack for trackIDs and captureStream() -- bug 1215769
+var gLongerTests = [
+ {
+ name: "seek.webm",
+ type: "video/webm",
+ width: 320,
+ height: 240,
+ duration: 3.966,
+ },
+ {
+ name: "gizmo.mp4",
+ type: "video/mp4",
+ width: 560,
+ height: 320,
+ duration: 5.56,
+ },
+];
+
+// Used by test_progress to ensure we get the correct progress information
+// during resource download.
+var gProgressTests = [
+ { name: "r11025_u8_c1.wav", type: "audio/x-wav", duration: 1.0, size: 11069 },
+ { name: "big-short.wav", type: "audio/x-wav", duration: 1.11, size: 12366 },
+ { name: "seek-short.ogv", type: "video/ogg", duration: 1.03, size: 79921 },
+ {
+ name: "320x240.ogv",
+ type: "video/ogg",
+ width: 320,
+ height: 240,
+ duration: 0.266,
+ size: 28942,
+ },
+ { name: "seek-short.webm", type: "video/webm", duration: 0.23, size: 19267 },
+ { name: "gizmo-short.mp4", type: "video/mp4", duration: 0.27, size: 29905 },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+// Used by test_played.html
+var gPlayedTests = [
+ { name: "big-short.wav", type: "audio/x-wav", duration: 1.11 },
+ { name: "seek-short.ogv", type: "video/ogg", duration: 1.03 },
+ { name: "seek-short.webm", type: "video/webm", duration: 0.23 },
+ { name: "gizmo-short.mp4", type: "video/mp4", duration: 0.27 },
+ { name: "owl-short.mp3", type: "audio/mpeg", duration: 0.52 },
+ { name: "very-short.mp3", type: "audio/mpeg", duration: 0.07 },
+ // Disable vbr.mp3 to see if it reduces the error of AUDCLNT_E_CPUUSAGE_EXCEEDED.
+ // See bug 1110922 comment 26.
+ //{ name:"vbr.mp3", type:"audio/mpeg", duration:10.0 },
+ { name: "bug495794.ogg", type: "audio/ogg", duration: 0.3 },
+];
+
+if (
+ manifestNavigator().userAgent.includes("Windows") &&
+ manifestVideo().canPlayType('video/mp4; codecs="avc1.42E01E"')
+) {
+ gPlayedTests = gPlayedTests.concat(
+ { name: "red-46x48.mp4", type: "video/mp4", duration: 1.0 },
+ { name: "red-48x46.mp4", type: "video/mp4", duration: 1.0 }
+ );
+}
+
+// Used by test_mozLoadFrom. Need one test file per decoder backend, plus
+// anything for testing clone-specific bugs.
+var cloneKey = Math.floor(Math.random() * 100000000);
+var gCloneTests = [
+ // short-video is more like 1s, so if you load this twice you'll get an unexpected duration
+ {
+ name:
+ "dynamic_resource.sjs?key=" +
+ cloneKey +
+ "&res1=320x240.ogv&res2=short-video.ogv",
+ type: "video/ogg",
+ duration: 0.266,
+ },
+];
+
+// Used by test_play_twice. Need one test file per decoder backend, plus
+// anything for testing bugs that occur when replying a played file.
+var gReplayTests = gSmallTests.concat([
+ { name: "bug533822.ogg", type: "audio/ogg" },
+]);
+
+// Used by test_paused_after_ended. Need one test file per decoder backend, plus
+// anything for testing bugs that occur when replying a played file.
+var gPausedAfterEndedTests = gSmallTests.concat([
+ { name: "r11025_u8_c1.wav", type: "audio/x-wav", duration: 1.0 },
+ { name: "small-shot.ogg", type: "video/ogg", duration: 0.276 },
+]);
+
+// Test the mozHasAudio property, and APIs that detect different kinds of
+// tracks
+var gTrackTests = [
+ {
+ name: "big-short.wav",
+ type: "audio/x-wav",
+ duration: 1.11,
+ size: 12366,
+ hasAudio: true,
+ hasVideo: false,
+ },
+ {
+ name: "320x240.ogv",
+ type: "video/ogg",
+ width: 320,
+ height: 240,
+ duration: 0.266,
+ size: 28942,
+ hasAudio: false,
+ hasVideo: true,
+ },
+ {
+ name: "short-video.ogv",
+ type: "video/ogg",
+ duration: 1.081,
+ hasAudio: true,
+ hasVideo: true,
+ },
+ {
+ name: "seek-short.webm",
+ type: "video/webm",
+ duration: 0.23,
+ size: 19267,
+ hasAudio: false,
+ hasVideo: true,
+ },
+ {
+ name: "flac-s24.flac",
+ type: "audio/flac",
+ duration: 4.04,
+ hasAudio: true,
+ hasVideo: false,
+ },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+var gClosingConnectionsTest = [
+ { name: "seek-short.ogv", type: "video/ogg", duration: 1.03 },
+];
+
+// Used by any media recorder test. Need one test file per decoder backend
+// currently supported by the media encoder.
+var gMediaRecorderTests = [
+ // Duration should be greater than 500ms because we will record 2
+ // time slices (250ms per slice)
+ {
+ name: "detodos-recorder-test.opus",
+ type: "audio/ogg; codecs=opus",
+ duration: 0.62,
+ },
+];
+
+// Used by video media recorder tests
+var gMediaRecorderVideoTests = [
+ {
+ name: "seek-short.webm",
+ type: "video/webm",
+ width: 320,
+ height: 240,
+ duration: 0.23,
+ },
+];
+
+// These are files that we want to make sure we can play through. We can
+// also check metadata. Put files of the same type together in this list so if
+// something crashes we have some idea of which backend is responsible.
+// Used by test_playback, which expects no error event and one ended event.
+var gPlayTests = [
+ // Test playback of a WebM file with vp9 video
+ { name: "vp9cake-short.webm", type: "video/webm", duration: 1.0 },
+ // 8-bit samples
+ { name: "r11025_u8_c1.wav", type: "audio/x-wav", duration: 1.0 },
+ // 8-bit samples, file is truncated
+ { name: "r11025_u8_c1_trunc.wav", type: "audio/x-wav", duration: 1.8 },
+ // file has trailing non-PCM data
+ { name: "r11025_s16_c1_trailing.wav", type: "audio/x-wav", duration: 1.0 },
+ // file with list chunk
+ { name: "r16000_u8_c1_list.wav", type: "audio/x-wav", duration: 4.2 },
+ // file with 2 extra bytes of metadata
+ {
+ name: "16bit_wave_extrametadata.wav",
+ type: "audio/x-wav",
+ duration: 1.108,
+ },
+ // IEEE float wave file
+ { name: "wavedata_float.wav", type: "audio/x-wav", duration: 1.0 },
+ // 24-bit samples
+ { name: "wavedata_s24.wav", type: "audio/x-wav", duration: 1.0 },
+ // aLaw compressed wave file
+ { name: "wavedata_alaw.wav", type: "audio/x-wav", duration: 1.0 },
+ // uLaw compressed wave file
+ { name: "wavedata_ulaw.wav", type: "audio/x-wav", duration: 1.0 },
+ // Data length 0xFFFFFFFF
+ { name: "bug1301226.wav", type: "audio/x-wav", duration: 0.003673 },
+ // Data length 0xFFFFFFFF and odd chunk lengths.
+ { name: "bug1301226-odd.wav", type: "audio/x-wav", duration: 0.003673 },
+
+ // Ogg stream without eof marker
+ { name: "bug461281.ogg", type: "application/ogg", duration: 2.208 },
+
+ // oggz-chop stream
+ { name: "bug482461.ogv", type: "video/ogg", duration: 4.34 },
+ // Theora only oggz-chop stream
+ { name: "bug482461-theora.ogv", type: "video/ogg", duration: 4.138 },
+ // With first frame a "duplicate" (empty) frame.
+ {
+ name: "bug500311.ogv",
+ type: "video/ogg",
+ duration: 1.96,
+ contentDuration: 1.958,
+ },
+ // Small audio file
+ { name: "small-shot.ogg", type: "audio/ogg", duration: 0.276 },
+ // More audio in file than video.
+ { name: "short-video.ogv", type: "video/ogg", duration: 1.081 },
+ // First Theora data packet is zero bytes.
+ { name: "bug504613.ogv", type: "video/ogg", duration: Number.NaN },
+ // Multiple audio streams.
+ { name: "bug516323.ogv", type: "video/ogg", duration: 4.208 },
+ // oggz-chop with non-keyframe as first frame
+ {
+ name: "bug556821.ogv",
+ type: "video/ogg",
+ duration: 2.936,
+ contentDuration: 2.903,
+ },
+
+ // Encoded with vorbis beta1, includes unusually sized codebooks
+ { name: "beta-phrasebook.ogg", type: "audio/ogg", duration: 4.01 },
+ // Small file, only 1 frame with audio only.
+ { name: "bug520493.ogg", type: "audio/ogg", duration: 0.458 },
+ // Small file with vorbis comments with 0 length values and names.
+ { name: "bug520500.ogg", type: "audio/ogg", duration: 0.123 },
+
+ // Various weirdly formed Ogg files
+ {
+ name: "bug499519.ogv",
+ type: "video/ogg",
+ duration: 0.24,
+ contentDuration: 0.22,
+ },
+ { name: "bug506094.ogv", type: "video/ogg", duration: 0 },
+ { name: "bug498855-1.ogv", type: "video/ogg", duration: 0.24 },
+ { name: "bug498855-2.ogv", type: "video/ogg", duration: 0.24 },
+ { name: "bug498855-3.ogv", type: "video/ogg", duration: 0.24 },
+ {
+ name: "bug504644.ogv",
+ type: "video/ogg",
+ duration: 1.6,
+ contentDuration: 1.52,
+ },
+ {
+ name: "chain.ogv",
+ type: "video/ogg",
+ duration: Number.NaN,
+ contentDuration: 0.266,
+ },
+ {
+ name: "bug523816.ogv",
+ type: "video/ogg",
+ duration: 0.766,
+ contentDuration: 0,
+ },
+ { name: "bug495129.ogv", type: "video/ogg", duration: 2.41 },
+ {
+ name: "bug498380.ogv",
+ type: "video/ogg",
+ duration: 0.7663,
+ contentDuration: 0,
+ },
+ { name: "bug495794.ogg", type: "audio/ogg", duration: 0.3 },
+ { name: "bug557094.ogv", type: "video/ogg", duration: 0.24 },
+ { name: "multiple-bos.ogg", type: "video/ogg", duration: 0.431 },
+ { name: "audio-overhang.ogg", type: "video/ogg", duration: 2.3 },
+ { name: "video-overhang.ogg", type: "video/ogg", duration: 3.966 },
+
+ // bug461281.ogg with the middle second chopped out.
+ { name: "audio-gaps.ogg", type: "audio/ogg", duration: 2.208 },
+
+ // Test playback/metadata work after a redirect
+ {
+ name: "redirect.sjs?domain=mochi.test:8888&file=320x240.ogv",
+ type: "video/ogg",
+ duration: 0.266,
+ },
+
+ // Test playback of a webm file
+ { name: "seek-short.webm", type: "video/webm", duration: 0.23 },
+
+ // Test playback of a webm file with 'matroska' doctype
+ { name: "bug1377278.webm", type: "video/webm", duration: 4.0 },
+
+ // Test playback of a WebM file with non-zero start time.
+ { name: "split.webm", type: "video/webm", duration: 1.967 },
+
+ // Test playback of a WebM file with resolution changes.
+ { name: "resolution-change.webm", type: "video/webm", duration: 6.533 },
+
+ // A really short, low sample rate, single channel file. This tests whether
+ // we can handle playing files when only push very little audio data to the
+ // hardware.
+ { name: "spacestorm-1000Hz-100ms.ogg", type: "audio/ogg", duration: 0.099 },
+
+ // Opus data in an ogg container
+ {
+ name: "detodos-short.opus",
+ type: "audio/ogg; codecs=opus",
+ duration: 0.22,
+ contentDuration: 0.2135,
+ },
+ // Opus data in a webm container
+ {
+ name: "detodos-short.webm",
+ type: "audio/webm; codecs=opus",
+ duration: 0.26,
+ contentDuration: 0.2535,
+ },
+ // Opus in webm channel mapping=2 sample file
+ {
+ name: "opus-mapping2.webm",
+ type: "audio/webm; codecs=opus",
+ duration: 10.01,
+ contentDuration: 9.99,
+ },
+ { name: "bug1066943.webm", type: "audio/webm; codecs=opus", duration: 1.383 },
+
+ // Multichannel Opus in an ogg container
+ { name: "test-1-mono.opus", type: "audio/ogg; codecs=opus", duration: 1.044 },
+ {
+ name: "test-2-stereo.opus",
+ type: "audio/ogg; codecs=opus",
+ duration: 2.925,
+ },
+ { name: "test-3-LCR.opus", type: "audio/ogg; codecs=opus", duration: 4.214 },
+ { name: "test-4-quad.opus", type: "audio/ogg; codecs=opus", duration: 6.234 },
+ { name: "test-5-5.0.opus", type: "audio/ogg; codecs=opus", duration: 7.558 },
+ { name: "test-6-5.1.opus", type: "audio/ogg; codecs=opus", duration: 10.333 },
+ { name: "test-7-6.1.opus", type: "audio/ogg; codecs=opus", duration: 11.69 },
+ { name: "test-8-7.1.opus", type: "audio/ogg; codecs=opus", duration: 13.478 },
+
+ {
+ name: "gizmo-short.mp4",
+ type: "video/mp4",
+ duration: 0.27,
+ contentDuration: 0.267,
+ },
+ // Test playback of a MP4 file with a non-zero start time (and audio starting
+ // a second later).
+ { name: "bipbop-lateaudio.mp4", type: "video/mp4" },
+ // Ambisonics AAC, requires AAC extradata to be set when creating decoder (see bug 1431169)
+ // Also test 4.0 decoding.
+ { name: "ambisonics.mp4", type: "audio/mp4", duration: 16.48 },
+ // Opus in MP4 channel mapping=0 sample file (content shorter due to preskip)
+ {
+ name: "opus-sample.mp4",
+ type: "audio/mp4; codecs=opus",
+ duration: 10.92,
+ contentDuration: 10.09,
+ },
+ // Opus in MP4 channel mapping=2 sample file
+ { name: "opus-mapping2.mp4", type: "audio/mp4; codecs=opus", duration: 10.0 },
+
+ { name: "small-shot.m4a", type: "audio/mp4", duration: 0.29 },
+ { name: "small-shot.mp3", type: "audio/mpeg", duration: 0.27 },
+ { name: "owl.mp3", type: "audio/mpeg", duration: 3.343 },
+ // owl.mp3 as above, but with something funny going on in the ID3v2 tag
+ // that caused DirectShow to fail.
+ { name: "owl-funny-id3.mp3", type: "audio/mpeg", duration: 3.343 },
+ // owl.mp3 as above, but with something even funnier going on in the ID3v2 tag
+ // that caused DirectShow to fail.
+ { name: "owl-funnier-id3.mp3", type: "audio/mpeg", duration: 3.343 },
+ // One second of silence with ~140KB of ID3 tags. Usually when the first MP3
+ // frame is at such a high offset into the file, MP3FrameParser will give up
+ // and report that the stream is not MP3. However, it does not count ID3 tags
+ // in that offset. This test case makes sure that ID3 exclusion holds.
+ { name: "huge-id3.mp3", type: "audio/mpeg", duration: 1.0 },
+ // A truncated VBR MP3 with just enough frames to keep most decoders happy.
+ // The Xing header reports the length of the file to be around 10 seconds, but
+ // there is really only one second worth of data. We want MP3FrameParser to
+ // trust the header, so this should be reported as 10 seconds.
+ {
+ name: "vbr-head.mp3",
+ type: "audio/mpeg",
+ duration: 10.0,
+ contentDuration: 1.019,
+ },
+
+ // A flac file where the STREAMINFO block was removed.
+ // It is necessary to parse the file to find an audio frame instead.
+ { name: "flac-noheader-s16.flac", type: "audio/flac", duration: 4.0 },
+ { name: "flac-s24.flac", type: "audio/flac", duration: 4.04 },
+ {
+ name: "flac-sample.mp4",
+ type: "audio/mp4; codecs=flac",
+ duration: 4.95,
+ contentDuration: 5.03,
+ },
+ // Ogg with theora video and flac audio.
+ {
+ name: "A4.ogv",
+ type: "video/ogg",
+ width: 320,
+ height: 240,
+ duration: 3.13,
+ },
+
+ // Invalid file
+ { name: "bogus.duh", type: "bogus/duh", duration: Number.NaN },
+];
+
+const win32 =
+ SpecialPowers.Services.appinfo.OS == "WINNT" &&
+ !SpecialPowers.Services.appinfo.is64Bit;
+if (!win32) {
+ gPlayTests.push({ name: "av1.mp4", type: "video/mp4", duration: 1.0 });
+}
+
+var gSeekToNextFrameTests = [
+ // Test playback of a WebM file with vp9 video
+ { name: "vp9-short.webm", type: "video/webm", duration: 0.2 },
+ { name: "vp9cake-short.webm", type: "video/webm", duration: 1.0 },
+ // oggz-chop stream
+ { name: "bug482461.ogv", type: "video/ogg", duration: 4.34 },
+ // Theora only oggz-chop stream
+ { name: "bug482461-theora.ogv", type: "video/ogg", duration: 4.138 },
+ // With first frame a "duplicate" (empty) frame.
+ { name: "bug500311.ogv", type: "video/ogg", duration: 1.96 },
+
+ // More audio in file than video.
+ { name: "short-video.ogv", type: "video/ogg", duration: 1.081 },
+ // First Theora data packet is zero bytes.
+ { name: "bug504613.ogv", type: "video/ogg", duration: Number.NaN },
+ // Multiple audio streams.
+ { name: "bug516323.ogv", type: "video/ogg", duration: 4.208 },
+ // oggz-chop with non-keyframe as first frame
+ { name: "bug556821.ogv", type: "video/ogg", duration: 2.936 },
+ // Various weirdly formed Ogg files
+ { name: "bug498855-1.ogv", type: "video/ogg", duration: 0.24 },
+ { name: "bug498855-2.ogv", type: "video/ogg", duration: 0.24 },
+ { name: "bug498855-3.ogv", type: "video/ogg", duration: 0.24 },
+ { name: "bug504644.ogv", type: "video/ogg", duration: 1.6 },
+
+ { name: "bug523816.ogv", type: "video/ogg", duration: 0.766 },
+
+ { name: "bug498380.ogv", type: "video/ogg", duration: 0.2 },
+ { name: "bug557094.ogv", type: "video/ogg", duration: 0.24 },
+ { name: "multiple-bos.ogg", type: "video/ogg", duration: 0.431 },
+ // Test playback/metadata work after a redirect
+ {
+ name: "redirect.sjs?domain=mochi.test:8888&file=320x240.ogv",
+ type: "video/ogg",
+ duration: 0.266,
+ },
+ // Test playback of a webm file
+ { name: "seek-short.webm", type: "video/webm", duration: 0.23 },
+ // Test playback of a WebM file with non-zero start time.
+ { name: "split.webm", type: "video/webm", duration: 1.967 },
+
+ { name: "gizmo-short.mp4", type: "video/mp4", duration: 0.27 },
+
+ // Test playback of a MP4 file with a non-zero start time (and audio starting
+ // a second later).
+ { name: "bipbop-lateaudio.mp4", type: "video/mp4" },
+];
+
+// A file for each type we can support.
+var gSnifferTests = [
+ { name: "big.wav", type: "audio/x-wav", duration: 9.278982, size: 102444 },
+ {
+ name: "320x240.ogv",
+ type: "video/ogg",
+ width: 320,
+ height: 240,
+ duration: 0.233,
+ size: 28942,
+ },
+ { name: "seek.webm", type: "video/webm", duration: 3.966, size: 215529 },
+ { name: "gizmo.mp4", type: "video/mp4", duration: 5.56, size: 383631 },
+ // A mp3 file with id3 tags.
+ { name: "id3tags.mp3", type: "audio/mpeg", duration: 0.28, size: 3530 },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+// Files that contain resolution changes
+var gResolutionChangeTests = [
+ { name: "resolution-change.webm", type: "video/webm", duration: 6.533 },
+];
+
+// Files we must reject as invalid.
+var gInvalidTests = [
+ { name: "invalid-m0c0.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-m0c3.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-m1c0.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-m1c9.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-m2c0.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-m2c1.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-cmap-short.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-cmap-s0c0.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-cmap-s0c2.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-cmap-s1c2.opus", type: "audio/ogg; codecs=opus" },
+ { name: "invalid-preskip.webm", type: "audio/webm; codecs=opus" },
+];
+
+var gInvalidPlayTests = [
+ { name: "invalid-excess_discard.webm", type: "audio/webm; codecs=opus" },
+ { name: "invalid-excess_neg_discard.webm", type: "audio/webm; codecs=opus" },
+ { name: "invalid-neg_discard.webm", type: "audio/webm; codecs=opus" },
+ {
+ name: "invalid-discard_on_multi_blocks.webm",
+ type: "audio/webm; codecs=opus",
+ },
+];
+
+// Files to check different cases of ogg skeleton information.
+// sample-fisbone-skeleton4.ogv
+// - Skeleton v4, w/ Content-Type,Role,Name,Language,Title for both theora/vorbis
+// sample-fisbone-wrong-header.ogv
+// - Skeleton v4, wrong message field sequence for vorbis
+// multiple-bos-more-header-fields.ogg
+// - Skeleton v3, w/ Content-Type,Role,Name,Language,Title for both theora/vorbis
+// seek-short.ogv
+// - No skeleton, but theora
+// audio-gaps-short.ogg
+// - No skeleton, but vorbis
+var gMultitrackInfoOggPlayList = [
+ { name: "sample-fisbone-skeleton4.ogv", type: "video/ogg", duration: 1.0 },
+ { name: "sample-fisbone-wrong-header.ogv", type: "video/ogg", duration: 1.0 },
+ {
+ name: "multiple-bos-more-header-fileds.ogg",
+ type: "video/ogg",
+ duration: 0.431,
+ },
+ { name: "seek-short.ogv", type: "video/ogg", duration: 1.03 },
+ { name: "audio-gaps-short.ogg", type: "audio/ogg", duration: 0.5 },
+];
+// Pre-parsed results of gMultitrackInfoOggPlayList.
+var gOggTrackInfoResults = {
+ "sample-fisbone-skeleton4.ogv": {
+ audio_id: " audio_1",
+ audio_kind: "main",
+ audio_language: " en-US",
+ audio_label: " Audio track for test",
+ video_id: " video_1",
+ video_kind: "main",
+ video_language: " fr",
+ video_label: " Video track for test",
+ },
+ "sample-fisbone-wrong-header.ogv": {
+ audio_id: "1",
+ audio_kind: "main",
+ audio_language: "",
+ audio_label: "",
+ video_id: " video_1",
+ video_kind: "main",
+ video_language: " fr",
+ video_label: " Video track for test",
+ },
+ "multiple-bos-more-header-fileds.ogg": {
+ audio_id: "1",
+ audio_kind: "main",
+ audio_language: "",
+ audio_label: "",
+ video_id: "2",
+ video_kind: "main",
+ video_language: "",
+ video_label: "",
+ },
+ "seek-short.ogv": {
+ video_id: "2",
+ video_kind: "main",
+ video_language: "",
+ video_label: "",
+ },
+ "audio-gaps-short.ogg": {
+ audio_id: "1",
+ audio_kind: "main",
+ audio_language: "",
+ audio_label: "",
+ },
+};
+
+// Returns a promise that resolves to a function that converts
+// relative paths to absolute, to test loading files from file: URIs.
+// Optionally checks whether the file actually exists on disk at the location
+// we've specified.
+function makeAbsolutePathConverter() {
+ const url = SimpleTest.getTestFileURL("chromeHelper.js");
+ const script = SpecialPowers.loadChromeScript(url);
+ return new Promise((resolve, reject) => {
+ script.addMessageListener("media-test:cwd", cwd => {
+ if (!cwd) {
+ ok(false, "Failed to find path to test files");
+ }
+
+ resolve((path, mustExist) => {
+ // android mochitest doesn't support file://
+ if (manifestNavigator().appVersion.includes("Android")) {
+ return path;
+ }
+
+ const { Ci, Cc } = SpecialPowers;
+ var f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ f.initWithPath(cwd);
+ var split = path.split("/");
+ for (var i = 0; i < split.length; ++i) {
+ f.append(split[i]);
+ }
+ if (mustExist && !f.exists()) {
+ ok(false, "We expected '" + path + "' to exist, but it doesn't!");
+ }
+ return f.path;
+ });
+ });
+ script.sendAsyncMessage("media-test:getcwd");
+ });
+}
+
+// Returns true if two TimeRanges are equal, false otherwise
+function range_equals(r1, r2) {
+ if (r1.length != r2.length) {
+ return false;
+ }
+ for (var i = 0; i < r1.length; i++) {
+ if (r1.start(i) != r2.start(i) || r1.end(i) != r2.end(i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// These are URIs to files that we use to check that we don't leak any state
+// or other information such that script can determine stuff about a user's
+// environment. Used by test_info_leak.
+function makeInfoLeakTests() {
+ return makeAbsolutePathConverter().then(fileUriToSrc => [
+ {
+ type: "video/ogg",
+ src: fileUriToSrc("tests/dom/media/test/320x240.ogv", true),
+ },
+ {
+ type: "video/ogg",
+ src: fileUriToSrc("tests/dom/media/test/404.ogv", false),
+ },
+ {
+ type: "audio/x-wav",
+ src: fileUriToSrc("tests/dom/media/test/r11025_s16_c1.wav", true),
+ },
+ {
+ type: "audio/x-wav",
+ src: fileUriToSrc("tests/dom/media/test/404.wav", false),
+ },
+ {
+ type: "audio/ogg",
+ src: fileUriToSrc("tests/dom/media/test/bug461281.ogg", true),
+ },
+ {
+ type: "audio/ogg",
+ src: fileUriToSrc("tests/dom/media/test/404.ogg", false),
+ },
+ {
+ type: "video/webm",
+ src: fileUriToSrc("tests/dom/media/test/seek.webm", true),
+ },
+ {
+ type: "video/webm",
+ src: fileUriToSrc("tests/dom/media/test/404.webm", false),
+ },
+ {
+ type: "video/ogg",
+ src: "http://localhost/404.ogv",
+ },
+ {
+ type: "audio/x-wav",
+ src: "http://localhost/404.wav",
+ },
+ {
+ type: "video/webm",
+ src: "http://localhost/404.webm",
+ },
+ {
+ type: "video/ogg",
+ src: "http://example.com/tests/dom/media/test/test_info_leak.html",
+ },
+ {
+ type: "audio/ogg",
+ src: "http://example.com/tests/dom/media/test/test_info_leak.html",
+ },
+ ]);
+}
+
+// These are files that must fire an error during load or playback, and do not
+// cause a crash. Used by test_playback_errors, which expects one error event
+// and no ended event. Put files of the same type together in this list so if
+// something crashes we have some idea of which backend is responsible.
+var gErrorTests = [
+ { name: "bogus.wav", type: "audio/x-wav" },
+ { name: "bogus.ogv", type: "video/ogg" },
+ { name: "448636.ogv", type: "video/ogg" },
+ { name: "bug504843.ogv", type: "video/ogg" },
+ { name: "bug501279.ogg", type: "audio/ogg" },
+ { name: "bug603918.webm", type: "video/webm" },
+ { name: "bug604067.webm", type: "video/webm" },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+// These files would get error after receiving "loadedmetadata", we would like
+// to check duration in "onerror" and make sure the duration is still available.
+var gDurationTests = [
+ { name: "bug603918.webm", duration: 6.076 },
+ { name: "bug604067.webm", duration: 6.076 },
+];
+
+// These are files that have nontrivial duration and are useful for seeking within.
+var gSeekTests = [
+ { name: "r11025_s16_c1.wav", type: "audio/x-wav", duration: 1.0 },
+ { name: "audio.wav", type: "audio/x-wav", duration: 0.031247 },
+ { name: "seek.ogv", type: "video/ogg", duration: 3.966 },
+ { name: "320x240.ogv", type: "video/ogg", duration: 0.266 },
+ { name: "seek.webm", type: "video/webm", duration: 3.966 },
+ { name: "sine.webm", type: "audio/webm", duration: 4.001 },
+ { name: "bug516323.indexed.ogv", type: "video/ogg", duration: 4.208333 },
+ { name: "split.webm", type: "video/webm", duration: 1.967 },
+ { name: "detodos.opus", type: "audio/ogg; codecs=opus", duration: 2.9135 },
+ { name: "gizmo.mp4", type: "video/mp4", duration: 5.56 },
+ { name: "owl.mp3", type: "audio/mpeg", duration: 3.343 },
+ { name: "bogus.duh", type: "bogus/duh", duration: 123 },
+
+ // Bug 1242338: hit a numerical problem while seeking to the duration.
+ { name: "bug482461-theora.ogv", type: "video/ogg", duration: 4.138 },
+];
+
+var gFastSeekTests = [
+ {
+ name: "gizmo.mp4",
+ type: "video/mp4",
+ keyframes: [0, 1.0, 2.0, 3.0, 4.0, 5.0],
+ },
+ // Note: Not all keyframes in the file are actually referenced in the Cues in this file.
+ { name: "seek.webm", type: "video/webm", keyframes: [0, 0.8, 1.6, 2.4, 3.2] },
+ // Note: the sync points are the points on both the audio and video streams
+ // before the keyframes. You can't just assume that the keyframes are the sync
+ // points, as the audio required for that sync point may be before the keyframe.
+ {
+ name: "bug516323.indexed.ogv",
+ type: "video/ogg",
+ keyframes: [0, 0.46, 3.06],
+ },
+];
+
+// These files are WebMs without cues. They're seekable within their buffered
+// ranges. If work renders WebMs fully seekable these files should be moved
+// into gSeekTests
+var gCuelessWebMTests = [
+ { name: "no-cues.webm", type: "video/webm", duration: 3.967 },
+];
+
+// These are files that are non seekable, due to problems with the media,
+// for example broken or missing indexes.
+var gUnseekableTests = [{ name: "bogus.duh", type: "bogus/duh" }];
+
+var androidVersion = -1; // non-Android platforms
+if (
+ manifestNavigator().userAgent.includes("Mobile") ||
+ manifestNavigator().userAgent.includes("Tablet")
+) {
+ androidVersion = SpecialPowers.Services.sysinfo.getProperty("version");
+}
+
+function getAndroidVersion() {
+ return androidVersion;
+}
+
+// These are files suitable for using with a "new Audio" constructor.
+var gAudioTests = [
+ { name: "r11025_s16_c1.wav", type: "audio/x-wav", duration: 1.0 },
+ { name: "sound.ogg", type: "audio/ogg" },
+ { name: "owl.mp3", type: "audio/mpeg", duration: 3.343 },
+ { name: "small-shot.m4a", type: "audio/mp4", duration: 0.29 },
+ { name: "bogus.duh", type: "bogus/duh", duration: 123 },
+ { name: "empty_size.mp3", type: "audio/mpeg", duration: 2.235 },
+];
+
+// These files ensure our handling of 404 errors is consistent across the
+// various backends.
+var g404Tests = [
+ { name: "404.wav", type: "audio/x-wav" },
+ { name: "404.ogv", type: "video/ogg" },
+ { name: "404.oga", type: "audio/ogg" },
+ { name: "404.webm", type: "video/webm" },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+// These are files suitable for testing various decoder failures that are
+// expected to fire MEDIA_ERR_DECODE. Used by test_decode_error, which expects
+// an error and emptied event, and no loadedmetadata or ended event.
+var gDecodeErrorTests = [
+ // Valid files with unsupported codecs
+ { name: "r11025_msadpcm_c1.wav", type: "audio/x-wav" },
+ { name: "dirac.ogg", type: "video/ogg" },
+ // Invalid files
+ { name: "bogus.wav", type: "audio/x-wav" },
+ { name: "bogus.ogv", type: "video/ogg" },
+
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+// These are files that are used for media fragments tests
+var gFragmentTests = [
+ { name: "big.wav", type: "audio/x-wav", duration: 9.278982, size: 102444 },
+];
+
+// Used by test_chaining.html. The |links| attributes is the number of links in
+// this file that we should be able to play.
+var gChainingTests = [
+ // Vorbis and Opus chained file. They have user comments |index=n| where `n`
+ // is the index of this segment in the file, 0 indexed.
+ { name: "chain.ogg", type: "audio/ogg", links: 4 },
+ { name: "chain.opus", type: "audio/ogg; codec=opus", links: 4 },
+ // Those files are chained files with a different number of channels in each
+ // part. This is not supported and should stop playing after the first part.
+ { name: "variable-channel.ogg", type: "audio/ogg", links: 1 },
+ { name: "variable-channel.opus", type: "audio/ogg; codec=opus", links: 1 },
+ // Those files are chained files with a different sample rate in each
+ // part. This is not supported and should stop playing after the first part.
+ { name: "variable-samplerate.ogg", type: "audio/ogg", links: 1 },
+ // Opus decoding in Firefox outputs 48 kHz PCM despite having a different
+ // original sample rate, so we can safely play Opus chained media that have
+ // different samplerate accross links.
+ { name: "variable-samplerate.opus", type: "audio/ogg; codec=opus", links: 2 },
+ // A chained video file. We don't support those, so only one link should be
+ // reported.
+ { name: "chained-video.ogv", type: "video/ogg", links: 1 },
+ // A file that consist in 4 links of audio, then another link that has video.
+ // We should stop right after the 4 audio links.
+ { name: "chained-audio-video.ogg", type: "video/ogg", links: 4 },
+ // An opus file that has two links, with a different preskip value for each
+ // link. We should be able to play both links.
+ { name: "variable-preskip.opus", type: "audio/ogg; codec=opus", links: 2 },
+ { name: "bogus.duh", type: "bogus/duh" },
+];
+
+// Videos with an aspect ratio. Used for testing that displaying frames
+// on a canvas works correctly in the case of non-standard aspect ratios.
+// See bug 874897 for an example.
+var gAspectRatioTests = [
+ { name: "VID_0001.ogg", type: "video/ogg", duration: 19.966 },
+];
+
+// These are files with non-trivial tag sets.
+// Used by test_metadata.html.
+var gMetadataTests = [
+ // Ogg Vorbis files
+ {
+ name: "short-video.ogv",
+ tags: {
+ TITLE: "Lepidoptera",
+ ARTIST: "Epoq",
+ ALBUM: "Kahvi Collective",
+ DATE: "2002",
+ COMMENT: "http://www.kahvi.org",
+ },
+ },
+ {
+ name: "bug516323.ogv",
+ tags: {
+ GENRE: "Open Movie",
+ ENCODER: "Audacity",
+ TITLE: "Elephants Dream",
+ ARTIST: "Silvia Pfeiffer",
+ COMMENTS: "Audio Description",
+ },
+ },
+ {
+ name: "bug516323.indexed.ogv",
+ tags: {
+ GENRE: "Open Movie",
+ ENCODER: "Audacity",
+ TITLE: "Elephants Dream",
+ ARTIST: "Silvia Pfeiffer",
+ COMMENTS: "Audio Description",
+ },
+ },
+ {
+ name: "detodos.opus",
+ tags: {
+ title: "De todos. Para todos.",
+ artist: "Mozilla.org",
+ },
+ },
+ { name: "sound.ogg", tags: {} },
+ {
+ name: "small-shot.ogg",
+ tags: {
+ title: "Pew SFX",
+ },
+ },
+ {
+ name: "badtags.ogg",
+ tags: {
+ // We list only the valid tags here, and verify
+ // the invalid ones are filtered out.
+ title: "Invalid comments test file",
+ empty: "",
+ "": "empty",
+ "{- [(`!@\"#$%^&')] -}": "valid tag name, surprisingly",
+ // The file also includes the following invalid tags.
+ // "A description with no separator is a common problem.",
+ // "雨":"Likely, but an invalid key (non-ascii).",
+ // "not\nval\x1fid":"invalid tag name",
+ // "not~valid":"this isn't a valid name either",
+ // "not-utf-8":"invalid sequences: \xff\xfe\xfa\xfb\0eol"
+ },
+ },
+ {
+ name: "wave_metadata.wav",
+ tags: {
+ name: "Track Title",
+ artist: "Artist Name",
+ comments: "Comments",
+ },
+ },
+ {
+ name: "wave_metadata_utf8.wav",
+ tags: {
+ name: "歌曲名稱",
+ artist: "作曲者",
+ comments: "註解",
+ },
+ },
+ {
+ name: "wave_metadata_unknown_tag.wav",
+ tags: {
+ name: "Track Title",
+ comments: "Comments",
+ },
+ },
+ {
+ name: "wave_metadata_bad_len.wav",
+ tags: {
+ name: "Track Title",
+ artist: "Artist Name",
+ comments: "Comments",
+ },
+ },
+ {
+ name: "wave_metadata_bad_no_null.wav",
+ tags: {
+ name: "Track Title",
+ artist: "Artist Name",
+ comments: "Comments!!",
+ },
+ },
+ {
+ name: "wave_metadata_bad_utf8.wav",
+ tags: {
+ name: "歌曲名稱",
+ comments: "註解",
+ },
+ },
+ { name: "wavedata_u8.wav", tags: {} },
+];
+
+// Now Fennec doesn't support flac, so only test it on non-android platforms.
+if (getAndroidVersion() < 0) {
+ gMetadataTests = gMetadataTests.concat([
+ {
+ name: "flac-s24.flac",
+ tags: {
+ ALBUM: "Seascapes",
+ TITLE: "(La Mer) - II. Jeux de vagues. Allegro",
+ COMPOSER: "Debussy, Claude",
+ TRACKNUMBER: "2/9",
+ DISCNUMBER: "1/1",
+ encoder: "Lavf57.41.100",
+ },
+ },
+ ]);
+}
+
+// Test files for Encrypted Media Extensions
+var gEMETests = [
+ {
+ name: "vp9 in mp4",
+ tracks: [
+ {
+ name: "video",
+ type: 'video/mp4; codecs="vp9.0"',
+ fragments: ["short-vp9-encrypted-video.mp4"],
+ },
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: ["short-aac-encrypted-audio.mp4"],
+ },
+ ],
+ keys: {
+ "2cdb0ed6119853e7850671c3e9906c3c": "808B9ADAC384DE1E4F56140F4AD76194",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 0.47,
+ },
+ {
+ name: "video-only with 2 keys",
+ tracks: [
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop-cenc-videoinit.mp4",
+ "bipbop-cenc-video1.m4s",
+ "bipbop-cenc-video2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333",
+ "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444",
+ },
+ sessionType: "temporary",
+ sessionCount: 1,
+ duration: 1.6,
+ },
+ {
+ name: "video-only with 2 keys, CORS",
+ tracks: [
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop-cenc-videoinit.mp4",
+ "bipbop-cenc-video1.m4s",
+ "bipbop-cenc-video2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333",
+ "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444",
+ },
+ sessionType: "temporary",
+ sessionCount: 1,
+ crossOrigin: true,
+ duration: 1.6,
+ },
+ {
+ name: "audio&video tracks, both with all keys",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop-cenc-audioinit.mp4",
+ "bipbop-cenc-audio1.m4s",
+ "bipbop-cenc-audio2.m4s",
+ "bipbop-cenc-audio3.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop-cenc-videoinit.mp4",
+ "bipbop-cenc-video1.m4s",
+ "bipbop-cenc-video2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333",
+ "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "audio&video tracks, both with all keys, CORS",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop-cenc-audioinit.mp4",
+ "bipbop-cenc-audio1.m4s",
+ "bipbop-cenc-audio2.m4s",
+ "bipbop-cenc-audio3.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop-cenc-videoinit.mp4",
+ "bipbop-cenc-video1.m4s",
+ "bipbop-cenc-video2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d03": "7e5733337e5733337e5733337e573333",
+ "7e571d047e571d047e571d047e571d04": "7e5744447e5744447e5744447e574444",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ crossOrigin: true,
+ duration: 1.6,
+ },
+ {
+ name: "400x300 audio&video tracks, each with its key",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_300_215kbps-cenc-audio-key1-init.mp4",
+ "bipbop_300_215kbps-cenc-audio-key1-1.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-2.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-3.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_300_215kbps-cenc-video-key1-init.mp4",
+ "bipbop_300_215kbps-cenc-video-key1-1.m4s",
+ "bipbop_300_215kbps-cenc-video-key1-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "640x480@624kbps audio&video tracks, each with its key",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-audio-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-audio-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-2.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-3.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-video-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-video-key1-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "640x480@959kbps audio&video tracks, each with its key",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_480_959kbps-cenc-audio-key1-init.mp4",
+ "bipbop_480_959kbps-cenc-audio-key1-1.m4s",
+ "bipbop_480_959kbps-cenc-audio-key1-2.m4s",
+ "bipbop_480_959kbps-cenc-audio-key1-3.m4s",
+ "bipbop_480_959kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_480_959kbps-cenc-video-key1-init.mp4",
+ "bipbop_480_959kbps-cenc-video-key1-1.m4s",
+ "bipbop_480_959kbps-cenc-video-key1-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "640x480 then 400x300, same key (1st) per track",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-audio-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-audio-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-2.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-3.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-video-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key1-1.m4s",
+ "bipbop_300_215kbps-cenc-video-key1-init.mp4",
+ "bipbop_300_215kbps-cenc-video-key1-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "640x480 then 400x300, same key (2nd) per track",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-audio-key2-init.mp4",
+ "bipbop_480_624kbps-cenc-audio-key2-1.m4s",
+ "bipbop_480_624kbps-cenc-audio-key2-2.m4s",
+ "bipbop_480_624kbps-cenc-audio-key2-3.m4s",
+ "bipbop_480_624kbps-cenc-audio-key2-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-video-key2-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key2-1.m4s",
+ "bipbop_300_215kbps-cenc-video-key2-init.mp4",
+ "bipbop_300_215kbps-cenc-video-key2-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312",
+ "7e571d047e571d047e571d047e571d22": "7e5744447e5744447e5744447e574422",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "640x480 with 1st keys then 400x300 with 2nd keys",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-audio-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-audio-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-2.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-3.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-video-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key1-1.m4s",
+ "bipbop_300_215kbps-cenc-video-key2-init.mp4",
+ "bipbop_300_215kbps-cenc-video-key2-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "400x300 with 1st keys then 640x480 with 2nd keys",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_300_215kbps-cenc-audio-key1-init.mp4",
+ "bipbop_300_215kbps-cenc-audio-key1-1.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-2.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-3.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_300_215kbps-cenc-video-key1-init.mp4",
+ "bipbop_300_215kbps-cenc-video-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-video-key2-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key2-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "640x480@959kbps with 1st keys then 640x480@624kbps with 2nd keys",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_480_959kbps-cenc-audio-key1-init.mp4",
+ "bipbop_480_959kbps-cenc-audio-key1-1.m4s",
+ "bipbop_480_959kbps-cenc-audio-key1-2.m4s",
+ "bipbop_480_959kbps-cenc-audio-key1-3.m4s",
+ "bipbop_480_959kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_480_959kbps-cenc-video-key1-init.mp4",
+ "bipbop_480_959kbps-cenc-video-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-video-key2-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key2-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "640x480@624kbps with 1st keys then 640x480@959kbps with 2nd keys",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-audio-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-audio-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-2.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-3.m4s",
+ "bipbop_480_624kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_480_624kbps-cenc-video-key1-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key1-1.m4s",
+ "bipbop_480_959kbps-cenc-video-key2-init.mp4",
+ "bipbop_480_959kbps-cenc-video-key2-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "400x300 with presentation size 533x300",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_300wp_227kbps-cenc-audio-key1-init.mp4",
+ "bipbop_300wp_227kbps-cenc-audio-key1-1.m4s",
+ "bipbop_300wp_227kbps-cenc-audio-key1-2.m4s",
+ "bipbop_300wp_227kbps-cenc-audio-key1-3.m4s",
+ "bipbop_300wp_227kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_300wp_227kbps-cenc-video-key1-init.mp4",
+ "bipbop_300wp_227kbps-cenc-video-key1-1.m4s",
+ "bipbop_300wp_227kbps-cenc-video-key1-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "400x300 as-is then 400x300 presented as 533x300",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_300_215kbps-cenc-audio-key1-init.mp4",
+ "bipbop_300_215kbps-cenc-audio-key1-1.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-2.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-3.m4s",
+ "bipbop_300_215kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_300_215kbps-cenc-video-key1-init.mp4",
+ "bipbop_300_215kbps-cenc-video-key1-1.m4s",
+ "bipbop_300wp_227kbps-cenc-video-key1-init.mp4",
+ "bipbop_300wp_227kbps-cenc-video-key1-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "400x225",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_225w_175kbps-cenc-audio-key1-init.mp4",
+ "bipbop_225w_175kbps-cenc-audio-key1-1.m4s",
+ "bipbop_225w_175kbps-cenc-audio-key1-2.m4s",
+ "bipbop_225w_175kbps-cenc-audio-key1-3.m4s",
+ "bipbop_225w_175kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_225w_175kbps-cenc-video-key1-init.mp4",
+ "bipbop_225w_175kbps-cenc-video-key1-1.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "640x360",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_360w_253kbps-cenc-audio-key1-init.mp4",
+ "bipbop_360w_253kbps-cenc-audio-key1-1.m4s",
+ "bipbop_360w_253kbps-cenc-audio-key1-2.m4s",
+ "bipbop_360w_253kbps-cenc-audio-key1-3.m4s",
+ "bipbop_360w_253kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_360w_253kbps-cenc-video-key1-init.mp4",
+ "bipbop_360w_253kbps-cenc-video-key1-1.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "400x225 then 640x360",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_225w_175kbps-cenc-audio-key1-init.mp4",
+ "bipbop_225w_175kbps-cenc-audio-key1-1.m4s",
+ "bipbop_225w_175kbps-cenc-audio-key1-2.m4s",
+ "bipbop_225w_175kbps-cenc-audio-key1-3.m4s",
+ "bipbop_225w_175kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_225w_175kbps-cenc-video-key1-init.mp4",
+ "bipbop_225w_175kbps-cenc-video-key1-1.m4s",
+ "bipbop_360w_253kbps-cenc-video-key2-init.mp4",
+ "bipbop_360w_253kbps-cenc-video-key2-1.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ name: "640x360 then 640x480",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="mp4a.40.2"',
+ fragments: [
+ "bipbop_360w_253kbps-cenc-audio-key1-init.mp4",
+ "bipbop_360w_253kbps-cenc-audio-key1-1.m4s",
+ "bipbop_360w_253kbps-cenc-audio-key1-2.m4s",
+ "bipbop_360w_253kbps-cenc-audio-key1-3.m4s",
+ "bipbop_360w_253kbps-cenc-audio-key1-4.m4s",
+ ],
+ },
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc1.64000d"',
+ fragments: [
+ "bipbop_360w_253kbps-cenc-video-key1-init.mp4",
+ "bipbop_360w_253kbps-cenc-video-key1-1.m4s",
+ "bipbop_480_624kbps-cenc-video-key2-init.mp4",
+ "bipbop_480_624kbps-cenc-video-key2-2.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d037e571d037e571d037e571d11": "7e5733337e5733337e5733337e573311",
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ "7e571d037e571d037e571d037e571d12": "7e5733337e5733337e5733337e573312",
+ },
+ sessionType: "temporary",
+ sessionCount: 3,
+ duration: 1.6,
+ },
+ {
+ // File generated with shaka packager:
+ // packager-osx --enable_raw_key_encryption --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421 --segment_duration 1 --clear_lead 0 in=test-flac.mp4,stream=audio,output=flac-sample-cenc.mp4
+ name: "flac in mp4 clearkey",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="flac"',
+ fragments: ["flac-sample-cenc.mp4"],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 1,
+ duration: 2.05,
+ },
+ {
+ // File generated with shaka packager:
+ // packager-osx --enable_raw_key_encryption --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421 --segment_duration 1 --clear_lead 0 in=test-opus.mp4,stream=audio,output=opus-sample-cenc.mp4
+ name: "opus in mp4 clearkey",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/mp4; codecs="opus"',
+ fragments: ["opus-sample-cenc.mp4"],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "7e571d047e571d047e571d047e571d21": "7e5744447e5744447e5744447e574421",
+ },
+ sessionType: "temporary",
+ sessionCount: 1,
+ duration: 1.98,
+ },
+ {
+ name: "WebM vorbis audio & vp8 video clearkey",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/webm; codecs="vorbis"',
+ fragments: ["bipbop_360w_253kbps-clearkey-audio.webm"],
+ },
+ {
+ name: "video",
+ type: 'video/webm; codecs="vp8"',
+ fragments: ["bipbop_360w_253kbps-clearkey-video-vp8.webm"],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ f1f3ee1790527e9de47217d43835f76a: "97b9ddc459c8d5ff23c1f2754c95abe8",
+ "8b5df745ad84145b5617c33116e35a67": "bddfd35dd9be033ee73bc18bc1885056",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "WebM vorbis audio & vp9 video clearkey",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/webm; codecs="vorbis"',
+ fragments: ["bipbop_360w_253kbps-clearkey-audio.webm"],
+ },
+ {
+ name: "video",
+ type: 'video/webm; codecs="vp9"',
+ fragments: ["bipbop_360w_253kbps-clearkey-video-vp9.webm"],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ f1f3ee1790527e9de47217d43835f76a: "97b9ddc459c8d5ff23c1f2754c95abe8",
+ eedf63a94fa7c398ee094f123a4ee709: "973b679a746c82f3acdb856b30e9378e",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 1.6,
+ },
+ {
+ name: "WebM vorbis audio & vp9 video clearkey with subsample encryption",
+ tracks: [
+ {
+ name: "audio",
+ type: 'audio/webm; codecs="vorbis"',
+ fragments: ["sintel-short-clearkey-subsample-encrypted-audio.webm"],
+ },
+ {
+ name: "video",
+ type: 'video/webm; codecs="vp9"',
+ fragments: ["sintel-short-clearkey-subsample-encrypted-video.webm"],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "2cdb0ed6119853e7850671c3e9906c3c": "808B9ADAC384DE1E4F56140F4AD76194",
+ },
+ sessionType: "temporary",
+ sessionCount: 2,
+ duration: 2.0,
+ },
+ {
+ // Files adapted from testcase for bug 1560092. See bug 1630381 for a
+ // detailed explanation on how they were adapted.
+ name: "avc3 h264 video in mp4 using clearkey cenc encryption",
+ tracks: [
+ {
+ name: "video",
+ type: 'video/mp4; codecs="avc3.640015"',
+ fragments: [
+ "big-buck-bunny-cenc-avc3-init.mp4",
+ "big-buck-bunny-cenc-avc3-1.m4s",
+ ],
+ },
+ ],
+ keys: {
+ // "keyid" : "key"
+ "10000000100010001000100000000001": "3A2A1B68DD2BD9B2EEB25E84C4776668",
+ },
+ sessionType: "temporary",
+ sessionCount: 1,
+ duration: 2.08,
+ },
+];
+
+var gEMENonMSEFailTests = [
+ {
+ name: "short-cenc.mp4",
+ audioType: 'audio/mp4; codecs="mp4a.40.2"',
+ videoType: 'video/mp4; codecs="avc1.64000d"',
+ duration: 0.47,
+ },
+];
+
+// These are files that are used for video decode suspend in
+// background tabs tests.
+var gDecodeSuspendTests = [
+ { name: "gizmo.mp4", type: "video/mp4", duration: 5.56 },
+ { name: "gizmo-noaudio.mp4", type: "video/mp4", duration: 5.56 },
+ { name: "gizmo.webm", type: 'video/webm; codecs="vp9,opus"', duration: 5.56 },
+ {
+ name: "gizmo-noaudio.webm",
+ type: 'video/webm; codecs="vp9"',
+ duration: 5.56,
+ },
+];
+
+function checkMetadata(msg, e, test) {
+ if (test.width) {
+ is(e.videoWidth, test.width, msg + " video width");
+ }
+ if (test.height) {
+ is(e.videoHeight, test.height, msg + " video height");
+ }
+ if (test.duration) {
+ ok(
+ Math.abs(e.duration - test.duration) < 0.1,
+ msg + " duration (" + e.duration + ") should be around " + test.duration
+ );
+ }
+ is(
+ !!test.keys,
+ SpecialPowers.do_lookupGetter(e, "isEncrypted").apply(e),
+ msg + " isEncrypted should be true if we have decryption keys"
+ );
+}
+
+// Returns the first test from candidates array which we can play with the
+// installed video backends.
+function getPlayableVideo(candidates) {
+ var resources = getPlayableVideos(candidates);
+ if (resources.length > 0) {
+ return resources[0];
+ }
+ return null;
+}
+
+function getPlayableVideos(candidates) {
+ var v = manifestVideo();
+ return candidates.filter(function(x) {
+ return /^video/.test(x.type) && v.canPlayType(x.type);
+ });
+}
+
+function getPlayableAudio(candidates) {
+ var v = manifestVideo();
+ var resources = candidates.filter(function(x) {
+ return /^audio/.test(x.type) && v.canPlayType(x.type);
+ });
+ if (resources.length > 0) {
+ return resources[0];
+ }
+ return null;
+}
+
+// Returns the type of element that should be created for the given mimetype.
+function getMajorMimeType(mimetype) {
+ if (/^video/.test(mimetype)) {
+ return "video";
+ }
+ return "audio";
+}
+
+// Force releasing decoder to avoid timeout in waiting for decoding resource.
+function removeNodeAndSource(n) {
+ n.remove();
+ // reset |srcObject| first since it takes precedence over |src|.
+ n.srcObject = null;
+ n.removeAttribute("src");
+ n.load();
+ while (n.firstChild) {
+ n.firstChild.remove();
+ }
+}
+
+function once(target, name, cb) {
+ var p = new Promise(function(resolve, reject) {
+ target.addEventListener(
+ name,
+ function() {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ if (cb) {
+ p.then(cb);
+ }
+ return p;
+}
+
+/**
+ * @param {HTMLMediaElement} video target of interest.
+ * @param {string} eventName the event to wait on.
+ * @returns {Promise} A promise that is resolved when event happens.
+ */
+function nextEvent(video, eventName) {
+ return new Promise(function(resolve, reject) {
+ let f = function(event) {
+ video.removeEventListener(eventName, f);
+ resolve(event);
+ };
+ video.addEventListener(eventName, f);
+ });
+}
+
+function TimeStamp(token) {
+ function pad(x) {
+ return x < 10 ? "0" + x : x;
+ }
+ var now = new Date();
+ var ms = now.getMilliseconds();
+ var time =
+ "[" +
+ pad(now.getHours()) +
+ ":" +
+ pad(now.getMinutes()) +
+ ":" +
+ pad(now.getSeconds()) +
+ "." +
+ ms +
+ "]" +
+ // eslint-disable-next-line no-nested-ternary
+ (ms < 10 ? " " : ms < 100 ? " " : "");
+ return token ? time + " " + token : time;
+}
+
+function Log(token, msg) {
+ info(TimeStamp(token) + " " + msg);
+}
+
+// Number of tests to run in parallel.
+var PARALLEL_TESTS = 2;
+
+// Prefs to set before running tests. Use this to improve coverage of
+// conditions that might not otherwise be encountered on the test data.
+var gTestPrefs = [
+ ["media.recorder.max_memory", 1024],
+ ["media.audio-max-decode-error", 0],
+ ["media.video-max-decode-error", 0],
+];
+
+// When true, we'll loop forever on whatever test we run. Use this to debug
+// intermittent test failures.
+const DEBUG_TEST_LOOP_FOREVER = false;
+
+// Manages a run of media tests. Runs them in chunks in order to limit
+// the number of media elements/threads running in parallel. This limits peak
+// memory use, particularly on Linux x86 where thread stacks use 10MB of
+// virtual address space.
+// Usage:
+// 1. Create a new MediaTestManager object.
+// 2. Create a test startTest function. This takes a test object and a token,
+// and performs anything necessary to start the test. The test object is an
+// element in one of the g*Tests above. Your startTest function must call
+// MediaTestManager.start(token) if it starts a test. The test object is
+// guaranteed to be playable by our supported decoders; you don't need to
+// check canPlayType.
+// 3. When your tests finishes, call MediaTestManager.finished(), passing
+// the token back to the manager. The manager may either start the next run
+// or end the mochitest if all the tests are done.
+function MediaTestManager() {
+ // Set a very large timeout to prevent Mochitest timeout.
+ // Instead MediaTestManager will manage timeout of each test.
+ SimpleTest.requestLongerTimeout(1000);
+
+ // Return how many seconds elapsed since |begin|.
+ function elapsedTime(begin) {
+ var end = new Date();
+ return (end.getTime() - begin.getTime()) / 1000;
+ }
+ // Sets up a MediaTestManager to runs through the 'tests' array, which needs
+ // to be one of, or have the same fields as, the g*Test arrays of tests. Uses
+ // the user supplied 'startTest' function to initialize the test. This
+ // function must accept two arguments, the test entry from the 'tests' array,
+ // and a token. Call MediaTestManager.started(token) if you start the test,
+ // and MediaTestManager.finished(token) when the test finishes. You don't have
+ // to start every test, but if you call started() you *must* call finish()
+ // else you'll timeout.
+ this.runTests = function(tests, startTest) {
+ this.startTime = new Date();
+ SimpleTest.info(
+ "Started " +
+ this.startTime +
+ " (" +
+ this.startTime.getTime() / 1000 +
+ "s)"
+ );
+ this.testNum = 0;
+ this.tests = tests;
+ this.startTest = startTest;
+ this.tokens = [];
+ this.isShutdown = false;
+ this.numTestsRunning = 0;
+ this.handlers = {};
+ this.timers = {};
+
+ // Always wait for explicit finish.
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({ set: gTestPrefs }, () => {
+ this.nextTest();
+ });
+
+ SimpleTest.registerCleanupFunction(() => {
+ if (this.tokens.length > 0) {
+ info("Test timed out. Remaining tests=" + this.tokens);
+ }
+ for (var token of this.tokens) {
+ var handler = this.handlers[token];
+ if (handler && handler.ontimeout) {
+ handler.ontimeout();
+ }
+ }
+ });
+ };
+
+ // Registers that the test corresponding to 'token' has been started.
+ // Don't call more than once per token.
+ this.started = function(token, handler) {
+ this.tokens.push(token);
+ this.numTestsRunning++;
+ this.handlers[token] = handler;
+
+ var onTimeout = async () => {
+ ok(false, "Test timed out!");
+ info(`${token} timed out!`);
+ await dumpDebugInfoForToken(token);
+ this.finished(token);
+ };
+ // Default timeout to 180s for each test.
+ // Call SimpleTest._originalSetTimeout() to bypass the flaky timeout checker.
+ this.timers[token] = SimpleTest._originalSetTimeout.call(
+ window,
+ onTimeout,
+ 180000
+ );
+
+ is(
+ this.numTestsRunning,
+ this.tokens.length,
+ "[started " +
+ token +
+ " t=" +
+ elapsedTime(this.startTime) +
+ "] Length of array should match number of running tests"
+ );
+ };
+
+ // Registers that the test corresponding to 'token' has finished. Call when
+ // you've finished your test. If all tests are complete this will finish the
+ // run, otherwise it may start up the next run. It's ok to call multiple times
+ // per token.
+ this.finished = function(token) {
+ var i = this.tokens.indexOf(token);
+ if (i != -1) {
+ // Remove the element from the list of running tests.
+ this.tokens.splice(i, 1);
+ }
+
+ if (this.timers[token]) {
+ // Cancel the timer when the test finishes.
+ clearTimeout(this.timers[token]);
+ this.timers[token] = null;
+ }
+
+ info("[finished " + token + "] remaining= " + this.tokens);
+ this.numTestsRunning--;
+ is(
+ this.numTestsRunning,
+ this.tokens.length,
+ "[finished " +
+ token +
+ " t=" +
+ elapsedTime(this.startTime) +
+ "] Length of array should match number of running tests"
+ );
+ if (this.tokens.length < PARALLEL_TESTS) {
+ this.nextTest();
+ }
+ };
+
+ // Starts the next batch of tests, or finishes if they're all done.
+ // Don't call this directly, call finished(token) when you're done.
+ this.nextTest = function() {
+ while (
+ this.testNum < this.tests.length &&
+ this.tokens.length < PARALLEL_TESTS
+ ) {
+ var test = this.tests[this.testNum];
+ var token = (test.name ? test.name + "-" : "") + this.testNum;
+ this.testNum++;
+
+ if (DEBUG_TEST_LOOP_FOREVER && this.testNum == this.tests.length) {
+ this.testNum = 0;
+ }
+
+ // Ensure we can play the resource type.
+ if (
+ test.type &&
+ !document.createElement("video").canPlayType(test.type)
+ ) {
+ continue;
+ }
+
+ // Do the init. This should start the test.
+ this.startTest(test, token);
+ }
+
+ if (
+ this.testNum == this.tests.length &&
+ !DEBUG_TEST_LOOP_FOREVER &&
+ this.tokens.length == 0 &&
+ !this.isShutdown
+ ) {
+ this.isShutdown = true;
+ if (this.onFinished) {
+ this.onFinished();
+ }
+ var onCleanup = () => {
+ var end = new Date();
+ SimpleTest.info(
+ "Finished at " + end + " (" + end.getTime() / 1000 + "s)"
+ );
+ SimpleTest.info("Running time: " + elapsedTime(this.startTime) + "s");
+ SimpleTest.finish();
+ };
+ mediaTestCleanup(onCleanup);
+ }
+ };
+}
+
+// Ensures we've got no active video or audio elements in the document, and
+// forces a GC to release the address space reserved by the decoders' threads'
+// stacks.
+function mediaTestCleanup(callback) {
+ var V = document.getElementsByTagName("video");
+ for (let i = 0; i < V.length; i++) {
+ removeNodeAndSource(V[i]);
+ V[i] = null;
+ }
+ var A = document.getElementsByTagName("audio");
+ for (let i = 0; i < A.length; i++) {
+ removeNodeAndSource(A[i]);
+ A[i] = null;
+ }
+ SpecialPowers.exactGC(callback);
+}
+
+async function dumpDebugInfoForToken(token) {
+ for (let v of document.getElementsByTagName("video")) {
+ if (token === v.token) {
+ info(JSON.stringify(await SpecialPowers.wrap(v).mozRequestDebugInfo()));
+ return;
+ }
+ }
+ for (let a of document.getElementsByTagName("audio")) {
+ if (token === a.token) {
+ info(JSON.stringify(await SpecialPowers.wrap(a).mozRequestDebugInfo()));
+ return;
+ }
+ }
+}
+
+// Could be undefined in a page opened by the parent test page
+// like file_access_controls.html.
+if ("SimpleTest" in window) {
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ // Register timeout function to dump debugging logs.
+ SimpleTest.registerTimeoutFunction(async function() {
+ for (const v of document.getElementsByTagName("video")) {
+ SimpleTest.info(
+ JSON.stringify(await SpecialPowers.wrap(v).mozRequestDebugInfo())
+ );
+ }
+ for (const a of document.getElementsByTagName("audio")) {
+ SimpleTest.info(
+ JSON.stringify(await SpecialPowers.wrap(a).mozRequestDebugInfo())
+ );
+ }
+ });
+}
diff --git a/dom/media/test/midflight-redirect.sjs b/dom/media/test/midflight-redirect.sjs
new file mode 100644
index 0000000000..d4dae537a0
--- /dev/null
+++ b/dom/media/test/midflight-redirect.sjs
@@ -0,0 +1,78 @@
+function parseQuery(query, key) {
+ for (let p of query.split('&')) {
+ if (p == key) {
+ return true;
+ }
+ if (p.startsWith(key + "=")) {
+ return p.substring(key.length + 1);
+ }
+ }
+}
+
+// Return the first few bytes in a short byte range response. When Firefox
+// requests subsequent bytes in a second range request, respond with a
+// redirect. Requests after the first redirected are serviced as expected.
+function handleRequest(request, response)
+{
+ var query = request.queryString;
+ var resource = parseQuery(query, "resource");
+ var type = parseQuery(query, "type") || "application/octet-stream";
+ var redirected = parseQuery(query, "redirected") || false;
+ var useCors = parseQuery(query, "cors") || false;
+
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+ var paths = "tests/dom/media/test/" + resource;
+ var split = paths.split("/");
+ for (var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+
+ bis.setInputStream(fis);
+ var bytes = bis.readBytes(bis.available());
+ let [from, to] = request.getHeader("range").split("=")[1].split("-").map(s => parseInt(s));
+
+ if (!redirected && from > 0) {
+ var origin = request.host == "mochi.test" ? "example.org" : "mochi.test:8888";
+ response.setStatusLine(request.httpVersion, 303, "See Other");
+ let url = "http://" + origin +
+ "/tests/dom/media/test/midflight-redirect.sjs?redirected&" + query;
+ response.setHeader("Location", url);
+ response.setHeader("Content-Type", "text/html");
+ return;
+ }
+
+ if (isNaN(to)) {
+ to = bytes.length - 1;
+ }
+
+ if (from == 0 && !redirected) {
+ to = parseInt(parseQuery(query, "redirectAt")) || Math.floor(bytes.length / 4);
+ }
+ to = Math.min(to, bytes.length - 1);
+
+ // Note: 'to' is the first index *excluded*, so we need (to + 1)
+ // in the substring end here.
+ byterange = bytes.substring(from, to + 1);
+
+ let contentRange = "bytes " + from + "-" + to + "/" + bytes.length;
+ let contentLength = byterange.length.toString();
+
+ response.setStatusLine(request.httpVersion, 206, "Partial Content");
+ response.setHeader("Content-Range", contentRange);
+ response.setHeader("Content-Length", contentLength, false);
+ response.setHeader("Content-Type", type, false);
+ response.setHeader("Accept-Ranges", "bytes", false);
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ if (redirected && useCors) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ }
+ response.write(byterange, byterange.length);
+ bis.close();
+}
diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini
new file mode 100644
index 0000000000..6561f1e614
--- /dev/null
+++ b/dom/media/test/mochitest.ini
@@ -0,0 +1,1172 @@
+# Media tests should be backend independent, i.e., not conditioned on ogg,
+# wave etc. (The only exception is the can_play_type tests, which
+# necessarily depend on the backend(s) configured.) As far as possible, each
+# test should work with any resource type. This makes it easy to add new
+# backends and reduces the amount of test duplication.
+
+# For each supported backend, resources that can be played by that backend
+# should be added to the lists in manifest.js. Media tests that aren't
+# testing for a bug in handling a specific resource type should pick one of
+# the lists in manifest.js and run the test for each resource in the list
+# that is supported in the current build (the canPlayType API is useful for
+# this).
+
+# To test whether a valid resource can simply be played through correctly,
+# and optionally that its metadata is read correctly, just add it to
+# gPlayTests in manifest.js. To test whether an invalid resource correctly
+# throws an error (and does not cause a crash or hang), just add it to
+# gErrorTests in manifest.js.
+
+# To test for a specific bug in handling a specific resource type, make the
+# test first check canPlayType for the type, and if it's not supported, just
+# do ok(true, "Type not supported") and stop the test.
+
+[DEFAULT]
+subsuite = media
+skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536604
+support-files =
+ 16bit_wave_extrametadata.wav
+ 16bit_wave_extrametadata.wav^headers^
+ 320x240.ogv
+ 320x240.ogv^headers^
+ 448636.ogv
+ 448636.ogv^headers^
+ A4.ogv
+ A4.ogv^headers^
+ VID_0001.ogg
+ VID_0001.ogg^headers^
+ allowed.sjs
+ ambisonics.mp4
+ ambisonics.mp4^headers^
+ audio-gaps.ogg
+ audio-gaps.ogg^headers^
+ audio-gaps-short.ogg
+ audio-gaps-short.ogg^headers^
+ audio-overhang.ogg
+ audio-overhang.ogg^headers^
+ audio.wav
+ audio.wav^headers^
+ av1.mp4
+ av1.mp4^headers^
+ background_video.js
+ badtags.ogg
+ badtags.ogg^headers^
+ bear-640x360-v_frag-cenc-key_rotation.mp4
+ bear-640x360-a_frag-cenc-key_rotation.mp4
+ beta-phrasebook.ogg
+ beta-phrasebook.ogg^headers^
+ big.wav
+ big.wav^headers^
+ big-buck-bunny-cenc-avc3-1.m4s
+ big-buck-bunny-cenc-avc3-1.m4s^headers^
+ big-buck-bunny-cenc-avc3-init.mp4
+ big-buck-bunny-cenc-avc3-init.mp4^headers^
+ big-short.wav
+ big-short.wav^headers^
+ bipbop.mp4
+ bipbop-cenc-audio1.m4s
+ bipbop-cenc-audio1.m4s^headers^
+ bipbop-cenc-audio2.m4s
+ bipbop-cenc-audio2.m4s^headers^
+ bipbop-cenc-audio3.m4s
+ bipbop-cenc-audio3.m4s^headers^
+ bipbop-cenc-audioinit.mp4
+ bipbop-cenc-audioinit.mp4^headers^
+ bipbop-cenc-video1.m4s
+ bipbop-cenc-video1.m4s^headers^
+ bipbop-cenc-video2.m4s
+ bipbop-cenc-video2.m4s^headers^
+ bipbop-cenc-videoinit.mp4
+ bipbop-cenc-videoinit.mp4^headers^
+ bipbop-cenc-video-10s.mp4
+ bipbop-cenc-video-10s.mp4^headers^
+ bipbop-clearkey-keyrotation-clear-lead-audio.mp4
+ bipbop-clearkey-keyrotation-clear-lead-audio.mp4^headers^
+ bipbop-clearkey-keyrotation-clear-lead-video.mp4
+ bipbop-clearkey-keyrotation-clear-lead-video.mp4^headers^
+ bipbop_225w_175kbps.mp4
+ bipbop_225w_175kbps.mp4^headers^
+ bipbop_225w_175kbps-cenc-audio-key1-1.m4s
+ bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key1-2.m4s
+ bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key1-3.m4s
+ bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key1-4.m4s
+ bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key1-init.mp4
+ bipbop_225w_175kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_225w_175kbps-cenc-audio-key2-1.m4s
+ bipbop_225w_175kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key2-2.m4s
+ bipbop_225w_175kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key2-3.m4s
+ bipbop_225w_175kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key2-4.m4s
+ bipbop_225w_175kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_225w_175kbps-cenc-audio-key2-init.mp4
+ bipbop_225w_175kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_225w_175kbps-cenc-video-key1-1.m4s
+ bipbop_225w_175kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_225w_175kbps-cenc-video-key1-init.mp4
+ bipbop_225w_175kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_225w_175kbps-cenc-video-key2-1.m4s
+ bipbop_225w_175kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_225w_175kbps-cenc-video-key2-init.mp4
+ bipbop_225w_175kbps-cenc-video-key2-init.mp4^headers^
+ bipbop_300_215kbps-cenc-audio-key1-1.m4s
+ bipbop_300_215kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key1-2.m4s
+ bipbop_300_215kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key1-3.m4s
+ bipbop_300_215kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key1-4.m4s
+ bipbop_300_215kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key1-init.mp4
+ bipbop_300_215kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_300_215kbps-cenc-audio-key2-1.m4s
+ bipbop_300_215kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key2-2.m4s
+ bipbop_300_215kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key2-3.m4s
+ bipbop_300_215kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key2-4.m4s
+ bipbop_300_215kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_300_215kbps-cenc-audio-key2-init.mp4
+ bipbop_300_215kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_300_215kbps-cenc-video-key1-1.m4s
+ bipbop_300_215kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_300_215kbps-cenc-video-key1-2.m4s
+ bipbop_300_215kbps-cenc-video-key1-2.m4s^headers^
+ bipbop_300_215kbps-cenc-video-key1-init.mp4
+ bipbop_300_215kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_300_215kbps-cenc-video-key2-1.m4s
+ bipbop_300_215kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_300_215kbps-cenc-video-key2-2.m4s
+ bipbop_300_215kbps-cenc-video-key2-2.m4s^headers^
+ bipbop_300_215kbps-cenc-video-key2-init.mp4
+ bipbop_300_215kbps-cenc-video-key2-init.mp4^headers^
+ bipbop_300wp_227kbps-cenc-audio-key1-1.m4s
+ bipbop_300wp_227kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key1-2.m4s
+ bipbop_300wp_227kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key1-3.m4s
+ bipbop_300wp_227kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key1-4.m4s
+ bipbop_300wp_227kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key1-init.mp4
+ bipbop_300wp_227kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_300wp_227kbps-cenc-audio-key2-1.m4s
+ bipbop_300wp_227kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key2-2.m4s
+ bipbop_300wp_227kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key2-3.m4s
+ bipbop_300wp_227kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key2-4.m4s
+ bipbop_300wp_227kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_300wp_227kbps-cenc-audio-key2-init.mp4
+ bipbop_300wp_227kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_300wp_227kbps-cenc-video-key1-1.m4s
+ bipbop_300wp_227kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_300wp_227kbps-cenc-video-key1-2.m4s
+ bipbop_300wp_227kbps-cenc-video-key1-2.m4s^headers^
+ bipbop_300wp_227kbps-cenc-video-key1-init.mp4
+ bipbop_300wp_227kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_300wp_227kbps-cenc-video-key2-1.m4s
+ bipbop_300wp_227kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_300wp_227kbps-cenc-video-key2-2.m4s
+ bipbop_300wp_227kbps-cenc-video-key2-2.m4s^headers^
+ bipbop_300wp_227kbps-cenc-video-key2-init.mp4
+ bipbop_300wp_227kbps-cenc-video-key2-init.mp4^headers^
+ bipbop_360w_253kbps-cenc-audio-key1-1.m4s
+ bipbop_360w_253kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key1-2.m4s
+ bipbop_360w_253kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key1-3.m4s
+ bipbop_360w_253kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key1-4.m4s
+ bipbop_360w_253kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key1-init.mp4
+ bipbop_360w_253kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_360w_253kbps-cenc-audio-key2-1.m4s
+ bipbop_360w_253kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key2-2.m4s
+ bipbop_360w_253kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key2-3.m4s
+ bipbop_360w_253kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key2-4.m4s
+ bipbop_360w_253kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_360w_253kbps-cenc-audio-key2-init.mp4
+ bipbop_360w_253kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_360w_253kbps-cenc-video-key1-1.m4s
+ bipbop_360w_253kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_360w_253kbps-cenc-video-key1-init.mp4
+ bipbop_360w_253kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_360w_253kbps-cenc-video-key2-1.m4s
+ bipbop_360w_253kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_360w_253kbps-cenc-video-key2-init.mp4
+ bipbop_360w_253kbps-cenc-video-key2-init.mp4^headers^
+ bipbop_360w_253kbps-clearkey-audio.webm
+ bipbop_360w_253kbps-clearkey-audio.webm^headers^
+ bipbop_360w_253kbps-clearkey-video-vp8.webm
+ bipbop_360w_253kbps-clearkey-video-vp8.webm^headers^
+ bipbop_360w_253kbps-clearkey-video-vp9.webm
+ bipbop_360w_253kbps-clearkey-video-vp9.webm^headers^
+ bipbop_480_624kbps-cenc-audio-key1-1.m4s
+ bipbop_480_624kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key1-2.m4s
+ bipbop_480_624kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key1-3.m4s
+ bipbop_480_624kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key1-4.m4s
+ bipbop_480_624kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key1-init.mp4
+ bipbop_480_624kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_480_624kbps-cenc-audio-key2-1.m4s
+ bipbop_480_624kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key2-2.m4s
+ bipbop_480_624kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key2-3.m4s
+ bipbop_480_624kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key2-4.m4s
+ bipbop_480_624kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_480_624kbps-cenc-audio-key2-init.mp4
+ bipbop_480_624kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_480_624kbps-cenc-video-key1-1.m4s
+ bipbop_480_624kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_480_624kbps-cenc-video-key1-2.m4s
+ bipbop_480_624kbps-cenc-video-key1-2.m4s^headers^
+ bipbop_480_624kbps-cenc-video-key1-init.mp4
+ bipbop_480_624kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_480_624kbps-cenc-video-key2-1.m4s
+ bipbop_480_624kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_480_624kbps-cenc-video-key2-2.m4s
+ bipbop_480_624kbps-cenc-video-key2-2.m4s^headers^
+ bipbop_480_624kbps-cenc-video-key2-init.mp4
+ bipbop_480_624kbps-cenc-video-key2-init.mp4^headers^
+ bipbop_480_959kbps-cenc-audio-key1-1.m4s
+ bipbop_480_959kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key1-2.m4s
+ bipbop_480_959kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key1-3.m4s
+ bipbop_480_959kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key1-4.m4s
+ bipbop_480_959kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key1-init.mp4
+ bipbop_480_959kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_480_959kbps-cenc-audio-key2-1.m4s
+ bipbop_480_959kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key2-2.m4s
+ bipbop_480_959kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key2-3.m4s
+ bipbop_480_959kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key2-4.m4s
+ bipbop_480_959kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_480_959kbps-cenc-audio-key2-init.mp4
+ bipbop_480_959kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_480_959kbps-cenc-video-key1-1.m4s
+ bipbop_480_959kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_480_959kbps-cenc-video-key1-2.m4s
+ bipbop_480_959kbps-cenc-video-key1-2.m4s^headers^
+ bipbop_480_959kbps-cenc-video-key1-init.mp4
+ bipbop_480_959kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_480_959kbps-cenc-video-key2-1.m4s
+ bipbop_480_959kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_480_959kbps-cenc-video-key2-2.m4s
+ bipbop_480_959kbps-cenc-video-key2-2.m4s^headers^
+ bipbop_480_959kbps-cenc-video-key2-init.mp4
+ bipbop_480_959kbps-cenc-video-key2-init.mp4^headers^
+ bipbop_480wp_663kbps-cenc-audio-key1-1.m4s
+ bipbop_480wp_663kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key1-2.m4s
+ bipbop_480wp_663kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key1-3.m4s
+ bipbop_480wp_663kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key1-4.m4s
+ bipbop_480wp_663kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key1-init.mp4
+ bipbop_480wp_663kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_480wp_663kbps-cenc-audio-key2-1.m4s
+ bipbop_480wp_663kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key2-2.m4s
+ bipbop_480wp_663kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key2-3.m4s
+ bipbop_480wp_663kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key2-4.m4s
+ bipbop_480wp_663kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_480wp_663kbps-cenc-audio-key2-init.mp4
+ bipbop_480wp_663kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_480wp_663kbps-cenc-video-key1-1.m4s
+ bipbop_480wp_663kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_480wp_663kbps-cenc-video-key1-2.m4s
+ bipbop_480wp_663kbps-cenc-video-key1-2.m4s^headers^
+ bipbop_480wp_663kbps-cenc-video-key1-init.mp4
+ bipbop_480wp_663kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_480wp_663kbps-cenc-video-key2-1.m4s
+ bipbop_480wp_663kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_480wp_663kbps-cenc-video-key2-2.m4s
+ bipbop_480wp_663kbps-cenc-video-key2-2.m4s^headers^
+ bipbop_480wp_663kbps-cenc-video-key2-init.mp4
+ bipbop_480wp_663kbps-cenc-video-key2-init.mp4^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key1-1.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key1-2.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key1-3.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key1-4.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4
+ bipbop_480wp_1001kbps-cenc-audio-key1-init.mp4^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key2-1.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key2-2.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key2-3.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s
+ bipbop_480wp_1001kbps-cenc-audio-key2-4.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4
+ bipbop_480wp_1001kbps-cenc-audio-key2-init.mp4^headers^
+ bipbop_480wp_1001kbps-cenc-video-key1-1.m4s
+ bipbop_480wp_1001kbps-cenc-video-key1-1.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-video-key1-2.m4s
+ bipbop_480wp_1001kbps-cenc-video-key1-2.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-video-key1-init.mp4
+ bipbop_480wp_1001kbps-cenc-video-key1-init.mp4^headers^
+ bipbop_480wp_1001kbps-cenc-video-key2-1.m4s
+ bipbop_480wp_1001kbps-cenc-video-key2-1.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-video-key2-2.m4s
+ bipbop_480wp_1001kbps-cenc-video-key2-2.m4s^headers^
+ bipbop_480wp_1001kbps-cenc-video-key2-init.mp4
+ bipbop_480wp_1001kbps-cenc-video-key2-init.mp4^headers^
+ bipbop-lateaudio.mp4
+ bipbop-lateaudio.mp4^headers^
+ black100x100-aspect3to2.ogv
+ black100x100-aspect3to2.ogv^headers^
+ bogus.duh
+ bogus.ogv
+ bogus.ogv^headers^
+ bogus.wav
+ bogus.wav^headers^
+ bug461281.ogg
+ bug461281.ogg^headers^
+ bug482461-theora.ogv
+ bug482461-theora.ogv^headers^
+ bug482461.ogv
+ bug482461.ogv^headers^
+ bug495129.ogv
+ bug495129.ogv^headers^
+ bug495794.ogg
+ bug495794.ogg^headers^
+ bug498380.ogv
+ bug498380.ogv^headers^
+ bug498855-1.ogv
+ bug498855-1.ogv^headers^
+ bug498855-2.ogv
+ bug498855-2.ogv^headers^
+ bug498855-3.ogv
+ bug498855-3.ogv^headers^
+ bug499519.ogv
+ bug499519.ogv^headers^
+ bug500311.ogv
+ bug500311.ogv^headers^
+ bug501279.ogg
+ bug501279.ogg^headers^
+ bug504613.ogv
+ bug504613.ogv^headers^
+ bug504644.ogv
+ bug504644.ogv^headers^
+ bug504843.ogv
+ bug504843.ogv^headers^
+ bug506094.ogv
+ bug506094.ogv^headers^
+ bug516323.indexed.ogv
+ bug516323.indexed.ogv^headers^
+ bug516323.ogv
+ bug516323.ogv^headers^
+ bug520493.ogg
+ bug520493.ogg^headers^
+ bug520500.ogg
+ bug520500.ogg^headers^
+ bug520908.ogv
+ bug520908.ogv^headers^
+ bug523816.ogv
+ bug523816.ogv^headers^
+ bug533822.ogg
+ bug533822.ogg^headers^
+ bug556821.ogv
+ bug556821.ogv^headers^
+ bug557094.ogv
+ bug557094.ogv^headers^
+ bug603918.webm
+ bug603918.webm^headers^
+ bug604067.webm
+ bug604067.webm^headers^
+ bug1066943.webm
+ bug1066943.webm^headers^
+ bug1301226.wav
+ bug1301226.wav^headers^
+ bug1301226-odd.wav
+ bug1301226-odd.wav^headers^
+ bug1377278.webm
+ bug1377278.webm^headers^
+ bunny.webm
+ can_play_type_dash.js
+ can_play_type_ogg.js
+ can_play_type_wave.js
+ can_play_type_webm.js
+ cancellable_request.sjs
+ chain.ogg
+ chain.ogg^headers^
+ chain.ogv
+ chain.ogv^headers^
+ chain.opus
+ chain.opus^headers^
+ chained-audio-video.ogg
+ chained-audio-video.ogg^headers^
+ chained-video.ogv
+ chained-video.ogv^headers^
+ chromeHelper.js
+ cloneElementVisually_helpers.js
+ contentType.sjs
+ detodos.opus
+ detodos.opus^headers^
+ detodos.webm
+ detodos.webm^headers^
+ detodos-short.webm
+ detodos-short.webm^headers^
+ detodos-recorder-test.opus
+ detodos-recorder-test.opus^headers^
+ detodos-short.opus
+ detodos-short.opus^headers^
+ dirac.ogg
+ dirac.ogg^headers^
+ dynamic_resource.sjs
+ eme.js
+ empty_size.mp3
+ file_access_controls.html
+ file_eme_createMediaKeys.html
+ flac-s24.flac
+ flac-s24.flac^headers^
+ flac-noheader-s16.flac
+ flac-noheader-s16.flac^headers^
+ flac-sample.mp4
+ flac-sample.mp4^headers^
+ flac-sample-cenc.mp4
+ flac-sample-cenc.mp4^headers^
+ fragment_noplay.js
+ fragment_play.js
+ gizmo.mp4
+ gizmo.mp4^headers^
+ gizmo-noaudio.mp4
+ gizmo-noaudio.mp4^headers^
+ gizmo-short.mp4
+ gizmo-short.mp4^headers^
+ gizmo.webm
+ gizmo.webm^headers^
+ gizmo-noaudio.webm
+ gizmo-noaudio.webm^headers^
+ gUM_support.js
+ gzipped_mp4.sjs
+ huge-id3.mp3
+ huge-id3.mp3^headers^
+ id3tags.mp3
+ id3tags.mp3^headers^
+ invalid-cmap-s0c0.opus
+ invalid-cmap-s0c0.opus^headers^
+ invalid-cmap-s0c2.opus
+ invalid-cmap-s0c2.opus^headers^
+ invalid-cmap-s1c2.opus
+ invalid-cmap-s1c2.opus^headers^
+ invalid-cmap-short.opus
+ invalid-cmap-short.opus^headers^
+ invalid-discard_on_multi_blocks.webm
+ invalid-discard_on_multi_blocks.webm^headers^
+ invalid-excess_discard.webm
+ invalid-excess_discard.webm^headers^
+ invalid-excess_neg_discard.webm
+ invalid-excess_neg_discard.webm^headers^
+ invalid-m0c0.opus
+ invalid-m0c0.opus^headers^
+ invalid-m0c3.opus
+ invalid-m0c3.opus^headers^
+ invalid-m1c0.opus
+ invalid-m1c0.opus^headers^
+ invalid-m1c9.opus
+ invalid-m1c9.opus^headers^
+ invalid-m2c0.opus
+ invalid-m2c0.opus^headers^
+ invalid-m2c1.opus
+ invalid-m2c1.opus^headers^
+ invalid-neg_discard.webm
+ invalid-neg_discard.webm^headers^
+ invalid-preskip.webm
+ invalid-preskip.webm^headers^
+ manifest.js
+ midflight-redirect.sjs
+ multiple-bos.ogg
+ multiple-bos.ogg^headers^
+ multiple-bos-more-header-fileds.ogg
+ multiple-bos-more-header-fileds.ogg^headers^
+ multi_id3v2.mp3
+ no-cues.webm
+ no-cues.webm^headers^
+ notags.mp3
+ notags.mp3^headers^
+ opus-mapping2.mp4
+ opus-mapping2.mp4^headers^
+ opus-mapping2.webm
+ opus-mapping2.webm^headers^
+ opus-sample.mp4
+ opus-sample.mp4^headers^
+ opus-sample-cenc.mp4
+ opus-sample-cenc.mp4^headers^
+ owl-funnier-id3.mp3
+ owl-funnier-id3.mp3^headers^
+ owl-funny-id3.mp3
+ owl-funny-id3.mp3^headers^
+ owl.mp3
+ owl.mp3^headers^
+ owl-short.mp3
+ owl-short.mp3^headers^
+ pixel_aspect_ratio.mp4
+ play_promise.js
+ poster-test.jpg
+ r11025_msadpcm_c1.wav
+ r11025_msadpcm_c1.wav^headers^
+ r11025_s16_c1.wav
+ r11025_s16_c1.wav^headers^
+ r11025_s16_c1_trailing.wav
+ r11025_s16_c1_trailing.wav^headers^
+ r11025_s16_c1-short.wav
+ r11025_s16_c1-short.wav^headers^
+ r11025_u8_c1.wav
+ r11025_u8_c1.wav^headers^
+ r11025_u8_c1_trunc.wav
+ r11025_u8_c1_trunc.wav^headers^
+ r16000_u8_c1_list.wav
+ r16000_u8_c1_list.wav^headers^
+ reactivate_helper.html
+ red-46x48.mp4
+ red-46x48.mp4^headers^
+ red-48x46.mp4
+ red-48x46.mp4^headers^
+ redirect.sjs
+ referer.sjs
+ resolution-change.webm
+ resolution-change.webm^headers^
+ sample.3gp
+ sample.3g2
+ sample-encrypted-sgpdstbl-sbgptraf.mp4
+ sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^
+ sample-fisbone-skeleton4.ogv
+ sample-fisbone-skeleton4.ogv^headers^
+ sample-fisbone-wrong-header.ogv
+ sample-fisbone-wrong-header.ogv^headers^
+ seek.ogv
+ seek.ogv^headers^
+ seek-short.ogv
+ seek-short.ogv^headers^
+ seek.webm
+ seek.webm^headers^
+ seek-short.webm
+ seek-short.webm^headers^
+ seek_support.js
+ seekLies.sjs
+ seek_with_sound.ogg^headers^
+ short-cenc.mp4
+ sine.webm
+ sine.webm^headers^
+ sintel-short-clearkey-subsample-encrypted-audio.webm
+ sintel-short-clearkey-subsample-encrypted-audio.webm^headers^
+ sintel-short-clearkey-subsample-encrypted-video.webm
+ sintel-short-clearkey-subsample-encrypted-video.webm^headers^
+ short.mp4
+ short.mp4.gz
+ short.mp4^headers^
+ short-aac-encrypted-audio.mp4
+ short-aac-encrypted-audio.mp4^headers^
+ short-audio-fragmented-cenc-without-pssh.mp4
+ short-audio-fragmented-cenc-without-pssh.mp4^headers^
+ short-video.ogv
+ short-video.ogv^headers^
+ short-vp9-encrypted-video.mp4
+ short-vp9-encrypted-video.mp4^headers^
+ small-shot-mp3.mp4
+ small-shot-mp3.mp4^headers^
+ small-shot.m4a
+ small-shot.mp3
+ small-shot.mp3^headers^
+ small-shot.ogg
+ small-shot.ogg^headers^
+ small-shot.flac
+ sound.ogg
+ sound.ogg^headers^
+ spacestorm-1000Hz-100ms.ogg
+ spacestorm-1000Hz-100ms.ogg^headers^
+ split.webm
+ split.webm^headers^
+ street.mp4
+ street.mp4^headers^
+ test-1-mono.opus
+ test-1-mono.opus^headers^
+ test-2-stereo.opus
+ test-2-stereo.opus^headers^
+ test-3-LCR.opus
+ test-3-LCR.opus^headers^
+ test-4-quad.opus
+ test-4-quad.opus^headers^
+ test-5-5.0.opus
+ test-5-5.0.opus^headers^
+ test-6-5.1.opus
+ test-6-5.1.opus^headers^
+ test-7-6.1.opus
+ test-7-6.1.opus^headers^
+ test-8-7.1.opus
+ test-8-7.1.opus^headers^
+ test-stereo-phase-inversion-180.opus
+ test-stereo-phase-inversion-180.opus^headers^
+ variable-channel.ogg
+ variable-channel.ogg^headers^
+ variable-channel.opus
+ variable-channel.opus^headers^
+ variable-preskip.opus
+ variable-preskip.opus^headers^
+ variable-samplerate.ogg
+ variable-samplerate.ogg^headers^
+ variable-samplerate.opus
+ variable-samplerate.opus^headers^
+ vbr-head.mp3
+ vbr-head.mp3^headers^
+ vbr.mp3
+ vbr.mp3^headers^
+ very-short.mp3
+ video-overhang.ogg
+ video-overhang.ogg^headers^
+ vp9-superframes.webm
+ vp9-superframes.webm^headers^
+ vp9.webm
+ vp9.webm^headers^
+ vp9-short.webm
+ vp9-short.webm^headers^
+ vp9cake.webm
+ vp9cake.webm^headers^
+ vp9cake-short.webm
+ vp9cake-short.webm^headers^
+ wave_metadata.wav
+ wave_metadata.wav^headers^
+ wave_metadata_bad_len.wav
+ wave_metadata_bad_len.wav^headers^
+ wave_metadata_bad_no_null.wav
+ wave_metadata_bad_no_null.wav^headers^
+ wave_metadata_bad_utf8.wav
+ wave_metadata_bad_utf8.wav^headers^
+ wave_metadata_unknown_tag.wav
+ wave_metadata_unknown_tag.wav^headers^
+ wave_metadata_utf8.wav
+ wave_metadata_utf8.wav^headers^
+ wavedata_alaw.wav
+ wavedata_alaw.wav^headers^
+ wavedata_float.wav
+ wavedata_float.wav^headers^
+ wavedata_s24.wav
+ wavedata_s24.wav^headers^
+ wavedata_s16.wav
+ wavedata_s16.wav^headers^
+ wavedata_u8.wav
+ wavedata_u8.wav^headers^
+ wavedata_ulaw.wav
+ wavedata_ulaw.wav^headers^
+ !/dom/canvas/test/captureStream_common.js
+ !/dom/html/test/reflect.js
+ !/dom/media/webrtc/tests/mochitests/head.js
+ hls/bipbop_16x9_single.m3u8
+ hls/bipbop_4x3_single.m3u8
+ hls/bipbop_4x3_variant.m3u8
+ hls/400x300_prog_index.m3u8
+ hls/400x300_prog_index_5s.m3u8
+ hls/416x243_prog_index_5s.m3u8
+ hls/640x480_prog_index.m3u8
+ hls/960x720_prog_index.m3u8
+ hls/400x300_seg0.ts
+ hls/400x300_seg0_5s.ts
+ hls/400x300_seg1.ts
+ hls/416x243_seg0_5s.ts
+ hls/640x480_seg0.ts
+ hls/640x480_seg1.ts
+ hls/960x720_seg0.ts
+ hls/960x720_seg1.ts
+
+[test_access_control.html]
+[test_arraybuffer.html]
+[test_aspectratio_mp4.html]
+[test_audio1.html]
+[test_audio2.html]
+[test_audioDocumentTitle.html]
+skip-if = true # bug 475110 - disabled since we don't play Wave files standalone
+[test_buffered.html]
+[test_bug448534.html]
+[test_bug463162.xhtml]
+[test_bug465498.html]
+[test_bug495145.html]
+skip-if = os == "win" #Bug 1404373
+[test_bug495300.html]
+[test_bug654550.html]
+[test_bug686942.html]
+[test_bug726904.html]
+[test_bug874897.html]
+[test_bug879717.html]
+skip-if = toolkit == 'android' # bug 1285441, android(bug 1232305)
+tags=capturestream
+[test_bug895305.html]
+skip-if = (android_version == '25' && debug) # android(bug 1232305)
+[test_bug919265.html]
+skip-if = (android_version == '25' && debug) # android(bug 1232305)
+[test_bug1113600.html]
+tags=capturestream
+[test_bug1242338.html]
+[test_bug1248229.html]
+tags=capturestream
+[test_bug1512958.html]
+tags=mtg capturestream
+[test_bug1553262.html]
+tags=mtg capturestream
+[test_can_play_type.html]
+skip-if = (android_version == '25' && debug) # android(bug 1232305)
+[test_can_play_type_mpeg.html]
+[test_can_play_type_no_ogg.html]
+skip-if = (android_version == '25' && debug) # android(bug 1232305)
+[test_can_play_type_ogg.html]
+skip-if = (android_version == '25' && debug) # android(bug 1232305)
+[test_chaining.html]
+[test_clone_media_element.html]
+skip-if = toolkit == 'android' # bug 1108558, android(bug 1232305)
+[test_closing_connections.html]
+[test_constants.html]
+[test_controls.html]
+[test_cueless_webm_seek-1.html]
+[test_cueless_webm_seek-2.html]
+[test_cueless_webm_seek-3.html]
+[test_currentTime.html]
+[test_decode_error.html]
+[test_decode_error_crossorigin.html]
+[test_decoder_disable.html]
+[test_defaultMuted.html]
+[test_delay_load.html]
+[test_duration_after_error.html]
+[test_eme_autoplay.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_pssh_in_moof.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_session_callable_value.html]
+skip-if = verify && debug && (os == 'linux')
+scheme=https
+[test_eme_canvas_blocked.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_createMediaKeys_iframes.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_detach_media_keys.html]
+skip-if = toolkit == 'android' || (verify && debug && (os == 'linux' || os == 'win')) # bug 1149374
+scheme=https
+[test_eme_detach_reattach_same_mediakeys_during_playback.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_initDataTypes.html]
+skip-if = toolkit == 'android' || (verify && debug && (os == 'linux' || os == 'mac')) # bug 1149374
+scheme=https
+[test_eme_missing_pssh.html]
+skip-if = toolkit == 'android' || (verify && debug && (os == 'mac')) # bug 1149374
+scheme=https
+[test_eme_non_mse_fails.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_request_notifications.html]
+skip-if = toolkit == 'android' || (verify && debug && (os == 'linux')) # bug 1149374
+scheme=https
+[test_eme_playback.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_requestKeySystemAccess.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_requestMediaKeySystemAccess_with_app_approval.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_sample_groups_playback.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_setMediaKeys_before_attach_MediaSource.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_stream_capture_blocked_case1.html]
+tags=mtg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_stream_capture_blocked_case2.html]
+tags=mtg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_stream_capture_blocked_case3.html]
+tags=mtg capturestream
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_unsetMediaKeys_then_capture.html]
+skip-if = xorigin || toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_waitingforkey.html]
+skip-if = xorigin || toolkit == 'android' # bug 1149374
+scheme=https
+[test_eme_getstatusforpolicy.html]
+skip-if = toolkit == 'android' # bug 1149374
+scheme=https
+[test_empty_resource.html]
+[test_error_in_video_document.html]
+[test_error_on_404.html]
+[test_fastSeek.html]
+[test_fastSeek-forwards.html]
+[test_imagecapture.html]
+scheme=https
+[test_info_leak.html]
+[test_invalid_reject.html]
+[test_invalid_reject_play.html]
+[test_invalid_seek.html]
+[test_load.html]
+[test_load_candidates.html]
+[test_load_same_resource.html]
+[test_load_source.html]
+[test_load_source_empty_type.html]
+[test_loop.html]
+[test_looping_eventsOrder.html]
+[test_media_selection.html]
+[test_media_sniffer.html]
+[test_mediacapabilities_resistfingerprinting.html]
+[test_mediarecorder_avoid_recursion.html]
+skip-if = os == 'win' && !debug
+scheme=https
+tags=mtg
+[test_mediarecorder_bitrate.html]
+skip-if = toolkit == 'android' # bug 1297432, android(bug 1232305)
+tags=mtg
+[test_mediarecorder_creation.html]
+tags=mtg capturestream
+[test_mediarecorder_creation_fail.html]
+tags=mtg
+[test_mediarecorder_fires_start_event_once_when_erroring.html]
+tags=mtg
+[test_mediarecorder_onerror_pause.html]
+scheme=https
+tags=mtg
+[test_mediarecorder_pause_resume_video.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+[test_mediarecorder_playback_can_repeat.html]
+tags=mtg
+[test_mediarecorder_principals.html]
+skip-if = (os == 'win' && os_version == '10.0' && webrender) # Bug 1453375
+tags=mtg
+[test_mediarecorder_record_4ch_audiocontext.html]
+tags=mtg
+skip-if = os == "linux" && bits == 64 #Bug 1598101
+[test_mediarecorder_record_addtracked_stream.html]
+skip-if = toolkit == 'android' # Bug 1408241
+tags=mtg capturestream
+[test_mediarecorder_record_audiocontext.html]
+tags=mtg
+[test_mediarecorder_record_audiocontext_mlk.html]
+tags=mtg
+[test_mediarecorder_record_audionode.html]
+tags=mtg
+[test_mediarecorder_record_canvas_captureStream.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+tags=mtg
+[test_mediarecorder_record_changing_video_resolution.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+tags=mtg
+[test_mediarecorder_record_upsize_resolution.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+tags=mtg
+[test_mediarecorder_record_downsize_resolution.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+tags=mtg
+[test_mediarecorder_record_gum_video_timeslice.html]
+scheme=https
+tags=mtg
+[test_mediarecorder_record_gum_video_timeslice_mixed.html]
+scheme=https
+tags=mtg
+[test_mediarecorder_record_immediate_stop.html]
+tags=mtg capturestream
+[test_mediarecorder_record_no_timeslice.html]
+tags=mtg capturestream
+[test_mediarecorder_record_session.html]
+tags=mtg capturestream
+[test_mediarecorder_record_startstopstart.html]
+tags=mtg
+[test_mediarecorder_record_timeslice.html]
+tags=mtg capturestream
+[test_mediarecorder_reload_crash.html]
+tags=mtg capturestream
+[test_mediarecorder_state_transition.html]
+tags=mtg capturestream
+[test_mediarecorder_state_event_order.html]
+tags=mtg capturestream
+[test_mediarecorder_webm_support.html]
+tags=mtg
+[test_mediarecorder_record_getdata_afterstart.html]
+tags=mtg capturestream
+[test_mediatrack_consuming_mediaresource.html]
+[test_mediatrack_consuming_mediastream.html]
+scheme=https
+tags=mtg
+[test_mediatrack_events.html]
+scheme=https
+[test_mediatrack_parsing_ogg.html]
+[test_mediatrack_replay_from_end.html]
+[test_metadata.html]
+[test_midflight_redirect_blocked.html]
+[test_mixed_principals.html]
+skip-if = toolkit == 'android' # bug 1309814, android(bug 1232305)
+[test_mozHasAudio.html]
+[test_mp3_with_multiple_ID3v2.html]
+[test_multiple_mediastreamtracks.html]
+scheme=https
+[test_networkState.html]
+[test_new_audio.html]
+[test_no_load_event.html]
+[test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html]
+[test_paused.html]
+[test_paused_after_ended.html]
+[test_play_events.html]
+[test_play_events_2.html]
+[test_play_promise_1.html]
+tags=promise-play
+[test_play_promise_2.html]
+tags=promise-play
+[test_play_promise_3.html]
+tags=promise-play
+[test_play_promise_4.html]
+tags=promise-play
+[test_play_promise_5.html]
+tags=promise-play
+[test_play_promise_6.html]
+tags=promise-play
+[test_play_promise_7.html]
+tags=promise-play
+[test_play_promise_8.html]
+tags=promise-play
+[test_play_promise_9.html]
+tags=promise-play
+[test_play_promise_10.html]
+tags=promise-play
+[test_play_promise_11.html]
+tags=promise-play
+[test_play_promise_12.html]
+tags=promise-play
+[test_play_promise_13.html]
+tags=promise-play
+[test_play_promise_14.html]
+tags=promise-play
+[test_play_promise_15.html]
+tags=promise-play
+[test_play_promise_16.html]
+tags=promise-play
+[test_play_promise_17.html]
+tags=promise-play
+[test_play_promise_18.html]
+tags=promise-play
+[test_play_twice.html]
+skip-if = appname == "seamonkey" # Seamonkey: Bug 598252, bug 1307337, bug 1143695
+[test_playback.html]
+skip-if = toolkit == 'android' || (debug && os == "mac") # bug 1316177, 1484451
+[test_playback_errors.html]
+[test_playback_rate.html]
+[test_playback_rate_playpause.html]
+[test_playback_reactivate.html]
+[test_played.html]
+skip-if = toolkit == 'android' && is_emulator # Times out on android-em, Bug 1613946
+[test_preload_actions.html]
+[test_preload_attribute.html]
+[test_preload_suspend.html]
+[test_preserve_playbackrate_after_ui_play.html]
+[test_progress.html]
+[test_reactivate.html]
+skip-if = true # see bug 1319725
+[test_readyState.html]
+[test_referer.html]
+skip-if = android_version == '25' && debug # android(bug 1232305)
+[test_replay_metadata.html]
+[test_reset_events_async.html]
+[test_reset_src.html]
+skip-if = (verify && debug && os == 'win')
+[test_video_dimensions.html]
+[test_resolution_change.html]
+tags=capturestream
+[test_resume.html]
+skip-if = true # bug 1021673
+[test_seamless_looping.html]
+[test_seek_negative.html]
+[test_seek_nosrc.html]
+[test_seek_out_of_range.html]
+skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305)
+[test_seek_promise_bug1344357.html]
+skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305)
+[test_seek-1.html]
+skip-if = toolkit == 'android' # bug 1322806, android(bug 1232305)
+[test_seek-2.html]
+skip-if = toolkit == 'android' # bug 1309778, android(bug 1232305)
+[test_seek-3.html]
+skip-if = toolkit == 'android' # bug 1321082, android(bug 1232305)
+[test_seek-4.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+[test_seek-5.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+[test_seek-6.html]
+skip-if = toolkit == 'android' # bug 1336629, bug 1324482, android(bug 1232305)
+[test_seek-7.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+[test_seek-8.html]
+skip-if = toolkit == 'android' # bug 1310584, android(bug 1232305)
+[test_seek-9.html]
+skip-if = toolkit == 'android' # bug 1332019, android(bug 1232305)
+[test_seek-10.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+[test_seek-11.html]
+skip-if = toolkit == 'android' # bug 1323133, android(bug 1232305)
+[test_seek-12.html]
+skip-if = toolkit == 'android' # bug 1321081, android(bug 1232305)
+[test_seek-13.html]
+skip-if = toolkit == 'android' # bug 1299174, android(bug 1232305)
+[test_seek-14.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+[test_seekable1.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+[test_seekLies.html]
+[test_seekToNextFrame.html]
+skip-if = toolkit == 'android' # bug 1329391, android(bug 1232305)
+tags=seektonextframe
+[test_seek_duration.html]
+[test_source.html]
+[test_source_null.html]
+[test_source_write.html]
+[test_standalone.html]
+[test_streams_capture_origin.html]
+tags=mtg capturestream
+[test_streams_element_capture.html]
+skip-if = true # bug 1372457 # bug 1557901 # bug 1554808
+tags=mtg capturestream
+[test_streams_element_capture_mediatrack.html]
+tags=mtg capturestream
+[test_streams_element_capture_playback.html]
+tags=mtg capturestream
+[test_streams_element_capture_reset.html]
+tags=mtg capturestream
+[test_streams_element_capture_twice.html]
+tags=mtg capturestream
+[test_streams_firstframe.html]
+tags=mtg capturestream
+[test_streams_gc.html]
+tags=mtg capturestream
+[test_streams_individual_pause.html]
+scheme=https
+tags=mtg
+[test_streams_srcObject.html]
+skip-if = toolkit == 'android' # bug 1300443, android(bug 1232305)
+tags=mtg capturestream
+[test_streams_tracks.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+tags=mtg capturestream
+[test_suspend_media_by_inactive_docshell.html]
+[test_timeupdate_small_files.html]
+[test_unseekable.html]
+[test_video_to_canvas.html]
+skip-if = toolkit == 'android' # android(bug 1232305), bugs 1320418,1347953,1347954,1348140,1348386
+[test_video_in_audio_element.html]
+[test_video_stats_resistfingerprinting.html]
+tags = resistfingerprinting
+[test_videoDocumentTitle.html]
+[test_VideoPlaybackQuality.html]
+[test_VideoPlaybackQuality_disabled.html]
+[test_volume.html]
+[test_vp9_superframes.html]
+skip-if = os == 'mac' && os_version == '10.14' # mac due to bug 1545737
+# The tests below contain backend-specific tests. Write backend independent
+# tests rather than adding to this list.
+[test_can_play_type_webm.html]
+[test_can_play_type_wave.html]
+[test_fragment_noplay.html]
+[test_fragment_play.html]
+[test_background_video_cancel_suspend_taint.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
+[test_background_video_cancel_suspend_visible.html]
+tags = suspend
+[test_background_video_no_suspend_disabled.html]
+tags = suspend
+[test_background_video_no_suspend_short_vid.html]
+tags = suspend
+[test_background_video_no_suspend_not_in_tree.html]
+tags = suspend
+[test_background_video_resume_after_end_show_last_frame.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
+[test_background_video_resume_looping_video_without_audio.html]
+tags = suspend
+[test_background_video_suspend.html]
+skip-if = os == 'android' #Bug 1304480
+tags = suspend
+[test_background_video_suspend_ends.html]
+tags = suspend
+[test_background_video_tainted_by_capturestream.html]
+tags = suspend
+[test_background_video_tainted_by_createimagebitmap.html]
+tags = suspend
+[test_background_video_tainted_by_drawimage.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
+[test_background_video_drawimage_with_suspended_video.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
+[test_background_video_ended_event.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
+
+[test_temporary_file_blob_video_plays.html]
+skip-if = toolkit == 'android' || (os == 'win' && processor == 'aarch64') # bug 1533534 # android(bug 1232305)
+[test_videoPlaybackQuality_totalFrames.html]
+skip-if = os == 'win' || (os == 'mac' && os_version == '10.14') # bug 1374189, mac due to bug 1544938
+
+[test_video_gzip_encoding.html]
+
+[test_playback_hls.html]
+# HLS is only supported on Fennec with API level >= 16
+# TODO: This test is similar to test_playback.html, will remove the
+# redundant code once test_playback.html is enabled on Fennec.
+skip-if = toolkit != 'android'
+tags = hls
+
+[test_hls_player_independency.html]
+# There's a limit for creating decoder when API lever < 18(Bug 1278574)
+# We could skip the test in that case as we cannot play 2 video at a time.
+skip-if = toolkit != 'android' || android_version < '18'
+tags = hls
+
+[test_bug1431810_opus_downmix_to_mono.html]
+
+[test_cloneElementVisually_paused.html]
+tags = cloneelementvisually
+[test_cloneElementVisually_mediastream.html]
+tags = cloneelementvisually
+[test_cloneElementVisually_mediastream_multitrack.html]
+tags = cloneelementvisually
+[test_cloneElementVisually_resource_change.html]
+tags = cloneelementvisually
+[test_cloneElementVisually_no_suspend.html]
+tags = cloneelementvisually
+[test_cloneElementVisually_poster.html]
+tags = cloneelementvisually
+[test_cloneElementVisually_ended_video.html]
+tags = cloneelementvisually
diff --git a/dom/media/test/multi_id3v2.mp3 b/dom/media/test/multi_id3v2.mp3
new file mode 100644
index 0000000000..253f19a9b6
--- /dev/null
+++ b/dom/media/test/multi_id3v2.mp3
Binary files differ
diff --git a/dom/media/test/multiple-bos-more-header-fileds.ogg b/dom/media/test/multiple-bos-more-header-fileds.ogg
new file mode 100644
index 0000000000..c9721cb98e
--- /dev/null
+++ b/dom/media/test/multiple-bos-more-header-fileds.ogg
Binary files differ
diff --git a/dom/media/test/multiple-bos-more-header-fileds.ogg^headers^ b/dom/media/test/multiple-bos-more-header-fileds.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/multiple-bos-more-header-fileds.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/multiple-bos.ogg b/dom/media/test/multiple-bos.ogg
new file mode 100644
index 0000000000..193200868e
--- /dev/null
+++ b/dom/media/test/multiple-bos.ogg
Binary files differ
diff --git a/dom/media/test/multiple-bos.ogg^headers^ b/dom/media/test/multiple-bos.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/multiple-bos.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/no-cues.webm b/dom/media/test/no-cues.webm
new file mode 100644
index 0000000000..8ed761099e
--- /dev/null
+++ b/dom/media/test/no-cues.webm
Binary files differ
diff --git a/dom/media/test/no-cues.webm^headers^ b/dom/media/test/no-cues.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/no-cues.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/notags.mp3 b/dom/media/test/notags.mp3
new file mode 100644
index 0000000000..7f298131aa
--- /dev/null
+++ b/dom/media/test/notags.mp3
Binary files differ
diff --git a/dom/media/test/notags.mp3^headers^ b/dom/media/test/notags.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/notags.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/opus-mapping2.mp4 b/dom/media/test/opus-mapping2.mp4
new file mode 100644
index 0000000000..72401a9c0b
--- /dev/null
+++ b/dom/media/test/opus-mapping2.mp4
Binary files differ
diff --git a/dom/media/test/opus-mapping2.mp4^headers^ b/dom/media/test/opus-mapping2.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/opus-mapping2.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/opus-mapping2.webm b/dom/media/test/opus-mapping2.webm
new file mode 100644
index 0000000000..4379f2534a
--- /dev/null
+++ b/dom/media/test/opus-mapping2.webm
Binary files differ
diff --git a/dom/media/test/opus-mapping2.webm^headers^ b/dom/media/test/opus-mapping2.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/opus-mapping2.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/opus-sample-cenc.mp4 b/dom/media/test/opus-sample-cenc.mp4
new file mode 100644
index 0000000000..22bb787540
--- /dev/null
+++ b/dom/media/test/opus-sample-cenc.mp4
Binary files differ
diff --git a/dom/media/test/opus-sample-cenc.mp4^headers^ b/dom/media/test/opus-sample-cenc.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/opus-sample-cenc.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/opus-sample.mp4 b/dom/media/test/opus-sample.mp4
new file mode 100644
index 0000000000..80329ce14b
--- /dev/null
+++ b/dom/media/test/opus-sample.mp4
Binary files differ
diff --git a/dom/media/test/opus-sample.mp4^headers^ b/dom/media/test/opus-sample.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/opus-sample.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/owl-funnier-id3.mp3 b/dom/media/test/owl-funnier-id3.mp3
new file mode 100644
index 0000000000..05ec507530
--- /dev/null
+++ b/dom/media/test/owl-funnier-id3.mp3
Binary files differ
diff --git a/dom/media/test/owl-funnier-id3.mp3^headers^ b/dom/media/test/owl-funnier-id3.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/owl-funnier-id3.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/owl-funny-id3.mp3 b/dom/media/test/owl-funny-id3.mp3
new file mode 100644
index 0000000000..6533755a32
--- /dev/null
+++ b/dom/media/test/owl-funny-id3.mp3
Binary files differ
diff --git a/dom/media/test/owl-funny-id3.mp3^headers^ b/dom/media/test/owl-funny-id3.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/owl-funny-id3.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/owl-short.mp3 b/dom/media/test/owl-short.mp3
new file mode 100644
index 0000000000..9b31531f22
--- /dev/null
+++ b/dom/media/test/owl-short.mp3
Binary files differ
diff --git a/dom/media/test/owl-short.mp3^headers^ b/dom/media/test/owl-short.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/owl-short.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/owl.mp3 b/dom/media/test/owl.mp3
new file mode 100644
index 0000000000..9fafa32f93
--- /dev/null
+++ b/dom/media/test/owl.mp3
Binary files differ
diff --git a/dom/media/test/owl.mp3^headers^ b/dom/media/test/owl.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/owl.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/pixel_aspect_ratio.mp4 b/dom/media/test/pixel_aspect_ratio.mp4
new file mode 100644
index 0000000000..fce12cc03e
--- /dev/null
+++ b/dom/media/test/pixel_aspect_ratio.mp4
Binary files differ
diff --git a/dom/media/test/play_promise.js b/dom/media/test/play_promise.js
new file mode 100644
index 0000000000..7051fedc19
--- /dev/null
+++ b/dom/media/test/play_promise.js
@@ -0,0 +1,3 @@
+function getNotSupportedFile(name) {
+ return name + ".bad";
+}
diff --git a/dom/media/test/poster-test.jpg b/dom/media/test/poster-test.jpg
new file mode 100644
index 0000000000..595a5315f8
--- /dev/null
+++ b/dom/media/test/poster-test.jpg
Binary files differ
diff --git a/dom/media/test/r11025_msadpcm_c1.wav b/dom/media/test/r11025_msadpcm_c1.wav
new file mode 100644
index 0000000000..2e883ba5ed
--- /dev/null
+++ b/dom/media/test/r11025_msadpcm_c1.wav
Binary files differ
diff --git a/dom/media/test/r11025_msadpcm_c1.wav^headers^ b/dom/media/test/r11025_msadpcm_c1.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/r11025_msadpcm_c1.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/r11025_s16_c1-short.wav b/dom/media/test/r11025_s16_c1-short.wav
new file mode 100644
index 0000000000..e08d5bbdc0
--- /dev/null
+++ b/dom/media/test/r11025_s16_c1-short.wav
Binary files differ
diff --git a/dom/media/test/r11025_s16_c1-short.wav^headers^ b/dom/media/test/r11025_s16_c1-short.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/r11025_s16_c1-short.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/r11025_s16_c1.wav b/dom/media/test/r11025_s16_c1.wav
new file mode 100644
index 0000000000..ab2e08befb
--- /dev/null
+++ b/dom/media/test/r11025_s16_c1.wav
Binary files differ
diff --git a/dom/media/test/r11025_s16_c1.wav^headers^ b/dom/media/test/r11025_s16_c1.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/r11025_s16_c1.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/r11025_s16_c1_trailing.wav b/dom/media/test/r11025_s16_c1_trailing.wav
new file mode 100644
index 0000000000..af53beaf25
--- /dev/null
+++ b/dom/media/test/r11025_s16_c1_trailing.wav
Binary files differ
diff --git a/dom/media/test/r11025_s16_c1_trailing.wav^headers^ b/dom/media/test/r11025_s16_c1_trailing.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/r11025_s16_c1_trailing.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/r11025_u8_c1.wav b/dom/media/test/r11025_u8_c1.wav
new file mode 100644
index 0000000000..97dc453b9e
--- /dev/null
+++ b/dom/media/test/r11025_u8_c1.wav
Binary files differ
diff --git a/dom/media/test/r11025_u8_c1.wav^headers^ b/dom/media/test/r11025_u8_c1.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/r11025_u8_c1.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/r11025_u8_c1_trunc.wav b/dom/media/test/r11025_u8_c1_trunc.wav
new file mode 100644
index 0000000000..4d2db39777
--- /dev/null
+++ b/dom/media/test/r11025_u8_c1_trunc.wav
Binary files differ
diff --git a/dom/media/test/r11025_u8_c1_trunc.wav^headers^ b/dom/media/test/r11025_u8_c1_trunc.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/r11025_u8_c1_trunc.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/r16000_u8_c1_list.wav b/dom/media/test/r16000_u8_c1_list.wav
new file mode 100644
index 0000000000..afde32e9a3
--- /dev/null
+++ b/dom/media/test/r16000_u8_c1_list.wav
Binary files differ
diff --git a/dom/media/test/r16000_u8_c1_list.wav^headers^ b/dom/media/test/r16000_u8_c1_list.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/r16000_u8_c1_list.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/reactivate_helper.html b/dom/media/test/reactivate_helper.html
new file mode 100644
index 0000000000..1834131559
--- /dev/null
+++ b/dom/media/test/reactivate_helper.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script>
+var loadsWaiting = 0;
+var elements = [];
+
+function checkAllLoaded() {
+ --loadsWaiting;
+ if (loadsWaiting == 0) {
+ parent.loadedAll(elements);
+ }
+}
+
+function loadedData(event) {
+ var e = event.target;
+ parent.ok(!elements.includes(e), "Element already loaded: " + e._name);
+ parent.info("Loaded " + e._name);
+ elements.push(e);
+ // Reset "onerror" handler to avoid triggering another error in removeNodeAndSource().
+ e.onerror = null;
+ checkAllLoaded();
+
+}
+
+function error(event) {
+ var e = event.target;
+ parent.info("Error " + e._name);
+ // Don't wait for the element encounting errors.
+ checkAllLoaded();
+}
+
+for (var i = 0; i < parent.gSmallTests.length; ++i) {
+ var test = parent.gSmallTests[i];
+ var elemType = /^audio/.test(test.type) ? "audio" : "video";
+ // Associate these elements with the subframe's document
+ var e = document.createElement(elemType);
+ e.preload = "metadata";
+ if (e.canPlayType(test.type)) {
+ e.src = test.name;
+ e._name = test.name;
+ e.onloadeddata = loadedData;
+ e.onerror = error;
+ e.load();
+ ++loadsWaiting;
+ parent.info("Loading " + e._name);
+ }
+}
+
+if (loadsWaiting == 0) {
+ parent.todo(false, "Can't play anything");
+} else {
+ parent.SimpleTest.waitForExplicitFinish();
+}
+</script>
+</body>
+</html>
diff --git a/dom/media/test/red-46x48.mp4 b/dom/media/test/red-46x48.mp4
new file mode 100644
index 0000000000..0760cc1c16
--- /dev/null
+++ b/dom/media/test/red-46x48.mp4
Binary files differ
diff --git a/dom/media/test/red-46x48.mp4^headers^ b/dom/media/test/red-46x48.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/red-46x48.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/red-48x46.mp4 b/dom/media/test/red-48x46.mp4
new file mode 100644
index 0000000000..d83de4027d
--- /dev/null
+++ b/dom/media/test/red-48x46.mp4
Binary files differ
diff --git a/dom/media/test/red-48x46.mp4^headers^ b/dom/media/test/red-48x46.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/red-48x46.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/redirect.sjs b/dom/media/test/redirect.sjs
new file mode 100644
index 0000000000..1653e8efc0
--- /dev/null
+++ b/dom/media/test/redirect.sjs
@@ -0,0 +1,26 @@
+function parseQuery(request, key) {
+ var params = request.queryString.split('&');
+ for (var j = 0; j < params.length; ++j) {
+ var p = params[j];
+ if (p == key)
+ return true;
+ if (p.indexOf(key + "=") == 0)
+ return p.substring(key.length + 1);
+ if (p.indexOf("=") < 0 && key == "")
+ return p;
+ }
+ return false;
+}
+
+// Return file content for the first request with a given key.
+// All subsequent requests return a redirect to a different-origin resource.
+function handleRequest(request, response)
+{
+ var domain = parseQuery(request, "domain");
+ var file = parseQuery(request, "file");
+ var allowed = parseQuery(request, "allowed");
+
+ response.setStatusLine(request.httpVersion, 303, "See Other");
+ response.setHeader("Location", "http://" + domain + "/tests/dom/media/test/" + (allowed ? "allowed.sjs?" : "") + file);
+ response.setHeader("Content-Type", "text/html");
+}
diff --git a/dom/media/test/referer.sjs b/dom/media/test/referer.sjs
new file mode 100644
index 0000000000..69c27afe9a
--- /dev/null
+++ b/dom/media/test/referer.sjs
@@ -0,0 +1,45 @@
+function parseQuery(request, key) {
+ var params = request.queryString.split('&');
+ for (var j = 0; j < params.length; ++j) {
+ var p = params[j];
+ if (p == key)
+ return true;
+ if (p.indexOf(key + "=") == 0)
+ return p.substring(key.length + 1);
+ if (p.indexOf("=") < 0 && key == "")
+ return p;
+ }
+ return false;
+}
+
+function handleRequest(request, response)
+{
+ var referer = request.hasHeader("Referer") ? request.getHeader("Referer")
+ : undefined;
+ if (referer == "http://mochi.test:8888/tests/dom/media/test/test_referer.html") {
+ var name = parseQuery(request, "name");
+ var type = parseQuery(request, "type");
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+ var paths = "tests/dom/media/test/" + name;
+ var split = paths.split("/");
+ for(var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+ bis.setInputStream(fis);
+ var bytes = bis.readBytes(bis.available());
+ response.setHeader("Content-Length", ""+bytes.length, false);
+ response.setHeader("Content-Type", type, false);
+ response.write(bytes, bytes.length);
+ bis.close();
+ }
+ else {
+ response.setStatusLine(request.httpVersion, 404, "Not found");
+ }
+}
diff --git a/dom/media/test/reftest/av1hdr2020.mp4 b/dom/media/test/reftest/av1hdr2020.mp4
new file mode 100644
index 0000000000..295bec8a3c
--- /dev/null
+++ b/dom/media/test/reftest/av1hdr2020.mp4
Binary files differ
diff --git a/dom/media/test/reftest/av1hdr2020.png b/dom/media/test/reftest/av1hdr2020.png
new file mode 100644
index 0000000000..c5d3344a80
--- /dev/null
+++ b/dom/media/test/reftest/av1hdr2020.png
Binary files differ
diff --git a/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html
new file mode 100644
index 0000000000..575acb107d
--- /dev/null
+++ b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0"
+src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEsCAYAAADtt+XCAAAgAElEQVR4nOxdeUiU2xs+MzKOijqKK2qKWpFWtBiVSWWRpuGKuaCWuFIumCVqhm1kC1ZGakWlUdbl3kpasSxabrTSStuPVtEyLA1Xxg19fn8M59xvVscyJ+/9hAf1m2++7z3b+5x3OecQQgh48ODBgwePH4DOBeDBgwcPHqMTOheABw8ePHiMTuhcAB48ePDgMTqhcwF48ODBg8fohM4F4MGDBw8eoxM6F4AHDx48eIxO6FwAHjx48OAxOqFzAXjw4MGDx+iEzgXgwYMHDx6jEzoXgAcPHjx4jE7oXAAePHjw4DE6oXMBePDgwYPH6ITOBeDBgwcPHqMTOheABw8ePHiMTuhcAB48ePDgMTqhcwF48ODBg8fohM4F4MGDBw8eoxM6F+CHIBAI5KDt90QikVb3C4VCle/8leXh/q3q/UOBUCiEUCgcNplpPQ/lmUNtm5+Fnp7eoHKok4fWF6334WgDRYhEohGrC1VlHe7y8OBBfgMB5KCN0qGDm0Kd4lAFfX19rZSaqsE+nAOQW05aBsX/f+b5enp6cgrxR2XjyiQUClUSsLp3DCeBadu26upCm/amoLJrO9n4WfmGm2i5JDjUevgVGMk+wGPEoXMBYG5uDhcXF0gkEq3u586GucrW3Nxc6V7uoDU2NoaxsTH7jqWlJUxNTWFmZgYDAwMYGhrCwcEBhCgPNoFAIHdN1WC0traGiYmJVvLTQU7LbGJiArFYDEIIDA0NYWZmxv6n3yGEwMjISKnuqNIzNTWVK7e+vr5apWFhYQFC5BUnfY7igKfy6unpQV9fH2ZmZrC3t5e7x8rKCra2trC1tYWVlRUMDQ0hEAjkiJiWx8jICCYmJjAyMoJYLGbv1tPTg4GBAYyNjWFhYTEkxUOfp6r9uQStp6cHY2NjWFtbgxACe3t7Vte0vgwMDCASiVjfMTMzg6GhIcRisRK49S4UCmFtbQ0XFxfY2NjIEauVlRWMjY1Zndvb28PKygoGBgZK/fpnoaq/EkJgYGDwQ88Z6nfo94yNjbUaDzxGNXQuwJCgavZLB6S67+jr68spLysrKzllqw5GRkZwdnZm/4vFYlhaWoIQze4IsVisduAMNts0NjaGg4OD3ODnymprawt7e3s5xaOqXoyNjSGRSJSsA4FAgOnTp6t9P30Xd/atSNhDaS9aD2PHjh1yWyu2kUAggKGhoVoZKBHZ29sr1b+BgQGsra2ZEldsQ2NjY6XJxs/OnG1tbSESiZi8qgiOW7/DAScnJ7l+yH2PRCIZ0rOoBfkjBMK7y/4z0K0AlAAmTpyIgICAQe93dXWFUCiEs7Mz4uLiEBkZqdY1QCEWi+Hl5YXExEQQIlNMS5YsQU5ODhYtWoQFCxYgPT0dKSkpWLZsGYKDgzFr1iz2XSsrK3h7e2PZsmUIDw/HnDlzYGdnB0JkM9jw8HBkZGQgICAAixcvliMddXBzc0N6ejqCg4Mxf/58zJ8/nym9CRMmIDk5GWlpafDw8AAhBH5+fsjNzcWMGTNgYGAAe3t7REdHY/bs2TAzM0N0dDRCQkIQHBzMrAsnJyckJycjISEBkZGR7F2mpqbw9fVFSEgIYmNjkZiYyBAREQFCZAqUztq5imHs2LFITExEZGQk4uPjkZubi7lz58LDwwNJSUkoKChAXl4eMjMzkZqaihUrVsDd3Z0Roru7O+Li4pCWloaFCxdi9uzZ8PHxwfLly5Geno7k5GQkJiYiJydHLUlS5eTv74+MjAysXLkScXFxiImJgYODAwQCAWxsbBAcHIzFixdj4sSJMDQ0hKGhIVxdXREeHo6kpCQkJSUhMTERycnJmD17NsRiMaKiopCZmYmkpCSsXr0aJSUl8PT0REFBAXJycpCdnY3c3FysXbsWa9euxZQpUxAXF4fFixcjLy8PsbGx8PHxgZWVFQghmDlzJtauXcv6xOLFi5GWlgZ/f39WJ6osPvpbGyXP/Z5YLIaFhQXmzJmD8PBweHl5wc/Pj0181D1HT09PzhLlusHoZ/R93HvUEQgdO/PmzUNkZKTW3gUeow4j/1LFjmZlZYWioiLU1tZq9f358+dj+/btKCoqwrZt25CdnQ0bGxuV99IZZ2JiIt6/fw9/f384OTkhLCwM58+fR2BgIKqrq5GYmIiHDx+isrIScXFx2LdvHwiRKfq0tDRs2bIF0dHRKCgowOvXrxEbGwsHBweUlJQgKysLqampKCwsxIsXLxAWFqZWdpFIBG9vb6Ysc3JycPz4cdTV1YEQgqCgIGRmZmLjxo3IysrCn3/+ifnz58PHxwf19fXw9PQEITJ32c2bN7Fy5UoQQhAXF4eioiJUV1cjODgY1tbWmDZtGnbt2oXCwkLk5OQgMzMTx44dg7e3N8aOHYsDBw4gPz9fDidPnoSenh4MDQ2ZkuAqqdDQUFRVVSEsLAwPHz5EVlYWdu/ejXXr1iEgIADnz5/HwYMHsXLlSoSEhODs2bPIzMwEITIS3Lx5M4KDg5GVlYW7d+/i1KlTOHbsGIqLi7F161ZUVVUhISEBT58+xeLFi+X6jFAohFgshpmZGQghWLJkCSoqKlBWVobNmzdj7dq12LdvH3NLrVu3DteuXcOmTZswadIkEEIwa9YsVFRUwNPTE9evX8fu3btx8OBBZGdnw83NDSdPnkRYWBju3buHVatW4eDBg9i+fTsiIyNx/vx5FBYWymHjxo0ICQnB/fv3ceDAASxbtgxbt25FcnIyCCHw8fHBly9fWDstWbIE58+fx/r161m5hpNAxo0bh/j4eOTk5CA0NBT5+floamqCj4+Pymeqs6QVyZoQmWtV1X2KFgv9bOnSpaipqcG9e/cwa9Ys3ir5d2LkX8rtZEZGRkhKSkJxcTF27doFQggcHR2xY8cObNmyBaampjAxMUFBQQG2bduG+Ph4mJuby1kdRUVF8PPzU/kuasbPmjULx44dw+nTpzFmzBgQQnDixAmIxWKcOHEChBCkpKRg7dq1IISgqqoKhBBER0djzZo1cs/MysqCn58fMjMzMXPmTLnP4uPjB51tHThwgMkgFArh4OCA/Px8TJ48GQUFBXL14+TkhNOnT2P27NkoKyuTe05ZWRkmT57Mypmamgp/f3/U1NSwexYsWIDExERm3djY2ODWrVsghCAtLQ3BwcFyzywqKgIh/7j9FJVbdHQ0Nm3aBEIISkpKWN0mJSWBEIKKigq5WFR8fDwyMjJACEFlZaXcs2xtbbFixQocO3YMbm5usLCwwJEjR0AIwbFjx+Dr6yvXZ6gs1JoghGDLli2YOHEiuy85OZmVwc/PD4mJiYiKikJWVhYIkRH0nj17QAhBeXk5LCwsMHnyZGRlZSEoKAgHDhwAIQRHjhyBqakpFi9ezJ5HP6Oz+cWLFyM/Px+EEJw8eVKubIWFhZg3bx4IIbh58yaOHDnCyH/Hjh3MstTk0tTT04NYLGblNTIyklP4BgYGSnGO/Px8REdHy/V9X19f1k/9/f2xceNGbNy4Ef7+/qwPBgQEIDs7G2vWrEF8fDxsbW3ZM2NjY1n9eXh4IC0tDQUFBZg4caJGNxYdB3v27MHcuXNHPAuNx4hgZF/INYMJIZgyZQrOnTuHsWPHoqKiAiKRCFOmTEFeXh7evXvHTP8zZ84gJycHrq6uTEFNmTIFa9asQXFxsVoLRJFAEhISmEK7cuUKJBIJcnNzQQhBZmYm8vLyQAjBqlWrQIhsxnjx4kWsWLECHh4esLW1hbW1Nezt7bFz504Q8o+fn+tzFolESj5oit27d6O0tBQRERFysYG1a9ciPDwchBBGkiYmJkhLS8OqVatQXl4u95yysjLmcoqPj2ez2l27dmHOnDkwMjJCUFAQtmzZAjs7O+jr68PX1xelpaUgREYgxcXFCA8Px+HDh2FqaipHzoouJEJks8rc3FyIRCKcOHECRkZG8PLyQlxcHMRiMS5evMiU+OrVq1l7EkKwdetWbN68Gf7+/rC1tWVtlp+fD1tbW4wfP56RzMqVKzF37lylvkOIPIFkZWUxEiWEYOfOnYzMiouL4efnByMjIzY5mTZtGrOIDh8+jBkzZmDWrFlYsWIFlixZgtWrV4MQGYFYW1sjMDCQ9Y/KykpERERgzZo1rJ2oEqV1SlFZWcncoFu3bkVAQAC2bdvG3kuJWxWBDJYtZWBgwOJD3HstLS1x+vRp1v8U40Dp6ekoLy9HVFQUoqKiUF5ezvpPQkICEhMTMW/ePBQXF7MyGxsbY/r06Th//jyqq6uxdetW+Pr6oqqqCqGhoXJlUEckPIH8qzHyL+V2pIqKCsyfPx+EEBw8eFDus/DwcFy+fBnbtm3D0qVLQYhstmRpaQlbW1sEBQVh9+7dyM7OBiFELkBKoUggAoEAlZWVsLe3x9mzZ+Ho6MgC8OvXr8fatWshFArlBl90dDRKSkqwc+dOXL9+HVu3boW/vz8OHToEQmTKnmYB0QweTeU3NDTEunXrcOHCBZw+fRrl5eXw9fXF+vXrsXDhQhAii63QWWBSUhIOHTqEHTt2yD2nrKyMzXJ37tyJvLw82NnZyVlz/v7+ePDgAfLz81FWVoZjx45h9uzZIIQgNTUV165dw+rVq7F//372XEogqlwWIpGIBWp37NjBFBkNEFdVVaGoqAilpaUoKCiQa4M5c+agpKQE+/btw8WLF3H9+nUEBwez4Dh1uSn2EVX1R2UrLS1FUVERtm/fjuLiYhw6dAjjx4+HpaUlKioqMH36dFhaWqKmpgZRUVEghDCL5eDBg8y1RZ83btw4ECIjkPHjx8vJf+TIEWzYsAFFRUXYunUrTExM4OrqCkIIqqurUVFRgcLCQpw4cQJ79+6FWCzG2LFjmeW4bt06TJkyBSUlJUwWVbN2PT09uLq64vDhw0qIiYmBSCRikygapxIKhZg0aRKz4AiRWQvbtm3D9u3bsWfPHmzduhXW1tYsg8za2hpHjhyBvb09PDw8sHnzZhQUFKC8vBwHDx5kfZsQGQkUFxerbI/BguqUQAaLVfIYldDdy1etWoVbt24hOjoaSUlJOHXqFHJycjBp0iTm5y4rK8O7d++YkqezVq4S2b9/P5sRKkKRQAgh2L59O5t9U4VBiMztQAPtqjBnzhxMnToVly5dQmFhIXbt2sUGiSq3lTrXBFc2FxcXZGZm4uLFi9i1axciIyNBiCx1lM4us7KycOjQIWzdulXu+2VlZfDz84O9vT3++usvnDx5Ejt27MDJkyeZSyUgIAB//fUX5s+fj+zsbDx9+pQp+5UrV7L3OTk5wcbGRm7Wr4qQ9fT0WPppeXk5c41Q5VBYWAh7e3uIxWJMmTIFdnZ2WLBgAfsufc64ceMQFRWFqqoqRro+Pj6MdGgGmaoZOrfti4uLUVBQAH9/f7i5uTGl7+/vj9u3b2P//v1Yu3YtTpw4wawb+t0DBw4gKChIpaV45MgRZjlRS6yoqIjda2ZmhnHjxmHq1KkQiUS4ceMGFi9ejODgYNy+fZvFHKytrRk5x8TEIDExEbm5ucw9p0rh6unpYfr06bh8+TLu3bsnhxUrVkAoFLIgPU1/trOzg7e3NyNgQ0NDuLu7w8XFBSUlJTh+/DjOnTuH0tJSnDt3jv29fft2eHp64vTp04iOjoaXlxcyMjLY5IjKWFpaCi8vLwiFQjg6OsLU1BRGRkZy67G4ZeDGQ3gC+VdDNy8WCASYOXMmUlNTkZGRgSVLlqCyshJRUVFs1pObm4uMjAwsXLgQmZmZbOCnp6ezAWpiYoKSkhKlWAQFnSFPnz4d69atAyGE+dpv377N1n2Ymppi5cqVCA0NhY2NDevsEydORGBgIAiRDUpzc3MUFhZiw4YN2Lp1K/Ly8uQU47Zt2+R896oQExOD8ePHy82yy8vL4ebmhuLiYmaREULg7OyMyspKTJ8+HXv27GFWiZWVFSorK2FsbIzg4GDmcqNYvXo1vLy8MGXKFKxYsYJdLykpwYYNG+Dg4AB/f38EBAQwa0sikeDq1asgRKYgVaWdUgUqFouxefNmpc9zcnLg6OjI/g8NDUVKSgoIkU0YaH0TQuDg4ICKigr2nqlTp2L37t0gRHlxpSKoAt2zZw+zIrgKKiMjA7GxsXLfKSoqwrRp09j/W7ZsgYeHB5ycnJSU2/bt21nfofWzfft2uXvmz5/PXF7Hjx9n982aNQsnT56EpaUlbGxs2GyeEIK9e/fixYsXrI0HC4yrGzv0XYqLTvfu3Ss3CTIyMkJ6ejrCw8OZi5MLiUSCsLAwFvsTCASIiIhg8S2K9evXw8XFBYTISIuORbpgVZX8tO02b97MshZ5/Osw8i+lC7O415YtW4YHDx5g48aNWLBgAfbs2YN3794xhVlaWoqKigqEhIQgIiICBw4cQElJCQ4fPozc3FyYmJiw4KYqpKWl4fLly4iIiICJiQkSExNRW1sLIyMj2NjYYOXKlaisrERpaSmio6NZKuyWLVtw6tQpbNy4EX/++Sf279/PfOiWlpY4dOgQSkpKsG3bNpSXl2Pbtm1s5qoO+/btw8GDB7FmzRqUlJSgtLQUy5cvByEEYWFhKCkpwe7du5GXl4fq6mrmp46IiMChQ4dQVFSEqqoqbNy4ETNmzMCtW7ewb98+rFq1CnZ2dti4cSMuXryII0eOoLq6Wi7Aa2pqiqqqKuTn5+PixYu4dOkSduzYgTVr1qCoqIgFjM3MzFTOGCUSCby8vLB582Zcv34dW7ZsQVZWFiQSCSIjI3H//n0cPHgQmzZtQm5uLs6fP8985Q8fPsS+fftYEHffvn3w8vKCRCJBcnIyTp8+jevXr2P16tXMZakIbhxk8eLFuHnzJvbu3YuwsDA4OztDX18fs2bNwqVLl7Bz505ERETA29sbBQUFOH/+PI4cOQI/Pz9s2LABf//9N3bt2oX4+HjWd/z8/LBt2zZcvnwZx48fR3Z2NhwdHbF+/XqcOHECGzduREpKCjZs2IBz585h06ZNyMzMxJ07d1g7ESKbSKSnpyMkJARVVVXsHW5ubujo6GBEqolAKFFIJBJIJBKYmZmx9SncRZ+KadY5OTk4ceIEsrOzsWXLFvzxxx9wdXVFQEAADh8+jDVr1mDNmjU4fPgwLly4gIkTJyI7OxuFhYXIysrCX3/9hZcvXyImJgbx8fHYvn07rl27hiNHjiA2NpZZXYQQleRBP/Py8kJhYSGuXLmC0tJSRrY8/lXQzYtpZ6MrpoOCghAYGIjw8HB4eHggODgYS5YsYfd6e3sjOTkZ06dPh1AohI+PD7Kzs+Ht7a3kq+ZCX18fRkZG8PT0REBAAPz9/Rl5ubi4QCwWQyKRIDo6GosWLUJ0dDQWLFjA3DdOTk6YMWMGFixYgIiICHh6ejILib4vKCgIoaGhCAkJgUgkgpWVFWxsbNTOnk1NTTFhwgT4+/sjLCwMbm5uIISw9NmKigpcvXoVlZWVLMhOZ3Bz5szBihUrMGbMGOjp6WHMmDHw9fVFQkICy6pZuHAhJk2ahPnz52Pp0qUICgqSe7+bmxvCwsKwYMECxMTEICQkBAEBAQgJCWHxAepCVAVXV1csW7YMERERSE5OZmnLnp6eiI6ORlRUFCIjIxEYGIglS5awOIGTkxPGjBmDhIQEJCQksEw0WofLly/H8uXLERwcLGcpcEFn2yKRCAEBAQgMDERAQACLBdH30H7k5eUFKysrhIaGYs6cOYiMjISHhwciIyOxdOlSpKWlwc/Pj1kbEydORFpaGgIDA5GVlcUshZiYGHh5eSEiIgKhoaGIjIxEQkICrK2tERkZiXnz5rFyEkIwadIkhIeHw9PTE/7+/iwzasKECZg3bx5zi6kjkMEsEVV9SyAQwMDAABKJhK1bio6OhqWlJbNYpk6dyoLoU6dOZf1cIBBg6dKliIuLw8SJEzFjxgzExMRgxowZCA0NRVhYGOLj47F8+XJMnDhRjuQ0EUhcXBy8vb2xdOlSZsnz+FdB5wIoDRRVvncKTavOVWWvqBqEVPHTdQVDkVUgkG3RwN12RPFzTe9WB/ocLy8vXLt2Ddu2bUNRUREOHz6MP/74g2UWESIjRbrdBzdTytraWi79Uh202V5C00p6brkMDAxgYmKidVnpXmQikYhtH2NoaMisHW7bq7Io6XtUtbWpqSlb+KZJBnVp1mKxWGXfU7UmQh3MzMwYMRoZGand8YCWd7gJhBDlFfaq+im3zOpWyGuCNgQyWAyQx78COhdACapSAX/V87mL5fT09LQGN0ioTtYf3ciQyqJqnyrFa9qCK6diORTdEPQ9g9XdUKD4XcXNMBXlUSWXqmsjqaR+tOzq6mAwqEsi+BGoI9UfXdynSWZ15MITyr8SOhdACSPZ4bhK60cJZLByDLXs6gjkZ6FJYatSdJrahSeQ0UUgmso0knUxUm3FY8SgcwGUMJIdjjtIFZWXJvzqQT2YPOoU7mAyKz5Xk6LT1C48gfw7CGSk60TX8vIYduhcACWMZIdTp9S0UcQjJZc65aKOAEYDfoRAdK2QdFFHv7vi5QnkPw+dC6AWI9Xhhkogo2Fg/+74EeLWRC66Ls9w1ANPIDxGIXQugFr87jNMXdfPaAZPIIP3OZ5AeIwC6FwAtVDV4X5lJxzuwfCrYyWjCYoKkYKbkPBfJBBaN6r6H08gg7+TEO0yybjv5ta3ruvvXwCdCzBoQ3Oh7aDSplOpevdwDQauQhzJjvq7Dgoa61BU/PQY2d/FAtHVBEXV5/81AtFmwqg4rgQC9WeacL9P9YHiREXX9fcvgG4F4J5TQf9W1SEcHBzY6mttlLKmxWS041haWg55AGg7IGhHpXLY2dn9su2sxWIxDAwM2OZ2v7rN1JVf8d3cz7nnnnPvo+d0K76DLrSjCw//zRYIt/4ULbLRUr7hHDuDvUNdaj2tQ6FQyBaqUnBP1+SeuMjjp6FzAZSwYMECFBQUYMeOHdi5cyd8fHwgEolgZGSkVeejSmfOnDnIy8vDqVOncODAAYSFhcHa2hqGhoZsS/KfzWYarCzceyZNmoSMjAysX78e69evR0ZGBlsFbGpqqtVW8EKhbDv79evXY9OmTew0PbrViapDoEYCkZGRKCwsRHl5OdatW4cJEyYotYm+vr7S4OWugpZIJHB0dERQUBA2b96MkJAQEELk1rCMNIFYWFjAx8cHeXl57KwYbWBubs42H9QEiUQCa2truf4ymiwQVXKrahfF8gy1XJRcuZMQoVCo1a4KFHSnAm12a+ChNXQuAAiRKRgHBwds2LABt2/fBv3p7u7G169fUVhYyLagUOyAqlxFRUVF+PTpE7g/AwMDOHr0KKysrGBoaAixWKwVgSjK+iODOjQ0FB8/foTiz4sXLzB16lQQIlM6g82MoqKi8Pfff6O7u1vuObW1tVi0aBFMTU0H3cpDsSz0b21mZYrPtrOzQ3V1tVK5Pn/+jL/++osRm56eHpsJct8jkUiwYMECZGVl4fbt23j37h0r2/Xr10GIzEoZSQKZM2cOVq9ejT///BP19fXo7e1Fd3c3Pn78qLEeubIFBARg3759+N///qeEN2/e4O3bt3j//j3q6urQ0NAgtxvuaCQQ7jhU57rVlKY92LP19fVhbm4OGxsbODg4wNHRES4uLnBxcYGDgwO8vLyQlJSEsLAwOaxYsULtEdNDGSc81EK3AtCNCUUiEbZu3QoAaG1txf79+7F06VIcPXoUnz9/BgBcuHABhMgPVtoRuDvH5uTkoKWlBZ2dnTh58iQSExNRUFCAjx8/oqenB0ePHmWzvsH86+rk1nZQi0QiBAUF4cOHD2hra8OjR4+QkpKClJQUPHr0iCnKxYsXK3VoRf8tIQSfPn1Cc3MzHjx4gNWrVyMmJgZ///03AKChoQGRkZGMRDSRItfUpy4AkUg0qMUlFovlVrffunULX79+xadPn1BcXIxly5bh/Pnz6OvrQ1tbGzZt2gQnJycIBAIYGRkpldHd3R21tbUAgI6ODjQ3N+Pz589obGzElStXQMjIEwitz+7ubnR3d6OjowNNTU1obGyUaxsuaP1RSys1NRXPnz/H69ev1aK+vh5SqRQA2Jb7o51AuDsoKMpO21HTMwwMDGBjY4PZs2fDz88PPj4+CAoKQnx8PFJTU5Geno7MzExkZWVhzZo1yMnJQU5ODm7evKk0ieFOHC9duoSbN2/i6tWruH79Oo4fP44ZM2bA1dV10COoeWiEzgUAIQRr1qxBc3MzOjo6sGHDBrnPwsLC0NnZCQDsTANra2vm/hCLxYxA5s2bh7a2NvT09LAjZykWLVqE2tpadHd3s/MOtAnUDrUs3O+MGTOGWVQXLlyQM7lNTEzw6NEjSKVSnD9/ftDnnTx5klkt3PMVhEIhampqAAA3btyAg4ODUsxBHSFwXWbU9acJdICLxWLs2rULHR0d+PjxI7y8vORkLi0tRXt7O75+/crObjE2NlaqU4FAgNTUVBQUFCAuLg6EENy5cwcAcPbsWRCinkB+VV+MiorChg0bmFWwZ88eAEB9fb2c3IrKnksiGRkZAID09HSMHz+ebcVOjw0mRLbzLyVf6vIbrQSi2DbU5axuE0dTU1OMHTsWnp6eWLJkCWJjYxETE4Ply5cjJSUFK1euRFpa2qDIzMxEfHw8rl27BgBoaWnRCq2trWhqakJDQwOkUikeP36MkpISbN++HUlJSWy7fe7mmvQQLcUx/h+HzgWAsbExTp8+jYGBAVy9elVut12BQABTU1Ps2rULAHD+/Hl27gNtTKqY9PX1ceDAAQDAo0eP2FnZNN5BCMHRo0chlUrx7NkzODs7ayQPrs+VKw83OK4KtHMJBAKkpaWhvb0dDQ0NbEddc3NzpkTy8/PR0NCAxsZGue2uFTvovHnzUFdXh76+PnZgEHcnXj8/P7S3t+Pjx49IS0sDIerjO1R+Si6qgtvqQOvc1dUV9+/fBwDs378fenp6MDMzYzLZ2trixYsX6OnpQUVFBUzdjfUAACAASURBVAj5xwc92DsogVRVVbFyjiSBKGLv3r0AgLq6Oo1tTmUSi8VYuHAhzp49Cw8PD7Xf8fT0RG1tLc6cOSO3rfpoJBBuPaiS19zcHBYWFpg+fToCAwMRGRmJrKwsZGRkIDU1lVnllDwGQ2pqKlJTUxmBXL9+fUgEQkmEorOzEwMDA8wD8vz5c9y6dYttyU+PVSDkn52NuaeGalsP/0LoXAA4Ozvjw4cPAID8/HwQQligiw4sLy8v9PX14fv37yy4SrcCp1tmW1tb4+nTp+jt7WWnx9G4AlV8CxcuRG9vLwYGBhAeHj7oponqYiCajufkfufPP/9kbioLCwul2beFhQXevXsHAHIn3inO1Ldv3w4AePXqFbtubGwsF2CmCp17gNRgFsVQ24rW9YoVK9DX14dPnz4x4qPbs1NypC7Jr1+/DnoinaGhIXMlUAI5ffo0e+5IEgidnNA2LikpASCLM2n6Hq1PmhXn7u4Oc3NzWFpaMvcd7VOurq7Yv38/Ojo6sGnTJqX2Gm3ZZkKhkJWbXjMzM4O3tzfmzZuHsLAwZGVlITU1FatWrUJ6ejpWrFiBjIwMZGZmMkKgBLFixQq14BJJRkYGli9fjmvXrmFgYECOFDShpaUFUqkUAwMDqK2txZs3b9Da2oqOjg6l+OKHDx/w4sULbN68GWlpaWwMGBoaqu2Hupjo6Ag6FwBRUVFoaWlBY2MjO9ucpt/Re8aMGYOnT5+iq6sLe/fuZQ1ICcTJyQnBwcHMJZCVlQV9fX3mMqKzBnNzc7x48QIAsG7dukEJhL5/0qRJWLhwIRYtWsROK6QdSLGz0O9ZWFiwmVFZWRnrWNxORojsONSenh5UVlYyJcolL3t7ezx8+BAAcO7cORDyjyXAlbO4uBgAcOfOHZUuEVVwcXFBYGAgAgMDtcoaooS+Z88eDAwM4MSJE+xAL257EUKQmJiIgYEBdHR0MKtIFajiofVBCeTUqVPsubqwQGg7l5aWAoDGIDoXipMLVccDu7u749KlS/j69SuWLVsGQv5JK9dFttlQoUh09Lq1tTUmTpyIRYsWISEhgbknafxi7dq1iI2NxfLly7FixQqkpKRgxYoVw0Ig/f39WhMIl0i+f/+O5uZmtLa2oq2tDc3NzZBKpejq6kJ7ezuamprQ2dmJnp4eNDU14eXLl9i9ezeSkpJgY2OjNK7p/zyBjBBOnDgBAHj69CnGjh3L/Kf0czog7969y/z8hMiUKD3QiBCCvLw8AMDLly8xffp0mJiYsJmfWCxmivb06dMAgCtXrmhFIPv27cP9+/fR3d2NT58+4fHjx/D09AQhROX9NK4QHByMzs5O9Pf3IyoqSqlD0b/XrFmD79+/48uXL3Lne1OF4e7ujrdv3wIADh06JFc/XGsoMzMTAwMDaGxslDvNUXGg007v7u6Ouro69PT0AADa2tqY318ikajs/PQabYtjx47JreXgku6ECRPw/PlzAGBnnasCjcXQZ2hLICOlWMvKythMVJv7Fd0a3LJRrF69Gj09Pbh06RJMTExgYmLC+ih1J6rbbl8XhMJ9F81epP8bGhrCw8MDKSkpyM7OllPwiq4pLgmocldpIg5VSE9PR2xsLK5cuYK+vj6tiaOtrU0Jmj5Th/r6elRXVyMsLEyu3em58fr6+v92EtG5ADh27BgjEHNzczllTzuoQCBgSuvatWsgREYgYrGYEUhBQQEA4Pnz5xg3bhyMjY2ZJcN9JnUr3b17d1ACWbhwIV69eoUPHz6wIP/Lly9ZrEYTgfj7+6OtrQ0DAwOIjY0FIaotEC6BzJkzR2mQUgIZGBhg50pTpcolkCVLlqC/v18jgQiFQmaCl5WVsZkVAEilUty4cQPe3t4gRHOaI22Lo0ePsvbhlp0QmbXy6tUrAMCuXbs0KqffmUD27dsHAHj//r1W9w+mMJydnfHnn3+iubmZrS2xsLBg62RUnT2jjkhGYnxyM81o2YyMjODg4IDx48cjKCiIkYKihaBIGkMlCG0IJCYmBjU1Nejt7R2S8v8ZAuno6GCJPQDQ3NyMq1evYuPGjXLHK1M3869aRPwbQOcCMIX+4MEDpZRcQv45gvbBgwdyBEKPlqUEkp+fzwjEzc0NpqamLK2Sm6lF33fr1q1BCSQ+Ph5SqRStra1obm5GY2Mjvnz5gk+fPsHa2lojgSxZsgStra0AMCiBNDU1yREI16UzZcoUfPz4EV1dXfDz8wMhMlKlLiO6Cn3ChAno7e1VSSDcLCp6va6uDp8/f8bnz5/R2tqKvr4+dHV1sbx5TQqKEsjx48flMlUUZ9vUXVhcXKxRQXFjPr8bgezfvx8A8O7dO63uH0yexYsXo76+Hk1NTZg1axYIIayP/i4EohiroyRvYmICZ2dnhIWFYeXKlYiJiUF8fDxWrlyJzMxMrF69mhGFti6pn0FaWhqioqJw+fJl9PT0jBiB0PtpWr1UKsX3798ByNLR9+/fz7wUdHz+Ti7IYYTOBcCpU6cAAPfu3YOBgYFaArl69aoSgYhEIqbAKIE8ffoU48aNkyMQCkII/vjjD60sEJFIhEWLFuHDhw9oaGjA9+/fWaDt3r17kEgkKo+O5VoEXV1dAIDIyEgQop5Avn37Jkcg3ADd9OnTUV9fj/b2dvj7+4OQf9x3tIOKRCJMnjwZPT09KglEVb0/fPgQXV1d6OnpQX9/PwYGBvDt2zcEBwdrTBIg5B8C2b9/P5OTKhluLIS6sNQRiCqF+LsRCM3se/v27U8/SywWIysrC0+fPsWVK1fg7OzM5KftqEgimgjkV5Wf20/p883MzBAcHIz4+HikpaXJEUVqaipLraX/c6Eum2q4CKSmpgbd3d0/RSA/Amr11NXVobW1Fb29vfj+/Tu6u7vR2dmJw4cPY/78+SOiR3UEnQuAqqoqALLgr6L7ipB/ZvTl5eVaEcjz588xYcIEtQRCLZA7d+5oJBA6iNauXYuuri40NzcDAJ48ecIU+WAEQn9o5pgqAgkPD8e3b98AQCWBzJgxg1kJNMmAuhAI+Wc/sYkTJzJ31MqVK9ngV6dggoKC8PjxY7S0tLCZU1JSktzWGqpgZWXFFncWFBSw6zTWRF1kNjY2ePnyJQD1MZCfIZDBSGS4lOtwEoienh6zykpLS2FoaMgIg0sgqohE00ru4SIRgUAAExMT1vfMzc0xefJk+Pn5ITY2VsklxU2r/S8RSHt7O9rb29HR0YH29nZ2vbOzEx0dHQzU9bljxw7Mnj1bp3r2F0HnArAg+qNHj9iA4n5OZ7R0sdDVq1dBiDKB5OXlob+/H2/evMG0adPk3FdcFxYlrJqaGq2C6CKRCJMmTUJ2djZiY2NhaWkpZ6WoIxB/f39m1iYkJIAQ1QSSlJSE79+/o6+vTyWBeHh44NOnT2hra2MrlvX19WFrawuBQMAU9qJFi9DT04Ouri5ER0czhaDojlCs26ioKMTHx8Pf339Q8qDvprGNAwcOsFRi6ien7eXm5oYPHz6gv78fW7ZsUauwhkog6mbmis8eriyY4SSQhQsX4suXL/j69Styc3OZ1UYXIOqaQLiTNzMzM/j7+yM2NhZpaWlKmVBpaWlIT09Henq6RvL4LxAIBSUQ6vL+8uULGhsb0dvbi69fv+KPP/7AjBkzmP76FwTYdSuAUChEeXk5Ojo68ObNG5ZPzr1HIpHAwsIC9fX1LHZBG0AsFrOMpE2bNqGxsRHfvn3D3LlzQcg//keq5AwMDFBdXY3e3l6cPHlSo/VBBxP9TQc4lcvU1FRp4BPyj8UUEhLCZpuZmZmsvNyyE0KwefNmtLW14fnz52zhGXdTRGdnZzx79gwAsGHDBri4uMgpU/q+7OxsRqB0ZbiictWkaIayydylS5cYCTs4OCi5vIRCIebNm4evX7+itbUV69atUxlIpEkAXCV47949AKoXEqpKc1VHIMOFnyUQrmy7d+/GwMAArl+/jrlz56qchOiSQGh9072lUlNTtUqn1ZYw/o0EQkmks7OTEUhnZyekUimkUik6OzvR1dXFLP3m5mbs2LGDxXtHeWxE5wIgKysLzc3NaGtrg6Ojo1xHFgqFMDMzw7Rp09De3g4A+OOPP0CIzHVDCYIQgtTUVDQ0NKC/v5+5egQCASQSCYyNjdm6h7///hsdHR0oLi7WikAIkeXo+/v7w8/Pj62Up8pP0UVGFeWsWbNYrIBuncJVtPTvvXv3orOzE9euXcO0adPYZ7RjSSQSplSLiorkUpzNzMzYfXS1/oMHD1g6MC0LN5OGq9RcXFywYMECzJgxA1ZWVlq3GXUDPn78mK05UXQ9Ll26FP39/fj06RMiIyNVzrYEAgEjSyoTTZZQ3MqEfq7OGuF+TgjB+PHjERgYKNenNJGNuus/SyDcejl9+jT6+vpw6tQpODk5qbQ6fjWBcO9X/J6bmxt8fX0RHx+P5ORkJCcnszRcTUTyqy2N351AKGlwCYSSCL3W3t4ut/bk9u3bmDlz5mhP9dW5ADA3N8f79+8BAKtWrWLXHR0d4ezsDIlEgvT0dHR3d6OhoQFRUVEghLA0XbpIcMyYMWhqagIA7Nu3jz3HwsIClpaWIES2K+779+/R0tKCiIgIjQRCGzUjI4PtpNvS0oLbt2/D3d0dhBAlAuEqaUIIzpw5AwB48+YNzMzM2AFKdPZhYWGBJ0+eMGKkhMB9hlAoRGFhIQDZdhrc8nLrkVoF58+fZ+s4FAmEEMIWK44dOxY3b97Ex48f0dnZiaamJqSmpqpVNNw6ycrKwosXL9DQ0ICZM2eCEKLk/jp06BAAWXKEuj2RRCIRJBIJbG1tmeuLEsiZM2dUfodLHIoEIhaLYW5ujrKyMjaw6+vrUVZWphVB0jKbmJgwi4wSyMuXL9UqcE3PpG0ZFBSE/v5+9Pf3MwtZE3loIpShvF+VPNz+SohsV+XAwEA5pU8JJDk5WUlxp6SkyH32qy2N34lAuMRBiUIRdCEil1SolUK3TqEp9JWVlZg4cSLrf7SPm5mZ6Vw3awGdCwBCZLPw3t5eXLhwgS0CpPvPODk54cSJE+jr60NFRQUmTZrEXF1cF5VIJGKKtqGhAbNmzYKNjY2c4li3bh3bPG38+PGDWiBjx47F48eP0dfXh4aGBnR0dKCxsZFtNa5IIEKhkKWyCoVCBAcHo6urC42NjWwvLCsrKybT0qVL0dPTg+bmZvj7+8PU1JQ9g5vdFBQUhIcPH6K7uxu5ublK9Zefn4/a2lr09/ezTQm56bFcRUMV9ZYtW9jaFgDo6+vD9evXmfWiSmnT1f3m5uY4d+4cmpubUVpayu6j5DR//nx8+fIFra2t2Lhxo9zgUARd7ElJnhJIVVUV222AxrG45VJngURFRaGuro7tbkCDm5oWM3LLSd9Df+/cuRNdXV24f/8+W1XOzRbURoEbGhqirKwM/f39uHPnDtvaRdEV+asIRDFVmlveCRMmICYmBrm5uWoXAf4qAvi3EgjX+uAG1js7O1l/pJO2vr4+fPz4ERs3bmTto7hzwW8M3byYO7MVCASYOXMm2xOKKhyqkFJTU5mSi4qKgp2dHfT19dkiQbrik157+vSpnKuLYuXKlXj9+jWkUikSEhIGzcDS09NDZGQkuru70djYyBYOvXv3Dg8ePIC5ubkSgVBFwJ2t//nnnxgYGMD//vc/uLq6MnlcXV1ZMJoqYbqAkBvEFwqFsLCwQG5uLsvq4D7H1NQUjx8/RkNDA9vqRFFpcHP5aQd98OAB25G0sbERLS0t6OnpYbNjRUUjFMo2rKTWRGpqKrq7u1FfX49169bJxa4qKysByDLd3N3dmSyq+gJVxDQZ4vHjxwD+2b5flSLUFAOhk4iuri4MDAww3/OlS5e06ps0KYH7vIGBATQ1Nam8X5ViVlTsY8eOxefPn9Hf369EZLRMv4JAFD/nWoLW1tbw8fFBUlKSykWAuiaHwZCUlKRzAhmMRFSBGzdpaWlh68tqa2tRVlbGNn8dJbER3ZCH4uzRxcUFhw8fZtkLlZWViI6ORmlpKWpra9HX14dr165h0qRJkEgkzPpQBCGyYDKNl5SUlCAxMRG5ubloa2tDf38/7t69Czs7O5bxpcrXTOHh4QFAtg/Su3fv8O3bN/T39+PRo0dM+akCNw145syZeP36NQBZCnBBQQEKCgqY6+r69evsUCluvXBl09fXx5w5c/Dhwwd0d3fj8ePHCAwMRGZmJqqqqtDZ2Yl3796xxUt0kSO3I3KVuFgsxpkzZ9gs/c2bN3j27Bnq6uoQERGhpHy4s2R9fX04OzvDzs6O7fVVV1eHLVu2IC0tDX/99RcA2SaK27ZtY/Wkp6cn1/Y0kywgIADLli1DQUGB3MFb9+7dQ0JCghw0pfPS/rV+/Xo0NTWhq6sL79+/R09PDwYGBtS6xBQxf/58hIeHY9WqVVi0aBGqqqoglUrx+vVrxMfHy8HW1lbJTciFUCiEubk5YmJi0NzcjDt37ijtC/arCYRL3JTkLSws4O3tjbi4OBbDyMnJGTHX03AgMTFx1BIIN/WXLmCkOuvs2bNsf7lRAN0KQJUJVf4bNmxgCwbpz6tXr7Bq1SqMGzdOSclS1wbXxUEIQXp6Ot6+fYu+vj45HDx4EB4eHkwZDpbGK5FIsG7dOraiHACePXvG3ESaCIQbx/D19cXz58/ltj/o7OzEsWPHVOaHK/r56fXo6GgUFRXhy5cvcnV06tQplgLMVR7c39xnCwQCLFq0CM+ePUNbWxsAoL29HXFxccwNpUkp0r+dnJywZ88eZprTnxcvXmD79u0YO3Ysc/dQAuE+x8vLC0P50YZAxowZg9LSUrY9d29vL96/f48pU6YM2h/19fXZQV/a/KgjECoXXb1Nt4SvqamBm5vbDxOIKiJRRSKKlhr3M29vb8TGxiIlJYWl3qpLwx2pbKrR5MKiJKJIJD9CJtwUYPr97u5utLe3Mw+KkZER29hT1QRBx5aKbglE0bViaGiIadOmITIyEqWlpSgsLGTneqgaHNxBRdd60AETEhKi1NE9PDyUAszqXFi0YUxNTeHj44PCwkKsWbMGM2fOhFgshoWFxaAD3djYmPn2x4wZg5iYGGaBxMTEYPbs2XJbgaiqG8VOMmHCBEyfPh3Z2dnYsGED4uPj4ejoqJQFpe55tGwmJiYYN24cMjIykJ6ejqVLl0Iikci5zgYjEFo/7u7uCA0NxaZNm5Cfn4+pU6fC2toaJiYmcutEFOWxs7NDTk4O8vPzsWvXLmRkZGiEJhcWfb5IJIK5uTkyMzNRVFSEVatWKSUcaEJsbCzbRTY7O1sjBrNAqC979uzZyMjIQGxsLKtfiUSiti//LIHQ9qOkzSWP5cuXyy3+4wnkxwlkuKwRLoHQ658/f8bWrVs19lVNWYX/CQLhVoTiNZp+SQPL6pQhHUjcxYKDKVJ1M1lVM1ru9wghLIiszaCnCu1n0/RoeRXXonDrb7B1HKqIgPu54pnz6pQit/4IIYwgfxbqZlnatJ02M7FfEZgcjEDo2SiK99M242Iwa1jTREfVRIMQwjJ57O3t4evri+joaCxfvhyBgYE/tQDwdyGS341Ahgt07Qggc8OrO2pBlYU5wtDZi5UUAyH/nA1hbGwMY2Njtk2HQCBQShPlKhNVyoPrplL8niplqIpAxGIxrKysWCPR7UPocwYb4IoBTHNzc1Y2xfMzBqsf7vvMzMxYfWhz1rRiubnXnZyc5EhAHWlwQd2H1OITCoVwdHRUkkPbzm1hYQGJRAJLS0uYm5vDzMwMEomEwdjYmG1INxQCMTIygpmZGRwcHJQWqGqCmZkZTExMWBYY3dFZERYWFhoJhHuEMSGyyQf3DG7FidBQiEOx7OrqYPz48YiJiUFMTAyWLVvGzhX/GYuDJ5B2lRhOAuFmcrW1tSntqaXOsh9h6J48uINpqJ/9qA9QGyU52HO1sWR+RR1pso6G8x2D1Tm3/IO5z7SBKstKU10Ptb2GIqMqC03V54aGhmqD2pRoNfXnwSYy2ljK6sovEAgwe/ZsxMbGIi4uDomJiUhKSpJLy6WurN+FEEYLgWhDJj8DSiSK7/L392cTITqx5QmE/DwRDPW+4SAQbZ4zkvU3nO0wmPwjXVbuLF2V8tTmGUMh9MEIhFsH6iwhxd+K11Qp/KESibo2EAgEGDduHGJjY5GUlMRWlCsq4N/NouAJRJ5EFN/V1NTENnKl1j9PIDx4aAGqXH+UtIZyv7YWqDZyafteVVbJYFDlvjM0NMS4ceMQFBQktyWJOhIZzfgdCORXEhP9m3sEb0tLCyMRdRPjERyXulcMPHjwUMZQCIRLJJQ8YmJikJSUhBUrZIvuNFkioxX/ZgJRhdevX6OlpUXOEuFCU0z4F0H3A4UHDx7K0EQa3AxA7s7NM2fOREREBFJSUthOuqmpqUrbkowWFxVPIPIWCP2bXg8ODgYhsr3M6PbwvAXCgwcPreJq3DVEbm5ujDwocVCy4Crd0RTj4AlENZnQv2tra9nmsnZ2drwLiwcPHjJoCpArxj5cXFwQHh4uZ2Vok5arawLgCeTnCEQqleLz58+MRBT7zwj0U90PFB48eChDE4Fwr7m5uSE8PBzx8fFyVoa6xYGq1nzomgh4AvkxtLe3QyqVora2lrmzKEZogaHuBwoPHjxk0MZtJRQK2SLSmTNnMvKgZ3NwLRCeQP6doDERit7eXty+fRuLFi0CIWRIh8P9JHQ/aHjw4CGDNllXFBMmTEB0dLSc1cGFNgSiawLgCWR4yKSlpQVSqRSvXr1CUFAQ608jYIXoftDw4MFDBk0LDLlnzVhZWSE8PBzp6elYtWqVnCXxO289whPIrwM996axsZHtCcg9VuIXxUR0P2h48OAhgzoCUdwex8vLC5GRkcjKykJmZiZPIP9hAuG6stra2tDT04OysjKVfesX9FndDxoePHjIoC4Nk163tLSEn58fkpKSEB0dLbeXlaJy5QnkvwkaE7lx4wY7uI2QX+bO0v2g4cFjtGK49wNT9RwjIyO20eTcuXMRERGBxMRExMfH/6tWlfMEMnzo6OhAT08PS++lxwr8goWGuh+EPHiMVgyWMTUcz6KrzV1dXREfH8921eVuTfJfJRKeQOShuH/Ww4cP5Y7H/QULDXU/CHnwGK0YCQIhhMDV1RVxcXFyxKG4SaIidK3ceQLRDYFQ0GOzX758CUtLS6WTVocJuh+EPHiMJgxlt9yfBX3nvHnzkJWVpWR1/NcIgyeQwYmDorOzE58/f4ZUKsW2bdtAyD8nVA5XXya6How8eIw2cLOifhVxcGeKjo6OSEpKwtq1a+UWC/IEwhOIIoHQg6jo/21tsnjIp0+fkJmZyfovPRqbJ5BRAk0ZENyT+H7GJfKL0vR+KQYr5+9YJk0Eoqkdh0oghMgC6MHBwcjIyEBiYqISeehagesaPIGoJ5COjg60traiqakJ/f39ePnypdwuzvTIbcVJC08gvwm4CkXTca3cs7q1PedaFUZo75thBfdIXLFYDENDQ4jFYrZgbrCja3+UbLUFPQudW7c/SyDcMtPFgYrnqtO/p02bhsTERKSkpCA+Pv5fkXrLE8ivJxB6rjpdod7U1AQA2Lx5s5KOGsqJl78dgRgaGoIQ2UzLwcFBSaH+KCwsLNR+JhaLYWRk9MvLxlUq1GRUdZ+JiQlsbW3VNuZgipHWoS6Ot3R2dlaSQyQSsb/V1Qv3eFruatnB6nI4odhGFMbGxhr7B1f5K7YV974fOSdeT08PBgYGIITA1tYW4eHhiImJQVxcHE8ePIFoRSJcImlpaZEjlfr6esyaNYvpQW7fVdWfBwMZSWWjDtwzDUxNTZGcnIxLly7h0aNHOHbsGJKSkmBtbc0WxGgCnbmKRCJkZ2fj8uXLePDgAc6dO4dt27ZhypQp7N6RIhEDAwPo6elh7ty5OHr0KB4+fIiHDx/i6NGjCAgIgIGBAYRCIWxtbdnsW/Hsb6qcDAwMEBERgbNnz+LFixd49+4djh49itDQUPa+kSgTV0G6ublh165duHv3Lm7cuIGTJ08iNDQUpqamGr+vKOvYsWMREhKC3bt3o7q6Gvn5+SBERr6/gkBUWQ5c4hMKhfD19UVeXh6qq6tx9OhRJremGRshsmDlokWLMHfuXMydOxfe3t5YsGABfHx84OvrC39/fyxYsADTp0+Hu7s7XFxcGHlQK8zLywuxsbGIi4vj3VU8gQyJQLhnqnMJRCqVYseOHSCEsMwsLkYlgdCdRd3c3HDz5k10dHQAAFpbW/H9+3cAwP379zF58mQ5JaTObSESiXDhwgX09fUBAPr6+iCVSgEAV69exaRJk0DI0Cydn3WR7Nq1Cw0NDVD8effuHZYsWQIDAwOYmJgwS0WxYQkhGDNmDPbs2YNXr15hYGAAPT09AICmpibU19djw4YNcHJyYnIO5v7Rpi5VgWtJBQYG4tSpU+jv70drayv6+/sBAJ8+fcLVq1flLBR1iIuLQ3V1Nbq7u+Xq5saNGyCEMILlzvxVdXjF+voRAhEIBAgMDMTJkyfx8uVLAEBvby/6+/tRW1urVGf0fVxSIYQgJCQEt2/fxsDAAAYGBpTanf709PSgtbUVx44dg42NDXv++PHjERYWxtZ98ATCE8iPEAj3d3t7OxobG9HZ2YnU1FQ2lvX19UcXgdCBJxKJYGBgwCyBw4cPo6enB69evUJ0dDSEQiFiY2Nx7tw5AMDDhw9hamoqF/xR5SbYuXMnPn/+jHfv3mHbtm2YPHkygoOD8fDhQ0YiXEtEG8L4GQLJy8tDW1sburq6cOTIEXh6esLT0xNHjhxhJBIQEABC/nF10YbV19dnROfl5cU2S9u0aROmTp2KefPmoaKiAn19feju7kZBQQEjEW18mT9SHmr6TpkyBTdu3EBvby8ePHiAZcuWwd7eHunp6airqwMAbN26FePGjVP7rClTpqClpQUDAwOoq6vDw4cP8f37k0bMRQAAIABJREFUd7S3t+PatWsgRGYVcOMF3CCg4jWuq1DRVaVYD6ra1dzcHP/73/8AAO3t7bh69So+fPiAvr4+fPr0CW5ubjAzM1MiekrY9O9Vq1YBAN6+fYu3b9/i9evXePXqFZ4/f45nz57h8ePHuHPnDi5duoSqqipkZmbC1taWlTckJAQJCQlISkriyYMnkB8mEG5Qva2tDU1NTZBKpXj69CnMzc2ZDuaOqd+eQFQhNzcXra2teP/+PQIDA9l1AwMDeHp64uPHj+ju7kZpaSkIIbCxsWFkwn1OUFAQenp60NHRgfj4eLnPhEIhXr16BQAqNxtTBe4OqJS4hlIuJycn1NbWoqOjA4cOHVJqgGvXrgEAzp07xxpQkUAEAgFcXV1x69YtdHV14cSJE6w8EyZMgJ6eHk6ePMnyvqdOnQojIyPo6+v/UExlMBgbG8PY2Bg1NTXo7+/HixcvGElIJBIQQuDv74+mpia8f/8ecXFxKutVX18fLi4ucHZ2xtixY+Hi4gJCCG7duoWBgQGcOXOG9QEuOdCV2WKxmNWR4jVF8ue+W5FEFQPkzs7OmDlzJlxdXUEIQVZWFgCgtrYWVlZWMDY2Zu+h8ii+IywsDK2trUhPT4eHh4fKerS0tERvby+ePHnC3AmEEPj4+GD58uVszYeulfTvDJ5AtCORtrY25tFpbW3Ft2/fmIvY0NBQJXmomnD+lgRiZmaGS5cuYWBgAFVVVXB0dAQhsuCyRCKBnp4eioqKAAB///033N3dYWBgwEwv+hx9fX388ccfAIDr16/DxsYGJiYmcgqioKAAAPDy5UtMnz5daxnFYjFMTU0hkUhYkFMbbNmyBW1tbairq8OcOXOUGiAuLg7fv39HU1MTUlJSmEJSDMxGRESgu7sbzc3NWLJkCYsbUYU9YcIE1NXVoaOjgy0aMjExUWmacmfdQqEQFhYWsLCw0NqlJxaLMWnSJDx69AhSqRRr1qwBIbIYBlWu1tbWqKmpQWtrK86cOaMUYNZEyDdv3gQAnD9/HoTIYlXqrA8u9PX1WbxJT08PJiYmMDExUSqXKitEVTmpZbxv3z5mTZiamsrFqbj1a2hoCGNjY5iZmcHe3h65ubkYN26cUpadWCyGgYEBQkNDMTAwgOPHj7M+ZWpqiuXLlzOrgy4a1LWi/l3BE8jQCKS5uRktLS1oa2vDxYsXYWlpqVafjRoCmTlzJvN/U1Yk5J/ZPyEE3t7ekEqlaGtrQ0JCgpxioQrAzc2NuU62b98OQmRnRVPXACGyWV9nZyf6+/uRk5OjUS6BQMACmqo+U/c9rrK8d+8eAODKlSvsMy5EIhGePHkCADh79iyMjIwgFAqVyHHjxo0AgCdPnsDd3Z2dOEZn3oQQXLp0CQDw4MEDpuhVKVruu4fSTgKBgCUyrFq1Cm1tMpPYy8sLhBAWAKb3UJm/ffuG8ePHy7UVVfh0Fq+vr88C2NevX5cjEO6MnzvrV3RhcWMfmtpFnRuLtje9n2YFnjhxAgDw5s0bmJmZyWWvcGMgIpEI1tbW7BkmJiYwMjKCWCxWksnCwgJ79+5FW1sbkpKS2PVZs2YhISGBkQa/3oMnkOEikLa2Nnz//h0tLS0sxTc7O3vQMf9bE4itrS1ycnIAAJ8/f4a3tzcIkVkllpaWTDGNGTMGp06dQnd3NyoqKphCoZkrTk5OSExMxPv37/H48WMsW7aMKSpFF8WVK1fw7ds37Nu3b9DKI0TmF09KSsKuXbuwadMmBAYGalS+9LMZM2bg7t27AMDISpU7ae/evWhoaMCDBw9gYmICgUDAZrmEyILn1dXVAMAyKPT19VnQnc6U8/Ly0NPTgzdv3mDatGlq4wVU2RFCEB4ejvLycpSXlyM8PFzjUZhULkII9u7dCwC4ffs2nJyc2D1cuX19fdHV1YX29nakpqaystGOqCgbnQlRArlw4QIIIWzWT8mDxkRoPXCJhWZ+OTg4ICMjA0ePHkVBQQFmzJgBMzMzrQLptKx024cDBw5AKpXi3r17cgTGlX2oMaVJkybh7t27ePDgAUvqEAgECA4O5t1WPIEMO4HQGAj39MKOjg7cv39f45KH355ACCEsmPzixQs2aLnBY+quoa6N27dvs+tCoZB9vn79enR1deHTp0+wtbWV8+tx33f8+HEWvNZGvjNnzuDbt28AAKlUiq6uLqxevVrt/XQGvmTJEvT09KCtrY0dM6miAZCUlISenh60tLSw4D6NYRAiy8ipra0FAEaMlDSo64QQmd+9o6MDzc3NCAkJUXL7UGuFEnNUVJRSB6yurmaKXrEjUcuIEIL79+8DAE6dOiVXbmr1EEJgb2+Puro69PT0MLcatyMqLqKjz758+TIAoLq6GoTIZuvUSqEuKkKIHClyg+eOjo549eoV+vr60Nvbiy9fvuDx48cICQnROiOLK8/evXvR39+P9+/fy71PMQYyFALZsWMHWlpasHPnTnZt6dKlSExM5AmEJ5BfQiDt7e3s0Cm60FAqlSItLQ2EqF7sPCoIpKKiAgDw7Nkzphy4rhl1BEIVHZdAuru7UVdXN2wE4uvri5aWFrmKl0qluH37NttjXxFUbl9fX/T09KC9vV1rApk2bRojCNqgqgiEu3aGKrrZs2ejs7MT379/R0hICKtHRQKhcZOamhqlDtjY2MjWlCi6fLhZRpRATp8+zRQnXbVN5RaLxfj48SPLxlL1LO5iQlqOmpoaOQKxtLRkFgglTj09PZiamipZIIQQbNq0Cb29vfj8+TMbNABw4sSJHyKQQ4cOsRgI/UzRfcZtz8Hg4OCAy5cv48uXL1i5ciVr7/j4eD7mwRPILyUQeg/3+pUrV2BlZQVDQ0Mlt65iMP23JJDy8nIAwNOnT+UG6GAEQt0V9DclkNraWtja2qrdvngoBBIZGQkA6O/vR0tLC+rr69Hd3Y1Hjx6p/Q6Ve9GiRejp6UFnZ6dGAklMTGQEQgP7XDfNYARCidLR0RFSqRTt7e1KBMKd7dPnvn37VqkDDgwMIDY2Vq4TcRU+lZkSCM2UojJTRU7vowSiaIEodkhuvOvWrVtyLiwrKyslAqGWJzfzin6fute+fPkCqVTK1t/U1NQovV/xzGhFFxshBCdPnpSb4ChmyiluuzJYn4qPj0dLSwtev37NrEFfX9///NkeQwE9UZEnkKERiKqFhr29vUhOTmb9k44HxcXMvy2B0BnekydP5AYyVcTUN64tgXz8+BG2trZqB/VQCGTGjBn48uULOjs70drayoL9ly9fHpRA4uPj0dPTA6lUqrUFQgnEwMCAya2tBeLi4sJiDqoIhLtegRCC8+fPK3XAT58+ITIykgXzVSl7LoEcP36cyUGtJm0IRLEeuPLdvn0bgCy1mRCZBUKtDJrSS9+pyoUVHh6Orq4uAEBzczPoT0lJiRKBcGWl8ikmGRQXFwMAvn79Cnt7e5iamjJra6gE4uDggOLiYnR1deHJkycwMDCAubk54uLieALhCWTYCUQbdHZ24ubNm7Czs5PTD9xlAL81gZSVlQGQLRTkppTR2RlFSUkJALDZv+LnR44cgVQqxbt371glqBrU1OJpaGjQSr6AgABcuXKFKaTy8nKNi+MogSQmJgKQrTb29PRUu3dSSkoKent7IZVKMXXqVDlSIES2LoCuZqYLDrnbsND3LViwAL29vQCA4OBgOQKhoEqSEFnq75kzZ/Dhwwe0t7fjf//7H+Lj41UG0mnnof/TmFBRUZESgXCzxyjxFRYWKhEI133FtQT+/vtvDAwMMAJRVNjcrDjF7Cx6f35+Ptra2vDy5Ut8/PhRKduESzzczCuufPRaaWkpANmKfzs7O5UEom0wfcyYMYwgaWLF3LlzkZmZ+VO77Kra3l0bjJSi/1VnsfMEMjRCoddbW1sZ2tvbUV9fz7Ipqe6hSUpcEvktCWT37t0YGBjAw4cPYW9vz65zF2jp6emxdMqamho5RUgHe3FxMTo7O/H27Vv2HFUEcubMGTki0gTudydOnIh58+bJZfOo+g5tAB8fH/T396O3txchISEqF/YRQpCWloa+vj40NDT8n703D2vq3Pe+b6CEJIZJCCAgbrX6OLRva+vTqr202qNV3I5bKx5R3IpwgOBW0YOip06vCm5rdVu1Hnerj512h91Xi9aKVMZAQETcSvEBRF7GJww5YdoJCQe+zx/pfXclJAwKBHR5Xd/LkGFlrTtr/T7r/k03C6Jz77LfeOMNqNVqAGApn1zQUgMYEBCAtrY2NDY24p133jELENPaC1tbQ0X56NGj4eXl1W0WFn2cn5+PtrY2fPLJJ0b7wXVFubi4QKlUQqfTYe/evWZdYlx40M+ZZmHR2htTg20uGM/dXz8/P/j6+pr9neh+cGMqlgBCa5Du3LkDGxsbBjFzAOluFrJu3TpUV1fjwYMH7FxatmwZtm3bxkBg7bt7HiDPjroCiFqthlqtRlNTE9RqNc6fPw9HR0d2HZpWpw9agOzduxcqlQrFxcWYOXOm2ff4+fmxViS0Gp1e8BQWmzdvRkdHB6qqqtidOtegEWJw/dy7dw8dHR34+OOPu903Nzc3I8NEXUfmgvN0fyhAXn75ZTx+/Bh6vR7vv/8+M9hcqhNiKDbU6/WQy+WYMGFCJ2Pt6emJe/fuATBU0Lu4uHS66x09ejQOHToEnU6HgoICth1TgJhmWnD/pj3JeqKvv/4agKFgkxYscutOCDHM3Jqbm1FXV4cdO3bAzc3NbBYWHQs6bqYA4WbmWTqhzf0elpo5ckFBt20uYYC+l86QFQoFA52lmhTudN/c99JC17///e8gxOCe3LRpE7Zu3fpMAqS/xQPkycQFSENDA7RaLVQqlcWuCYPWhSUQCBAdHY2qqipUVFQww2+685MnT2Y9iQ4cOMBes7W1ZQ371q5dy1wN9E6dG4ynRrKsrMzIqHc1YEKhEFKpFAsXLkR0dDT27NnTZQU71yi6uLggLy8P7e3tDHrU2HEzHE6ePImOjg7Ex8fjxRdf7LRNoVCI1NRUAMD333/PnndxcTGC27lz51hfKurP5IKDa/DoZ15++WWEhYVh165dWLNmTY9/N+pOvHv3LktYoACgs6Pw8HB0dHSgsrISa9asgVgsNgsQ+pwpQGiciUKbGm1TgJi6wLhB8K7a6HN/a+7fphlVJ0+eBADk5uZ2mjmZJieY+oy5cCKEIDk5GRqNBnFxcRAKhZgxYwbCw8OxdetWvl07DxCrAESlUqGkpAQdHR3YunWr2Wti0ALEwcEBPj4+KCwsBACcP3/e6HWacjp37lzWK+vtt982utipwbKxscH9+/cBAOfOnQMhhvUquO6e2bNnQ6VSQalUWpztcAfM3t6eGUv6j5vqaulz9Ac4deoUdDodHjx4wNqFcAO3o0ePRmlpKetNY9pinrqn4uLiAACZmZl4/fXXWRorfd+oUaOQmpoKjUaDb7/9lsHS1H1FwUOIoT2KUqlkHXRbW1vxySefwMbGhm3bNDhMZynBwcEoLy8HAAQEBBgBke4TjR3cvHmTfae5u3NqlLlGFjBU5nPBws0mMx1zrtGn3zFv3jzMnj0bS5Ysgb+/P1sHwXQ/TD9HH9Pv+e677xgshUKh0bibAsQUItztr169GgDQ0tKCOXPmwNbWFkFBQZDJZNi8ebPVjfFQFA+QpweJSqVCY6Oh2WtycrLF2fOgAgj3YrW3t8fRo0fR0tKCx48f47XXXoOdnR1rQSIWi5n76syZM6zpHt0ONTA2NjbYsmULGhoaUFRUhLlz57KL3MnJCSKRCN9//z0AQ6twbosTS1q+fDkePXoEAKwJGa1K7urY6GN/f3/We2bnzp0MCtSo7N+/HxqNBg0NDZg+fXqnbTk6OoIQgwuvrKwMKpWKrSpGK9EJ+a37a21tLWbNmmVk4Lhpp9xtx8fHQ6PRsMwyrVaL4uJivPvuu0bw4BpEb29v2NoaUmivXLkCAMjIyOi03++88w5KSkqgUqkQGBgIQgwuJdMsKFdXV9ja2kIsFrOKdhpkvnXrFosbcVue0FkI10BzDTghBEFBQaipqWFjAgDffPONxd+JjpVpRhghv2UJ5ubmwtfXl8WJzKUimwMIhTZN3khISMCYMWPg6uqKzZs3s2wiaxvjoSgeIH0DEfp/YWGh2SUYBu0MhGrs2LFISkqCWq1GSkoKxo8fD0IM/v+vv/6are0xf/58ODk5Gd01cgOgtra27I7xp59+wtSpU0GIoWXKjh07AAAqlQqLFy82SoW1pMDAQDbzqK6uRn19Paqrq6FQKNhddXeihZKVlZUIDg5mz69YsQIVFRVobW3FpUuXzLpauMHlnTt3AgAaGxuxf/9+9p6FCxeydVP++te/dvqsaRovVUlJCZRKJaqqqqBSqVjq65w5c0CIoYWLqVHkzmSWLFmC9vZ2aDQafPzxx+w3mTJlCpKSkqBUKpGYmAhfX1/WgqS7LCUbGxuWIswN0HOPyc7ODiKRyCiwTiFJ42H79+9niQcqlQr379/HjBkzLH4n9zwyFa0rKSoq6jIgbw649D2vvfYaAxlNKHj11VcRERGBsLAw3n3FA8QqMg2wd3R0GNmWIQMQqVSKXbt2sbTPK1eu4OTJk4iPj2e1FFu3boW3tzdrP8w9MO625syZw2YsRUVF+O6775CXl8eqkj/44APY2dmxdt1d6ZVXXkF9fT1UKhUqKiqYoc7Nze1xV97Ro0cjJSUFbW1tKC0txZUrV5CWlob79+9DpVLh8uXL8PT0tGjA7OzsWDEdDcLW1dXhq6++wqVLl1ihXHJyMmsASO/aTVc45G738uXL0Ov10Ol00Gq10Gq1KC0tZdXwTk5OZgFkb2/PFj86ePAgA+yVK1fwww8/sBlbVlYWAgIC4OjoyFxfpjOQV199FQcOHMBPP/2EEydO4KOPPkJRURGUSiXS0tJw5swZnD17Fh9//DE+/vhjs6m/3JRciUQCb29vJCcno6WlBXV1dejo6MDRo0eNYhPmRONWoaGhiI+Px5dffonz588jKyuLrVdy9epVXLhwAd9//z3OnTvH0rnNXVy2tobOu56enggLC0NlZSWUSiWCgoJgY2ODt99+2+oGeKiLB8jTgcO00LClpQWff/45CDG4q81dI4MSIFQbN27E+fPn2QqC1MAuWrSI+Z7NBUZNQTJt2jRcvXoVVVVVRlq/fj18fHy6vAvmytXVFTKZDAUFBWzwL1y4gFdffdVsFpapaKMyb29vnDt3DsXFxczgFhcXY9u2bZg2bRo7BnPb4D7v7OyMLVu2oKioiFWdt7S04OzZs6yGhEKHe4duGhgmxJABRNNxOzo68ODBAyxdupTFnbgLOZkabO72IiIicP/+fXbHX1tbi48//hhbtmyxeBxUixcvZgCks0zAEI8pLCxk5wFg6FQwceLETr85N2BOCMGsWbNQW1vLtpufnw8/Pz+jgL25i4JW89MgPvcfnZ1ReNPsFdPMMjMXGGxsbHD48GGo1Wrk5uayme/GjRutboCHuniAPB1ATEHS0NCAhw8f4rXXXjM6t4cMQKjhmjBhAtavX4/58+czg2ZaOGhODg4OzLXk5eWFl156yUg9XQedawDs7e0xcuRIBAUFYfbs2Z1iMN3tD50V2NjYwM/Pj62TTdc96W5bps/7+vrCz88PY8aMYftDq7S5MQNuqqlpphIXSFOnTsWECRMwYcIEuLi4wNnZmd09dwcQW1tbODs7QyKRYOTIkZg/fz7eeOMNtkBST0D92muvYfHixfD394e/vz/mzZuHqVOnYurUqZg/fz4WL16MGTNmdCreNM3mojcVq1atQkNDA6qqqlBfX4/Y2FjWnLKr2Qcdm0mTJmHOnDlMS5cuxcqVKzFlyhRMmzYNb731FubOnYspU6YYueXMAYTO2qZOnQqZTIYlS5bAyckJM2bM4OMePECsLtP6ELVajdbWVrYY35AECBVNRXVwcOiy5XB/iA4YNY6EkE7woQNq6fOmaaUUhJbg0NNZkam4nXtphpe5AsKeFLvR9Su48Q5zGUamd9mWxqCrv/vqN+IeLyEEsbGxaGtrg1qtRk5ODl566SXY29sbtZM3d2GYC6p39f30e7sCCDdRg27P0dERGzdu5Lvu8gB5anErynsjS9uhLvr4+HizM/YhBRBCCMvSoem+3b2/J5lVvTFOhBjqGsy1Ee/KGNO7T27HWKFQCIlEArFYDLFYbOQi6i64zN0n7g9Jg7+msQHTOgVzosafxjoogMxtoyuA0BUNaTaV6ZKy5saUa/gdHBwgkUjg7OwMqVQKqVTKjk0sFluMD1magVy8eBHt7e1GqzO6urqyBci4Y2cqOh40+810RkdjP4QQNsU3NyZ0v+j76SqEEokEnp6eWLBgAZuB8AF0HiDWBAh3BkJr7VJSUjrZnCEJEFPDY43tdFV3YGl7pi4w06Z9VJZcS5aMpTnjR1+3VGBnDiTmgED31ZJB7K3ofpk7ES3BRCQSsXYK1HBLJBKzachceNLX33zzTWg0GrS0tCAtLY011aTH1lUQne4PtxjUNAmhu89z4WFnZ8egRQFCiMGdFRkZCZlMxsODB8gTQ4PG4Z4GIOZanQCGkoVFixbxAOkLmTPupkanu89bgk1vttPVe7v6oXsDEEuzjCdRd8bWHFxMx5w+x40nmI4r99jGjBmDzMxMNDU1QSaTMTB1N17mAGLuguktQGgcids/y9/fHzt27EBkZKTVDfBQ1/MIkL6QafCcqqysDE1NTWhvb8ehQ4eeDYBwDclQ/O7eQKIv99mcS6UrV1RX7piBBEhXsxRLx8itUB87dixWrVoFGxsbs/DoCiB0W096XJagTV1gUqkU69atw/vvv88H0XmAdGncBwogLS0t7P/y8nLU19dDrVbjq6++sgiOIQcQXr0HiCWj1l0cYLCLe3ymx8Ztg9/T8enqDqsvAMIFyaRJkxAUFISYmBhWRMiLB4i1ANLS0oKWlhYAhhY7NTU1qKurY81tuR4AHiDPmboyjtY0/v15bH21va7Gq6djbC4JwcvLC0FBQdi2bRsf/+ABYhWAmM4+WlpaGExoXEWlUkGlUrH2QpZio8TaRo5X/8naM4X+BIi5Y+xrgPTkGHoyg+HO+l555RUEBQUhMjKSBwgPkEEDEPq4sfG3Tr0qlYqtpGrp3CbWNnK8ePWV+gJQTwLB7mYv3FmMv78/goOD+QaKPECsDigaOKcAoWukc1u9nzx50ug85wHCi1c/qSvo0NlRQEAAZDIZNmzYwBaR4sUDZDAAxNySt6YdrHmA8OLVT+oKHnTmEhQUhJCQEISEhLBW7rwriweItSFiWgtCAXLt2jWjc9w0W5NY+6LjxetZkTl4cNOLPTw8jBoo8vDgAWJtgFCImFs3vbGxEfn5+aylFO/C4sWrH2UOILT+gxCC6dOnIzw8HBEREUw8RHiADGaAtLe3s2XGeYDw4tWPMgcQ2k6fEIKZM2dCJpNBJpPxAOEBMmgBwoUIAKxZs4ad3zxAePHqJ5mLfdBeWoQQTJw4kQcID5BBJVNwmAoAli1bBkII69LNA4QXr36QOYBwFz+bPn06DxAeIINKPQHIxo0bQYhhPSYeILx49ZPM1YHQViZisRiLFi3iAcIDZFCpO4C0t7fjzJkzIMSwrAUPEF68+kldAcTJyYnVgPAA4QEyWNQdQOrr65GUlMQW9OMBwotXP8mcC4u2oHdyckJISAgPEB4gg0rdAUSv1+PevXsQiUQghAfIoBUtNOvr3lH90X+Kl+WxNpfGS4gBIJs2beIB0kcKDQ1FaGgoA8jNmzeh0+ksZhPxejKA6HQ65OXlseW8eYAMUtFWF+b6Jw30vvDQefJx6wogISEhRjUgzxNAQkNDe/zersaEgoMLkMDAQNy8eRN6vb7LmgZePECeWeNmut66aRvx/jR4vRlj7mvP6m/xtONpDiDOzs5GAHlewNGXAOGCg7aDkclkWLt2LRISEtDW1tZphT1+RvIcAEQkEsHV1RWOjo5GhsnFxQWzZs1ia0mPGjWKLTXa3TaFQiEmTpwIT09PvPLKKxg/fjwbBDs7O+bTM5WrqysIIfDx8YG7uzsIIWxFOalUCm9vbzg5OfX42Ozs7ODu7s4Mibu7O8aMGYMxY8YYbd/e3h6jR4+2uOSsOWNP15egrQZ6KzrrEYlELNWUfoeDg4NZ42jtc2Uwy1IhISEGgISGhjLjyAOk95+n4Ni0aROCg4OxYcMGrF69GmFhYbhy5Qp0Op1RZ1keIE8HkIaGhsEPEFNDPm/ePJw7dw4VFRVoaWlBUlISPvvsMyxYsAC2trYslayri5gQgsWLF+OLL76ASqVCe3s7lEol0tLSIJPJ2HspLMxtgxr8qVOnIi4uDvfv30dFRQVqa2uhUCiwcuXKHh0fhYSvry/i4uKgUChQX1+P+vp6KBQKREdHgxBitOxpdwChP+a8efPQ3NwMuVyOy5cvY9asWd3uT2RkJLZu3YqoqChs374d27ZtQ1RUFGJiYrBnzx7s3r0bGzduRExMDHbu3MkMYF/GZZ5V8QAZOICEhISw5yIiIpCUlMRW1eMB8hwBhGvEX3/9dVRVVbETQaFQoKSkBABQWFiIBQsWwMXFBQKBwPQg2MHZ2dlh7ty5uHXrFpqbm9He3o6rV6/i+vXrAID29naEh4dbnMlwnxs9ejQUCgVUKhXq6uqQmpqK+vp6AEBDQwNOnjyJUaNGwc7Ozih109SF4evri08++QT0X1FREYqKigAAFRUVDCJ0SdbuAOLo6Ihx48bh73//O7RaLZRKJdrb27F06dJux7uoqAhKpRLcf21tbTD9V1dXhzt37rD0PR4gTwYQOnt2cXGxuhF/FgBCRXuKxcTEICoqCgUFBcxucCHCA+QZBwh1W73++uu4ePEiAKC4uBjz5s0DIQQzZszA3//+dwDAgwcP4OPjA7FYbGRkudvz9fXF119/jYaGBiQmJmLt2rX1aoAGAAAgAElEQVQQiUR48cUXceHCBQBAa2sr1q5da3Z/6IxBIBDg9OnT0Ov1yM/PZzOOV155BQcOHEBdXR0ePnyIvXv3Gi37aLr0IyEEcXFxDBaHDh1i33Xo0CE0NTUZQYQ7CzEHEnq8gYGByM/PR0lJCfR6PWprazFjxoxux7ugoADJyck4ceIEdu7ciVOnTiEkJMSoyd+FCxdQVFSEL7/8kp+B9EI8QAYGRFxFRUXh888/h1qtNjKIPECeE4BQnT59Go2NjSguLsb06dOZIZdIJJg8eTIyMjIAAN9++y0IIXjxxRfh7OzcCSBbtmwBADx+/Bj+/v4gxOAmoxdyTk4O2w59zpxkMhlqampQUlICmUzGYhkODg4Qi8W4ceMGACA/Px8+Pj4QiUSscR4XIsuXL0dNTQ0aGxuxb9++Tt/z1VdfAQBSU1MxduzYbgFCiMEdUlhYiPr6eqxevRp6vR5qtRpz5szpdpyzsrIQGxvLwGAunpOYmIja2lq8++67Zg2jtc+VwSoeIP0vUxdgaGgoHj16hObmZmg0Gmg0Gn4G8rwBxN3dHQqFAm1tbUZ36HQ9BUdHR3YX//DhQ7z22mtwc3ODRCIxAoiTkxMDxCeffAKpVMqa2dnb28PBwQF79uwBAJSWlmL+/Plm98fNzQ0XL16EWq1GUlISvL294eHhAbFYzGIjMTExUKvVaG5uxrJly+Dq6gqJRNJpHeyPP/4YAHDv3j1mkLlasmQJiouLoVarsWvXrh4BZOfOnWhqasKFCxd6DZBZs2bh5ZdfNgKHk5MTHBwcIBKJ8MYbb6CyshKPHj3CpEmTOrnkeIDwALE2PCIiIlg9zbFjx1BWVsZcVzxAnkOAzJ49m/nh6ayBXoDUxfXuu+9Co9EAACIiIkAIYQChRu2NN95gAxMSEgJCfnMrUY0cORJlZWUAYAQrrry9vVFeXg6dToe4uDg2cIQQZhAmT56MvLw8AMCJEyeMvksgEMDGxgYSiYTFcK5cuQKJRGL2++RyOQAgPj4eYrGYgZMLIroPdN8eP36Ml19+udcAoRKJRPDx8YGvry/c3NzYCbFnzx7U19ezmR4dXx4gvQMIfY5mt7m5uT1XQfO+Fo2h0P8pQBISElBbW8sAwgfR+wYeXIDcvXt38ALEz88P27dvZ7EP6senBpsCxNfXF/Hx8QCA7777zugO3d7eHh4eHggPD0dTUxMKCgqwePFio9kHNz7x9ddfQ6PR4MqVK2YN68svv4zGxka0tbXhrbfeYoPn4+NjlN56/vx5AMDNmzfh4uLCguBCoRA2NjZwcnJid0U7d+60OAYfffQRlEolbt26xY6XBvlNARIREcGAJBaLewwQCiM6DqYgoK9/8cUXaGhoYFCkIOMB8mQAob+dm5sbQkJCrG6Ih6q4ANm0aRObjRQWFqKxsZEHSB/DgwuQwsLCwQsQQggLksvlcri4uBjtKN1xBwcHJCcnAwCuX79udIHSu7yYmBiWQTR27FhmiLkihODTTz8FANy5c8fs/mzatIllJ82ePZs9b5r2u3//frS3tyM7OxujR49macZ0pjF9+nTo9Xro9XqsXLnSYsxl8+bNaG1tRWVlJUaOHMmOnxDjNOdJkyahqakJKpWKgfZJZyCmEggEOHToENrb2/Hdd99BLBZDKBQy6PIA6V7mxog+dnV1RWBgoNUN8VAVDZpv2rQJGzZswObNm1FaWgqVSmUWHDxAnh4kjY2N0Ov1KC4uZq57brbpoAHI119/DQDIzMw0usOnC/JQA5eYmNgJINx2H7t27QIAKJVKjB8/HoSYBwhNqc3JyTG7PxQger0eU6ZMYXeRFGZU+/fvR0dHB7KzszFq1CiW908BMm3aNLS1tUGv12PVqlXs+01FAVJVVcUyuszpyJEjaGhowLlz5+Dr6wtC+g4gIpEIR48eBQD89NNPFuHBA8SyuhojoVCIJUuWWN0QD0XR+g8KkLVr1+L8+fNQq9VoaGjoFPfgAdI3AFGr1Whra8MPP/xgZG8HLUCys7ONLj56R0+fS0lJAQBcu3bN6D30cXR0NACgqqoK48ePZ+20TQFC03mzs7PN7g8FiE6nw5gxY4y+g7t/+/fvBwBkZWXBz8+PAY8GqGfOnAmdToe2tjasWrXK4vFHRkaitbUVtbW1GDNmjFnDNGXKFFRWVqK4uBiTJk1iYF2zZg3a29vR2NiId95554nG38HBAV5eXmyGR2NMXPGzkO5lOj6m4zR9+nSrG+OhKFOABAUFobCwEBqNhmVfmYMHD5CnA4hKpUJbW5uR+52b0DPoAJKTkwNHR0d24dna2kIkErG/r1y5AgBITExkho9r3A8dOsTqLSZMmNAJHhQgZ8+eRUdHB+7fv292f7gAGTdunFHMgPt9FCD37t3D+PHjYWdnZwSQt99+G4CheHH58uUWjz8iIgJarRa1tbV48cUXO71ua2uLs2fPorW1FUePHgUhhM1y1qxZAwBobm5+YoAQYqjD0ev1AICJEyd2aSCtfb4MVlkCCP2fB8iTA4QWD4aGhmLjxo1oamqCRqOBVqu1OPvgAfJ0AKmvr+908zsoAfL5558D+K2mgvsaFyAffvghizkQQjB+/Hgjt9Bnn30GACgvL8dLL73ECg5NARIXF4f29naUlZWZ3R+ZTGY0A6EDRwgxyqTasWMHAEPNydSpU+Hg4GAWIAAspgwT8tsMpLGx0SxAVq1ahfr6ehQUFBhVuNvZ2WHt2rUAAI1Gw4ove2LoTNvBnDp1CgCQkJDA3GOWDKS1z5fBKi5AuK5Veu4sWLDA6sZ4qIq2LAkNDcUnn3yCpqYmduPUlXiAPB1AdDod1qxZw85jel4PKoB89NFHzPVkGgOgmVSEEFy9ehVNTU346quvQIihoI4bmD5w4ACamppYVbZpphbdzsWLF9HR0YHU1FSz+xMYGIiOjg7odDrMnDkThPw28+ACJDY2FgCQlpYGb29viEQiCAQClkk1a9YstLS0AACCgoIsHv/27duh1+tRXl5uFiDffPMNdDodDh06BKFQaKTg4GAAhhYOixYtgr+/P9avX9+r8R87diwyMzMBAB999BF7nnusfByk9wChGXTU3Th16lSrG+KhKNr/as2aNdixYwcUCgV0Oh1aW1tZ/KMnWUX9BZCefvdA7U9f7XN9fT0aGhrYjSn1+Ay6Gcjhw4fR0dEBrVbLspCo7O3tIRQK4evri0ePHkGpVBpVdItEInh5eYEQgg0bNqC8vBx6vZ4V7VFq0uI8Qgjri3Xx4kWz+zNjxgyUlJRAp9MhKioKIpGIxWNoVbuDgwPLHvv888/ZvggEAhZsnzt3Lu7fvw8A2L59u8XjP3r0KDo6OiCXy80C5K9//SubgTx+/NhItOlkTU0N7ty5g5KSEiQlJXVp5Lh/SyQS7Ny5E7W1tdBqtcx9JRAI4ODgYLY4jgdIzwFib28PsVgMiUSCkSNHIiys6/UueHUWdVutXbsWX375JZqbm40yr6xtoJ8lgDQ3N6OxsRENDQ1oaGhAdna2kUt7ULqwwsLC0NRkWMB95syZnQyUWCzGjBkz0NLSgoqKCjalIsRgtGk78xkzZuCXX35Ba2srNm3axD5LZw8CgQAeHh7Iz8+HTqfD/v37ze7PpEmTkJubC51Oh+PHjzOXlFAoZG4xd3d3FnQ+fvw4e93e3p6l3r766qv46aefAADnzp0zMjT0hyCE4MyZMyw5wNIMpLa2FlVVVaioqEBVVRWqq6uhVCpRU1MDpVKJsrIy3L9/H3l5eaxA0rRNiTm3ipubGz777DM0NjYiOzsbUqkUjo6ORk0d+UyspwOIg4MDhEIhnJycOhlGaxvnwS4a+1i/fj0iIiKQlZXFjFxPDGJ/Q2GoAsTScdLZnFqthlarxRdffMHsBLUbgw4g48ePZ900T58+DYlEYpTO6+DggH379gEw1G7QGg8XFxej9iKEGGYXGo0G33zzDXsP14AGBQUBMBQtLliwoNO+SKVS2NraIi4uDq2trcjLy4Ofnx+7IxcIBJBIJAgICEB5eTmampoQHBwMQgiLS3D35+jRo2hpacHjx4+Ze87R0RESiQQSiQSvvfYa6urqAACRkZGd2tvTH85UtCZl7dq1rEJ/zpw5bNZgzrhxmzHSxAAPDw88ePAAANjMjuvD56Hx5DCh481tQ0OL4NatW8cDpBcQ2bx5MzIyMpjRo3fIDQ0NVjfCQ1mmANFqtVCpVGhsbIRarWYdKai9GZQuLEIIdu/ezQLgCxcuNHpt3LhxKC8vZ0ZOIpHAxsYG7u7ukEgkRgZbJpNBo9GguLjYKBZAW3JQX/+NGzfMFvbRC33KlCmorq4GANYpl/v+y5cvAzDUpHAXhuIaaUIMfu9Hjx5Bp9Ph9OnT7HkaJ/niiy8AGHp8+fn5ddof04WdTPczICCALeVJix5tbW3Z9k2NGr0rpp+nQfjKykq8+eabFo0hD5DeiTsT4T7/3nvvITw8HIGBgXxleg8kk8mwfv16HD9+HMXFxaitrR0Ud+7PiswBpL6+ngHk8uXLIIQwGzvoZiDU2Do7O+PWrVvMl79s2TKMGjUKs2bNwtWrV9HY2IjS0lKjOgknJyd210+No5OTE0v3zc3NRUBAAKZNm4aJEyeyVigqlQrTpk3rdt+OHz/OoBYdHY3Zs2dj5MiROHPmDJtGczv+UsNBjQY1unv37oVKpUJzczPOnTsHb29vjBs3DseOHQMANDY2Ijw8vMdjRtObhUIhFi5ciJaWFjQ2NnZaD8Q0G4j+7ejoyPaRutgqKiosVsrzAHlymY7bm2++idWrVyM4OBjBwcFGrcmtbawHm8LDwxEZGYl169YhISEBarWaGTde/QcQlUqFpiZDHQgNBVAby3VrWx0g1D9Mp0czZ87EnTt3AADV1dUoKyvD48eP0dHRgby8PJaPzK0I57oJKIxeffVVKBQKtLa2oqWlBVVVVWwhKLVazdb24Lq3uIaSbsvZ2RkXL16ETqdDZWUlysvL2eJNlZWV2LVrFwgh8PT0NLtuBt2WUCjEqVOnoFKpUFtbi/z8fKjVatTV1aG+vp41bDRtTW9Jq1atQl1dHUpKStDQ0ADAUGuSk5ODX375BVu2bLFozEzviuniUseOHQMhhLnpzB2LtY3xs6AJEybgvffew6ZNmzqtrGdtgz2YxO28Gx4ezpJjGhoaBk384FmQOYA0NDSgubkZ9fX1kEqlzFabpvBaHSCEECP3EyEGd9X+/fuRn5/PdOTIkU7ZWaaGkXunTYghdvDee+8hPj4e2dnZyM7ORnx8PFasWAEPDw+L26GiUHJ2dsb69euRmprKlrQ9f/48pk6dCk9PT5aqyZ0JcQeZC6RJkyYhJSUFDx8+xMOHD5GSkoLVq1ezMTB1d1jS0qVLUVNTg4qKCqjVavzyyy/Iz89HYWEh7ty5gy1bthhV8Js7Rvq3TqfDzZs3MXXqVBBimElxZ1M8QPpWY8eOxYoVK4zgQf+3ttEebJLJZFi9ejX279+PmpoaNlsfTEHooS5LQXSa5TZ9+nRmpwclQMzddTs7O8PJyQlubm6dAGNJ9MDorIb7Gi3wowPR032jRlMgEEAkErHgPnedDir6nClAzLUAsbQ/PTHS1P1kZ2cHPz8/SKXSTmuicLfX3TZpBhvdD269DA+QvpeXlxdWr16NkJAQo9kHDxBj0TTn8PBwXLt2jRXL1tTUmM3C4vX0AKHp0U1NTdDr9Xj48CHGjRvH7Js520asfUGZGiZaa0GNmlAo7JXRp4ac+7e5x08iari5+2yalWCpboI7s3naMetqPExnYt2J68YzTQLgAdL3srOzg7+/PzZs2IBNmzbxAOlCISEhOHDgAHJzc9He3s7SS/mZR/8AhM48amtrAQAXLlxg56ylm2Ni7QuKK5pZ1RtgdHWh0sI++lxfbNdcu4reAIRriLmf7e0+PMlrlkRTobnBf754sP/0zjvvICgoiC8m7EKhoaFYv349Ll26hKqqKouGz9oGeKjLXCEhjRfTtH5T74qJ3bL+BWWq3hrUrsS94++Lu/++Vl8Y5qfdBjfttyfg4/V0v5VQKERgYCC2bNlidUM92LRnzx6sWbMGoaGh+PDDD1FXVwe1Ws1kbYP7rMlcYaNWq2UNa2lYwNTT8twAhLutvtzusyweIP0roVCIFStWYP369VY32INJNG2X6vr161Aqlaivr+cBMkAAaWpqQkdHBxQKBQgxznIdMgDpS2Nl6jKy9rENBfEA6X8tWLAAMpnM6kZ7sCg8PBwymQwREREICwvDqVOnUFVVhfLyctTX10OlUvEAGSCAaLVafPDBByCkc4x3SACEl3XFA6T/xpU+fuWVV5ix5GUACHXprV69Grm5uWhuboZSqWTw4AHS/wBRq9XQaDSsQJoHCK8eq6fBf15PNraE/NZFwMnJCWvWrLG64ba2aLFgZGQkS2umRbc6nY7veTXAAGltbUVCQgJrz8QDhFePxQOk/0UBYmtrixkzZljdgFtbtOJcJpNhy5YtiIiIwOXLl6HX61FdXW11A/usyzSbra2tja16apphygOEV5fiAdL/srW1Za17fH19rW7Ara2IiAg2A5HJZNi8eTPy8/Oh1+uhVCr5VN0BAghVe3s75s6dC0J+63/FA4RXj8QDpP/FTZl2cXFhhvR5DahTgGzevBlhYWE4cuQICgoKUF1djba2Nh4gAwiR5uZm1NTUsO4WPEB49YlMIcID5enGktsRYebMmdi5cye2b99udWNuLYDs3bsX27Ztw7p165CZmQmlUtmj1QZ5uPQdQOiaQjT7ipDfulrwAOHFa5DIFLxjxozBhg0bsHPnTqsb84EUDZ5HRERgy5YtiI6OxpEjR3oFDx4gTwYL0+caGhpQV1eHuro6LFq0iJ2bPEB48Rrksre3x6JFi55LgISFGfpdRURE4MCBA0hMTGRr23ANHg+Q/gEIt4EiAFy+fJktv0yI+aUpeIDw4jXINGXKFGzYsMHqRn2gFRoaio0bNyIoKAgffvghysrKjJar5QHSP6Jj29zcjI6ODrS2tqK6uhrLli0DIb8tX8sDhBevISBvb2/IZLLnsiPvxo0bERUVhXv37qGsrAxqtRoqlYoHyACotbUVAKDValFWVgZPT0+2dAU9N7uDCLH2xcOL1/MuZ2dnLFiwAMHBwVY36AOtkJAQnD9/nq322dDQALVazQNkAASAzfY+++wzEELYekf03OQBwovXIBW9KO3t7eHj44ONGzda3aAPtCIiIpCVlQWVSoX6+vpOlec8QPpP7e3taG9vR0lJCV577TUQYgAIt+ksDxBevAahuBeknZ0d3NzcsHz5coSFhT0X64TQY4yNjUVtba2R24orHiD9JxpEP3v2LAgxrGYqEAg6NaDlAcKL1yAU96J0dHSEl5cX1q1b99zEQoKCgpCQkMAA0lt48ADpnWhqNP1br9ejqakJvr6+sLW1hUAgMFq61px4gPDiNYhEL0x68fr7+z/Triza9yo4OBjR0dG4f/8+6urqeID0s7gpu7TORqPR4NNPP2VV56bLdfMA4cVriIhekGPGjMG6deusbuj7EyDbtm1DUFAQPvvsMzb7MNeqnQfI04vrFuS2LAGAiooKTJ061eI5yQOEF68hJBsbG4hEIjYLoZ1qn6WYSEREBHbs2IHAwEAkJCSw1QZ7GjTnAdJ7gHCTEhoaGlBRUQEAOHDgAAgxBM4tnY88QHjxGmLy8vIyumO3ttHv6xlIYGAgLl26hNbWVpZ59STw4AFiWbSehooLktbWVmi1WkyYMIG1KzF3HvIA4cVriMnGxgbOzs7w9/d/JqvTQ0JCsGvXLsjlcrS2tjJw8ADpW9F6murqalRWVrLnqPvq4sWLnWIe5s5FHiC8eA0h2drawtnZGWPGjMHKlSutbvD7Whs2bMClS5fQ1NQEnU6H5ubmHjdO5AHSe4g0NhrqPVpaWqBSqdDe3o779+/DyckJDg4OsLOzMzr/ulvSgQcIL16DWDSlVyAQPJMrFkZFRUEul7MeTDxA+ld5eXloajKsdV5ZWYm2tjYEBweDEMKaJnLVHUDMrBFk/YuGFy9eBtHCQvp3eHg49uzZg9WrV2P79u1DukZkzZo1yMnJQW1tba9atvMAeXKVlpayeIher0dmZiY7t3oy+zA9P3mA8OI1iGV60b755pvYtm0bZDIZQkJChiRAwsPDERoais2bN+Pu3buoq6vrE3jwAOle1dXV0Gq1AAClUomFCxeyanMnJyej827IAITeZXF7rgxW2djYMFLTfe7Nc9bef15DX4sWLcL27dsRFBRkdRg8iTZv3oyQkBBcuHABDQ0N0Gg0qK2tRV1dndk6BR4gfaOmpia21odOp8O5c+fYOUVtFNVT2KqBvyBMp068ePGyLKlUinXr1iEyMnJIpvVS19sPP/wAAGhuboZSqURNTY1RASEPkL6FR3NzM2vZfuPGDdauvY9vbAf+ghAIBNi+fTvCw8Oxffv2p9K+ffsQHR0NmUyGkydP4tixY0+ls2fPYv/+/SguLkZ4eDjeeustxMfHgxCCs2fPYtWqVZgyZQq+//57EEJw6dIlBAYG4u2338bly5dBCMGnn36KgIAAzJkzBwUFBSCE4ObNmygoKEBBQQEePnyIoqIiFBcXo6SkBKWlpSgrK0N5eXmXqqioQFlZGYqLizF58mSrGzZe/S/aWtvT0xOrV68eUgAJDQ1l1/iBAweg0WjYioNqtZqJrwPpH4A0NTVBq9VCq9Vi1qxZIMRQNDjkASISiXDx4kWcOHECJ0+efCodPXoUu3fvRnh4OE6cOIGjR48+lc6cOYN9+/ahqKgIYWFhWLlyJdra2kAIQX5+PmJiYjBv3jxoNBoQQlBZWYkDBw5gzZo1aG1tBSEEJSUl2L17N9atWwcAsLe3x44dOxAZGYmIiAhERkZiy5Yt2Lp1K6KiorBjxw5ER0dj586d3So8PByxsbGYNGkSJBKJ1Q0cr/4Vd22GcePGdYqBhIaGMlkbGFzRCnq67rlcLoder2f1Cabw4AHSPxB59OgRFi5c2J9en4G/KMRiMU6fPo3Y2Fh88MEHT6W4uLg+BcipU6ewd+9ePHr0CJGRkVi0aBHq6+tBCEFmZia2bduGt99+GxUVFSCE4MGDB9i1axdWrFiBqqoqEEJw79497NixAytXrkRxcTEIIVi6dCnmz5+PefPmYf78+fD398fChQuxePFiLF26FMuWLcPy5cu71dy5c7Fx40Yjw8Lr2RY3vrZs2TKjZouDFSAUIqGhoTh+/DgePXrEGiZyq6KtAZBnGTymx7Zr1y4QYggbCASC/jg/B/6CkEgkOH36NA4dOtRnAImIiOgTF9bJkyexd+9ePH78GFu2bMHixYvR3NwMQgju3LmDqKgozJ49G9XV1SCEoKCgADExMVi5ciVqamqMnluxYgVqamrg5uaG3Nxc1NTUoKamBrW1taivr2ctBqi/knbKtCSNRoPm5maoVCq8++67QyIJgdfTy97ent0wTJw4EQEBAUbgGKwACQsLQ0BAAG7cuIHa2lqLRp/PwuobcJgbE19fX3h4eMDZ2bm/zs+BvyAkEglOnDiB999/f9AB5Pjx49izZw9KS0sRFRWFRYsWMXdVbm5ujwBSWFiIPXv2YOnSpQAAQggmT55stNYwL149FRcgtra2mDRpEru7H2wA4cZowsPDER8fb9RCnJu+ywOk7yHS3NwMrVaLgoICeHp6DsT5OfAXhFgsxocffojdu3cPOoAcO3YMu3fvRllZGXbs2AF/f3/odDoQQnD37t0eAeTRo0fYt28fFi9ejLq6Ojg5OeHmzZvIy8tDYWEhCgsLUVRUxALpVI8ePepWTU1NuHHjBubOncsD6TkRp20EbGxs4ODgAB8fH2zatAnBwcGDDiA0eB4WFoacnBzodLpOALF098wD5OnU0tKCsrIyjBkzZqDOz4G/IMRiMY4fP46YmJhBB5C4uDjExMSgvLwc0dHRWLBgAfR6PQghyMvL6xFASkpKsH//fixcuBAtLS0ghGDv3r3YtWsXoqOjsWPHDpZFtmPHDqbo6OhudfDgQaxfvx7e3t5WN2y8BkamRV00HjJt2jSjhouDofV7REQEQkNDERERgbCwMDx8+NAsQLgA4AHSd9JoNNi/fz8IsdymvY818BfEYAZIbGwsYmJiUFFRgV27dmHBggVob28HIYbgeE8AUlpaioMHD2LBggUMIP7+/nj99dexdOlSFjQ3DZyvWLGiW61evRr+/v6YPHkyfH19rW7ceFlH1KcdEBDAKtRpxpM1ISKTybB7924EBAQgOjoaFRUVaG1ttQiQp5W1DfZg07Fjx0DIgMEDxBon//MEEK1WC0IMAfiCggI8evQIjx8/ZnUfFRUVTJWVlRZVV1eH+vp60H8bNmyAn5+f1Q0ZL+tr4cKFDB4UIAMNkZCQEOZS27RpE0JCQlBUVAS1Wm0EkL6EBw+QRty9exdNTU3QaDQ4fPgwxo4dO9AdMAb+hO8LgHBdTgMNkDlz5kCpVIIQgocPHzKA1NbWghDzAOlLubm5Wd1o8bKuuP2I3N3dGURordFAAoRb77F161a89957OH78OFt/QqPRsAAvD5C+Ex3TlpYWnDhxAoSY77Dbzxr4k/9pANI5ZvFn7N79H4iIiMTJk6dw7M/He6UP/nzMSEePxGLPrhhUlVcgZucuLJy/AB3/bQDI/Xv3sG3LVsx95x3UKg2zjeLCIsTs3IWVf1iB/6oz1IuUlZbi0MGDWDh/AVo1BoD88iAf93Lv4h938/DLg3z8718KUPjwIYr+dyFTcWEREw2yUz1+/Bjl5eWorKyEUqnEvHnz+JYwz6m48KA9jShEgoODGUQGEiAUIvT/nJwcIwPX1+DgAWKo5tdoNAwejo6OvTqH+uh8HPgL4EkBwgUHLfyzBkDm/ctc1NcYZhuPioqx89+j8Ydly6BWqcAkbDMAACAASURBVEBIZ4A42AsQ+K9rEBq8CZHhEfiTLBJb/7QF27Zsxfat25h2bItiiooyr5iYGJw9exZvvvnmQKXp8RpkMgcQejMxe/ZsBAcHGxn0gQJIWJihc8OBAwdYjVN/ua6eZ4A0NTWxpWkPHz4MQgzw6E1izXMHEHMzj7i4PyMu9gPsjtmLiPA/4eTJ0zj25xM90gdHDTp67DiOHjveYxdWZGQk5s+fD9WvsCgvLsGubduxYtES/FPVAEIIKktKEbv/IBa/uwDt/2yFDSF4adwELJo3HwHLVmDV0uVYtXQ53luyDH9YshTLFy3Bst8vNtKSRcZavNiguXPnYunSpRg3bhxcXV0hFoutbtB4DaxMAcLtrEoIwZw5cxASEoJt27YhMjJywGYi69atw/vvvw+tVgudTge1Wt2v8HheAaJWq1FTU4MzZ86AENKrdH7uyoJdtW3vhQb+AngagHBnHtYASEREBBYsWAC1Wg1CDLDYuTUKf1i4GNqGJvYcBUiHVgehjR2K8wsAAO3/bMV/t2jx3y1a6Js1aG1ugaapGf9saDJSS6OxWltb2RKgAPDuu+9a3ZDxso5MlxjlAoQak9mzZyMkJGRAZiERERGQyWQIDw/HF1980cltxc9A+latra0MHvb29gwAPQHBMwsQasC7Agf9PzY2FkcOx+HI4TgOQLbi5ImzOHb0Lz0SBYipK6s7F1ZkeAR+7++PRrVhtvF/SsoQHbkVy+cvhL7B0PKk+lEpju47iKXzFgBaPWx+PW5neyHsCIHtr7J5ynHsx/YEvAa5LK1T7eDgwAKpc+bMQXBwsFE8pC8ztGjxokwmw+bNmxEVFYWHDx+yoDm3Ap0HSN+IO/PojeE3d648EwChMwque8qcy4q+LzY21qoACf+3MCxauBDNDY0ghEBZWo4dEZuxdN4CtDUaaj64AOn4ZyuExAaf/ecnyEnNwMPceyi4k4eCO3nIz7mL+7dzcS/7Du5m5xjpzm1j5eQYlJubi8rKSixcuNBoRTFez5fMAYQ2zBMIBGwmMm/ePGzdupVBoz9SfENDQxESEoLDhw+zxYuoj74/3VfPI0DOnDmDcePGPfH58swChAsRcwHzuLg4Bo+hBBDaC2vrv0Xg32VbEB25FdGRW7HzT9uw80/bEP2rdmwxFje4vn3rNmzbto0tbXrw4EG89NJLcHFxsboh42VdmbqyTEUIwVtvvYXt27dj69atfZ7mu3jxYmzZsgVr165FUlISmpqaeID0geg65nV1dVAqlWhtbcVHH32EF198EWKxuMcAMF2C9pkDSFxcHJPpTMQiPI4cGToA0bXDnhBMnfQyVv5+CZbPX2ikpQsMWuJvrEULjbXwV82bNw+BgYF8J15eIMR8PMQUIEKhEDNnzsSmTZuM4hV9BZG1a9ciPDwclZWVrPajsbGxX9N3rQWQgf7+9vZ2FBQUIC4ujv2e5iDQk/PjmQGIUCjEiRMnsHv3bgYFLkBMYx+mADHoKGJjj+L44T9j387/wNbQrfj4g7P4y5G/9FAn8JcjJ3Ai7jhOxB3HX2KP4S+xx3D8UCz2Rcegovwx3t+zE7+fvxztOsMs4h+5D7E5fDsW+69E438ZOvT+n+I67Izcg2XzlkP/X4aFp6qKqnFs/3Esm7cc+CdgRxxQX1kM4L+hb2g2UmujQZomY/3TRLQNBADU19fjX2bNBSEEdsQWduTJYcJOIuJgkMkJ150sbdeOELxAbEBsiXnZ2Rhkb/ubXrDpQraG99PP2/yqQWDErSlLxoHKxcWFdfL19vbGihUrWHDdtFbkSZoy7tq1Cxs2bMD58+fZEgV03Y/+zsB61gDC3R5dK0WlUuHtt9/u9Fv39Dp8JgEiEAjw0UcfseaABw8exKFDh3D48GEcOXLkt1nGkSM4fPgwDh06xN73mw7h4MFDTw2Qk0c/xMmjH+JU3Ac4FfcBThyOw77oGNTWVGHf3t14953FzA318EEJtsj+Hf7zlkKv6QAhBP9V3oTdf3ofv5+9GNAa3lf//6tw/OAJLHx7Efusu+MLGC62hR15+uC5u7u78UnSJ7+LHQh5oc9+YztC4GBr1z1ABHYgDvaG/7lg4ALC3hY2AnseIGbUHUAIIcxYEEIwadIkLF++nFWtc7vn9gYgtM4kMjISUVFRyMnJgV6vh0qlglKpZMaQB0jPxF0XiP6tVqsxatQoEELg4eFh9FtayqbqTkMaIHRHbW1tcfbsWXz77bf44YcfmOLj482K+x6q6/+fQZk3k3D1b9/h0//1Ej78iy9OfU4M+vRXXTCv058adOwTgz48544T/ynFiU/s8cF/2iI46SxWXo7F8K//htkFv4Cc+gv+Z85teH7/dwz/+m+Y+eA+yKm/4O27ORj5/Tfw+vpv+JcH92Fz8gRm5t7BuGtXMeKbr+F96w6kCQoIbm4CuboOJM0GRG4Hu2Rf2CX7QpLqDMd0F7ikusA1zZVJmuwDabIPPG6NNijZD56poyDI8gNJ88LO1X/ClmWhSBAmIN4mvte6Rn7AVXIFcptUpJIk/OzwB/xktxjXnAiuORHEix271DXRMFwTDcO3hOBbQnDZjuDKCwRysQNSBC9gvtOf8CpZDRf7BLNydvgRToJrENl9Dnvyv/AC+SscbD+F6IWLENpdwDAbg5zINxCRL2FDVoCQJSC2LxhkRwx6zkFiyUBwH5tCRCwWswytNWvWICwsjDVk7O36IpGRkUhNTUVjo6ETLDV8g9H49wdA+gKAdFVGCo/S0lJER0dj4sSJZo39cw8QQgjOnz+PpKQkpKWlPZEUKQbdTkpD6o8JSElfgsSkhUi5J0TqP8RIy50Ied5kyO+NM6vMPIOuZRj0Y8pb+DHlLfyQ9D/wtx9H4r0rcfjXqx/gpbRU/O76j3glU47fXf8Rv7v+I/7HrZ/hd+0q/h95Ol68dgXjfvwBL//8M8bG/4DX01Lx4tV4jL/+I15JTupXgEQHbEaOdw5yvHNwx+cOcn1ze6y8EXdw1ysHD1z+gXuOubjjtREKt3WQ+7lA7ueCdO+RXUru7WOQjxRyHykyvd2Q6e2G+15SyMUOmOe4Gf+TrIWbQ6JZuYp+govwOpyEX8NJ+DUkws/hMuwruEr+BlfJ3+A+zCAP4Q9wtvkWduQ9HiDdAMTcDMSSn1wsFmPKlCmIioqCTCZDUFDQE7mwTp06hcrKSlajxL2b5gHSvairqqmpCXq9HnV1dUY1Xl0Z+966m58pgPz1r3/FjRs3kJSUhKSkJCQnJ3cp+j6q9ESDcm8mIevHBPySF4SstOWQ5wggzxEgI3OcQYpRZiXP9DPo/kjI749ERs4bUOROQ3ruWCRm+GBt5hksuRELjzQFvOS34XorHdLUTLj8nAq3JDk80hRwvZUO72Q5PBJT4flzGnxSMuCRmArvZDk8f06Dd7Ic3okFkP70Dwh+XgNyfSVIui1I5gt4IcUPgrRRcEx3gZPcAA239OFMHim+8EjxhWfSGINSR8ErbTQcskfBJt0bh5fvwcHFu5A94jayPLN7JYWHArelCmS7Z+KeYy5yhFlQDA9GmiQIySOGIXnEMCRJvbpUitQDye5SpHkNN8jTGWmezgwgi0WbMYf8ERLHWxY1TJIIsfgmhKIEOAhvQChKYHJ0MMj5hXSISRJsyB9ByGpDLOQF299AwgOkxwCh15+NjQ0EAgGEQiGkUikCAgKwY8cOs/CgwXYacKfxE/r61atXje6gexP3sDYMBgomXKjSxAK6DTpj6+joQEpKCmxtbeHm5gaBQGARFtznntsZyKeffoqEhIRuwWFOKSkpyExORWZyKnJvJuFOwi3kyFdAfmsR0m7bI+22PVLTxxgk9+1SidnuSMx2x82Uyfjp1gRcSXDGlQRnBGZ8hCU3YiFNzYQ0NRPuKRlmNSIp3ay8k+XwSckwAojNT6tA5HawUQggSBsFh/TfwTljOJwzDNBwz3Bj8kwdCc/UkRiRPBYjksfCK200RqSPgfD2aNjKfXDkD/+B/3dJTK/hQZXjkY3bUgV+cb2PPMkdI4CkeEuQ7DmiS6V6eBrk6YpUT1cjgGQME2Kp+E/4F7IRjk5JZsWFCAUJV07Cm3AWJcLFXo5hJAV2NsE8QJ4CIOb+ptejVCrF9OnTsWzZsk4AMQ22BwcHQyaTsb8TEhKgVCqf6O7c2gCwBkC0Wq3Ra83NzcjMzMSyZcvYb0oLQQUCQbc3BDxAnhAgGUkGUYDIby1CauJCpCjsDEod3WuAJKa+jBtpnrieLMUa+Sksun7IIjgYQG6Zl3dSJnxSFJ0AYqMQ4IVsIUQZYyDKGAPnjOFwzXQzgkdvAMKdgWSPuN1j5Xoa9NA9H3mSO8h03dgvAOlqBiJxvNUJHDxA+h4gPZmZCIVCeHl5ISgoCCEhISwri6b9ymQyREREYOPGjYiMjERgYCBu3LiBgoICNDQ0oLy8vNfG1doA6K2ou4l7fJZmXPQ9dIZBM9S0Wi0aGxuh0+mg0WiQmpqKGTNmGP2mtDWNg4MDD5D+AEhycjLkPxt0J+EWsq8nPjVAElNfRnLGq0hSjMRN+QgEZnzUpwBxuBWIFxJWwzbLAYIcMcSZYzFMMQ7DFVIMV0ghzTSWV9ooeKWNgnfKOHinjMOI9DHwlo+FKGcMXsjwReyKvTi0bPcTubDoDCTHIxuFHgUMICnD1iLJS2xwY/URQOgMw5J4gFgPIFwjYmdnx1ax8/Pzw7Jly7B9+3aEhoZCJpOxbCvaGoUGz+vr65lxpC6snrqxuHfmg23WwIWAqcuJ67LrDo4AWEFgR0cHWlpaAABXrlzBO++8Aw8PD1a/Q4FBa3js7e37HCCW9NwBJO2WQdk3UqD48RbSbi1B8s3fI01hY1DqSKSn+yE93Rvp6d5IlZsXA0jGBPycORGpGR5ISh2OtZkfYdH1gxieouhSXsmZZjUiRYERKQp4/VwAt5/+AWFyIOwTAyDMdoA4RwTnzLFwUbwItywPuGV5QGoirwxfeGX4YkT67wzKHAVvxe8gujsSdlmeiH1vHw5yAKLwUPRKt6UG5Xvcwx2nLKS7hOCWKAgpnmKkjhiGNKnnrxqBNOkIpHoYi73+KzjkUjfIpW544OkGxTABlgmjMI+EQiz+uRuZB4ij6CacxIlwEsghtkmBrS0PkL4GiLnPEGJYDtXd3R2TJ082clmFhIQgODgY69evx/Hjx5Gfn4+6ujq0tLRAr9f3etlacwDp79nJk7jYTAFCYxlarZYdq+m2uXUcdXV1bPZRXl6O/fv3w9nZ2SgmxbWN9LfgNsnkAcIDBIKfV0OY7YBhucP6BCAHlhpcWL2FBxcgD6R5yHXORprTJh4gQ1BPYzQsgUUgELDtS6VSvPHGG1izZg1kMhlCQ0MRGBiICxcuoKSkBCqVChqNxmjZWo1G0yOIcA1yf7u3LO2Duf2kEDT3WbrGCS3o5b7HHDxVKhXa29tRWFiIL7/8El5eXmx8LS0A1RuAPM35wv37uQFISpJBGYnpyEhMR9YNBTKvZyD91kYk3QhCulyEdLkIGalSZKZ5QC53h1zujtQMY6VkGkT/TsscgbTMEchM9UVKogcCUs7i91ePYnhKdie5pd6GW2oW3FKz4JEiZ/JKy2DySMuApzwTI1LyIb31D0jSI2H/cwgcsx3hfNsZbgpfuCl84ZrtAddsDwYSKo+s4fDIGg6vTHd4ZbrDM2sEvLK94XDfGbZ3RDi8ah8OLN+N254KZHtkQCGVm1Wmh3lluxv0D+lt5LpkINUpDAmCPyJVOhKp0pHIlLohU+oGhZsnFG6eyHQ3ldQgj2EGuY1EpttI/OLpjKxhtviDYBfeJZEQipO6lHjYjV913UjDxNchGfYTHIUpENr9DGK3HoSsBHmBGMQXFHZrIJ4EJtwWKFyD5uHhgTfffBOBgYG4dOkSGhsNbcWp8Wxra0NbWxtaW1t7lYnV05Tf/oijUKNvCTLmnm9sNLRoMefO0mg00Gq1qK6uBv1XXFyMI0eOwNfX1+g3Mm03w32tK2D04cyhrzSwJzd9/KwAhAsNrjzlmZ0AIkwKg2O2I1xyXAYNQPKGK5DrkoEUx3/jAfIMqC8AYukul7q41q9fj/v370OlUqGlpQVVVVUoLy9HdXU16urqGBSoce2p66i3acBPCpCebNOSW4rCgm6LFk1qtVro9XoG0gcPHuDEiRNwdXVlXZK9vb1ZnMMSCPrZ3dQfGtiTmz5+WoDIEzMgT8yAIiEbGT8pkJq0CbcS/ogMuQMy5A5QpLoiK204MtMNSs00Vsqvon+nKVyRpnBFVpo70n52QkDKafz+WixcU3I6aXjqHQxPu43habfh8etswyM93VgZcnhkyOGZmg/3W/+AJD0CwuRQOGcPg2uOE6QKb0gV3gwgppJmO0Ga7QSPrF+VLYXnbQ8I8h1A7trgUMA+7PvDbmR7pEMhTUOme6pZZUjNK8vNoDtuKcgZnowkp3BcF2xAmtQHaVIfKNxdoXB3RfZwT2QP94TCTWosdzeDPEQGuY6GwnU0Hno64raYYIX9biwgf4JgWIpZdQcQ8bDrGCb5CcOEKRC88DPICyYAMdfyhNcTXZPd+dS573V1dQUhhpYoFy9eRGlpKZRKJbvjpoHipqYmqNXqTllLptLr9Uazgd6uH9LX7qzm5majuAwXauYq7BsaGgAAbW1tKCsrwy+//ILjx4/jxRdf7NT8kBDSL24pK2tgT1b6+FkEiKdczmQKECd5JEQp/waX246DCiA5w5N5gDzH6u0MRSQSsbtqQggkEgnWrVuHrKwstLS04O7du6itrWVA6eoO3xQWNDDdG4j0VVyEuqBaWlqgVqvZ7IK7L/X19Uaga2lpYZ/56quvEBQUZDSu9vb2LJuKynQJ4iEMDqqBPVnpYx4gPEB4gFhfvc3SMfXZU0mlUvj4+CAiIgJffPEFSkpK2N25TqdDXV0damtrUVdXx2Dx+PFjKJVKqNVq6PV6VhvRmzbwTwoPU/daS0sLSwSorKyEVqtFbW0t9Ho9Ojo6oNFoUFlZyY7n2rVruHTpEpYsWYLx48cbjSeFAxcc9vb2EAgEDCKmALH2efAUGtiTlT7mAcIDhAeI9dVbgFBXDJW5VTElEgmmTZuGOXPmoKKiAlVVVdDpdGxWotPpUFpaCp1Oh/b2djQ3N0OlUrG1RPo7BkKD39yYhlarhU6ng1arRU1NDSoqKlBfX8/2uaSkBEqlEqmpqVi5ciWmTp3aaRy5SQjmxJ2BPEMQGdiTlT7u6yC6/OeNSLkRhAy5CBlyERQpnshK9UJmmgcy0zyQLnc3Eg2ep2cMR3rGbwDJTnVHeqITVqWehv+1WIgzMjppWGYmHDMMcslM/1WpcM1KYxquMIgCxFkeiWHJ/wbn245wueME1yxvg267m5XbbRe43XZhwXSPLB94ZvtCkG8PkkcYQG67ZyPbLQtZwxVGynY1KGt4hkFu6UbKcTUozzUNea5pkDuG4Wf7P0LuPgJy9xHIdnNFtpsrbrt6GjRcaiI33B7uhmz3YQa5jEa2y2gUSR2RK/oNIEKR3LzEaRCK0yCS3PpViUaSDEuEo+QWXAQpGGbzM+zs/hWELOGbKPbDNfmkgVvua3Z2dhCJRHB0dGStOOhsxcvLC5GRkfjqq69w9+5dAIBGo2GxAwoQ6jrqTeC9LwHS3t4OANDr9aiurkZZWRny8vJw7tw5REVFQSqVWhwH0yy2nogHyBOcrPQxD5DBAZB/DDcowykctwQbrAIQsWMyxI63jOQoucUDZICuyafJ/DGdkdDn7Ozs4ObmBhcXF6P32NraYs6cOZgzZw7Onz+PmpoaqNVq6HQ66PV6tLa29riO5GlcWDRzikIDAB4/foy0tDTEx8dj3rx5eOmllyAWi9m+c9133NhQb6DBA+QpT1b6+EkBQgsIFTfTobiZjts3FMj6MQOZPwch9cYapMsdDUoZBXnq7yBP94I83Qvpcg8jpWb8qhyC1BzSCSDLMz7E3BsHIMrI6CSxIhPCLDmEWXI4ZWYYpEjH/23v3n7jKM8wgL87s3Pandmd3Zm1E9txYof4HGIOSqRcAEH0jkORckWVqg2g0tKqLSq9qEQvqIyoRPMHEAUEyQXKHRWQkBAnjp0QJySqiJBorxF/ADcEIfH0YnbWu+vjHuxvZ/1c/JR11vvNO7Mz3+NvZvbb/I2FiuL1SO/1CwivnkP+2nFk547BuxkgdyuEv9gHf7EPwWKIYDFE6UatODji53Nf7EL+9iDkvwbkruBfz/4D/3zy7/giuIVbxZtYLCzWKl4rm8dicR63inM17viRL4uRz73f4rKxFCBLp7BKWFwWHksBcjPI1/jfTsFtV/Bz/QR+Jq8jYy+sKJuZh5u9Bte9HPEu1Uy2mM9egu/OIrTn4KUuwEyVA6T+Nl71B1CiNXq772qvq283noaj+sNyK4WNiKBQKOCpp57Ciy++iFdeeQVvv/027ty5U5mkMf7AXnyR/fvvv8e9e/dW9cMPP+DHH3/ETz/9VBnlfPfdd/j222/xzTff4N69e7h79y5OnTqFN954A6+99hqee+65ZXXF4VB9HWOlbVd9NxUDZJN31vgxA6QzAuRuMI+7wTxu5H6HK+ZxZQHi5WaRy1+u8N1ZBsgWHZPNBEj96+v/v3qqFJFoZlnXdeE4DhzHgW3bsG0blmUte72u6+jv78fQ0BB830dPTw+OHj2KEydO4MyZMzh9+vSazpw5g/fffx9nz57FuXPnMDMzgyeeeAIjIyMYGRnBwYMH0dvbCxGpzP8VP7YsC6ZpQtf1Fe9Cq1/v+ttxGSCbvLPGj0+ePIlz5841fwrrwjVcu3ANN87dxvWPb2H+wp8w+8nvcWmhPzI/hNmFYcxe7yvbUePS55HKBwoXRjG3MIqFuftw+eIgfjn3Op796NVVAySWu/55Rf7zGxXhtcjOqzdQunwNhSt/g3vxVWRu7EN2caQSIP0LkT1Xd9Sa64tcGcSeK4MYvLoPu+dHkLuTh73o4K+//gP+fOwl3A7n8UVwdVlA3C5crnGncKnGf/zI18EVfB1cwWL+j5izfoO5Uj/mSv1Y6PGw0OPheimP66U8FsM6QRAp9GOx0I+b+SHczA/hqx0ebmQFv5K/4Ki8BN+8Ale7tEwu/Rl8cxaeeTHinEcu82lFYJ5HYJ5HSbuInHwEW36BlBxdOnXFAGnbMdmOAKl/br1OcaVOubqPqO6gt2pbxMGx1qe/V1rvZiax5G28Te6s8eMPPvgAV69eZYBsgwDJyIUa2dRncPWLyKU/Q0Y/j4x+Ho75MTLWJxW+9jF87WME8ilc+TcDZBOPyXYFyFqviTvalTrelcJHJPpejHw+D5FoRBOGIXK53Lry+TwKhQLCMERPTw8KhQIsy6qEQ3xayvO8mlNstm03NVkhA2QLd9b48djYGJ588kmMjY1hcnISExMTDZkcm8bk2DT2jxyMTA5E9hcj42ORiT2RycEaByYGcGBiAA9OhHhwIsRDE35kysRDUyZ2PDSBHQ9NYM+Ble2+fxy77x/H8NQ4hibHMDQ5hj0TIxV7J8awd2IM902Nlg1geHwnhiZ87B7NoW80j/5xHz37C+i9v4jSAwF6HgwrStMlhAdClPaXEEyF2DkaYOdoAOcBD84DHg56R3DQO4K9O0Yx3DuCoZ59NfaG4zXuK+6vMVycwnBxCoPFvRgs7kV/WIyUcugLPewqlLCrUMKewg7s9nsx7Odr7M172Jv3MOLr2JfTMGoHGLUD7MkLdnkCKQ1DgiFk3WFk3WFkskOw7EFY9iBMaxcMux+61Qc73wfH74eR761hexEz2wPDKUG3BKm0LF1EF7NM+QGUaI0GSDM2upz62lYanbSivtOuHwFVf/9Gq8tggGwyz/PQ09PThrb0MuUbcn2mwMxHdD8iBYEUBVISSE+VkkCC8vO+QLJlpciAjGFAxpDXi/DTwTIFvbdGkOqv4af64Kf64EoRrhTh6KlIWmDrgqzoyIqOnFjwxIAvUqNY1lvWLyb6xUSYFgSaQLI7Idmd0NN9y6S0nZB0b8QqluVrGbECxChAM2QpPBggbdOOcGhkWWstc7Wa2lXresutv+6xlVTvBy1Qt+NqmgbbtpHJZCoX1zYqa+XLgoidjWQl4hiRjF6WKot+dp0UXCcF3zLgWwYKph7JCAoZqSwn5UX0OlrWhpa1oWdtaI4FzbGQss2KtGMh7VgwMjaMjI20VYCIE62/JksdoVFmCcSuYgnEXNpeRtnS7atZiHjQxYQpNqw6jrh1cjVs8WCLh7QYSIuBlEiFiEATQbpquVYdu/xvtiwnIXISIiOCjAhE0kiJgVQqh1QqB00rVKTEh2i5SNops2vpZVoGomWWRh+VU1fpMuUHUKKp7vg2uoyt6LxbGXmo3o4Kqd1xm28jVWaUNdeOVqbXaf86pyGydMfHhgOk/FmHuONe+gvcRRwgabFg1rElW8erYYoLU9wVAyS1wnYx68T1xIHhSQBPAmQkCpelDt6DiIeU+DUqARIHBQNECdUdXycESCdQvR+0QHkBLdE22bIpM+ppq1jp+eraG21nlfXXRYcuOjTR1qRLukb8/+udAoy3Q324xMGSltqRSvxz/DspMdammUhpJiRlrKkLDjSibqS8gJYkLkBW+/1G2qkKo60KkGrVI7X6AIlDZGkkF40UVguQ9YKDAULU0ZQXkGyNBIzIhkcWna7dpzDWe54BQtSRlBeQbK2OUFTX3yQGCBFJBxSQbAyQTW2HAULU0ZQXkGwMkE1thwFC1NGUF5BsDJBNbYcBQtTRlBfQkrU6ly3pcBggm9oOA4SooykvoCnVncpKE7J1jPUCRnV9HfI+buR97tj3mGj7Ul5AU6rDgwGSXAwQokRTXkB3Y4CsiQFClGjKC2hYouaTYYBs6L3c6Hutul4iqqG8gO7GAFkTA4Qo0ZQX0N0YIGtigBAlmvICuhsDZE0MEKJE/QrjcAAAA+RJREFUU15Ad2OAtIQBQtTRlBfQ3bZxgLDDJ+p6ygvobgwQIupeygvobts4QIio6ykvoLsxQIioeykvoLsxQIioeykvoLsxQIioeykvoLsxQIioeykvoLsxQIioW/m+DyIiokaJ8gQjIqJk+vLLL0FERNQomZmZARERUaMk/krYRnCKCiIiaipAqtm2DcdxkMlk4LouERFtEy0FiK7rDBAiom1KVvt+8bVUh4jyIRQREanRTIBsRKOFVF9f2awvD1qrxnat11Zss3a3reu6ku2/2ftUI8tdb99sdbmbvX3Wem9bPTaqf2ezttNqf5Buxn640rKr13szj4Gu0+pO266DvV2dxUaWs5EaNnOjb1WnuNHltLOzbnfd7ayn2XaT3JFsZe3t2J+3un6GRcuUF0BERMmkvAAiIkom5QUQEVEyKS+AiIiSSXkBRESUTMoLICKiZFJeABERJZPyAoiIKJmUF0BERMmkvAAiosSonnqllZk8uoGofjOIiJJKdQeumqh+A4iIulVdZ9vS65ttY7W26ttcq30GCBHRFmOAEBFRUxggRETUlOqL7c28fisCJG6XAUJE1EEYIERE1JR2dPxxO1tR60bWhQFCRETtoLwAIiJKJuUFEBFRMikvgIiIkkl5AURElEzKCyAiomRSXgARESWT8gKIiLaNdn02pEMoL4CIaNtggBARbWNxCMRTlVQ/98ILL+Ds2bMQEYRhiDAMISIYHh7G3NwcbNtGGIZYWFjA1NQURGRZGwmivAAiosR69NFH8c477+CZZ57BzMwMDh8+jA8//BCGYUBEYNs2RASu6+L06dMIwxCO4+Ddd9/F+Pg4RASGYSR1VKK8ACKiRIpHIm+++Sbee+89PP3003AcBydOnKg8LyIIggAiglOnTmFgYAAigrfeegtHjhyBiEDX9bZNmrjFlBdARJRIrutCRHDo0CGcPHkSIoKXX34Zt2/frgTFoUOHIBKdzvrqq69w+PBhiAhmZ2dx7NixSlvV37Guer0aoLwAIqJE0jQNuq4jCAI88sgjEBE8/vjjeP755/Hwww8jCIJKYAwNDeH48eOYnp7G9PQ0jh8/jsceewwi0SmshF4HUV4AEVEixSMGXddh2zZM02y4jVwuBxGpXDNJGOUFEBElmqZpMAwDuq7DMAxYlgXXdWHbNizLgkh0nSN+LCLIZDIQEXieB13Xla9Dk5QXQESUaPEtvSLRSKKR0URCRx4x5QUQESVefPFb1/UkjygapbwAIqLEiwNkpQ8XdjHlBRARdY0E3orbCuUFEBFRMikvgIiIkkl5AURElEzKCyAiomRSXgARESXQ/wHU3ocp6yqnfgAAAABJRU5ErkJggg=="
+>
diff --git a/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html
new file mode 100644
index 0000000000..600b04a4f0
--- /dev/null
+++ b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "../bipbop_300_215kbps.mp4";
+ video.play();
+ video.addEventListener("ended", function() {
+ document.documentElement.removeAttribute('class');
+ }, {once: true});
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/generateREF.html b/dom/media/test/reftest/generateREF.html
new file mode 100644
index 0000000000..7249ef2579
--- /dev/null
+++ b/dom/media/test/reftest/generateREF.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="application/javascript">
+</script>
+</head>
+<body>
+<p id="out"></p>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+<canvas id="canvas"></canvas>
+<script type="application/javascript">
+/* READ ME first.
+The script is trying to make a reftest sample for reftest.
+HOW TO USE:
+1. Choose the first or last frame you want to generate. And set
+window.onload function to dumpFirstFrame/dumpLastFrame.
+2. Set the video.src in dumpFirstFrame/dumpLastFrame.
+3. Run the script on browser.
+4. Copy the base64 image url to your xxx-ref.html(short.mp4.firstframe-ref.html).
+You might hit security error if the video.src cross origin.
+Enable "media.seekToNextFrame.enabled" for the seekToNextFrame function
+or using nightly, the seekToNextFrame() ensure the ended event fired.
+*/
+
+//window.onload = function() { setTimeout(dumpFirstFrame, 0); };
+//window.onload = function() { setTimeout(dumpLastFrame, 0); };
+window.onload = function() { setTimeout(function(){dumpNthFrame(15);}, 0); };
+
+function drawVideoToInnerHTML(v) {
+ var canvas = document.getElementById("canvas");
+ canvas.width = v.videoWidth;
+ canvas.height = v.videoHeight;
+ var ctx = canvas.getContext("2d");
+ ctx.drawImage(v, 0, 0, v.videoWidth, v.videoHeight);
+ var dataURL = canvas.toDataURL();
+ document.getElementById("out").innerHTML=dataURL;
+}
+
+function dumpFirstFrame() {
+ var video = document.getElementById("v1");
+ video.src = "short.mp4";
+ video.preload = "metadata";
+ video.addEventListener("loadeddata", function() {
+ drawVideoToInnerHTML(video);
+ });
+}
+
+function dumpNthFrame(n) {
+ var video = document.getElementById("v1");
+ video.src = "street.mp4";
+ video.preload = "metadata";
+
+ function checkNthFrame() {
+ console.log((15-n+1)+"th Frame time is " + video.currentTime);
+ n--;
+ if (n == 0) {
+ drawVideoToInnerHTML(video);
+ } else {
+ video.seekToNextFrame();
+ }
+ }
+ video.addEventListener("loadeddata", checkNthFrame);
+ video.addEventListener("seeked", checkNthFrame);
+}
+
+function dumpLastFrame() {
+ var video = document.getElementById("v1");
+ video.src = "short.mp4";
+ video.preload = "metadata";
+ video.seenEnded = false;
+ // Seek to the end
+ video.addEventListener("loadeddata", function() {
+ video.currentTime = video.duration;
+ video.onseeked = () => {
+ video.onseeked = null;
+ callSeekToNextFrame();
+ };
+ });
+
+ function callSeekToNextFrame() {
+ video.seekToNextFrame().then(
+ () => {
+ if (!video.seenEnded)
+ callSeekToNextFrame();
+ },
+ () => {
+ // Reach the end, do nothing.
+ }
+ );
+ }
+
+ video.addEventListener("ended", function() {
+ video.seenEnded = true;
+ drawVideoToInnerHTML(video);
+ });
+}
+</script>
+</body>
+</html>
diff --git a/dom/media/test/reftest/gizmo.mp4.55thframe-ref.html b/dom/media/test/reftest/gizmo.mp4.55thframe-ref.html
new file mode 100644
index 0000000000..28a93cc268
--- /dev/null
+++ b/dom/media/test/reftest/gizmo.mp4.55thframe-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0"
+src="
+
+data:image/png;base64,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
+"
+>
diff --git a/dom/media/test/reftest/gizmo.mp4.seek.html b/dom/media/test/reftest/gizmo.mp4.seek.html
new file mode 100644
index 0000000000..e4c1fe9515
--- /dev/null
+++ b/dom/media/test/reftest/gizmo.mp4.seek.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<!--This testing should match the 55th frame of gizmo.mp4. The
+55th frame's time is 1.8s, so seek to a time which is a little
+greater than 1.8s, the display frame should be the 55th frame.
+-->
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "../gizmo.mp4";
+ video.preload = "metadata";
+
+ video.currentTime = 1.801;
+
+ video.addEventListener("seeked", function() {
+ // Since the our media pipeline send the frame to imageBridge, then fire
+ // seeked event, the target frame may not be shown on the screen.
+ // So using canvas to access the target frame in the imageContainer in
+ // videoElement.
+ var canvas = document.getElementById("canvas");
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+ var ctx = canvas.getContext("2d");
+ ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+<canvas id="canvas" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/image-10bits-rendering-720-90-ref.html b/dom/media/test/reftest/image-10bits-rendering-720-90-ref.html
new file mode 100644
index 0000000000..5e9a8e9b4c
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-720-90-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);"
+ src="vp9hdr2020.png"
+>
diff --git a/dom/media/test/reftest/image-10bits-rendering-720-90-video.html b/dom/media/test/reftest/image-10bits-rendering-720-90-video.html
new file mode 100644
index 0000000000..890aee1c50
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-720-90-video.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "vp9hdr2020.webm";
+ video.preload = "metadata";
+ video.addEventListener("loadeddata", function() {
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/image-10bits-rendering-720-ref.html b/dom/media/test/reftest/image-10bits-rendering-720-ref.html
new file mode 100644
index 0000000000..1ae393031a
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-720-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0"
+ src="vp9hdr2020.png"
+>
diff --git a/dom/media/test/reftest/image-10bits-rendering-720-video.html b/dom/media/test/reftest/image-10bits-rendering-720-video.html
new file mode 100644
index 0000000000..93d2651ffc
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-720-video.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "vp9hdr2020.webm";
+ video.preload = "metadata";
+ video.addEventListener("loadeddata", function() {
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/image-10bits-rendering-720.video.html b/dom/media/test/reftest/image-10bits-rendering-720.video.html
new file mode 100644
index 0000000000..93d2651ffc
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-720.video.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "vp9hdr2020.webm";
+ video.preload = "metadata";
+ video.addEventListener("loadeddata", function() {
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/image-10bits-rendering-90-ref.html b/dom/media/test/reftest/image-10bits-rendering-90-ref.html
new file mode 100644
index 0000000000..38f032f0fd
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-90-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);"
+ src="av1hdr2020.png"
+>
diff --git a/dom/media/test/reftest/image-10bits-rendering-90-video.html b/dom/media/test/reftest/image-10bits-rendering-90-video.html
new file mode 100644
index 0000000000..d8a4eca5a9
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-90-video.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "av1hdr2020.mp4";
+ video.preload = "metadata";
+ video.addEventListener("loadeddata", function() {
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0; filter:hue-rotate(90deg);"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/image-10bits-rendering-ref.html b/dom/media/test/reftest/image-10bits-rendering-ref.html
new file mode 100644
index 0000000000..de7fbbfc95
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0"
+ src="av1hdr2020.png"
+>
diff --git a/dom/media/test/reftest/image-10bits-rendering-video.html b/dom/media/test/reftest/image-10bits-rendering-video.html
new file mode 100644
index 0000000000..e6b3fda335
--- /dev/null
+++ b/dom/media/test/reftest/image-10bits-rendering-video.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "av1hdr2020.mp4";
+ video.preload = "metadata";
+ video.addEventListener("loadeddata", function() {
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/reftest.list b/dom/media/test/reftest/reftest.list
new file mode 100644
index 0000000000..5aeaec96e6
--- /dev/null
+++ b/dom/media/test/reftest/reftest.list
@@ -0,0 +1,8 @@
+skip-if(Android) fuzzy-if(OSX,0-80,0-76800) fuzzy-if(winWidget,0-62,0-76799) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-70,0-600) HTTP(..) == short.mp4.firstframe.html short.mp4.firstframe-ref.html
+skip-if(Android) fuzzy-if(OSX,0-87,0-76797) fuzzy-if(winWidget,0-60,0-76797) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-60,0-1800) HTTP(..) == short.mp4.lastframe.html short.mp4.lastframe-ref.html
+skip-if(Android) skip-if(winWidget) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-57,0-4281) fuzzy-if(OSX,55-80,4173-4417) HTTP(..) == bipbop_300_215kbps.mp4.lastframe.html bipbop_300_215kbps.mp4.lastframe-ref.html
+skip-if(Android) fuzzy-if(OSX,0-25,0-175921) fuzzy-if(winWidget,0-71,0-179198) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI)),0-255,0-179500) HTTP(..) == gizmo.mp4.seek.html gizmo.mp4.55thframe-ref.html
+skip-if(Android) skip-if(MinGW) skip-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI))) fuzzy(0-10,0-778236) == image-10bits-rendering-video.html image-10bits-rendering-ref.html
+skip-if(Android) skip-if(MinGW) skip-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI))) fuzzy(0-10,0-778536) == image-10bits-rendering-90-video.html image-10bits-rendering-90-ref.html
+skip-if(Android) fuzzy(0-26,0-567562) == image-10bits-rendering-720-video.html image-10bits-rendering-720-ref.html
+skip-if(Android) fuzzy(0-27,0-573249) == image-10bits-rendering-720-90-video.html image-10bits-rendering-720-90-ref.html
diff --git a/dom/media/test/reftest/short.mp4.firstframe-ref.html b/dom/media/test/reftest/short.mp4.firstframe-ref.html
new file mode 100644
index 0000000000..d80f2f985f
--- /dev/null
+++ b/dom/media/test/reftest/short.mp4.firstframe-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAADwCAYAAABxLb1rAAAHv0lEQVR4nO3dQWyfdR3H8R8d7XBjMDYcYzqCQY0SkWhQAomJJ2/GgyeNiTExHk3URE9e1INGEz3NA5CYETwQFi8YEvAgGg6LogFDIojbEBTGpsyNrWNt9/HQso0Btus2vu2+r1/yypN/+/x/+fbyzpP/0z4dGSMAHY3qAQCqCCDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSCl/jNGsmFi/vUVk5mZHDk2Rl4ZI5kaOTo25MC4/NT5h8b894+PTTk+NpXPz+omgJSaGyOZGDm48PrfY2R6YmRmzMvk9cm67afOPzpGpsfIsbExx8bG8vlZ3QSQFeFPYyQ3fyzJwSRHkiQ5+lqS55P9u5P163P0De+ZWlA/O6uXALICrEk+cEvy9W8nOZTk1Zxe/5yP4JVX5vgb3jOVjCtWwOysZgJIqSNjazJ5c+75zleTnMib12tJppOJLTnziu/IGHl1BczP6iaAlNoz1iSbbl+48nvlLQJ4/IwATp56nwByIQggpXbc+enk4d+edcV3IslskrnTX558YwDhQhBAaj3yaPLyoTMCOLsQwDkB5KITQEo8dP2G5N4dycwZkft/a921mRlryufm0iKA1Lh3R7L/2aXFTwC5SASQd9TOmzYnD9y99PC9vq7aeNbvAcL5E0DeWQ/cnTz3hACyIgggF8XJhePcmMihMfLzz9yW/PHhcw+fAHIRCSAX3sTEqeOJMZKP3568/Nfk8D4BZEURQC6eDRvy+599N8nR5YfvVACvyrHqn4dLjgBycWzfnnz/B8nJg0mOCSArkgByQcyNtTk6Ru6/447krrvy+hNdTi5atpnMPwFmkbXh7IchwPkTQM7fmg2ZHVPJ+29LnnoqOXDgHC7tZhYIIO88AWRZXr/L++jnPpvHv/TF5F9/yann+C2y5k4eTQ6eTHb/Lfd97fPJkX1LCOC6+RsqcAEJIMv30VuTx36XHNif0w8xWOJ6+kD+8OVv5Cs3XZ0c3iOAlBBAluWRa65Knt+7pNYdS5LZhU8D9+1JfrUrGZuSqffmnmu3Jc/sE0BKCCDLci4BPHPNfuub+cV1W5KxKRkb5wP47D8EkBICyLLs2rIteekcbnbs+HVy5xfetM9Da7cmTzwngJQQQJZlyQGcnsnuH/4key+/KU+PG07vMTF/fGjt1uRJV4DUEECW5b5t25KDbxHAuePzHns8+d6PkhuvSybffp+dm69N/u4mCDUEkGV52wBOH85/d96TJ2+9PQ9es3XRfQSQSgLIsjy45erkxb2Z/yuOY8lju5Mf/zT54KeSsXn+H54vwc7Nm5I9S3gwqgByEQggy3IqgIefyZO7duTPn7wzj37oI5kdIxlXJxNrl7SPAFJJAFmWE2Mkt3w42bg+WbP8fQSQSgLIssyMkVxx+XnvI4BUEkBKLTmA69ct+XNFWCoBpNQvr3t3sm/v4gGcmhJALjgBpNT929+TvPjC4gFcAbNy6RFASu163w3J/hcFkBICSCkBpJIAUkoAqSSAlBJAKgkgpQSQSgJIKQGkkgBSSgCpJICUEkAqCSClBJBKAkgpAaSSAFJKAKkkgJQSQCoJIKUEkEoCSCkBpJIAUkoAqSSAlBJAKgkgpQSQSgJIKQGkkgBSSgCpJICUEkAqCSClBJBKAkgpAaSSAFJKAKkkgJQSQCoJIKUEkEoCSCkBpJIAUkoAqSSAlBJAKgkgpQSQSgJIKQGkkgBSSgCpJICUEkAqCSClBJBKAkgpAaSSAFJKAKkkgJQSQCoJIKUEkEoCSKkHbtyevPySAFJCACklgFQSQEr9ZvMnkj2L92924rLMXFY/L5cWAaTUI5tuS/YtUr+T0zk4RrJ2snxeLi0CyIpy8qzX02MqM2NdXn3TuVML6mdm9RJAVrQT412ZfsvQrV1QPyOrlwCyqswuqJ6DS4MAsqoIIBeSAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0JYAAm0JINCWAAJtCSDQlgACbQkg0Nb/AJRYBp4E6qsBAAAAAElFTkSuQmCC"
+>
diff --git a/dom/media/test/reftest/short.mp4.firstframe.html b/dom/media/test/reftest/short.mp4.firstframe.html
new file mode 100644
index 0000000000..759bdc5ede
--- /dev/null
+++ b/dom/media/test/reftest/short.mp4.firstframe.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "../short.mp4";
+ video.preload = "metadata";
+ video.addEventListener("loadeddata", function() {
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/short.mp4.lastframe-ref.html b/dom/media/test/reftest/short.mp4.lastframe-ref.html
new file mode 100644
index 0000000000..7474b9039e
--- /dev/null
+++ b/dom/media/test/reftest/short.mp4.lastframe-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0"
+src="data:image/png;base64,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"
+>
diff --git a/dom/media/test/reftest/short.mp4.lastframe.html b/dom/media/test/reftest/short.mp4.lastframe.html
new file mode 100644
index 0000000000..abd27c5c8e
--- /dev/null
+++ b/dom/media/test/reftest/short.mp4.lastframe.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+ var video = document.getElementById("v1");
+ video.src = "../short.mp4";
+ video.preload = "metadata";
+ video.seenEnded = false;
+ // Seek to the end
+ video.addEventListener("loadeddata", function() {
+ video.currentTime = video.duration;
+ video.onseeked = () => {
+ video.onseeked = null;
+ callSeekToNextFrame();
+ };
+ });
+
+ function callSeekToNextFrame() {
+ video.seekToNextFrame().then(
+ () => {
+ if (!video.seenEnded)
+ callSeekToNextFrame();
+ },
+ () => {
+ // Reach the end, do nothing.
+ }
+ );
+ }
+
+ video.addEventListener("ended", function() {
+ video.seenEnded = true;
+ document.documentElement.removeAttribute('class');
+ });
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
diff --git a/dom/media/test/reftest/vp9hdr2020.png b/dom/media/test/reftest/vp9hdr2020.png
new file mode 100644
index 0000000000..afb68d9e0a
--- /dev/null
+++ b/dom/media/test/reftest/vp9hdr2020.png
Binary files differ
diff --git a/dom/media/test/reftest/vp9hdr2020.webm b/dom/media/test/reftest/vp9hdr2020.webm
new file mode 100644
index 0000000000..516f62093a
--- /dev/null
+++ b/dom/media/test/reftest/vp9hdr2020.webm
Binary files differ
diff --git a/dom/media/test/resolution-change.webm b/dom/media/test/resolution-change.webm
new file mode 100644
index 0000000000..29aad93b96
--- /dev/null
+++ b/dom/media/test/resolution-change.webm
Binary files differ
diff --git a/dom/media/test/resolution-change.webm^headers^ b/dom/media/test/resolution-change.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/resolution-change.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4 b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4
new file mode 100644
index 0000000000..720339bdc2
--- /dev/null
+++ b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4
Binary files differ
diff --git a/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^ b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sample-fisbone-skeleton4.ogv b/dom/media/test/sample-fisbone-skeleton4.ogv
new file mode 100644
index 0000000000..8afe0be7a4
--- /dev/null
+++ b/dom/media/test/sample-fisbone-skeleton4.ogv
Binary files differ
diff --git a/dom/media/test/sample-fisbone-skeleton4.ogv^headers^ b/dom/media/test/sample-fisbone-skeleton4.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/sample-fisbone-skeleton4.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sample-fisbone-wrong-header.ogv b/dom/media/test/sample-fisbone-wrong-header.ogv
new file mode 100644
index 0000000000..46c3933da5
--- /dev/null
+++ b/dom/media/test/sample-fisbone-wrong-header.ogv
Binary files differ
diff --git a/dom/media/test/sample-fisbone-wrong-header.ogv^headers^ b/dom/media/test/sample-fisbone-wrong-header.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/sample-fisbone-wrong-header.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sample.3g2 b/dom/media/test/sample.3g2
new file mode 100644
index 0000000000..769cb01dbd
--- /dev/null
+++ b/dom/media/test/sample.3g2
Binary files differ
diff --git a/dom/media/test/sample.3gp b/dom/media/test/sample.3gp
new file mode 100644
index 0000000000..4a3d8ea66f
--- /dev/null
+++ b/dom/media/test/sample.3gp
Binary files differ
diff --git a/dom/media/test/seek-short.ogv b/dom/media/test/seek-short.ogv
new file mode 100644
index 0000000000..a5ca6951d0
--- /dev/null
+++ b/dom/media/test/seek-short.ogv
Binary files differ
diff --git a/dom/media/test/seek-short.ogv^headers^ b/dom/media/test/seek-short.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/seek-short.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/seek-short.webm b/dom/media/test/seek-short.webm
new file mode 100644
index 0000000000..36abd1570e
--- /dev/null
+++ b/dom/media/test/seek-short.webm
Binary files differ
diff --git a/dom/media/test/seek-short.webm^headers^ b/dom/media/test/seek-short.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/seek-short.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/seek.ogv b/dom/media/test/seek.ogv
new file mode 100644
index 0000000000..ac7ece3519
--- /dev/null
+++ b/dom/media/test/seek.ogv
Binary files differ
diff --git a/dom/media/test/seek.ogv^headers^ b/dom/media/test/seek.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/seek.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/seek.webm b/dom/media/test/seek.webm
new file mode 100644
index 0000000000..72b0297233
--- /dev/null
+++ b/dom/media/test/seek.webm
Binary files differ
diff --git a/dom/media/test/seek.webm^headers^ b/dom/media/test/seek.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/seek.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/seekLies.sjs b/dom/media/test/seekLies.sjs
new file mode 100644
index 0000000000..3277950b08
--- /dev/null
+++ b/dom/media/test/seekLies.sjs
@@ -0,0 +1,23 @@
+function handleRequest(request, response)
+{
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsIFile);
+ var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
+ createInstance(Components.interfaces.nsIBinaryInputStream);
+ var paths = "tests/dom/media/test/seek.ogv";
+ var split = paths.split("/");
+ for(var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+ bis.setInputStream(fis);
+ var bytes = bis.readBytes(bis.available());
+ response.setHeader("Content-Length", ""+bytes.length, false);
+ response.setHeader("Content-Type", "video/ogg", false);
+ response.setHeader("Accept-Ranges", "bytes", false);
+ response.write(bytes, bytes.length);
+ bis.close();
+}
diff --git a/dom/media/test/seek_support.js b/dom/media/test/seek_support.js
new file mode 100644
index 0000000000..b100572a5a
--- /dev/null
+++ b/dom/media/test/seek_support.js
@@ -0,0 +1,61 @@
+// This file expects manifest.js to be included in the same scope.
+/* import-globals-from manifest.js */
+// This file expects SEEK_TEST_NUMBER to be defined by the test.
+/* global SEEK_TEST_NUMBER */
+var manager = new MediaTestManager();
+
+function createTestArray() {
+ var tests = [];
+ var tmpVid = document.createElement("video");
+
+ for (var testNum = 0; testNum < gSeekTests.length; testNum++) {
+ var test = gSeekTests[testNum];
+ if (!tmpVid.canPlayType(test.type)) {
+ continue;
+ }
+
+ var t = {};
+ t.name = test.name;
+ t.type = test.type;
+ t.duration = test.duration;
+ t.number = SEEK_TEST_NUMBER;
+ tests.push(t);
+ }
+ return tests;
+}
+
+function startTest(test, token) {
+ var video = document.createElement("video");
+ video.token = token += "-seek" + test.number + ".js";
+ manager.started(video.token);
+ video.src = test.name;
+ video.preload = "metadata";
+ document.body.appendChild(video);
+ var name = test.name + " seek test " + test.number;
+ var localIs = (function(n) {
+ return function(a, b, msg) {
+ is(a, b, n + ": " + msg);
+ };
+ })(name);
+ var localOk = (function(n) {
+ return function(a, msg) {
+ ok(a, n + ": " + msg);
+ };
+ })(name);
+ var localFinish = (function(v, m) {
+ return function() {
+ v.onerror = null;
+ removeNodeAndSource(v);
+ dump("SEEK-TEST: Finished " + name + " token: " + v.token + "\n");
+ m.finished(v.token);
+ };
+ })(video, manager);
+ dump("SEEK-TEST: Started " + name + "\n");
+ window["test_seek" + test.number](
+ video,
+ test.duration / 2,
+ localIs,
+ localOk,
+ localFinish
+ );
+}
diff --git a/dom/media/test/seek_with_sound.ogg b/dom/media/test/seek_with_sound.ogg
new file mode 100644
index 0000000000..c86d9946bd
--- /dev/null
+++ b/dom/media/test/seek_with_sound.ogg
Binary files differ
diff --git a/dom/media/test/seek_with_sound.ogg^headers^ b/dom/media/test/seek_with_sound.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/seek_with_sound.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/short-aac-encrypted-audio.mp4 b/dom/media/test/short-aac-encrypted-audio.mp4
new file mode 100644
index 0000000000..c1ba8d37e4
--- /dev/null
+++ b/dom/media/test/short-aac-encrypted-audio.mp4
Binary files differ
diff --git a/dom/media/test/short-aac-encrypted-audio.mp4^headers^ b/dom/media/test/short-aac-encrypted-audio.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/short-aac-encrypted-audio.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4 b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4
new file mode 100644
index 0000000000..bc58623999
--- /dev/null
+++ b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4
Binary files differ
diff --git a/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4^headers^ b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/short-audio-fragmented-cenc-without-pssh.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/short-cenc-pssh-in-moof.mp4 b/dom/media/test/short-cenc-pssh-in-moof.mp4
new file mode 100644
index 0000000000..aa44c3b9a1
--- /dev/null
+++ b/dom/media/test/short-cenc-pssh-in-moof.mp4
Binary files differ
diff --git a/dom/media/test/short-cenc.mp4 b/dom/media/test/short-cenc.mp4
new file mode 100644
index 0000000000..aa44c3b9a1
--- /dev/null
+++ b/dom/media/test/short-cenc.mp4
Binary files differ
diff --git a/dom/media/test/short-cenc.xml b/dom/media/test/short-cenc.xml
new file mode 100644
index 0000000000..9658c3e32f
--- /dev/null
+++ b/dom/media/test/short-cenc.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ This XML file describes the encryption applied to short-cenc.mp4. To generate
+ short-cenc, run the following command:
+
+ MP4Box -crypt short-cenc.xml -out short-cenc.mp4 short.mp4
+-->
+
+<GPACDRM type="CENC AES-CTR">
+
+ <DRMInfo type="pssh" version="1">
+ <!--
+ SystemID specified in
+ https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+ -->
+ <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b" />
+ <!-- Number of KeyIDs = 2 -->
+ <BS bits="32" value="2" />
+ <!-- KeyID -->
+ <BS ID128="0x7e571d017e571d017e571d017e571d01" />
+ <BS ID128="0x7e571d027e571d027e571d027e571d02" />
+ </DRMInfo>
+
+ <CrypTrack trackID="1" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d017e571d017e571d017e571d01"
+ value="0x7e5711117e5711117e5711117e571111" />
+ </CrypTrack>
+
+ <CrypTrack trackID="2" isEncrypted="1" IV_size="16" saiSavedBox="senc"
+ first_IV="0x00000000000000000000000000000000">
+ <key KID="0x7e571d027e571d027e571d027e571d02"
+ value="0x7e5722227e5722227e5722227e572222" />
+ </CrypTrack>
+
+</GPACDRM>
diff --git a/dom/media/test/short-video.ogv b/dom/media/test/short-video.ogv
new file mode 100644
index 0000000000..68dee3cf2b
--- /dev/null
+++ b/dom/media/test/short-video.ogv
Binary files differ
diff --git a/dom/media/test/short-video.ogv^headers^ b/dom/media/test/short-video.ogv^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/short-video.ogv^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/short-vp9-encrypted-video.mp4 b/dom/media/test/short-vp9-encrypted-video.mp4
new file mode 100644
index 0000000000..23d1083d18
--- /dev/null
+++ b/dom/media/test/short-vp9-encrypted-video.mp4
Binary files differ
diff --git a/dom/media/test/short-vp9-encrypted-video.mp4^headers^ b/dom/media/test/short-vp9-encrypted-video.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/short-vp9-encrypted-video.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/short.mp4 b/dom/media/test/short.mp4
new file mode 100644
index 0000000000..802da047bc
--- /dev/null
+++ b/dom/media/test/short.mp4
Binary files differ
diff --git a/dom/media/test/short.mp4.gz b/dom/media/test/short.mp4.gz
new file mode 100644
index 0000000000..efb95e38e3
--- /dev/null
+++ b/dom/media/test/short.mp4.gz
Binary files differ
diff --git a/dom/media/test/short.mp4^headers^ b/dom/media/test/short.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/short.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sine.webm b/dom/media/test/sine.webm
new file mode 100644
index 0000000000..3913ffa874
--- /dev/null
+++ b/dom/media/test/sine.webm
Binary files differ
diff --git a/dom/media/test/sine.webm^headers^ b/dom/media/test/sine.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/sine.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm
new file mode 100644
index 0000000000..7497096ba5
--- /dev/null
+++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm
Binary files differ
diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm^headers^ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-audio.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm
new file mode 100644
index 0000000000..0f7609439d
--- /dev/null
+++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm
Binary files differ
diff --git a/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm^headers^ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/sintel-short-clearkey-subsample-encrypted-video.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/small-shot-mp3.mp4 b/dom/media/test/small-shot-mp3.mp4
new file mode 100644
index 0000000000..61fe0ac719
--- /dev/null
+++ b/dom/media/test/small-shot-mp3.mp4
Binary files differ
diff --git a/dom/media/test/small-shot-mp3.mp4^headers^ b/dom/media/test/small-shot-mp3.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/small-shot-mp3.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/small-shot.flac b/dom/media/test/small-shot.flac
new file mode 100644
index 0000000000..0da7c9044e
--- /dev/null
+++ b/dom/media/test/small-shot.flac
Binary files differ
diff --git a/dom/media/test/small-shot.m4a b/dom/media/test/small-shot.m4a
new file mode 100644
index 0000000000..51a23c5b49
--- /dev/null
+++ b/dom/media/test/small-shot.m4a
Binary files differ
diff --git a/dom/media/test/small-shot.mp3 b/dom/media/test/small-shot.mp3
new file mode 100644
index 0000000000..f9397a5106
--- /dev/null
+++ b/dom/media/test/small-shot.mp3
Binary files differ
diff --git a/dom/media/test/small-shot.mp3^headers^ b/dom/media/test/small-shot.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/small-shot.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/small-shot.ogg b/dom/media/test/small-shot.ogg
new file mode 100644
index 0000000000..1a41623f81
--- /dev/null
+++ b/dom/media/test/small-shot.ogg
Binary files differ
diff --git a/dom/media/test/small-shot.ogg^headers^ b/dom/media/test/small-shot.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/small-shot.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/sound.ogg b/dom/media/test/sound.ogg
new file mode 100644
index 0000000000..edda4e9128
--- /dev/null
+++ b/dom/media/test/sound.ogg
Binary files differ
diff --git a/dom/media/test/sound.ogg^headers^ b/dom/media/test/sound.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/sound.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/spacestorm-1000Hz-100ms.ogg b/dom/media/test/spacestorm-1000Hz-100ms.ogg
new file mode 100644
index 0000000000..994041e1b0
--- /dev/null
+++ b/dom/media/test/spacestorm-1000Hz-100ms.ogg
Binary files differ
diff --git a/dom/media/test/spacestorm-1000Hz-100ms.ogg^headers^ b/dom/media/test/spacestorm-1000Hz-100ms.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/spacestorm-1000Hz-100ms.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/split.webm b/dom/media/test/split.webm
new file mode 100644
index 0000000000..9207017fb6
--- /dev/null
+++ b/dom/media/test/split.webm
Binary files differ
diff --git a/dom/media/test/split.webm^headers^ b/dom/media/test/split.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/split.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/street.mp4 b/dom/media/test/street.mp4
new file mode 100644
index 0000000000..837d23b383
--- /dev/null
+++ b/dom/media/test/street.mp4
Binary files differ
diff --git a/dom/media/test/street.mp4^headers^ b/dom/media/test/street.mp4^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/street.mp4^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-1-mono.opus b/dom/media/test/test-1-mono.opus
new file mode 100644
index 0000000000..d5198e9ceb
--- /dev/null
+++ b/dom/media/test/test-1-mono.opus
Binary files differ
diff --git a/dom/media/test/test-1-mono.opus^headers^ b/dom/media/test/test-1-mono.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-1-mono.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-2-stereo.opus b/dom/media/test/test-2-stereo.opus
new file mode 100644
index 0000000000..7115cac243
--- /dev/null
+++ b/dom/media/test/test-2-stereo.opus
Binary files differ
diff --git a/dom/media/test/test-2-stereo.opus^headers^ b/dom/media/test/test-2-stereo.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-2-stereo.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-3-LCR.opus b/dom/media/test/test-3-LCR.opus
new file mode 100644
index 0000000000..145536f3e7
--- /dev/null
+++ b/dom/media/test/test-3-LCR.opus
Binary files differ
diff --git a/dom/media/test/test-3-LCR.opus^headers^ b/dom/media/test/test-3-LCR.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-3-LCR.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-4-quad.opus b/dom/media/test/test-4-quad.opus
new file mode 100644
index 0000000000..731b867b2e
--- /dev/null
+++ b/dom/media/test/test-4-quad.opus
Binary files differ
diff --git a/dom/media/test/test-4-quad.opus^headers^ b/dom/media/test/test-4-quad.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-4-quad.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-5-5.0.opus b/dom/media/test/test-5-5.0.opus
new file mode 100644
index 0000000000..7eb2faa812
--- /dev/null
+++ b/dom/media/test/test-5-5.0.opus
Binary files differ
diff --git a/dom/media/test/test-5-5.0.opus^headers^ b/dom/media/test/test-5-5.0.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-5-5.0.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-6-5.1.opus b/dom/media/test/test-6-5.1.opus
new file mode 100644
index 0000000000..526515eb7b
--- /dev/null
+++ b/dom/media/test/test-6-5.1.opus
Binary files differ
diff --git a/dom/media/test/test-6-5.1.opus^headers^ b/dom/media/test/test-6-5.1.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-6-5.1.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-7-6.1.opus b/dom/media/test/test-7-6.1.opus
new file mode 100644
index 0000000000..8b6a7ce329
--- /dev/null
+++ b/dom/media/test/test-7-6.1.opus
Binary files differ
diff --git a/dom/media/test/test-7-6.1.opus^headers^ b/dom/media/test/test-7-6.1.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-7-6.1.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-8-7.1.opus b/dom/media/test/test-8-7.1.opus
new file mode 100644
index 0000000000..e56176a30f
--- /dev/null
+++ b/dom/media/test/test-8-7.1.opus
Binary files differ
diff --git a/dom/media/test/test-8-7.1.opus^headers^ b/dom/media/test/test-8-7.1.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-8-7.1.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test-stereo-phase-inversion-180.opus b/dom/media/test/test-stereo-phase-inversion-180.opus
new file mode 100644
index 0000000000..ce70290002
--- /dev/null
+++ b/dom/media/test/test-stereo-phase-inversion-180.opus
Binary files differ
diff --git a/dom/media/test/test-stereo-phase-inversion-180.opus^headers^ b/dom/media/test/test-stereo-phase-inversion-180.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/test-stereo-phase-inversion-180.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/test_VideoPlaybackQuality.html b/dom/media/test/test_VideoPlaybackQuality.html
new file mode 100644
index 0000000000..67636f9ea6
--- /dev/null
+++ b/dom/media/test/test_VideoPlaybackQuality.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test basic functionality of VideoPlaybackQuality</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var video = document.createElement("video");
+ ok(video.getVideoPlaybackQuality, "getVideoPlaybackQuality should be exposed with pref set");
+
+ var vpq = video.getVideoPlaybackQuality();
+ ok(vpq, "getVideoPlaybackQuality should return an object");
+ ok(vpq.creationTime <= performance.now(), "creationTime should be in the past");
+ is(vpq.totalVideoFrames, 0, "totalVideoFrames should be 0");
+ is(vpq.droppedVideoFrames, 0, "droppedVideoFrames should be 0");
+
+ var vpq2 = video.getVideoPlaybackQuality();
+ ok(vpq !== vpq2, "getVideoPlaybackQuality should return a new object");
+ ok(vpq.creationTime <= vpq2.creationTime, "VideoPlaybackQuality objects should have increasing creationTime");
+
+ var audio = document.createElement("audio");
+ ok(!audio.getVideoPlaybackQuality, "getVideoPlaybackQuality should not be available on Audio elements");
+
+ video.src = "seek.webm";
+ video.play();
+ video.addEventListener("ended", function () {
+ vpq = video.getVideoPlaybackQuality();
+ ok(vpq.creationTime <= performance.now(), "creationTime should be in the past");
+ ok(vpq.totalVideoFrames > 0, "totalVideoFrames should be > 0");
+ ok(vpq.droppedVideoFrames >= 0, "droppedVideoFrames should be >= 0");
+ ok(vpq.droppedVideoFrames <= vpq.totalVideoFrames, "droppedVideoFrames should be <= totalVideoFrames");
+
+ SpecialPowers.pushPrefEnv({"set": [["media.video_stats.enabled", false]]}, function () {
+ vpq = video.getVideoPlaybackQuality();
+ is(vpq.creationTime, 0, "creationTime should be 0");
+ is(vpq.totalVideoFrames, 0, "totalVideoFrames should be 0");
+ is(vpq.droppedVideoFrames, 0, "droppedVideoFrames should be 0");
+
+ SimpleTest.finish();
+ });
+ });
+}
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set":
+ [
+ ["media.mediasource.enabled", true],
+ ]
+ }, test);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_VideoPlaybackQuality_disabled.html b/dom/media/test/test_VideoPlaybackQuality_disabled.html
new file mode 100644
index 0000000000..1c41b79d8b
--- /dev/null
+++ b/dom/media/test/test_VideoPlaybackQuality_disabled.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test basic functionality of VideoPlaybackQuality</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var video = document.createElement("video");
+ ok(!video.getVideoPlaybackQuality, "getVideoPlaybackQuality should be hidden behind a pref");
+ var accessThrows = false;
+ try {
+ video.getVideoPlaybackQuality();
+ } catch (e) {
+ accessThrows = true;
+ }
+ ok(accessThrows, "getVideoPlaybackQuality should be hidden behind a pref");
+ SimpleTest.finish();
+}
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set":
+ [
+ ["media.mediasource.enabled", false],
+ ]
+ }, test);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_access_control.html b/dom/media/test/test_access_control.html
new file mode 100644
index 0000000000..816563f9f5
--- /dev/null
+++ b/dom/media/test/test_access_control.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=451958
+-->
+<head>
+ <title>Test for Bug 451958</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=451958">Mozilla Bug 451958</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 451958 **/
+
+function run() {
+ window.open("http://example.org:80/tests/dom/media/test/file_access_controls.html", "", "width=500,height=500");
+}
+
+function done() {
+ mediaTestCleanup();
+ SimpleTest.finish();
+}
+
+addLoadEvent(run);
+SimpleTest.waitForExplicitFinish();
+
+
+window.addEventListener("message", receiveMessage);
+
+function receiveMessage(event)
+{
+ if (event.origin !== "http://example.org") {
+ ok(false, "Received message from wrong domain");
+ return;
+ }
+
+ if (event.data.done == "true") {
+ done();
+ return;
+ }
+
+ ok(event.data.result, event.data.message);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_arraybuffer.html b/dom/media/test/test_arraybuffer.html
new file mode 100644
index 0000000000..9ef84c53dc
--- /dev/null
+++ b/dom/media/test/test_arraybuffer.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1462967
+-->
+<head>
+ <title>Test for Bug 1457661</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1457661">Mozilla Bug 1457661</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Test for Bug 1457661; Ensure that readyState properly move to 4 when using arrayBuffer obtained from XMLHttpRequest
+
+var manager = new MediaTestManager;
+
+function getBlob(url, callback) {
+ const req = new XMLHttpRequest();
+ req.addEventListener("load", function() {
+ callback(req.response);
+ });
+ req.open("GET", url, true);
+ req.responseType = "arraybuffer";
+ req.send();
+}
+
+function startTest(test, token) {
+ manager.started(token);
+ // Fetch the media resource using XHR so we can be sure the entire
+ // resource is loaded before we test buffered ranges. This ensures
+ // we have deterministic behaviour.
+ getBlob(test.name, function(arr) {
+ const v = document.createElement("video");
+ const events = [ "suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause", "durationchange", "seeking", "seeked" ];
+ function logEvent(e) {
+ info(test.name + ": got " + e.type + " event");
+ }
+ events.forEach(function(e) {
+ v.addEventListener(e, logEvent);
+ });
+ once(v, "stalled", function(e) {
+ // Resource fetch algorithm in local mode should never fire stalled event.
+ // https://html.spec.whatwg.org/multipage/media.html#concept-media-load-resource
+ ok(false, test.name + ": got stalled");
+ removeNodeAndSource(v);
+ manager.finished(token);
+ });
+ once(v, "canplaythrough", function(e) {
+ ok(true, test.name + ": got canplaythrough");
+ is(v.readyState, v.HAVE_ENOUGH_DATA, test.name + ": readyState is HAVE_ENOUGH_DATA");
+ removeNodeAndSource(v);
+ manager.finished(token);
+ });
+ const blob = new Blob([arr], {type:test.type});
+ const blobUrl = URL.createObjectURL(blob);
+
+ document.body.appendChild(v);
+ v.preload = "auto";
+ v.src = blobUrl;
+ v.load();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+// Note: No need to set media test prefs, since we're using XHR to fetch
+// media data.
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_aspectratio_mp4.html b/dom/media/test/test_aspectratio_mp4.html
new file mode 100644
index 0000000000..5e01875439
--- /dev/null
+++ b/dom/media/test/test_aspectratio_mp4.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=975978
+-->
+
+<head>
+ <title>Media test: default video size</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=975978">Mozilla Bug 975978</a>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// MP4 video with display size is difference to decode frame size.
+// The display size is recorded in TrackHeaderBox 'tkhd' of this mp4 video.
+var resource =
+ { name:"pixel_aspect_ratio.mp4", type:"video/mp4", width:525, height:288 };
+
+var v = document.createElement("video");
+v.onloadedmetadata = function() {
+ is(v.videoWidth, resource.width, "Intrinsic width should match video width");
+ is(v.videoHeight, resource.height, "Intrinsic height should match video height");
+ SimpleTest.finish();
+}
+v.addEventListener("error", function(ev) {
+ if (v.readyState < v.HAVE_METADATA) {
+ info("Video element returns with readyState " + v.readyState + " error.code " + v.error.code);
+ todo(false, "This platform doesn't support to retrieve MP4 metadata.");
+ SimpleTest.finish();
+ }
+});
+
+v.src = resource.name;
+v.preload = "auto";
+
+document.body.appendChild(v);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_audio1.html b/dom/media/test/test_audio1.html
new file mode 100644
index 0000000000..a5d9f12ab9
--- /dev/null
+++ b/dom/media/test/test_audio1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: Audio Constructor Test 1</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var a1 = new Audio();
+ if (!a1.canPlayType(test.type))
+ return;
+ manager.started(token);
+
+ is(a1.getAttribute("preload"), "auto", "preload:auto automatically set");
+ is(a1.src, "", "Src set?");
+ a1 = null;
+ manager.finished(token);
+}
+
+manager.runTests(gAudioTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_audio2.html b/dom/media/test/test_audio2.html
new file mode 100644
index 0000000000..fde9adc791
--- /dev/null
+++ b/dom/media/test/test_audio2.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: Audio Constructor Test 2</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var tmpAudio = new Audio();
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ if (!tmpAudio.canPlayType(test.type))
+ return;
+ manager.started(token);
+ var a1 = new Audio(test.name);
+ is(a1.getAttribute("preload"), "auto", "Preload automatically set to auto");
+ ok(a1.src.endsWith("/" + test.name), "src OK");
+ manager.finished(token);
+}
+
+manager.runTests(gAudioTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_audioDocumentTitle.html b/dom/media/test/test_audioDocumentTitle.html
new file mode 100644
index 0000000000..2913debb39
--- /dev/null
+++ b/dom/media/test/test_audioDocumentTitle.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=463830
+-->
+<head>
+ <title>Test for Bug 463830</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463830">Mozilla Bug 463830</a>
+<p id="display"></p>
+<iframe id="i"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 463830 **/
+
+var gTests = [
+ { file: "r11025_s16_c1.wav", title: "r11025_s16_c1.wav" }
+];
+
+var gTestNum = 0;
+
+addLoadEvent(runTest);
+
+var title;
+var i = document.getElementById("i");
+
+function runTest() {
+ if (gTestNum == gTests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ if (gTestNum == 0) {
+ i.addEventListener("load", function() {
+ is(i.contentDocument.title, title, "Doc title incorrect");
+ setTimeout(runTest, 0);
+ });
+ }
+
+ title = gTests[gTestNum].title;
+ i.src = gTests[gTestNum].file;
+ gTestNum++;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_background_video_cancel_suspend_taint.html b/dom/media/test/test_background_video_cancel_suspend_taint.html
new file mode 100644
index 0000000000..26691f3f43
--- /dev/null
+++ b/dom/media/test/test_background_video_cancel_suspend_taint.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Suspend Cancels (Element Taint)</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+/**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video decode resumes.
+ */
+function testSuspendTimerCanceledWhenTainted(video) {
+ function ended() {
+ video.removeEventListener("mozcancelvideosuspendtimer", canceled);
+ ok(false, `${video.token} ended before suspend cancels`);
+ this.ended_resolve();
+ }
+
+ function canceled() {
+ video.removeEventListener("ended", ended);
+ ok(true, `${video.token} suspend cancels`);
+ this.canceled_resolve();
+ }
+
+ let p = Promise.race([
+ new Promise((resolve) => {
+ video.ended_resolve = resolve;
+ video.addEventListener('ended', ended, { 'once': true });
+ }),
+ new Promise((resolve) => {
+ video.canceled_resolve = resolve;
+ video.addEventListener('mozcancelvideosuspendtimer', canceled, { 'once': true });
+ })
+ ]);
+
+ Log(video.token, "Mark tainted");
+
+ let c = document.createElement('canvas');
+ let g = c.getContext('2d');
+ g.drawImage(video, 0, 0, c.width, c.height);
+ ok(video.hasSuspendTaint(), 'video used with drawImage is tainted.');
+
+ return p;
+}
+
+startTest({
+ desc: 'Test Background Video Suspend Cancels (Element Taint)',
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 10000 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ waitUntilVisible(v)
+ .then(() => waitUntilPlaying(v))
+ .then(() => testSuspendTimerStartedWhenHidden(v))
+ .then(() => testSuspendTimerCanceledWhenTainted(v))
+ .then(() => { manager.finished(token); });
+ }
+});
+</script>
diff --git a/dom/media/test/test_background_video_cancel_suspend_visible.html b/dom/media/test/test_background_video_cancel_suspend_visible.html
new file mode 100644
index 0000000000..e947b27734
--- /dev/null
+++ b/dom/media/test/test_background_video_cancel_suspend_visible.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Suspend Cancels (Visibility)</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+/**
+ * Check that making the element visible before suspend timer delay causes the
+ * the suspend timer to be canceled.
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video decode resumes.
+ */
+function testSuspendTimerCanceledWhenShown(video) {
+ function ended() {
+ video.removeEventListener("mozcancelvideosuspendtimer", canceled);
+ ok(false, `${video.token} ended before suspend cancels`);
+ this.ended_resolve();
+ }
+
+ function canceled() {
+ video.removeEventListener("ended", ended);
+ ok(true, `${video.token} suspend cancels`);
+ this.canceled_resolve();
+ }
+
+ let p = Promise.race([
+ new Promise((resolve) => {
+ video.ended_resolve = resolve;
+ video.addEventListener('ended', ended, { 'once': true });
+ }),
+ new Promise((resolve) => {
+ video.canceled_resolve = resolve;
+ video.addEventListener('mozcancelvideosuspendtimer', canceled, { 'once': true });
+ })
+ ]);
+
+ Log(video.token, "Set visible");
+ video.setVisible(true);
+
+ return p;
+}
+
+startTest({
+ desc: 'Test Background Video Suspend Cancels (Visibility)',
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 10000 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ waitUntilPlaying(v)
+ .then(() => testSuspendTimerStartedWhenHidden(v))
+ .then(() => testSuspendTimerCanceledWhenShown(v))
+ .then(() => {
+ ok(true, `${v.token} finished`);
+ manager.finished(token);
+ });
+ }
+});
diff --git a/dom/media/test/test_background_video_drawimage_with_suspended_video.html b/dom/media/test/test_background_video_drawimage_with_suspended_video.html
new file mode 100644
index 0000000000..32bda51dbf
--- /dev/null
+++ b/dom/media/test/test_background_video_drawimage_with_suspended_video.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Displays Video Frame via drawImage When Suspended</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style>
+video, canvas {
+ border: 1px solid black;
+}
+</style>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+function drawVideoToCanvas(v) {
+ console.log('drawVideoToCanvas');
+ let c = document.createElement('canvas');
+ c.width = 4;
+ c.height = 4;
+ c.style.width = 64;
+ c.style.height = 64;
+ document.body.appendChild(c);
+
+ let gfx = c.getContext('2d');
+ if (!gfx) {
+ throw Error("Unable to obtain context '2d' from canvas");
+ }
+
+ gfx.drawImage(v, 0, 0, 4, 4);
+ let imageData = gfx.getImageData(0, 0, 4, 4);
+ let pixels = imageData.data;
+
+ // Check that pixels aren't all the same colour.
+ // Implements by checking against rgb of the first pixel.
+ let rr = pixels[0],
+ gg = pixels[1],
+ bb = pixels[2],
+ allSame = true;
+
+ for (let i = 0; i < 4*4; i++) {
+ let r = pixels[4*i+0];
+ let g = pixels[4*i+1];
+ let b = pixels[4*i+2];
+ if (r != rr || g != gg || b != bb) {
+ allSame = false;
+ break;
+ }
+ }
+
+ ok(!allSame, "Pixels aren't all the same color");
+}
+
+startTest({
+ desc: 'Test Background Video Displays Video Frame via drawImage When Suspended',
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 500 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ waitUntilPlaying(v)
+ .then(() => testVideoSuspendsWhenHidden(v))
+ .then(() => {
+ drawVideoToCanvas(v);
+ manager.finished(token);
+ });
+ }
+});
+</script>
diff --git a/dom/media/test/test_background_video_ended_event.html b/dom/media/test/test_background_video_ended_event.html
new file mode 100644
index 0000000000..99e2dd2f18
--- /dev/null
+++ b/dom/media/test/test_background_video_ended_event.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Suspends</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+startTest({
+ desc: "Test background video doesn't fire the 'ended' event twice.",
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 100 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ let count = 0;
+ v.addEventListener("ended", function() {
+ is(++count, 1, `${token} should get only one 'ended' event.`);
+ });
+
+ /*
+ * This test checks that, after a video element had finished its playback,
+ * resuming video decoder doesn't dispatch 2nd ended event.
+ * This issue was found in bug 1349097.
+ */
+ waitUntilPlaying(v)
+ .then(() => testVideoSuspendsWhenHidden(v))
+ .then(() => waitUntilEnded(v))
+ .then(() => testVideoResumesWhenShown(v))
+ .then(() => {
+ // Wait for a while and finish the test. We should get only one 'ended' event.
+ setTimeout(function() {
+ removeNodeAndSource(v);
+ manager.finished(token);
+ }, 3000);
+ });
+ }
+});
+</script>
diff --git a/dom/media/test/test_background_video_no_suspend_disabled.html b/dom/media/test/test_background_video_no_suspend_disabled.html
new file mode 100644
index 0000000000..f93977f2df
--- /dev/null
+++ b/dom/media/test/test_background_video_no_suspend_disabled.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Doesn't Suspend When Feature Disabled</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script>
+"use strict";
+
+var manager = new MediaTestManager;
+
+startTest({
+ desc: "Test Background Video Doesn't Suspend When Feature Disabled.",
+ prefs: [
+ [ 'media.test.video-suspend', true ],
+ [ 'media.suspend-bkgnd-video.enabled', false ],
+ [ 'media.suspend-bkgnd-video.delay-ms', 0 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ /* This test checks that suspend doesn't occur when the feature is disabled */
+ waitUntilPlaying(v)
+ .then(() => checkVideoDoesntSuspend(v))
+ .then(() => {
+ ok(true, 'Video ended before decode was suspended');
+ manager.finished(token); })
+ .catch((e) => {
+ ok(false, 'Test Failed: ' + e.toString());
+ manager.finished(token); });
+ }
+});
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_background_video_no_suspend_not_in_tree.html b/dom/media/test/test_background_video_no_suspend_not_in_tree.html
new file mode 100644
index 0000000000..f65d363bd6
--- /dev/null
+++ b/dom/media/test/test_background_video_no_suspend_not_in_tree.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Doesn't Suspend When Timeout Is Longer Than Video</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script>
+"use strict";
+
+var manager = new MediaTestManager;
+
+var MIN_DELAY = 100;
+
+/**
+ * @param {string} url video src.
+ * @returns {HTMLMediaElement} The created video element.
+ */
+function createVideoNotAppendToDoc(url, token, width, height) {
+ // Default size of (160, 120) is used by other media tests.
+ if (width === undefined) { width = 160; }
+ if (height === undefined) { height = 3*width/4; }
+
+ let v = document.createElement('video');
+ v.token = token;
+ v.width = width;
+ v.height = height;
+ v.src = url;
+ return v;
+}
+
+startTest({
+ desc: "Test Background Video Doesn't Suspend When If The Video Is Not In Tree.",
+ prefs: [
+ [ 'media.test.video-suspend', true ],
+ [ 'media.suspend-bkgnd-video.enabled', true ],
+ [ 'media.suspend-bkgnd-video.delay-ms', MIN_DELAY ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ let v = createVideoNotAppendToDoc(test.name, token);
+ manager.started(token);
+
+ /* This test checks that suspend doesn't occur if a video element is not
+ append to tree. */
+ waitUntilPlaying(v)
+ .then(() => checkVideoDoesntSuspend(v))
+ .then(() => {
+ ok(true, 'Video ended before decode was suspended');
+ manager.finished(token); })
+ .catch((e) => {
+ ok(false, 'Test Failed: ' + e.toString());
+ manager.finished(token); });
+ }
+});
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_background_video_no_suspend_short_vid.html b/dom/media/test/test_background_video_no_suspend_short_vid.html
new file mode 100644
index 0000000000..35597f6b96
--- /dev/null
+++ b/dom/media/test/test_background_video_no_suspend_short_vid.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Doesn't Suspend When Timeout Is Longer Than Video</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script>
+"use strict";
+
+var manager = new MediaTestManager;
+
+startTest({
+ desc: "Test Background Video Doesn't Suspend When Timeout Is Longer Than Video.",
+ prefs: [
+ [ 'media.test.video-suspend', true ],
+ [ 'media.suspend-bkgnd-video.enabled', true ],
+ // Gizmo.mp4 is about 5.6s
+ [ 'media.suspend-bkgnd-video.delay-ms', 10000 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ /* This test checks that suspend doesn't occur when the delay is longer
+ than the duration of the video that's playing */
+ waitUntilPlaying(v)
+ .then(() => checkVideoDoesntSuspend(v))
+ .then(() => {
+ ok(true, 'Video ended before decode was suspended');
+ manager.finished(token); })
+ .catch((e) => {
+ ok(false, 'Test Failed: ' + e.toString());
+ manager.finished(token); });
+ }
+});
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_background_video_resume_after_end_show_last_frame.html b/dom/media/test/test_background_video_resume_after_end_show_last_frame.html
new file mode 100644
index 0000000000..767567e116
--- /dev/null
+++ b/dom/media/test/test_background_video_resume_after_end_show_last_frame.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Suspends</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+function testSameContent(video1, video2) {
+ if (video1.videoWidth != video2.videoWidth ||
+ video1.videoHeight != video2.videoHeight) {
+ ok(false, `${video1.token} video1 and video2 have different dimensions.`);
+ return;
+ }
+
+ let w = video1.videoWidth;
+ let h = video1.videoHeight;
+ let c1 = document.createElement('canvas');
+ let c2 = document.createElement('canvas');
+ c1.width = w;
+ c1.height = h;
+ c2.width = w;
+ c2.height = h;
+
+ let gfx1 = c1.getContext('2d');
+ let gfx2 = c2.getContext('2d');
+ if (!gfx1 || !gfx2) {
+ ok(false, "Unable to obtain context '2d' from canvas");
+ return;
+ }
+
+ gfx1.drawImage(video1, 0, 0, w, h);
+ gfx2.drawImage(video2, 0, 0, w, h);
+
+ // Get content out.
+ let contentWidth = 4;
+ let contentHeight = 4;
+ let imageData1 = gfx1.getImageData(0, 0, contentWidth, contentHeight);
+ let imageData2 = gfx2.getImageData(0, 0, contentWidth, contentHeight);
+ let pixels1 = imageData1.data;
+ let pixels2 = imageData2.data;
+
+ // Check that the content of two video are identical.
+ for (let i = 0; i < contentWidth*contentHeight; i++) {
+ let pixelCount = 4 * i;
+ if (pixels1[pixelCount+0] != pixels2[pixelCount+0] ||
+ pixels1[pixelCount+1] != pixels2[pixelCount+1] ||
+ pixels1[pixelCount+2] != pixels2[pixelCount+2] ||
+ pixels1[pixelCount+3] != pixels2[pixelCount+3]) {
+ ok(false, `${video1.token} video1 and video2 have different content.`);
+ return;
+ }
+ }
+
+ ok(true, `${video1.token} video1 and video2 have identical content.`);
+}
+
+function waitUntilSeekToLastFrame(video) {
+ Log(video.token, "Waiting for seeking to the last frame");
+ function callSeekToNextFrame() {
+ video.seekToNextFrame().then(
+ () => {
+ if (!video.seenEnded) {
+ callSeekToNextFrame();
+ }
+ },
+ () => {
+ // When seek reaches the end, the promise is resolved before 'ended'
+ // is fired. The resolver calls callSeekToNextFrame() to schedule
+ // another seek and then the 'ended' handler calls finish() to shut
+ // down the MediaDecoder which will reject the seek promise. So we don't
+ // raise an error in this case.
+ ok(video.seenEnded, "seekToNextFrame() failed.");
+ }
+ );
+ }
+
+ return new Promise(function(resolve, reject) {
+ video.seenEnded = false;
+ video.addEventListener("ended", () => {
+ video.seenEnded = true;
+ resolve();
+ }, true);
+ callSeekToNextFrame(video);
+ });
+}
+
+startTest({
+ desc: "Test resume an ended video shows the last frame.",
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 100 ],
+ [ "media.dormant-on-pause-timeout-ms", -1],
+ [ "media.decoder.skip-to-next-key-frame.enabled", false]
+ ],
+ tests: gDecodeSuspendTests,
+
+ runTest: (test, token) => {
+ let v = appendVideoToDocWithoutLoad(token);
+ let vReference = appendVideoToDocWithoutLoad(token+"-ref");
+ manager.started(token);
+
+ /*
+ * This test checks that, after a video element had finished its playback,
+ * resuming video decoder should seek to the last frame.
+ * This issue was found in bug 1358057.
+ */
+ waitUntilVisible(v)
+ .then(() => {
+ return Promise.all([loadAndWaitUntilLoadedmetadata(v, test.name), loadAndWaitUntilLoadedmetadata(vReference, test.name, "auto")]);
+ })
+ .then(() => waitUntilPlaying(v))
+ .then(() => testVideoSuspendsWhenHidden(v))
+ .then(() => {
+ return Promise.all([waitUntilEnded(v), waitUntilSeekToLastFrame(vReference)]);
+ })
+ .then(() => testVideoOnlySeekCompletedWhenShown(v))
+ .then(() => {
+ testSameContent(v, vReference);
+ removeNodeAndSource(v);
+ removeNodeAndSource(vReference);
+ manager.finished(token);
+ });
+ }
+});
+</script>
diff --git a/dom/media/test/test_background_video_resume_looping_video_without_audio.html b/dom/media/test/test_background_video_resume_looping_video_without_audio.html
new file mode 100644
index 0000000000..53b380ce15
--- /dev/null
+++ b/dom/media/test/test_background_video_resume_looping_video_without_audio.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Resume suspended looping video which doesn't contain audio track</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="manifest.js"></script>
+ <script src="background_video.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+/**
+ * This test is used to ensure that the looping video (without audio track) which
+ * has been suspended can continute to playback correctly after we resume video
+ * decoding.
+ */
+async function startTest() {
+ const video = await createVisibleVideo();
+ await startVideo(video);
+ await suspendVideoDecoding(video);
+ await resumeVideoDecoding(video);
+ await checkIfVideoIsStillPlaying(video);
+ endTestAndClearVideo(video);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ 'set': [
+ ["media.test.video-suspend", true],
+ ["media.suspend-bkgnd-video.enabled", true],
+ ["media.suspend-bkgnd-video.delay-ms", 0],
+ ]}, () => {
+ startTest();
+});
+
+/**
+ * The following are test helper functions.
+ */
+async function createVisibleVideo() {
+ let video = document.createElement("video");
+ video.src = "gizmo-noaudio.webm";
+ video.controls = true;
+ video.loop = true;
+ document.body.appendChild(video);
+ info(`ensure video becomes visible`);
+ await waitUntilVisible(video);
+ return video;
+}
+
+async function startVideo(video) {
+ info(`start playing video`);
+ const played = video && await video.play().then(() => true, () => false);
+ ok(played, "video has started playing");
+}
+
+async function suspendVideoDecoding(video) {
+ info(`suspend video decoding`);
+ video.setVisible(false);
+ await nextVideoSuspends(video);
+ info(`suspended video decoding`);
+}
+
+async function resumeVideoDecoding(video) {
+ info(`resume video decoding.`);
+ video.setVisible(true);
+ await nextVideoResumes(video);
+ info(`resumed video decoding`);
+}
+
+async function checkIfVideoIsStillPlaying(video) {
+ await once(video, "timeupdate");
+ ok(!video.paused, "video is still playing after resuming video decoding.")
+}
+
+function endTestAndClearVideo(video) {
+ removeNodeAndSource(video);
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_background_video_suspend.html b/dom/media/test/test_background_video_suspend.html
new file mode 100644
index 0000000000..a6a720ad13
--- /dev/null
+++ b/dom/media/test/test_background_video_suspend.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Suspends</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<script type="text/javascript">
+ "use strict";
+
+ var manager = new MediaTestManager;
+
+ var MIN_DELAY = 100;
+
+ function testDelay(v, start, min) {
+ let end = performance.now();
+ let delay = end - start;
+ ok(delay > min, `${v.token} suspended with a delay of ${delay} ms`);
+ }
+
+ async function runTest(test, token) {
+ let video = appendVideoToDocWithoutLoad(token);
+ manager.started(token);
+
+ let visible = waitUntilVisible(video);
+ let ended = nextVideoEnded(video);
+ let playing = nextVideoPlaying(video);
+ let resumes = nextVideoResumes(video);
+ let suspends = nextVideoSuspends(video);
+
+ Log(token, "Waiting until video becomes visible");
+ await visible;
+
+ Log(token, "Waiting for metadata loaded");
+ await loadAndWaitUntilLoadedmetadata(video, test.name);
+
+ Log(token, "Start playing");
+ video.play();
+
+ Log(token, "Waiting for video playing");
+ await playing;
+
+ let start = performance.now();
+
+ Log(token, "Set hidden");
+ video.setVisible(false);
+
+ Log(token, "Waiting for video suspend");
+ await suspends;
+
+ testDelay(video, start, MIN_DELAY);
+
+ Log(token, "Set visible");
+ video.setVisible(true);
+
+ Log(token, "Waiting for video resume");
+ await resumes;
+
+ Log(token, "Waiting for ended");
+ await ended;
+
+ ok(video.currentTime >= video.duration, 'current time approximates duration.');
+
+ removeNodeAndSource(video);
+ manager.finished(token);
+ }
+
+ startTest({
+ desc: 'Test Background Video Suspends',
+ prefs: [
+ ["media.test.video-suspend", true],
+ ["media.suspend-bkgnd-video.enabled", true],
+ // Use a short delay to ensure video decode suspend happens before end
+ // of video.
+ ["media.suspend-bkgnd-video.delay-ms", MIN_DELAY],
+ ["privacy.reduceTimerPrecision", false]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest
+ });
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_background_video_suspend_ends.html b/dom/media/test/test_background_video_suspend_ends.html
new file mode 100644
index 0000000000..c16ffff290
--- /dev/null
+++ b/dom/media/test/test_background_video_suspend_ends.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Suspended Video Fires 'ended' Event</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<script type="text/javascript">
+ "use strict";
+
+ PARALLEL_TESTS = 1;
+
+ var manager = new MediaTestManager;
+
+ async function runTest(test, token) {
+ let video = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ // This test checks that 'ended' event is received for videos with
+ // suspended video decoding. This is important for looping video logic
+ // handling in HTMLMediaElement.
+
+ let ended = nextVideoEnded(video);
+ let suspends = nextVideoSuspends(video);
+
+ Log(token, "Start playing");
+ video.play();
+
+ Log(token, "Set hidden");
+ video.setVisible(false);
+
+ Log(token, "Waiting for video suspend");
+ await suspends;
+
+ Log(token, "Waiting for ended");
+ await ended;
+
+ ok(video.currentTime >= video.duration, 'current time approximates duration.');
+
+ manager.finished(token);
+ }
+
+ startTest({
+ desc: "Test Background Suspended Video Fires 'ended' Event",
+ prefs: [
+ ["media.test.video-suspend", true],
+ ["media.suspend-bkgnd-video.enabled", true],
+ // User a short delay to ensure video decode suspend happens before end
+ // of video.
+ ["media.suspend-bkgnd-video.delay-ms", 1000]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest
+ });
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_background_video_tainted_by_capturestream.html b/dom/media/test/test_background_video_tainted_by_capturestream.html
new file mode 100644
index 0000000000..5cbb40f3f7
--- /dev/null
+++ b/dom/media/test/test_background_video_tainted_by_capturestream.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Is Tainted By captureStream</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+function captureVideoAsStream(v) {
+ v.mozCaptureStream();
+}
+
+startTest({
+ desc: 'Test Background Video Is Tainted By captureStream',
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 1000 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ ok(true, `${test.name}`);
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ waitUntilPlaying(v)
+ .then(() => {
+ captureVideoAsStream(v);
+ ok(v.hasSuspendTaint(), "Video is tainted after captured");
+ return checkVideoDoesntSuspend(v);
+ })
+ .then(() => {
+ ok(true, 'Video ended before decode was suspended');
+ manager.finished(token);
+ })
+ .catch((e) => {
+ ok(false, 'Test failed: ' + e.toString());
+ manager.finished(token);
+ });
+ }
+});
+</script>
diff --git a/dom/media/test/test_background_video_tainted_by_createimagebitmap.html b/dom/media/test/test_background_video_tainted_by_createimagebitmap.html
new file mode 100644
index 0000000000..cd884f9144
--- /dev/null
+++ b/dom/media/test/test_background_video_tainted_by_createimagebitmap.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Is Tainted By createImageBitmap</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+startTest({
+ desc: 'Test Background Video Is Tainted By createImageBitmap',
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 1000 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ ok(true, `${test.name}`);
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ waitUntilPlaying(v)
+ .then(() => createImageBitmap(v))
+ .then(() => {
+ ok(v.hasSuspendTaint(), "Video is tainted after drawing to canvas");
+ return checkVideoDoesntSuspend(v);
+ })
+ .then(() => {
+ ok(true, 'Video ended before decode was suspended');
+ manager.finished(token);
+ })
+ .catch((e) => {
+ ok(false, 'Test failed: ' + e.toString());
+ manager.finished(token);
+ });
+ }
+});
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_background_video_tainted_by_drawimage.html b/dom/media/test/test_background_video_tainted_by_drawimage.html
new file mode 100644
index 0000000000..d5b8c75f3d
--- /dev/null
+++ b/dom/media/test/test_background_video_tainted_by_drawimage.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Is Tainted By drawImage</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+function drawVideoToCanvas(v) {
+ let w = v.width,
+ h = v.height,
+ c = document.createElement('canvas');
+ c.width = w;
+ c.height = h;
+ document.body.appendChild(c);
+
+ let gfx = c.getContext('2d');
+ if (!gfx) {
+ throw Error("Unable to obtain context '2d' from canvas");
+ }
+
+ gfx.drawImage(v, 0, 0, w, h);
+}
+
+startTest({
+ desc: 'Test Background Video Is Tainted By drawImage',
+ prefs: [
+ [ "media.test.video-suspend", true ],
+ [ "media.suspend-bkgnd-video.enabled", true ],
+ [ "media.suspend-bkgnd-video.delay-ms", 1000 ]
+ ],
+ tests: gDecodeSuspendTests,
+ runTest: (test, token) => {
+ ok(true, `${test.name}`);
+ let v = appendVideoToDoc(test.name, token);
+ manager.started(token);
+
+ waitUntilPlaying(v)
+ .then(() => {
+ drawVideoToCanvas(v);
+ ok(v.hasSuspendTaint(), "Video is tainted after drawing to canvas");
+ return checkVideoDoesntSuspend(v);
+ })
+ .then(() => {
+ ok(true, 'Video ended before decode was suspended');
+ manager.finished(token);
+ })
+ .catch((e) => {
+ ok(false, 'Test failed: ' + e.toString());
+ manager.finished(token);
+ });
+ }
+});
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_buffered.html b/dom/media/test/test_buffered.html
new file mode 100644
index 0000000000..86d8eec28a
--- /dev/null
+++ b/dom/media/test/test_buffered.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462957
+-->
+<head>
+ <title>Test for Bug 462957</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462957">Mozilla Bug 462957</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Test for Bug 462957; HTMLMediaElement.buffered.
+
+var manager = new MediaTestManager;
+
+function testBuffered(e) {
+ var v = e.target;
+
+ // The whole media should be buffered...
+ var b = v.buffered;
+ is(b.length, 1, v._name + ": Should be buffered in one range");
+ is(b.start(0), 0, v._name + ": First range start should be media start");
+ ok(Math.abs(b.end(0) - v.duration) < 0.1, v._name + ": First range end should be media end");
+
+ // Ensure INDEX_SIZE_ERR is thrown when we access outside the range
+ var caught = false;
+ try {
+ b.start(-1);
+ } catch (ex) {
+ caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR;
+ }
+ is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under start bounds range");
+
+ caught = false;
+ try {
+ b.end(-1);
+ } catch (ex) {
+ caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR;
+ }
+ is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under end bounds range");
+
+ caught = false;
+ try {
+ b.start(b.length);
+ } catch (ex) {
+ caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR;
+ }
+ is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over start bounds range");
+
+ caught = false;
+ try {
+ b.end(b.length);
+ } catch (ex) {
+ caught = ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR;
+ }
+ is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over end bounds range");
+
+ removeNodeAndSource(v);
+ manager.finished(v._token);
+}
+
+function fetch(url, fetched_callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.responseType = "blob";
+
+ var loaded = function (event) {
+ if (xhr.status == 200 || xhr.status == 206) {
+ ok(true, `${url}: Fetch succeeded, status=${xhr.status}`);
+ // Request fulfilled. Note sometimes we get 206... Presumably because either
+ // httpd.js or Necko cached the result.
+ fetched_callback(window.URL.createObjectURL(xhr.response));
+ } else {
+ ok(false, `${url}: Fetch failed, headers=${xhr.getAllResponseHeaders()}`);
+ }
+ };
+
+ xhr.addEventListener("load", loaded);
+ xhr.send();
+}
+
+function startTest(test, token) {
+ // Fetch the media resource using XHR so we can be sure the entire
+ // resource is loaded before we test buffered ranges. This ensures
+ // we have deterministic behaviour.
+ var onfetched = function(uri) {
+ var v = document.createElement('video');
+ v._token = token;
+ v.src = uri;
+ v._name = test.name;
+ v._test = test;
+ v.addEventListener("loadeddata", testBuffered, {once: true});
+ document.body.appendChild(v);
+ };
+
+ manager.started(token);
+ fetch(test.name, onfetched);
+}
+
+// Note: No need to set media test prefs, since we're using XHR to fetch
+// media data.
+manager.runTests(gSeekTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug1113600.html b/dom/media/test/test_bug1113600.html
new file mode 100644
index 0000000000..37a9cb0adb
--- /dev/null
+++ b/dom/media/test/test_bug1113600.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that a video element captured to a stream mid-playback can be played to the end</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+PARALLEL_TESTS = 1;
+SimpleTest.requestCompleteLog();
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.style = "background-color:#aca;";
+ v.width = 160;
+ v.height = 120;
+
+ manager.started(token);
+
+ v.src = test.name;
+
+ v.ontimeupdate = function() {
+ if (v.currentTime < test.duration / 4) {
+ // Allow some time to pass before starting the capture.
+ return;
+ }
+ v.ontimeupdate = null;
+ v.mozCaptureStreamUntilEnded();
+ info(test.name + " capture started at " + v.currentTime + ". Duration=" + test.duration);
+ };
+
+ v.onended = function() {
+ ok(true, test.name + " ended");
+ removeNodeAndSource(v);
+ manager.finished(token);
+ };
+
+ document.body.appendChild(v);
+ v.play();
+}
+
+manager.runTests(gSmallTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug1242338.html b/dom/media/test/test_bug1242338.html
new file mode 100644
index 0000000000..7b72153a6e
--- /dev/null
+++ b/dom/media/test/test_bug1242338.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Bug 1242338</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ video.preload = "metadata";
+ video.token = token;
+
+ var handler = {
+ "ontimeout": function() {
+ Log(token, "timed out");
+ }
+ };
+ manager.started(token, handler);
+
+ video.src = test.name;
+ video.name = test.name;
+
+ function finish() {
+ video.finished = true;
+ video.removeEventListener("loadedmetadata", onLoadedmetadata);
+ video.removeEventListener("ended", onEnded);
+ removeNodeAndSource(video);
+ manager.finished(video.token);
+ }
+
+ function onLoadedmetadata() {
+ // seek to the media's duration
+ var duration = video.duration;
+ console.log("onloadedmetadata(), duration = " + duration);
+ video.currentTime = duration;
+ }
+
+ function onEnded() {
+ ok(video.ended, test.name + " checking playback has ended");
+ ok(!video.finished, test.name + " shouldn't be finished");
+ ok(!video.seenEnded, test.name + " shouldn't be ended");
+ video.seenEnded = true;
+
+ ok(true, "Seeking to the duration triggers ended event");
+ finish();
+ }
+
+ video.addEventListener("loadedmetadata", onLoadedmetadata);
+ video.addEventListener("ended", onEnded);
+
+ document.body.appendChild(video);
+}
+
+manager.runTests(gSeekTests, startTest);
+
+</script>
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/media/test/test_bug1248229.html b/dom/media/test/test_bug1248229.html
new file mode 100644
index 0000000000..3165795622
--- /dev/null
+++ b/dom/media/test/test_bug1248229.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test garbage collection of captured stream (bug 1248229)</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>
+</head>
+<body onload="doTest()">
+<video id="v" src="black100x100-aspect3to2.ogv"></video>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function doTest() {
+ /* global v */
+ window.oak = v.mozCaptureStreamUntilEnded();
+ v.mozCaptureStreamUntilEnded();
+ v.play();
+
+ v.onended = function() {
+ info("Got ended.");
+ v.onended = null;
+ SpecialPowers.exactGC(function() {
+ info("GC completed.");
+ v.play();
+ SimpleTest.finish();
+ });
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug1431810_opus_downmix_to_mono.html b/dom/media/test/test_bug1431810_opus_downmix_to_mono.html
new file mode 100644
index 0000000000..647ddf0489
--- /dev/null
+++ b/dom/media/test/test_bug1431810_opus_downmix_to_mono.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: disable phase inversion in opus decoder</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<audio preload=none id="a" controls></audio>
+<audio preload=none id="b" controls></audio>
+<script class="testbody" type="text/javascript">
+/*
+ This test makes use of an (stereo) opus file with phase inversion of 180 degrees (right = -left => right + left = 0).
+ Firstly, the phase inversion is verified on a normal stereo playback.
+ Secondly, mono playback is forced which results in the phase inversion being disabled (Bug 1431810).
+*/
+SimpleTest.waitForExplicitFinish();
+
+/* global a, b */
+
+function areChannelsInverted(b1, b2) {
+ for (var i = 0; i < b1.length; i++) {
+ if (Math.abs(b1[i] + b2[i]) > 9e-2) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function areChannelsEqual(b1, b2) {
+ for (var i = 0; i < b1.length; i++) {
+ if (Math.abs(b1[i] - b2[i]) > 9e-3) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function isSilent(b) {
+ for (var i = 0; i < b.length; i++) {
+ if (b[i] != 0.0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function mediaElementWithPhaseInversion(audioContext, mediaElement, success) {
+ let audio_source = audioContext.createMediaElementSource(mediaElement);
+ let script_processor = audioContext.createScriptProcessor();
+ audio_source.connect(script_processor);
+
+ mediaElement.onplay = () => {
+ script_processor.onaudioprocess = (e) => {
+ let right = e.inputBuffer.getChannelData(0);
+ let left = e.inputBuffer.getChannelData(1);
+
+ // This is leading or trailing silence
+ // produced by ScriptProcessor.
+ if (isSilent(right) && isSilent(left)) {
+ return;
+ }
+
+ ok(areChannelsInverted(right, left), "Channels must be inverted");
+ }
+ }
+
+ mediaElement.onended = () => {
+ ok(true, "End of file.");
+ mediaElement.onended = null;
+ script_processor.onaudioprocess = null;
+ success();
+ }
+
+ mediaElement.src = "test-stereo-phase-inversion-180.opus";
+ // Normal playback channels will by inverted
+ mediaElement.play();
+}
+
+function mediaElementWithPhaseInversionDisabled(audioContext, mediaElement, success) {
+ let audio_source = audioContext.createMediaElementSource(mediaElement);
+ let script_processor = audioContext.createScriptProcessor();
+ audio_source.connect(script_processor);
+
+ mediaElement.onplay = () => {
+ script_processor.onaudioprocess = (e) => {
+ let right = e.inputBuffer.getChannelData(0);
+ let left = e.inputBuffer.getChannelData(1);
+
+ // This is leading or trailing silence
+ // produced by ScriptProcessor.
+ if (isSilent(right) && isSilent(left)) {
+ return;
+ }
+
+ ok(!areChannelsInverted(right, left), "Channels must not be inverted");
+ ok(areChannelsEqual(right, left), "Channels must be equal");
+ }
+ }
+
+ mediaElement.onended = () => {
+ ok(true, "End of file.");
+ mediaElement.onended = null;
+ script_processor.onaudioprocess = null;
+ success();
+ }
+
+ mediaElement.src = "test-stereo-phase-inversion-180.opus";
+
+ // Downmix to mono will force to disable opus phase inversion
+ SpecialPowers.pushPrefEnv({"set": [["accessibility.monoaudio.enable", true]]})
+ .then(() => {
+ mediaElement.play();
+ });
+}
+
+let ac = new AudioContext();
+
+function testPhaseInversion(mediaElement) {
+ return new Promise((accept, reject) => {
+ mediaElementWithPhaseInversion(ac, a, accept);
+ });
+}
+
+function testPhaseInversionDisabled(mediaElement) {
+ return new Promise((accept, reject) => {
+ mediaElementWithPhaseInversionDisabled(ac, b, accept);
+ });
+}
+
+// Start testing
+testPhaseInversion(a)
+.then( () => testPhaseInversionDisabled(b) )
+.then( () => SimpleTest.finish() )
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug1512958.html b/dom/media/test/test_bug1512958.html
new file mode 100644
index 0000000000..2513515542
--- /dev/null
+++ b/dom/media/test/test_bug1512958.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that pausing and resuming a captured media element with audio doesn't stall</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>
+</head>
+<body>
+<audio id="a"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function dumpEvent({target, type}) {
+ info(`${target.name} GOT EVENT ${type} currentTime=${target.currentTime} ` +
+ `paused=${target.paused} ended=${target.ended} ` +
+ `readyState=${target.readyState}`);
+}
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+const a = document.getElementById('a');
+
+const events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"];
+for (let ev of events) {
+ a.addEventListener(ev, dumpEvent);
+}
+
+(async () => {
+ try {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("Timeouts for shortcutting test-timeout");
+
+ const test = getPlayableAudio(gTrackTests.filter(t => t.duration > 2));
+ if (!test) {
+ todo(false, "No playable audio");
+ return;
+ }
+
+ // Start playing and capture
+ a.src = test.name;
+ a.name = test.name;
+ const ac = new AudioContext();
+ ac.createMediaElementSource(a);
+ a.play();
+ do {
+ await new Promise(r => a.ontimeupdate = r);
+ } while(a.currentTime == 0)
+
+ // Pause to trigger recreating tracks in DecodedStream
+ a.pause();
+ await new Promise(r => a.onpause = r);
+
+ // Resuming should now work. Bug 1512958 would cause a stall because the
+ // original track wasn't ended and we'd block on it.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1512958#c5
+ a.play();
+ await new Promise(r => a.onplaying = r);
+ a.currentTime = test.duration - 1;
+ await Promise.race([
+ new Promise(res => a.onended = res),
+ wait(30000).then(() => Promise.reject(new Error("Timeout"))),
+ ]);
+ } catch(e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug1553262.html b/dom/media/test/test_bug1553262.html
new file mode 100644
index 0000000000..19b937f7fe
--- /dev/null
+++ b/dom/media/test/test_bug1553262.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Bug 1553262</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+ const canvas = document.createElement('canvas')
+ const context = canvas.getContext('2d')
+
+ const video = document.createElement('video')
+ const source = new AudioContext().createMediaElementSource(video)
+ const stream = canvas.captureStream()
+
+ const xhr = new XMLHttpRequest()
+
+ context.rect(256, -32768, 16, 16)
+ video.srcObject = stream
+ for (let i = 0; i < 16; i++) {
+ xhr.open('P', '', false)
+ xhr.send()
+ }
+
+ video.srcObject = stream
+ ok(true, "success")
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_bug448534.html b/dom/media/test/test_bug448534.html
new file mode 100644
index 0000000000..ed1135d85a
--- /dev/null
+++ b/dom/media/test/test_bug448534.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=448534
+-->
+
+<head>
+ <title>Test for Bug 448534</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448535">Mozilla Bug 448534</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function loaded(event) {
+ var v = event.target;
+ info(v.token + ": event=" + event.type);
+ if (v._finished)
+ return;
+ v.play();
+}
+
+function started(event) {
+ var v = event.target;
+ info(v.token + ": event=" + event.type);
+ // For a short file, it could reach the end before 'play' received. We will
+ // skip the test for 'paused' would be true when ended.
+ if (v._finished || v.ended)
+ return;
+ ok(!v.paused, v.token + ": Video should not be paused while playing");
+ v.remove();
+ v._played = true;
+}
+
+function stopped(event) {
+ var v = event.target;
+ info(v.token + ": event=" + event.type);
+ if (v._finished)
+ return;
+ v._finished = true;
+ ok(v.paused, v.token + ": Video should be paused after removing from the Document");
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "metadata";
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._played = false;
+ v._finished = false;
+ v.addEventListener("loadedmetadata", loaded);
+ v.addEventListener("play", started);
+ v.addEventListener("pause", stopped);
+ document.body.appendChild(v);
+}
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug463162.xhtml b/dom/media/test/test_bug463162.xhtml
new file mode 100644
index 0000000000..a84b3488d1
--- /dev/null
+++ b/dom/media/test/test_bug463162.xhtml
@@ -0,0 +1,78 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=463162
+-->
+<head>
+ <title>Test for Bug 463162</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463162">Mozilla Bug 463162</a>
+
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+var gExpectedResult = {
+ 'a1' : 'error',
+ 'a2' : 'loaded',
+ 'a3' : 'loaded',
+ 'a4' : 'error',
+};
+
+var gResultCount = 0;
+
+function onError(event, id) {
+ is('error', gExpectedResult[id], 'unexpected error loading ' + id);
+ gResultCount++;
+ dump('error('+id+') expected ' + gExpectedResult[id] + ' gResultCount=' + gResultCount + '\n');
+ if (gResultCount == 4)
+ SimpleTest.finish();
+}
+
+function onMetaData(id) {
+ is('loaded', gExpectedResult[id], 'unexpected loadedmetadata loading ' + id);
+ gResultCount++;
+ dump('onMetaData('+id+') expected ' + gExpectedResult[id] + ' gResultCount=' + gResultCount + '\n');
+ if (gResultCount == 4)
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+<video id="a1" preload="metadata" onloadedmetadata="onMetaData('a1');"><sauce/><source type="bad" src="404" onerror="onError(event, 'a1');"/></video>
+<video id="a2" preload="metadata" onloadedmetadata="onMetaData('a2');"><source onerror="onError(event, 'a2');"/></video>
+<video id="a3" preload="metadata" onloadedmetadata="onMetaData('a3');"><html:source onerror="onError(event, 'a3');"/></video>
+<video id="a4" preload="metadata" onloadedmetadata="onMetaData('a4');"><svg:source/><source onerror="onError(event, 'a4');" type="bad" src="404"/></video>
+
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+function setSource(id, res) {
+ var v = document.getElementById(id);
+ v.firstChild.src = res.name;
+ v.firstChild.type = res.type;
+}
+
+var t = getPlayableVideo(gSmallTests);
+
+setSource('a1', t);
+setSource('a2', t);
+setSource('a3', t);
+setSource('a4', t);
+
+SimpleTest.waitForExplicitFinish();
+
+]]>
+</script>
+
+<pre id="test">
+
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug465498.html b/dom/media/test/test_bug465498.html
new file mode 100644
index 0000000000..157a972e77
--- /dev/null
+++ b/dom/media/test/test_bug465498.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: Bug 465498 - Seeking after playback ended</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=465498">Mozilla Bug 465498</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(e) {
+ var v = e.target;
+ info(v._name + " loadedmetadata");
+ e.target.play();
+}
+
+function playbackEnded(e) {
+ var v = e.target;
+ info(v._name + " ended");
+ if (v._finished)
+ return;
+ ok(v.currentTime >= v.duration - 0.1 && v.currentTime <= v.duration + 0.1,
+ "Checking currentTime at end: " + v.currentTime + " for " + v._name);
+ ok(v.ended, "Checking playback has ended for " + v._name);
+ v.pause();
+ v.currentTime = 0;
+ ok(!v.ended, "Checking ended is no longer true for " + v._name);
+ v._seeked = true;
+}
+
+function seekEnded(e) {
+ var v = e.target;
+ info(v._name + " seeked");
+ if (v._finished)
+ return;
+ ok(v.currentTime == 0, "Checking currentTime after seek: " +
+ v.currentTime + " for " + v._name);
+ ok(!v.ended, "Checking ended is false for " + v._name);
+ v._finished = true;
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function seeking(e) {
+ var v = e.target;
+ info(v._name + " seeking");
+}
+
+function initTest(test, token) {
+ var type = getMajorMimeType(test.type);
+ var v = document.createElement(type);
+ if (!v.canPlayType(test.type))
+ return;
+ v.preload = "metadata";
+ v.token = token;
+ manager.started(token);
+ v._name = test.name;
+
+ var s = document.createElement("source");
+ s.type = test.type;
+ s.src = test.name;
+ v.appendChild(s);
+
+ v._seeked = false;
+ v._finished = false;
+ v.addEventListener("loadedmetadata", startTest);
+ v.addEventListener("ended", playbackEnded);
+ v.addEventListener("seeked", seekEnded);
+ v.addEventListener("seeking", seeking);
+ document.body.appendChild(v);
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug495145.html b/dom/media/test/test_bug495145.html
new file mode 100644
index 0000000000..3686e10270
--- /dev/null
+++ b/dom/media/test/test_bug495145.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=495145
+-->
+
+<head>
+ <title>Bug 495145 - pausing while ended shouldn't cause problems</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>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=495145">Mozilla Bug 495145</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function start(e) {
+ e.target.play();
+}
+
+function ended1(e) {
+ var v = e.target;
+ if (v._finished)
+ return;
+
+ ++v._endCount;
+ if (v._endCount == 2) {
+ ok(true, "Playing after pause while ended works for " + v._name);
+ v._finished = true;
+ v.removeEventListener("loadedmetadata", start);
+ v.removeEventListener("ended", ended1);
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+ return;
+ }
+
+ v.pause();
+ v.play();
+}
+
+function ended2(e) {
+ var v = e.target;
+ if (v._finished)
+ return;
+
+ v.pause();
+ v.currentTime = 0;
+}
+
+function seeked2(e) {
+ var v = e.target;
+ if (v._finished)
+ return;
+
+ ok(v.paused, "Paused after seek after pause while ended for " + v._name);
+ v._finished = true;
+ v.removeEventListener("loadedmetadata", start);
+ v.removeEventListener("ended", ended2);
+ v.removeEventListener("seeked", seeked2);
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function createVideo(test, x, token) {
+ var v = document.createElement('video');
+ v.preload = "metadata";
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._name = test.name + "#" + x;
+ v._endCount = 0;
+ v._finished = false;
+ v.addEventListener("loadedmetadata", start);
+ v.addEventListener("ended", x == 1 ? ended1 : ended2);
+ if (x == 2)
+ v.addEventListener("seeked", seeked2);
+ document.body.appendChild(v);
+}
+
+function startTest(test, token) {
+ createVideo(test, 1, token + "a");
+ createVideo(test, 2, token + "b");
+}
+
+manager.runTests(gSmallTests, startTest);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug495300.html b/dom/media/test/test_bug495300.html
new file mode 100644
index 0000000000..bf3b921402
--- /dev/null
+++ b/dom/media/test/test_bug495300.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=495300
+-->
+
+<head>
+ <title>Bug 495300 - seeking to the end should behave as "ended"</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=495300">Mozilla Bug 495300</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function filename(uri) {
+ return uri.substr(uri.lastIndexOf("/")+1);
+}
+
+function mediaEnded(event) {
+ ok(true, "Got expected 'ended' event: " + filename(event.target.currentSrc));
+
+ if (event.target._expectedDuration)
+ ok(Math.abs(event.target.currentTime - event.target._expectedDuration) < 0.1,
+ "currentTime equals duration: " + filename(event.target.currentSrc));
+
+ event.target.removeEventListener("ended", mediaEnded);
+ manager.finished(event.target.token);
+ removeNodeAndSource(event.target);
+}
+
+function mediaLoadedmetadata(event) {
+ event.target.currentTime = event.target.duration;
+ event.target.removeEventListener("loadedmetadata", mediaLoadedmetadata);
+}
+
+function startTest(test, token) {
+ var elemType = /^audio/.test(test.type) ? "audio" : "video";
+ var v1 = document.createElement(elemType);
+ v1.preload = "auto";
+
+ v1.src = test.name;
+ if (test.duration) {
+ v1._expectedDuration = test.duration;
+ }
+ v1.addEventListener("loadedmetadata", mediaLoadedmetadata);
+ v1.addEventListener("ended", mediaEnded);
+ v1.load();
+
+ v1.token = token;
+ manager.started(token);
+}
+
+manager.runTests(gSeekTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug654550.html b/dom/media/test/test_bug654550.html
new file mode 100644
index 0000000000..f57afae2b4
--- /dev/null
+++ b/dom/media/test/test_bug654550.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=654550
+-->
+
+<head>
+ <title>Test for Bug 654550</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=654550">Mozilla Bug 654550</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ /* Test for Bug 654550 */
+
+ // Parallel test must be disabled for media.video_stats.enabled is a global setting
+ // to prevent the setting from changing unexpectedly in the middle of the test.
+ PARALLEL_TESTS = 1;
+ SimpleTest.waitForExplicitFinish();
+ var manager = new MediaTestManager;
+
+ function checkStats(v, aShouldBeEnabled) {
+ if (aShouldBeEnabled) {
+ ok(v.mozParsedFrames != 0,
+ "At least one value should be different from 0 if stats are enabled");
+ } else {
+ ok(!v.mozParsedFrames,
+ "mozParsedFrames should be 0 if stats are disabled");
+ ok(!v.mozDecodedFrames,
+ "mozDecodedFrames should be 0 if stats are disabled");
+ ok(!v.mozPresentedFrames,
+ "mozPresentedFrames should be 0 if stats are disabled");
+ ok(!v.mozPaintedFrames,
+ "mozPaintedFrames should be 0 if stats are disabled");
+ }
+
+ }
+
+ function ontimeupdate_statsEnabled(event) {
+ var v = event.target;
+ v.removeEventListener('timeupdate', ontimeupdate_statsEnabled);
+ checkStats(v, true);
+ SpecialPowers.popPrefEnv(
+ function() {
+ v.addEventListener("timeupdate", ontimeupdate_statsDisabled);
+ });
+ }
+
+ function ontimeupdate_statsDisabled(event) {
+ var v = event.target;
+ v.removeEventListener('timeupdate', ontimeupdate_statsDisabled);
+ checkStats(v, false);
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+ }
+
+ function startTest(test, token) {
+ var v = document.createElement('video');
+ v.token = token;
+ v.src = test.name;
+ // playback may reach the end before pref is changed for the duration is short
+ // set 'loop' to true to keep playing so that we won't miss 'timeupdate' events
+ v.loop = true;
+ manager.started(token);
+ SpecialPowers.pushPrefEnv({"set": [["media.video_stats.enabled", true]]},
+ function() {
+ v.play();
+ v.addEventListener("timeupdate", ontimeupdate_statsEnabled);
+ });
+ }
+
+ SpecialPowers.pushPrefEnv({"set": [["media.video_stats.enabled", false]]},
+ function() {
+ manager.runTests(gVideoTests, startTest);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug686942.html b/dom/media/test/test_bug686942.html
new file mode 100644
index 0000000000..66b48d69f0
--- /dev/null
+++ b/dom/media/test/test_bug686942.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=686942
+-->
+
+<head>
+ <title>Test for Bug 448534</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=686942">Mozilla Bug 686942</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function onloaded(event) {
+ var v = event.target;
+ v.removeEventListener("loadedmetadata", onloaded);
+ v.currentTime = v.duration;
+
+}
+
+function checkNotPlaying(v) {
+ ok(v.currentTime == 0, "Should not be playing after seek to end and back to beginning");
+ v._finished = true;
+ manager.finished(v.token);
+ removeNodeAndSource(v);
+}
+
+function onseeked(event) {
+ var v = event.target;
+ v.removeEventListener("seeked", onseeked);
+ setTimeout(function() { checkNotPlaying(v); }, 500);
+}
+
+function onended(event) {
+ var v = event.target;
+ v.removeEventListener("ended", onended);
+ if (v._finished)
+ return;
+ v.addEventListener("seeked", onseeked);
+ v.currentTime = 0;
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "auto";
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._played = false;
+ v._finished = false;
+ v.addEventListener("loadedmetadata", onloaded);
+ v.addEventListener("ended", onended);
+
+ document.body.appendChild(v);
+}
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug726904.html b/dom/media/test/test_bug726904.html
new file mode 100644
index 0000000000..bf8e2536ca
--- /dev/null
+++ b/dom/media/test/test_bug726904.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=726904
+-->
+
+<head>
+ <title>Media test: default video size</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>
+</head>
+<body onload="bodyLoaded();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=726904">Mozilla Bug 726904</a>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var v1 = document.createElement("video"),
+ v2 = document.createElement("video"),
+ poster = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAAAAACl1GkQAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAALJJREFUeNrtwQENAAAAwqD3T20ON6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHg0cq4AATRk8BYAAAAASUVORK5CYII",
+ resource = getPlayableVideo(gSmallTests);
+
+function bodyLoaded(){
+ // Note: For DASH, width and height would vary once the video started playing, so
+ // the values would not correlate with those in manifest.js. Since this test has
+ // no playing, this should not affect the result.
+ is(v1.videoWidth, resource.width, "Intrinsic width should match video width");
+ is(v1.videoHeight, resource.height, "Intrinsic height should match video height");
+ is(v2.clientWidth, 400, "clientWidth should be 400");
+ is(v2.clientHeight, 400, "clientHeight should be 400");
+ SimpleTest.finish();
+}
+
+if (resource) {
+ v1.poster = v2.poster = poster;
+
+ v1.src = v2.src = "http://mochi.test:8888/tests/dom/media/test/" + resource.name;
+
+ v1.preload = "auto";
+ v2.preload = "none";
+
+ v1.muted = v2.muted = true;
+
+ document.body.appendChild(v1);
+ document.body.appendChild(v2);
+} else {
+ todo(false, "No types supported");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug874897.html b/dom/media/test/test_bug874897.html
new file mode 100644
index 0000000000..7801487fe3
--- /dev/null
+++ b/dom/media/test/test_bug874897.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=874897
+-->
+
+<head>
+ <title>Test for Bug 874897</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function loadeddata(e) {
+ var v = e.target;
+ ok(v.readyState >= v.HAVE_CURRENT_DATA,
+ "readyState must be >= HAVE_CURRENT_DATA for " + v._name);
+
+ var canvas = document.createElement("canvas");
+ canvas.width = 210;
+ canvas.height = 120;
+ document.body.appendChild(canvas);
+ var ctx = canvas.getContext("2d");
+ try {
+ ctx.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, canvas.width, canvas.height);
+ ok(true, "Shouldn't throw exception while drawing to canvas from video for " + v._name);
+ } catch (ex) {
+ ok(false, "Shouldn't throw exception while drawing to canvas from video for " + v._name);
+ }
+
+ v._finished = true;
+ v.remove();
+ manager.finished(v.token);
+}
+
+function startTest(test, token) {
+ var type = getMajorMimeType(test.type);
+ if (type != "video")
+ return;
+
+ var v = document.createElement('video');
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._name = test.name;
+ v._finished = false;
+ v.autoplay = true;
+ v.style.display = "none";
+ v.addEventListener("loadeddata", loadeddata);
+ document.body.appendChild(v);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest);
+function beginTest() {
+ manager.runTests(gAspectRatioTests, startTest);
+}
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/media/test/test_bug879717.html b/dom/media/test/test_bug879717.html
new file mode 100644
index 0000000000..c669773d3f
--- /dev/null
+++ b/dom/media/test/test_bug879717.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 879717, check that a video element can be drawn into a canvas at various states of playback</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+var canvas = document.createElement('canvas');
+document.body.appendChild(canvas);
+
+var checkDrawImage = function(eventName, videoElement) {
+ var exception = null;
+ var exceptionName = "nothing";
+ try {
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
+ } catch (e) {
+ exception = e;
+ exceptionName = e.name;
+ }
+ ok(exception === null,
+ "drawImage shouldn't throw an exception on " + eventName +
+ " of " + videoElement.testName + ", got " + exceptionName);
+};
+
+var checkDrawImageEventHandler = function(ev) {
+ checkDrawImage(ev.type, ev.target);
+};
+var startTest = function(media, token) {
+ manager.started(token);
+
+ // File playback
+ var v1 = document.createElement("video");
+ v1.autoplay = true;
+
+ // Captured file playback
+ var v2 = document.createElement("video");
+
+ // Stream playback
+ var v3 = document.createElement("video");
+ v3.autoplay = true;
+
+ v1.gotLoadeddata = false;
+ v2.gotLoadeddata = false;
+ v3.gotLoadeddata = false;
+
+ v1.testName = "v1 (" + media.name + ")";
+ v2.testName = "v2 (Captured " + media.name + ")";
+ v3.testName = "v3 (Stream of " + media.name + ")";
+
+ checkDrawImage("beforeplay", v1);
+ checkDrawImage("beforeplay", v2);
+ checkDrawImage("beforeplay", v3);
+
+ v1.onloadedmetadata = checkDrawImageEventHandler;
+ v2.onloadedmetadata = checkDrawImageEventHandler;
+ v3.onloadedmetadata = checkDrawImageEventHandler;
+
+ v1.onplay = checkDrawImageEventHandler;
+ v2.onplay = checkDrawImageEventHandler;
+ v3.onplay = checkDrawImageEventHandler;
+
+ function onplaying(ev) {
+ if (!ev.target.gotPlaying) {
+ ev.target.gotPlaying = true;
+ checkDrawImageEventHandler(ev);
+ }
+ }
+ v1.onplaying = onplaying;
+ v2.onplaying = onplaying;
+ v3.onplaying = onplaying;
+
+ var onloadeddata = function(ev) {
+ ev.target.gotLoadeddata = true;
+ checkDrawImageEventHandler(ev);
+ };
+
+ v1.onloadeddata = onloadeddata;
+ v2.onloadeddata = onloadeddata;
+ v3.onloadeddata = onloadeddata;
+
+ var checkFinished = function() {
+ if (!v1.testFinished || !v2.testFinished || !v3.testFinished) {
+ return;
+ }
+
+ ok(v1.gotLoadeddata, v1.testName + " should have gotten the 'loadeddata' event callback");
+ ok(v2.gotLoadeddata, v2.testName + " should have gotten the 'loadeddata' event callback");
+ ok(v3.gotLoadeddata, v3.testName + " should have gotten the 'loadeddata' event callback");
+
+ manager.finished(token);
+ };
+
+ var onended = function(ev) {
+ checkDrawImageEventHandler(ev);
+ removeNodeAndSource(ev.target);
+ ev.target.testFinished = true;
+ checkFinished();
+ };
+
+ v1.onended = onended;
+ v2.onended = onended;
+ v3.onended = onended;
+
+ document.body.appendChild(v1);
+ document.body.appendChild(v2);
+ document.body.appendChild(v3);
+
+ v1.src = media.name;
+ v2.src = media.name;
+ v2.preload = 'metadata';
+
+ v2.addEventListener('loadedmetadata', function () {
+ v3.srcObject = v2.mozCaptureStreamUntilEnded();
+ v2.play();
+ });
+}
+
+manager.runTests(getPlayableVideos(gSmallTests), startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug895305.html b/dom/media/test/test_bug895305.html
new file mode 100644
index 0000000000..56ed3c775c
--- /dev/null
+++ b/dom/media/test/test_bug895305.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=895305
+https://bugzilla.mozilla.org/show_bug.cgi?id=905320
+-->
+<head>
+ <meta charset='utf-8'>
+ <title>Regression test for bug 895305 and 905320 - TextTrack* leaks</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var audio = document.createElement("audio");
+
+// Check leaking on TextTrackList objects.
+/* global ttl, ttc */
+window.ttl = audio.textTracks;
+ttl.addEventListener("click", function(){});
+
+// Check leaking on VTTCue objects.
+window.ttc = new VTTCue(3, 4, "Test.");
+ttc.addEventListener("click", function() {});
+
+// Check leaking on TextTrack objects.
+audio.addTextTrack("subtitles", "label", "en-CA");
+ttl[0].addEventListener("click", function() {});
+
+ok(true); // Need to have at least one assertion for Mochitest to be happy.
+SimpleTest.finish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_bug919265.html b/dom/media/test/test_bug919265.html
new file mode 100644
index 0000000000..40eff135e4
--- /dev/null
+++ b/dom/media/test/test_bug919265.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=919265
+-->
+<head>
+ <meta charset='utf-8'>
+ <title>Regression test for bug 919265 - Leak on VTTCue::GetCueAsHTML()</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+// We shouldn't leak upon shutdown.
+(new VTTCue(0, 0, "")).getCueAsHTML();
+
+// We need to assert something for Mochitest to be happy.
+ok(true);
+SimpleTest.finish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_can_play_type.html b/dom/media/test/test_can_play_type.html
new file mode 100644
index 0000000000..a28d137783
--- /dev/null
+++ b/dom/media/test/test_can_play_type.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+ <title>Test for Bug 469247</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script type="application/javascript">
+
+var v = document.getElementById('v');
+
+function check(type, expected) {
+ is(v.canPlayType(type), expected, type);
+}
+
+// Invalid types
+check("foo/bar", "");
+check("", "");
+check("!!!", "");
+
+mediaTestCleanup();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_can_play_type_mpeg.html b/dom/media/test/test_can_play_type_mpeg.html
new file mode 100644
index 0000000000..a4b87272c0
--- /dev/null
+++ b/dom/media/test/test_can_play_type_mpeg.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=799315
+-->
+<head>
+ <title>Test for MP4 and MP3 support</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>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script>
+
+function check_mp4(v, enabled) {
+ function check(type, expected) {
+ var ex = enabled ? expected : "";
+ is(v.canPlayType(type), ex, type + "='" + ex + "'");
+ }
+
+ check("video/mp4", "maybe");
+ check("video/x-m4v", "maybe");
+ check("audio/mp4", "maybe");
+ check("audio/x-m4a", "maybe");
+
+ // Not the MIME type that other browsers respond to, so we won't either.
+ check("audio/m4a", "");
+ check("video/m4v", "");
+
+ check("audio/aac", "maybe");
+ check("audio/aacp", "maybe");
+
+ // H.264 Constrained Baseline Profile Level 3.0, AAC-LC
+ check("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"", "probably");
+
+ // H.264 Constrained Baseline Profile Level 3.0, mp3
+ check("video/mp4; codecs=\"avc1.42E01E, mp3\"", "probably");
+
+ check("video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", "probably");
+ check("video/mp4; codecs=\"avc1.58A01E, mp4a.40.2\"", "probably");
+
+ // H.264 Main Profile Level 3.0, AAC-LC
+ check("video/mp4; codecs=\"avc1.4D401E, mp4a.40.2\"", "probably");
+ // H.264 Main Profile Level 3.1, AAC-LC
+ check("video/mp4; codecs=\"avc1.4D401F, mp4a.40.2\"", "probably");
+ // H.264 Main Profile Level 4.0, AAC-LC
+ check("video/mp4; codecs=\"avc1.4D4028, mp4a.40.2\"", "probably");
+ // H.264 High Profile Level 3.0, AAC-LC
+ check("video/mp4; codecs=\"avc1.64001E, mp4a.40.2\"", "probably");
+ // H.264 High Profile Level 3.1, AAC-LC
+ check("video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"", "probably");
+
+ check("video/mp4; codecs=\"avc1.42E01E\"", "probably");
+ check("video/mp4; codecs=\"avc1.42001E\"", "probably");
+ check("video/mp4; codecs=\"avc1.58A01E\"", "probably");
+ check("video/mp4; codecs=\"avc1.4D401E\"", "probably");
+ check("video/mp4; codecs=\"avc1.64001F\"", "probably");
+
+ // AAC-LC
+ check("audio/mp4; codecs=\"mp4a.40.2\"", "probably");
+ check("audio/mp4; codecs=mp4a.40.2", "probably");
+ check("audio/x-m4a; codecs=\"mp4a.40.2\"", "probably");
+ check("audio/x-m4a; codecs=mp4a.40.2", "probably");
+
+ check("audio/mp4; codecs=\"mp4a.40.2,\"", ""); // Invalid codecs string
+
+ // HE-AAC v1
+ check("audio/mp4; codecs=\"mp4a.40.5\"", "probably");
+ check("audio/mp4; codecs=mp4a.40.5", "probably");
+ check("audio/x-m4a; codecs=\"mp4a.40.5\"", "probably");
+ check("audio/x-m4a; codecs=mp4a.40.5", "probably");
+ // HE-AAC v2
+ check("audio/mp4; codecs=\"mp4a.40.29\"", "probably");
+
+ // Opus
+ check("audio/mp4; codecs=\"opus\"", "probably");
+ check("audio/mp4; codecs=opus", "probably");
+
+ // Flac.
+ var haveFlac = getPref("media.flac.enabled");
+ check("audio/mp4; codecs=\"flac\"", haveFlac ? "probably" : "");
+ check("audio/mp4; codecs=flac", haveFlac ? "probably" : "");
+
+ // VP9.
+ [ "video/mp4; codecs=vp9",
+ "video/mp4; codecs=\"vp9\"",
+ "video/mp4; codecs=\"vp9.0\""
+ ].forEach((codec) => {
+ // canPlayType should support VP9 in MP4...
+ check(codec, "probably");
+ if (!IsSupportedAndroid()) {
+ // VP9 codec is disabled on Android devices with no HW decoder. So skip it
+ // on Android for now.
+ ok(MediaSource.isTypeSupported(codec), "VP9 in MP4 should be supported in MSE");
+ }
+ });
+
+ var haveAV1 = getPref("media.av1.enabled");
+ check("video/mp4; codecs=\"av1\"", haveAV1 ? "probably" : "");
+}
+
+function check_mp3(v, enabled) {
+ function check(type, expected) {
+ var ex = enabled ? expected : "";
+ is(v.canPlayType(type), ex, type + "='" + ex + "'");
+ }
+
+ check("audio/mpeg", "maybe");
+ check("audio/mp3", "maybe");
+
+ check("audio/mpeg; codecs=\"mp3\"", "probably");
+ check("audio/mpeg; codecs=mp3", "probably");
+
+ check("audio/mp3; codecs=\"mp3\"", "probably");
+ check("audio/mp3; codecs=mp3", "probably");
+}
+
+function IsMacOS() {
+ return navigator.userAgent.includes("Mac OS");
+}
+
+function IsLinux() {
+ return navigator.userAgent.includes("Linux");
+}
+
+function getPref(name) {
+ return SpecialPowers.getBoolPref(name, false);
+}
+
+function IsSupportedAndroid() {
+ return getAndroidVersion() >= 14;
+}
+
+function IsJellyBeanOrLater() {
+ return getAndroidVersion() >= 16;
+}
+
+var haveMp4 =
+ getPref("media.wmf.enabled") ||
+ IsMacOS() ||
+ (IsSupportedAndroid() &&
+ (IsJellyBeanOrLater() || getPref("media.plugins.enabled"))) ||
+ (IsLinux() && getPref("media.ffmpeg.enabled"));
+
+check_mp4(document.getElementById('v'), haveMp4);
+
+var haveMp3 =
+ getPref("media.wmf.enabled") ||
+ (IsLinux() && getPref("media.ffmpeg.enabled")) ||
+ (IsSupportedAndroid() &&
+ ((IsJellyBeanOrLater() && getPref("media.android-media-codec.enabled")) ||
+ getPref("media.plugins.enabled"))) ||
+ IsMacOS();
+
+check_mp3(document.getElementById('v'), haveMp3);
+
+mediaTestCleanup();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_can_play_type_no_ogg.html b/dom/media/test/test_can_play_type_no_ogg.html
new file mode 100644
index 0000000000..2e63f191d2
--- /dev/null
+++ b/dom/media/test/test_can_play_type_no_ogg.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+ <title>Test for Bug 469247: Ogg backend disabled</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_ogg.js"></script>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function finish() {
+ mediaTestCleanup();
+ SimpleTest.finish();
+}
+
+SpecialPowers.pushPrefEnv({"set": [["media.ogg.enabled", false]]},
+ function() {
+ check_ogg(document.getElementById('v'), false, finish);
+ }
+);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_can_play_type_ogg.html b/dom/media/test/test_can_play_type_ogg.html
new file mode 100644
index 0000000000..3e44f8ba8c
--- /dev/null
+++ b/dom/media/test/test_can_play_type_ogg.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+ <title>Test for Bug 469247: Ogg backend</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_ogg.js"></script>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function finish() {
+ mediaTestCleanup();
+ SimpleTest.finish();
+}
+
+check_ogg(document.getElementById('v'), true, finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_can_play_type_wave.html b/dom/media/test/test_can_play_type_wave.html
new file mode 100644
index 0000000000..e6d4e29d86
--- /dev/null
+++ b/dom/media/test/test_can_play_type_wave.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+ <title>Test for Bug 469247: WAVE backend</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_wave.js"></script>
+<script>
+check_wave(document.getElementById('v'), true);
+
+mediaTestCleanup();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_can_play_type_webm.html b/dom/media/test/test_can_play_type_webm.html
new file mode 100644
index 0000000000..6b0e2adfad
--- /dev/null
+++ b/dom/media/test/test_can_play_type_webm.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566245
+-->
+<head>
+ <title>Test for Bug 566245: WebM backend</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566245">Mozill
+a Bug 566245</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_webm.js"></script>
+<script>
+ async function runTest() {
+ try {
+ await check_webm(document.getElementById('v'), true);
+ mediaTestCleanup();
+ } catch (e) {
+ info("Exception " + e.message);
+ ok(false, "Threw exception " + e.message);
+ }
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_chaining.html b/dom/media/test/test_chaining.html
new file mode 100644
index 0000000000..6ebf94c88d
--- /dev/null
+++ b/dom/media/test/test_chaining.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: chained ogg files.</title>
+ <meta charset='utf-8'>
+ <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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+function finish_test(element) {
+ removeNodeAndSource(element);
+ manager.finished(element.token);
+}
+
+function onended(e) {
+ var t = e.target;
+ is(t._metadataCount, t._links, "We should have received "+ t._links +
+ " metadataloaded event. " + t.src);
+
+ // If we encounter a file that has links with a different numbers of channel,
+ // we stop the decoding at the end of the first link. Hence, we report normal
+ // |seekable| and |buffered| values.
+ if (t._links != 1) {
+ is(t.seekable.length, 0, "No seekable ranges should be reported." + t.src);
+ is(t.buffered.length, 0, "No buffered region should be reported." + t.src);
+ }
+
+ is(t.played.length, 1, "The played region should be continuous." + t.src);
+
+ if (t._links != 1) {
+ var oldCurrentTime = t.currentTime;
+ t.currentTime = 0.0;
+ is(t.currentTime, oldCurrentTime,
+ "Seeking should be disabled when playing chained media." + t.src);
+ }
+
+ finish_test(t);
+}
+
+function onmetadataloaded(e) {
+ var t = e.target;
+ if (! t._metadataCount) {
+ t._metadataCount = 0;
+ }
+
+ if (t._metadataCount > 1 && t._links === 1) {
+ ok(false, "We should receive only one \"loadedmetadata\" event for a chained file we don't support.")
+ }
+
+ // We should be able to assert equality here, but somehow it fails (rarely but
+ // still) on try. Instead, we give it a little slack and assert that the index
+ // increases monotonically.
+ ok(t.mozGetMetadata().index >= t._metadataCount || t._links === 1,
+ "The metadata index value should increase." + t.src);
+
+ ok(t.currentTime >= t._prevCurrentTime,
+ "The currenttime should be increased correctly in new chained part.");
+ t._prevCurrentTime = t.currentTime;
+
+ // The files have all a user comment name 'index' that increments at each link
+ // in the chained media.
+ t._metadataCount++;
+ if (!t.playing && !t.ended) {
+ t.play();
+ }
+}
+
+function startTest(test, token) {
+ var elemType = /^audio/.test(test.type) ? "audio" : "video";
+ var element = document.createElement(elemType);
+ document.body.appendChild(element);
+ manager.started(token);
+ element._links= test.links;
+ element.src = test.name;
+ element.token = token;
+ element.controls = true;
+ element.addEventListener("loadedmetadata", onmetadataloaded);
+ element.addEventListener("ended", onended);
+ element.preload = "metadata";
+ element._prevCurrentTime = 0;
+}
+
+manager.runTests(gChainingTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_cloneElementVisually_ended_video.html b/dom/media/test/test_cloneElementVisually_ended_video.html
new file mode 100644
index 0000000000..331dda2db9
--- /dev/null
+++ b/dom/media/test/test_cloneElementVisually_ended_video.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test cloneElementVisually</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="content">
+ <h1>Original</h1>
+ <video id="original"></video>
+ <h1>Clone</h1>
+</div>
+<div id="results">
+ <h1>Results</h1>
+ <canvas id="left"></canvas>
+ <canvas id="right"></canvas>
+</div>
+
+<script type="application/javascript">
+
+/* import-globals-from cloneElementVisually_helpers.js */
+
+/**
+ * Test that when we start cloning a video that has already ended, the
+ * clone displays the last frame from the video.
+ */
+add_task(async () => {
+ await setup();
+
+ let originalVideo = document.getElementById("original");
+ let ended = waitForEventOnce(originalVideo, "ended");
+ await originalVideo.play();
+ await ended;
+
+ await withNewClone(originalVideo, async clone => {
+ await SpecialPowers.wrap(originalVideo).cloneElementVisually(clone);
+ ok(await assertVideosMatch(originalVideo, clone),
+ "Visual clone should display final frame.");
+ });
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/test_cloneElementVisually_mediastream.html b/dom/media/test/test_cloneElementVisually_mediastream.html
new file mode 100644
index 0000000000..5bfcf7673f
--- /dev/null
+++ b/dom/media/test/test_cloneElementVisually_mediastream.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test cloneElementVisually</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="content">
+ <h1>Original</h1>
+ <video id="original"></video>
+ <h1>MediaStream</h1>
+ <video id="streamTarget"></video>
+ <h1>Clone</h1>
+</div>
+<div id="results">
+ <h1>Results</h1>
+ <canvas id="left"></canvas>
+ <canvas id="right"></canvas>
+</div>
+
+<script type="application/javascript">
+
+/* import-globals-from cloneElementVisually_helpers.js */
+
+/**
+ * Test that we can clone a video that is playing a MediaStream.
+ */
+add_task(async () => {
+ await setup();
+
+ let originalVideo = document.getElementById("original");
+ let stream = originalVideo.mozCaptureStream();
+ let streamTarget = document.getElementById("streamTarget");
+ originalVideo.setAttribute("loop", true);
+ let playingPromise = waitForEventOnce(originalVideo, "playing");
+ await originalVideo.play();
+ await playingPromise;
+
+ streamTarget.srcObject = stream;
+ playingPromise = waitForEventOnce(streamTarget, "playing");
+ await streamTarget.play();
+ await playingPromise
+
+ await withNewClone(originalVideo, async clone => {
+ await SpecialPowers.wrap(streamTarget).cloneElementVisually(clone);
+
+ originalVideo.loop = false;
+ originalVideo.currentTime = originalVideo.duration - 0.1;
+ await waitForEventOnce(streamTarget, "ended");
+
+ ok(await assertVideosMatch(originalVideo, streamTarget),
+ "Should match Original video");
+ ok(await assertVideosMatch(streamTarget, clone),
+ "Should match MediaStream");
+ });
+
+ // Capturing a stream from a video "taints" it which prevents testing
+ // shutdown decoder behaviour. To avoid interfering with future tests,
+ // we replace the video.
+ let newVideo = originalVideo.cloneNode();
+ originalVideo.parentNode.replaceChild(newVideo, originalVideo);
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/test_cloneElementVisually_mediastream_multitrack.html b/dom/media/test/test_cloneElementVisually_mediastream_multitrack.html
new file mode 100644
index 0000000000..04d1ed484c
--- /dev/null
+++ b/dom/media/test/test_cloneElementVisually_mediastream_multitrack.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test cloneElementVisually</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="content">
+ <h1>Originals</h1>
+ <div id="originalContainer"></div>
+ <canvas id="canvas"></canvas>
+ <h1>MediaStream</h1>
+ <div id="streamTargetContainer"></div>
+ <h1>Clone</h1>
+</div>
+<div id="results">
+ <h1>Results</h1>
+ <canvas id="left"></canvas>
+ <canvas id="right"></canvas>
+</div>
+
+<script type="application/javascript">
+
+/* import-globals-from cloneElementVisually_helpers.js */
+
+/**
+ * Test that a clone of a video that is playing a MediaStream properly tracks
+ * the selected video track.
+ */
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.track.enabled", true],
+ ],
+ });
+
+ let originalVideo = document.createElement("video");
+ originalVideo.id = "original";
+ document.getElementById("originalContainer").appendChild(originalVideo);
+
+ let streamTarget = document.createElement("video");
+ document.getElementById("streamTargetContainer").appendChild(streamTarget);
+
+ await setup();
+
+ let [track1] = originalVideo.mozCaptureStream().getTracks();
+ let playingPromise = waitForEventOnce(originalVideo, "playing");
+ await originalVideo.play();
+ await playingPromise;
+
+ let canvas = document.getElementById("canvas");
+ canvas.width = originalVideo.videoWidth / 2;
+ canvas.height = originalVideo.videoHeight / 2;
+ let ctx = canvas.getContext("2d");
+ let [track2] = canvas.captureStream().getTracks();
+ ctx.fillStyle = "green";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ streamTarget.srcObject = new MediaStream([track1, track2]);
+ playingPromise = waitForEventOnce(streamTarget, "playing");
+ await streamTarget.play();
+ await playingPromise;
+
+ await withNewClone(originalVideo, async clone => {
+ SpecialPowers.wrap(streamTarget).cloneElementVisually(clone);
+
+ let selectedTrackIdx = streamTarget.videoTracks.selectedIndex;
+ streamTarget.videoTracks[++selectedTrackIdx % 2].selected = true;
+ await waitForEventOnce(streamTarget, "resize");
+
+ ok(await assertVideosMatch(streamTarget, clone),
+ "Should match MediaStream");
+ });
+
+ // Capturing a stream from a video "taints" it which prevents testing
+ // shutdown decoder behaviour. To avoid interfering with future tests,
+ // we replace the video.
+ let newVideo = originalVideo.cloneNode();
+ originalVideo.parentNode.replaceChild(newVideo, originalVideo);
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/test_cloneElementVisually_no_suspend.html b/dom/media/test/test_cloneElementVisually_no_suspend.html
new file mode 100644
index 0000000000..d576bf8ab3
--- /dev/null
+++ b/dom/media/test/test_cloneElementVisually_no_suspend.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test cloneElementVisually</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="content">
+ <h1>Original</h1>
+ <video id="original"></video>
+ <h1>MediaStream</h1>
+ <video id="streamTarget"></video>
+ <h1>Clone</h1>
+</div>
+<div id="results">
+ <h1>Results</h1>
+ <canvas id="left"></canvas>
+ <canvas id="right"></canvas>
+</div>
+
+<script type="application/javascript">
+
+/* import-globals-from cloneElementVisually_helpers.js */
+
+/**
+ * Tests that cloning a video prevents the decoder from being suspended
+ * if the original video stops being visible.
+ */
+add_task(async () => {
+ await setup();
+
+ let originalVideo = document.getElementById("original");
+ await setVideoSrc(originalVideo, LONG_VIDEO);
+
+ await originalVideo.play();
+
+ // Ensure that hiding and pausing this video will cause us to
+ // try suspending it.
+ await ensureVideoSuspendable(originalVideo);
+
+ await withNewClone(originalVideo, async clone => {
+ SpecialPowers.wrap(originalVideo).cloneElementVisually(clone);
+
+ // Go back to the beginning of the video to give us enough time to
+ // fail to suspend the video when it's being cloned before the
+ // video ends.
+ originalVideo.removeAttribute("loop");
+ originalVideo.currentTime = 0;
+ await waitForEventOnce(originalVideo, "seeked");
+
+ let suspendTimerFired = false;
+
+ let listener = () => {
+ suspendTimerFired = true;
+ }
+ originalVideo.addEventListener("mozstartvideosuspendtimer", listener);
+
+ // Have to do this to access normally-preffed off binding methods for some
+ // reason.
+ // See bug 1544257.
+ SpecialPowers.wrap(originalVideo).setVisible(false);
+
+ await waitForEventOnce(originalVideo, "ended");
+
+ originalVideo.removeEventListener("mozstartvideosuspendtimer", listener);
+
+ ok(!suspendTimerFired,
+ "mozstartvideosuspendtimer should not have fired.");
+
+ // Have to do this to access normally-preffed off binding methods for some
+ // reason.
+ // See bug 1544257.
+ SpecialPowers.wrap(originalVideo).setVisible(true);
+ });
+
+ await originalVideo.play();
+
+ // With the clone gone, the original video should be able to suspend now.
+ await ensureVideoSuspendable(originalVideo);
+
+ await setVideoSrc(originalVideo, TEST_VIDEO_1);
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/test_cloneElementVisually_paused.html b/dom/media/test/test_cloneElementVisually_paused.html
new file mode 100644
index 0000000000..1812becdd8
--- /dev/null
+++ b/dom/media/test/test_cloneElementVisually_paused.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test cloneElementVisually</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="content">
+ <h1>Original</h1>
+ <video id="original"></video>
+ <h1>Clone</h1>
+</div>
+<div id="results">
+ <h1>Results</h1>
+ <canvas id="left"></canvas>
+ <canvas id="right"></canvas>
+</div>
+
+<script type="application/javascript">
+
+/* import-globals-from cloneElementVisually_helpers.js */
+
+/**
+ * Test that when we start cloning a paused video, the clone displays
+ * the first paused frame.
+ */
+add_task(async () => {
+ await setup();
+
+ let originalVideo = document.getElementById("original");
+ await withNewClone(originalVideo, async clone => {
+ await SpecialPowers.wrap(originalVideo).cloneElementVisually(clone);
+
+ ok(await assertVideosMatch(originalVideo, clone),
+ "Initial paused frame should match.");
+ });
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/test_cloneElementVisually_poster.html b/dom/media/test/test_cloneElementVisually_poster.html
new file mode 100644
index 0000000000..e7ba00edb0
--- /dev/null
+++ b/dom/media/test/test_cloneElementVisually_poster.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test cloneElementVisually</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="content">
+ <h1>Original</h1>
+ <video id="original"></video>
+ <h1>Clone</h1>
+</div>
+<div id="results">
+ <h1>Results</h1>
+ <canvas id="left"></canvas>
+ <canvas id="right"></canvas>
+</div>
+
+<script type="application/javascript">
+
+/* import-globals-from cloneElementVisually_helpers.js */
+
+/**
+ * Test that when we start cloning a paused video, the clone displays
+ * the first paused frame.
+ */
+add_task(async () => {
+ await setup();
+
+ let originalVideo = document.getElementById("original");
+ const POSTER_URL = "https://example.com:443/tests/dom/media/test/poster-test.jpg";
+ originalVideo.setAttribute("poster", POSTER_URL);
+
+ await withNewClone(originalVideo, async clone => {
+ SpecialPowers.wrap(originalVideo).cloneElementVisually(clone);
+ await originalVideo.play();
+ await waitForEventOnce(originalVideo, "timeupdate");
+ originalVideo.pause();
+ await waitForEventOnce(originalVideo, "pause");
+
+ ok(await assertVideosMatch(originalVideo, clone),
+ "Video with a poster should clone properly.");
+ });
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/test_cloneElementVisually_resource_change.html b/dom/media/test/test_cloneElementVisually_resource_change.html
new file mode 100644
index 0000000000..3a66906ea2
--- /dev/null
+++ b/dom/media/test/test_cloneElementVisually_resource_change.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test cloneElementVisually</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="https://example.com:443/tests/dom/media/test/cloneElementVisually_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="content">
+ <h1>Original</h1>
+ <video id="original"></video>
+ <h1>MediaStream</h1>
+ <video id="streamTarget"></video>
+ <h1>Clone</h1>
+</div>
+<div id="results">
+ <h1>Results</h1>
+ <canvas id="left"></canvas>
+ <canvas id="right"></canvas>
+</div>
+
+<script type="application/javascript">
+
+/* import-globals-from cloneElementVisually_helpers.js */
+
+/**
+ * Tests that cloning survives changes to the underlying video resource.
+ */
+add_task(async () => {
+ await setup();
+
+ let originalVideo = document.getElementById("original");
+ originalVideo.setAttribute("loop", true);
+ await originalVideo.play();
+
+ await withNewClone(originalVideo, async clone => {
+ SpecialPowers.wrap(originalVideo).cloneElementVisually(clone);
+
+ await waitForEventOnce(originalVideo, "timeupdate");
+
+ originalVideo.pause();
+ await waitForEventOnce(originalVideo, "pause");
+
+ ok(await assertVideosMatch(originalVideo, clone),
+ "Initial video should match.");
+
+ await setVideoSrc(originalVideo, TEST_VIDEO_2);
+
+ await originalVideo.play();
+ await waitForEventOnce(originalVideo, "timeupdate");
+
+ originalVideo.pause();
+ await waitForEventOnce(originalVideo, "pause");
+
+ ok(await assertVideosMatch(originalVideo, clone),
+ "New video should match.");
+ });
+
+ await setVideoSrc(originalVideo, TEST_VIDEO_1);
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/test/test_clone_media_element.html b/dom/media/test/test_clone_media_element.html
new file mode 100644
index 0000000000..35e5cd69d0
--- /dev/null
+++ b/dom/media/test/test_clone_media_element.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test: cloned media element should continue to play to the end even after the source of the original element is cleared</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// tests must run in sequence otherwise concurrent running test will also
+// update media cache which will hide the fact media cache not updated
+// after changes in media cache streams.
+PARALLEL_TESTS = 1;
+
+function startTest(test, token) {
+ manager.started(token);
+ info("Trying to load " + token);
+ var v = document.createElement('video');
+ v.preload = "metadata";
+ v.token = token;
+ v.src = test.name;
+
+ v.onloadedmetadata = function(evt) {
+ info(evt.target.token + " metadata loaded.");
+ evt.target.onloadedmetadata = null;
+ var clone = evt.target.cloneNode(false);
+ clone.token = evt.target.token;
+ clone.play();
+
+ clone.onloadstart = function(event) {
+ info("cloned " + event.target.token + " start loading.");
+ event.target.onloadstart = null;
+ removeNodeAndSource(v);
+ }
+
+ clone.onended = function(event) {
+ ok(true, "cloned " + event.target.token + " ended.");
+ event.target.onended = null;
+ removeNodeAndSource(event.target);
+ manager.finished(event.target.token);
+ }
+ }
+}
+
+var manager = new MediaTestManager;
+manager.runTests(gSmallTests.concat(gPlayedTests), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_closing_connections.html b/dom/media/test/test_closing_connections.html
new file mode 100644
index 0000000000..c5eb565447
--- /dev/null
+++ b/dom/media/test/test_closing_connections.html
@@ -0,0 +1,58 @@
+hg diff<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=479863
+-->
+<head>
+ <title>Test for Bug 479863 --- loading many connections</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="use_large_cache.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479863">Mozilla Bug 479863</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<script type="application/javascript">
+window.onload = function() {
+ ok(true, "loaded metadata for all videos");
+ mediaTestCleanup();
+ SimpleTest.finish();
+}
+
+/* With normal per-domain connection limits and a naive implementation, we
+ won't ever be able to load all these videos because the first 15 (or whatever)
+ will each take up one HTTP connection (which will be suspended) and then
+ the others will be blocked by the per-domain HTTP connection limit. We
+ pass this test by closing the connection for non-buffered videos after
+ we've got the first frame.
+*/
+
+var resource = getPlayableVideo(gClosingConnectionsTest);
+
+SimpleTest.waitForExplicitFinish();
+function beginTest() {
+ if (resource != null) {
+ for (var i=0; i<20; ++i) {
+ var v = document.createElement("video");
+ v.src = resource.name;
+ v.preload = "metadata";
+ document.body.appendChild(v);
+ }
+ } else {
+ todo(false, "No types supported");
+ }
+}
+beginTest();
+</script>
+
+<pre id="test">
+<script type="application/javascript">
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_constants.html b/dom/media/test/test_constants.html
new file mode 100644
index 0000000000..1d4a8da250
--- /dev/null
+++ b/dom/media/test/test_constants.html
@@ -0,0 +1,228 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Adapted from:
+ http://simon.html5.org/test/html/dom/interfaces/htmlelement/htmlmediaelement/const-unsigned-short/001.htm
+-->
+<head>
+ <title>Media test: constants</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<video><source></video><audio><source></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+is(HTMLElement.NETWORK_EMPTY, undefined);
+is(HTMLElement.NETWORK_IDLE, undefined);
+is(HTMLElement.NETWORK_LOADING, undefined);
+is(HTMLElement.NETWORK_NO_SOURCE, undefined);
+is(HTMLElement.HAVE_NOTHING, undefined);
+is(HTMLElement.HAVE_METADATA, undefined);
+is(HTMLElement.HAVE_CURRENT_DATA, undefined);
+is(HTMLElement.HAVE_FUTURE_DATA, undefined);
+is(HTMLElement.HAVE_ENOUGH_DATA, undefined);
+is(HTMLElement.MEDIA_ERR_ABORTED, undefined);
+is(HTMLElement.MEDIA_ERR_NETWORK, undefined);
+is(HTMLElement.MEDIA_ERR_DECODE, undefined);
+is(HTMLElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLMediaElement.NETWORK_EMPTY, 0);
+is(HTMLMediaElement.NETWORK_IDLE, 1);
+is(HTMLMediaElement.NETWORK_LOADING, 2);
+is(HTMLMediaElement.NETWORK_NO_SOURCE, 3);
+is(HTMLMediaElement.HAVE_NOTHING, 0);
+is(HTMLMediaElement.HAVE_METADATA, 1);
+is(HTMLMediaElement.HAVE_CURRENT_DATA, 2);
+is(HTMLMediaElement.HAVE_FUTURE_DATA, 3);
+is(HTMLMediaElement.HAVE_ENOUGH_DATA, 4);
+is(HTMLMediaElement.MEDIA_ERR_ABORTED, undefined);
+is(HTMLMediaElement.MEDIA_ERR_NETWORK, undefined);
+is(HTMLMediaElement.MEDIA_ERR_DECODE, undefined);
+is(HTMLMediaElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLVideoElement.NETWORK_EMPTY, 0);
+is(HTMLVideoElement.NETWORK_IDLE, 1);
+is(HTMLVideoElement.NETWORK_LOADING, 2);
+is(HTMLVideoElement.NETWORK_NO_SOURCE, 3);
+is(HTMLVideoElement.HAVE_NOTHING, 0);
+is(HTMLVideoElement.HAVE_METADATA, 1);
+is(HTMLVideoElement.HAVE_CURRENT_DATA, 2);
+is(HTMLVideoElement.HAVE_FUTURE_DATA, 3);
+is(HTMLVideoElement.HAVE_ENOUGH_DATA, 4);
+is(HTMLVideoElement.MEDIA_ERR_ABORTED, undefined);
+is(HTMLVideoElement.MEDIA_ERR_NETWORK, undefined);
+is(HTMLVideoElement.MEDIA_ERR_DECODE, undefined);
+is(HTMLVideoElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLAudioElement.NETWORK_EMPTY, 0);
+is(HTMLAudioElement.NETWORK_IDLE, 1);
+is(HTMLAudioElement.NETWORK_LOADING, 2);
+is(HTMLAudioElement.NETWORK_NO_SOURCE, 3);
+is(HTMLAudioElement.HAVE_NOTHING, 0);
+is(HTMLAudioElement.HAVE_METADATA, 1);
+is(HTMLAudioElement.HAVE_CURRENT_DATA, 2);
+is(HTMLAudioElement.HAVE_FUTURE_DATA, 3);
+is(HTMLAudioElement.HAVE_ENOUGH_DATA, 4);
+is(HTMLAudioElement.MEDIA_ERR_ABORTED, undefined);
+is(HTMLAudioElement.MEDIA_ERR_NETWORK, undefined);
+is(HTMLAudioElement.MEDIA_ERR_DECODE, undefined);
+is(HTMLAudioElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLSourceElement.NETWORK_EMPTY, undefined);
+is(HTMLSourceElement.NETWORK_IDLE, undefined);
+is(HTMLSourceElement.NETWORK_LOADING, undefined);
+is(HTMLSourceElement.NETWORK_NO_SOURCE, undefined);
+is(HTMLSourceElement.HAVE_NOTHING, undefined);
+is(HTMLSourceElement.HAVE_METADATA, undefined);
+is(HTMLSourceElement.HAVE_CURRENT_DATA, undefined);
+is(HTMLSourceElement.HAVE_FUTURE_DATA, undefined);
+is(HTMLSourceElement.HAVE_ENOUGH_DATA, undefined);
+is(HTMLSourceElement.MEDIA_ERR_ABORTED, undefined);
+is(HTMLSourceElement.MEDIA_ERR_NETWORK, undefined);
+is(HTMLSourceElement.MEDIA_ERR_DECODE, undefined);
+is(HTMLSourceElement.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(MediaError.NETWORK_EMPTY, undefined);
+is(MediaError.NETWORK_IDLE, undefined);
+is(MediaError.NETWORK_LOADING, undefined);
+is(MediaError.NETWORK_NO_SOURCE, undefined);
+is(MediaError.HAVE_NOTHING, undefined);
+is(MediaError.HAVE_METADATA, undefined);
+is(MediaError.HAVE_CURRENT_DATA, undefined);
+is(MediaError.HAVE_FUTURE_DATA, undefined);
+is(MediaError.HAVE_ENOUGH_DATA, undefined);
+is(MediaError.MEDIA_ERR_ABORTED, 1);
+is(MediaError.MEDIA_ERR_NETWORK, 2);
+is(MediaError.MEDIA_ERR_DECODE, 3);
+is(MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, 4);
+is(document.body.NETWORK_EMPTY, undefined);
+is(document.body.NETWORK_IDLE, undefined);
+is(document.body.NETWORK_LOADING, undefined);
+is(document.body.NETWORK_NO_SOURCE, undefined);
+is(document.body.HAVE_NOTHING, undefined);
+is(document.body.HAVE_METADATA, undefined);
+is(document.body.HAVE_CURRENT_DATA, undefined);
+is(document.body.HAVE_FUTURE_DATA, undefined);
+is(document.body.HAVE_ENOUGH_DATA, undefined);
+is(document.body.MEDIA_ERR_ABORTED, undefined);
+is(document.body.MEDIA_ERR_NETWORK, undefined);
+is(document.body.MEDIA_ERR_DECODE, undefined);
+is(document.body.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(document.getElementsByTagName("video")[0].NETWORK_EMPTY, 0);
+is(document.getElementsByTagName("video")[0].NETWORK_IDLE, 1);
+is(document.getElementsByTagName("video")[0].NETWORK_LOADING, 2);
+is(document.getElementsByTagName("video")[0].NETWORK_NO_SOURCE, 3);
+is(document.getElementsByTagName("video")[0].HAVE_NOTHING, 0);
+is(document.getElementsByTagName("video")[0].HAVE_METADATA, 1);
+is(document.getElementsByTagName("video")[0].HAVE_CURRENT_DATA, 2);
+is(document.getElementsByTagName("video")[0].HAVE_FUTURE_DATA, 3);
+is(document.getElementsByTagName("video")[0].HAVE_ENOUGH_DATA, 4);
+is(document.getElementsByTagName("video")[0].MEDIA_ERR_ABORTED, undefined);
+is(document.getElementsByTagName("video")[0].MEDIA_ERR_NETWORK, undefined);
+is(document.getElementsByTagName("video")[0].MEDIA_ERR_DECODE, undefined);
+is(document.getElementsByTagName("video")[0].MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(document.getElementsByTagName("audio")[0].NETWORK_EMPTY, 0);
+is(document.getElementsByTagName("audio")[0].NETWORK_IDLE, 1);
+is(document.getElementsByTagName("audio")[0].NETWORK_LOADING, 2);
+is(document.getElementsByTagName("audio")[0].NETWORK_NO_SOURCE, 3);
+is(document.getElementsByTagName("audio")[0].HAVE_NOTHING, 0);
+is(document.getElementsByTagName("audio")[0].HAVE_METADATA, 1);
+is(document.getElementsByTagName("audio")[0].HAVE_CURRENT_DATA, 2);
+is(document.getElementsByTagName("audio")[0].HAVE_FUTURE_DATA, 3);
+is(document.getElementsByTagName("audio")[0].HAVE_ENOUGH_DATA, 4);
+is(document.getElementsByTagName("audio")[0].MEDIA_ERR_ABORTED, undefined);
+is(document.getElementsByTagName("audio")[0].MEDIA_ERR_NETWORK, undefined);
+is(document.getElementsByTagName("audio")[0].MEDIA_ERR_DECODE, undefined);
+is(document.getElementsByTagName("audio")[0].MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(document.getElementsByTagName("source")[0].NETWORK_EMPTY, undefined);
+is(document.getElementsByTagName("source")[0].NETWORK_IDLE, undefined);
+is(document.getElementsByTagName("source")[0].NETWORK_LOADING, undefined);
+is(document.getElementsByTagName("source")[0].NETWORK_NO_SOURCE, undefined);
+is(document.getElementsByTagName("source")[0].HAVE_NOTHING, undefined);
+is(document.getElementsByTagName("source")[0].HAVE_METADATA, undefined);
+is(document.getElementsByTagName("source")[0].HAVE_CURRENT_DATA, undefined);
+is(document.getElementsByTagName("source")[0].HAVE_FUTURE_DATA, undefined);
+is(document.getElementsByTagName("source")[0].HAVE_ENOUGH_DATA, undefined);
+is(document.getElementsByTagName("source")[0].MEDIA_ERR_ABORTED, undefined);
+is(document.getElementsByTagName("source")[0].MEDIA_ERR_NETWORK, undefined);
+is(document.getElementsByTagName("source")[0].MEDIA_ERR_DECODE, undefined);
+is(document.getElementsByTagName("source")[0].MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLElement.prototype.NETWORK_EMPTY, undefined);
+is(HTMLElement.prototype.NETWORK_IDLE, undefined);
+is(HTMLElement.prototype.NETWORK_LOADING, undefined);
+is(HTMLElement.prototype.NETWORK_NO_SOURCE, undefined);
+is(HTMLElement.prototype.HAVE_NOTHING, undefined);
+is(HTMLElement.prototype.HAVE_METADATA, undefined);
+is(HTMLElement.prototype.HAVE_CURRENT_DATA, undefined);
+is(HTMLElement.prototype.HAVE_FUTURE_DATA, undefined);
+is(HTMLElement.prototype.HAVE_ENOUGH_DATA, undefined);
+is(HTMLElement.prototype.MEDIA_ERR_ABORTED, undefined);
+is(HTMLElement.prototype.MEDIA_ERR_NETWORK, undefined);
+is(HTMLElement.prototype.MEDIA_ERR_DECODE, undefined);
+is(HTMLElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLMediaElement.prototype.NETWORK_EMPTY, 0, "HTMLMediaElement.prototype.NETWORK_EMPTY");
+is(HTMLMediaElement.prototype.NETWORK_IDLE, 1, "HTMLMediaElement.prototype.NETWORK_IDLE");
+is(HTMLMediaElement.prototype.NETWORK_LOADING, 2, "HTMLMediaElement.prototype.NETWORK_LOADING");
+is(HTMLMediaElement.prototype.NETWORK_NO_SOURCE, 3, "HTMLMediaElement.prototype.NETWORK_NO_SOURCE");
+is(HTMLMediaElement.prototype.HAVE_NOTHING, 0, "HTMLMediaElement.prototype.HAVE_NOTHING");
+is(HTMLMediaElement.prototype.HAVE_METADATA, 1, "HTMLMediaElement.prototype.HAVE_METADATA");
+is(HTMLMediaElement.prototype.HAVE_CURRENT_DATA, 2, "HTMLMediaElement.prototype.HAVE_CURRENT_DATA");
+is(HTMLMediaElement.prototype.HAVE_FUTURE_DATA, 3, "HTMLMediaElement.prototype.HAVE_FUTURE_DATA");
+is(HTMLMediaElement.prototype.HAVE_ENOUGH_DATA, 4, "HTMLMediaElement.prototype.HAVE_ENOUGH_DATA");
+is(HTMLMediaElement.prototype.MEDIA_ERR_ABORTED, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_ABORTED");
+is(HTMLMediaElement.prototype.MEDIA_ERR_NETWORK, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_NETWORK");
+is(HTMLMediaElement.prototype.MEDIA_ERR_DECODE, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_DECODE");
+is(HTMLMediaElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined, "HTMLMediaElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED");
+is(HTMLVideoElement.prototype.NETWORK_EMPTY, 0);
+is(HTMLVideoElement.prototype.NETWORK_IDLE, 1);
+is(HTMLVideoElement.prototype.NETWORK_LOADING, 2);
+is(HTMLVideoElement.prototype.NETWORK_NO_SOURCE, 3);
+is(HTMLVideoElement.prototype.HAVE_NOTHING, 0);
+is(HTMLVideoElement.prototype.HAVE_METADATA, 1);
+is(HTMLVideoElement.prototype.HAVE_CURRENT_DATA, 2);
+is(HTMLVideoElement.prototype.HAVE_FUTURE_DATA, 3);
+is(HTMLVideoElement.prototype.HAVE_ENOUGH_DATA, 4);
+is(HTMLVideoElement.prototype.MEDIA_ERR_ABORTED, undefined);
+is(HTMLVideoElement.prototype.MEDIA_ERR_NETWORK, undefined);
+is(HTMLVideoElement.prototype.MEDIA_ERR_DECODE, undefined);
+is(HTMLVideoElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLAudioElement.prototype.NETWORK_EMPTY, 0);
+is(HTMLAudioElement.prototype.NETWORK_IDLE, 1);
+is(HTMLAudioElement.prototype.NETWORK_LOADING, 2);
+is(HTMLAudioElement.prototype.NETWORK_NO_SOURCE, 3);
+is(HTMLAudioElement.prototype.HAVE_NOTHING, 0);
+is(HTMLAudioElement.prototype.HAVE_METADATA, 1);
+is(HTMLAudioElement.prototype.HAVE_CURRENT_DATA, 2);
+is(HTMLAudioElement.prototype.HAVE_FUTURE_DATA, 3);
+is(HTMLAudioElement.prototype.HAVE_ENOUGH_DATA, 4);
+is(HTMLAudioElement.prototype.MEDIA_ERR_ABORTED, undefined);
+is(HTMLAudioElement.prototype.MEDIA_ERR_NETWORK, undefined);
+is(HTMLAudioElement.prototype.MEDIA_ERR_DECODE, undefined);
+is(HTMLAudioElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(HTMLSourceElement.prototype.NETWORK_EMPTY, undefined);
+is(HTMLSourceElement.prototype.NETWORK_IDLE, undefined);
+is(HTMLSourceElement.prototype.NETWORK_LOADING, undefined);
+is(HTMLSourceElement.prototype.NETWORK_NO_SOURCE, undefined);
+is(HTMLSourceElement.prototype.HAVE_NOTHING, undefined);
+is(HTMLSourceElement.prototype.HAVE_METADATA, undefined);
+is(HTMLSourceElement.prototype.HAVE_CURRENT_DATA, undefined);
+is(HTMLSourceElement.prototype.HAVE_FUTURE_DATA, undefined);
+is(HTMLSourceElement.prototype.HAVE_ENOUGH_DATA, undefined);
+is(HTMLSourceElement.prototype.MEDIA_ERR_ABORTED, undefined);
+is(HTMLSourceElement.prototype.MEDIA_ERR_NETWORK, undefined);
+is(HTMLSourceElement.prototype.MEDIA_ERR_DECODE, undefined);
+is(HTMLSourceElement.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, undefined);
+is(MediaError.prototype.NETWORK_EMPTY, undefined);
+is(MediaError.prototype.NETWORK_IDLE, undefined);
+is(MediaError.prototype.NETWORK_LOADING, undefined);
+is(MediaError.prototype.NETWORK_NO_SOURCE, undefined);
+is(MediaError.prototype.HAVE_NOTHING, undefined);
+is(MediaError.prototype.HAVE_METADATA, undefined);
+is(MediaError.prototype.HAVE_CURRENT_DATA, undefined);
+is(MediaError.prototype.HAVE_FUTURE_DATA, undefined);
+is(MediaError.prototype.HAVE_ENOUGH_DATA, undefined);
+is(MediaError.prototype.MEDIA_ERR_ABORTED, 1);
+is(MediaError.prototype.MEDIA_ERR_NETWORK, 2);
+is(MediaError.prototype.MEDIA_ERR_DECODE, 3);
+is(MediaError.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED, 4);
+ok(document.getElementsByTagName("video")[0].buffered instanceof TimeRanges, "video.buffered must be TimeRanges object");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_controls.html b/dom/media/test/test_controls.html
new file mode 100644
index 0000000000..5c9015fdf2
--- /dev/null
+++ b/dom/media/test/test_controls.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: controls</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<video id='v1'></video><audio id='a1'></audio>
+<video id='v2' controls></video><audio id='a2' controls></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+var v2 = document.getElementById('v2');
+var a2 = document.getElementById('a2');
+ok(!v1.controls, "v1.controls should be false by default");
+ok(!a1.controls, "v1.controls should be false by default");
+ok(v2.controls, "v2.controls should be true");
+ok(a2.controls, "v2.controls should be true");
+v2.controls=false;
+a2.controls=false;
+ok(!v2.controls, "v2.controls should be false");
+ok(!a2.controls, "a2.controls should be false");
+v2.controls=true;
+a2.controls=true;
+ok(v2.controls, "v2.controls should be true");
+ok(a2.controls, "a2.controls should be true");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_cueless_webm_seek-1.html b/dom/media/test/test_cueless_webm_seek-1.html
new file mode 100644
index 0000000000..db58a89665
--- /dev/null
+++ b/dom/media/test/test_cueless_webm_seek-1.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657791
+-->
+<head>
+ <title>Test for Bug 657791</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Subset of seek tests for cueless WebMs. When random seeking (rather than just
+// in buffered ranges) is implemented for WebM, these tests can be removed and
+// the cueless WebM(s) references can be moved to the general test_seek test
+// array.
+// Test array is defined in manifest.js
+
+var manager = new MediaTestManager;
+
+// Exercise functionality as in test_seek-1
+function testWebM1(e) {
+ var v = e.target;
+ v.removeEventListener('loadeddata', testWebM1);
+
+ var startPassed = false;
+ var endPassed = false;
+ var seekFlagStart = false;
+ var seekFlagEnd = false;
+ var readonly = true;
+ var completed = false;
+
+ ok(v.buffered.length >= 1, "Should have a buffered range");
+ var halfBuffered = v.buffered.end(0) / 2;
+
+ function start() {
+ is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start");
+ is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end");
+ ok(!completed, "Should not be completed yet");
+ ok(!v.seeking, "seeking should default to false");
+ try {
+ v.seeking = true;
+ readonly = v.seeking === false;
+ }
+ catch(ex) {
+ readonly = "threw exception: " + ex;
+ }
+ is(readonly, true, "seeking should be readonly");
+
+ v.currentTime = halfBuffered;
+ seekFlagStart = v.seeking;
+ }
+
+ function seekStarted() {
+ ok(!completed, "should not be completed yet");
+ startPassed = true;
+ }
+
+ function seekEnded() {
+ ok(!completed, "should not be completed yet");
+ ok(Math.abs(v.currentTime - halfBuffered) < 0.1,
+ "Video currentTime should be around " + halfBuffered + ": " + v.currentTime + " (seeked)");
+ endPassed = true;
+ seekFlagEnd = v.seeking;
+ v.play();
+ }
+
+ function playbackEnded() {
+ ok(!completed, "should not be completed yet");
+
+ completed = true;
+ ok(startPassed, "seeking event");
+ ok(endPassed, "seeked event");
+ ok(seekFlagStart, "seeking flag on start should be true");
+ ok(!seekFlagEnd, "seeking flag on end should be false");
+ removeNodeAndSource(v);
+ manager.finished(v._token);
+ }
+
+ once(v, "ended", playbackEnded);
+ once(v, "seeking", seekStarted);
+ once(v, "seeked", seekEnded);
+
+ start();
+}
+
+// Fetch the media resource using XHR so we can be sure the entire
+// resource is loaded before we test buffered ranges. This ensures
+// we have deterministic behaviour.
+function fetch(url, fetched_callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.responseType = "blob";
+
+ var loaded = function (event) {
+ if (xhr.status == 200 || xhr.status == 206) {
+ // Request fulfilled. Note sometimes we get 206... Presumably because either
+ // httpd.js or Necko cached the result.
+ fetched_callback(window.URL.createObjectURL(xhr.response));
+ } else {
+ ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+ }
+ };
+
+ xhr.addEventListener("load", loaded);
+ xhr.send();
+}
+
+function startTest(test, token) {
+ var onfetched = function(uri) {
+ var v = document.createElement('video');
+ v._token = token;
+ v.src = uri;
+ v.addEventListener("loadeddata", testWebM1);
+ document.body.appendChild(v);
+ }
+ manager.started(token);
+ fetch(test.name, onfetched);
+}
+
+SimpleTest.waitForExplicitFinish();
+manager.runTests(gCuelessWebMTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_cueless_webm_seek-2.html b/dom/media/test/test_cueless_webm_seek-2.html
new file mode 100644
index 0000000000..720cc18399
--- /dev/null
+++ b/dom/media/test/test_cueless_webm_seek-2.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657791
+-->
+<head>
+ <title>Test for Bug 657791</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Subset of seek tests for cueless WebMs. When random seeking (rather than just
+// in buffered ranges) is implemented for WebM, these tests can be removed and
+// the cueless WebM(s) references can be moved to the general test_seek test
+// array.
+// Test array is defined in manifest.js
+
+var manager = new MediaTestManager;
+
+// Exercise functionality as in test_seek-2
+function testWebM2(e) {
+ var v = e.target;
+ v.removeEventListener('loadeddata', testWebM2);
+
+ var startPassed = false;
+ var endPassed = false;
+ var completed = false;
+
+ ok(v.buffered.length >= 1, "Should have a buffered range");
+ var halfBuffered = v.buffered.end(0) / 2;
+
+ function start() {
+ if (completed)
+ return;
+
+ is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start");
+ is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end");
+ v.currentTime=halfBuffered;
+ v.play();
+ }
+
+ function seekStarted() {
+ if (completed)
+ return;
+
+ startPassed = true;
+ }
+
+ function seekEnded() {
+ if (completed)
+ return;
+
+ endPassed = true;
+ }
+
+ function playbackEnded() {
+ if (completed)
+ return;
+
+ completed = true;
+ ok(startPassed, "send seeking event");
+ ok(endPassed, "send seeked event");
+ ok(v.ended, "Checking playback has ended");
+ ok(Math.abs(v.currentTime - v.duration) <= 0.1, "Checking currentTime at end: " + v.currentTime);
+ removeNodeAndSource(v);
+ manager.finished(v._token);
+ }
+
+ v.addEventListener("ended", playbackEnded);
+ v.addEventListener("seeking", seekStarted);
+ v.addEventListener("seeked", seekEnded);
+
+ start();
+}
+
+// Fetch the media resource using XHR so we can be sure the entire
+// resource is loaded before we test buffered ranges. This ensures
+// we have deterministic behaviour.
+function fetch(url, fetched_callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.responseType = "blob";
+
+ var loaded = function (event) {
+ if (xhr.status == 200 || xhr.status == 206) {
+ // Request fulfilled. Note sometimes we get 206... Presumably because either
+ // httpd.js or Necko cached the result.
+ fetched_callback(window.URL.createObjectURL(xhr.response));
+ } else {
+ ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+ }
+ };
+
+ xhr.addEventListener("load", loaded);
+ xhr.send();
+}
+
+function startTest(test, token) {
+ var onfetched = function(uri) {
+ var v = document.createElement('video');
+ v._token = token;
+ v.src = uri;
+ v.addEventListener("loadeddata", testWebM2);
+ document.body.appendChild(v);
+ }
+ manager.started(token);
+ fetch(test.name, onfetched);
+}
+
+SimpleTest.waitForExplicitFinish();
+manager.runTests(gCuelessWebMTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_cueless_webm_seek-3.html b/dom/media/test/test_cueless_webm_seek-3.html
new file mode 100644
index 0000000000..d6e3e50d7d
--- /dev/null
+++ b/dom/media/test/test_cueless_webm_seek-3.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657791
+-->
+<head>
+ <title>Test for Bug 657791</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Subset of seek tests for cueless WebMs. When random seeking (rather than just
+// in buffered ranges) is implemented for WebM, these tests can be removed and
+// the cueless WebM(s) references can be moved to the general test_seek test
+// array.
+// Test array is defined in manifest.js
+
+var manager = new MediaTestManager;
+
+// Exercise functionality as in test_seek-3
+function testWebM3(e) {
+ var v = e.target;
+ v.removeEventListener('loadeddata', testWebM3);
+
+ var completed = false;
+ var gotTimeupdate = false;
+
+ ok(v.buffered.length >= 1, "Should have a buffered range");
+ var halfBuffered = v.buffered.end(0) / 2;
+
+ function start() {
+ if (completed)
+ return;
+
+ is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start");
+ is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end");
+ v.currentTime=halfBuffered;
+ }
+
+ function timeupdate() {
+ gotTimeupdate = true;
+ v.removeEventListener("timeupdate", timeupdate);
+ }
+
+ function seekStarted() {
+ if (completed)
+ return;
+
+ v.addEventListener("timeupdate", timeupdate);
+ }
+
+ function seekEnded() {
+ if (completed)
+ return;
+
+ var t = v.currentTime;
+ ok(Math.abs(t - halfBuffered) <= 0.1, "Video currentTime should be around " + halfBuffered + ": " + t);
+ ok(gotTimeupdate, "Should have got timeupdate between seeking and seekended");
+ completed = true;
+ removeNodeAndSource(v);
+ manager.finished(v._token);
+ }
+
+ v.addEventListener("seeking", seekStarted);
+ v.addEventListener("seeked", seekEnded);
+
+ start()
+}
+
+// Fetch the media resource using XHR so we can be sure the entire
+// resource is loaded before we test buffered ranges. This ensures
+// we have deterministic behaviour.
+function fetch(url, fetched_callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.responseType = "blob";
+
+ var loaded = function (event) {
+ if (xhr.status == 200 || xhr.status == 206) {
+ // Request fulfilled. Note sometimes we get 206... Presumably because either
+ // httpd.js or Necko cached the result.
+ fetched_callback(window.URL.createObjectURL(xhr.response));
+ } else {
+ ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+ }
+ };
+
+ xhr.addEventListener("load", loaded);
+ xhr.send();
+}
+
+function startTest(test, token) {
+ var onfetched = function(uri) {
+ var v = document.createElement('video');
+ v._token = token;
+ v.src = uri;
+ v.addEventListener("loadeddata", testWebM3);
+ document.body.appendChild(v);
+ }
+ manager.started(token);
+ fetch(test.name, onfetched);
+}
+
+SimpleTest.waitForExplicitFinish();
+manager.runTests(gCuelessWebMTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_currentTime.html b/dom/media/test/test_currentTime.html
new file mode 100644
index 0000000000..b38c8c2c53
--- /dev/null
+++ b/dom/media/test/test_currentTime.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: currentTime</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<video id='v1'></video><audio id='a1'></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+is(v1.currentTime, 0.0);
+is(a1.currentTime, 0.0);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_decode_error.html b/dom/media/test/test_decode_error.html
new file mode 100644
index 0000000000..d6d71102f1
--- /dev/null
+++ b/dom/media/test/test_decode_error.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: unknown/invalid formats raise decode error</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var ok = function (condition, name) {
+ SimpleTest.ok(condition, test.name + ": " + name);
+ }
+ var is = function (a, b, name) {
+ SimpleTest.is(a, b, test.name + ": " + name);
+ }
+
+ var v = document.createElement("video");
+ manager.started(token);
+ v.addEventListener("error", function (event) {
+ var el = event.currentTarget;
+ is(event.type, "error", "Expected event of type 'error'");
+ ok(el.error, "Element 'error' attr expected to have a value");
+ ok(el.error instanceof MediaError, "Element 'error' attr expected to be MediaError");
+ if (v.readyState == v.HAVE_NOTHING) {
+ is(el.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, "Expected media not supported error");
+ } else {
+ is(el.error.code, MediaError.MEDIA_ERR_DECODE, "Expected a decode error");
+ }
+ ok(typeof el.error.message === 'string' || el.error.message instanceof String, "Element 'message' attr expected to be a string");
+ ok(el.error.message.length > 0, "Element 'message' attr has content");
+ el._sawError = true;
+ manager.finished(token);
+ });
+
+ v.addEventListener("loadeddata", function () {
+ ok(false, "Unexpected loadeddata event");
+ manager.finished(token);
+ });
+
+ v.autoplay = true;
+ v.addEventListener("ended", function () {
+ ok(false, "Unexpected ended event");
+ manager.finished(token);
+ });
+
+ v.src = test.name; // implicitly starts a load.
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ["media.cache_size", 40000],
+ ]
+}, beginTest);
+function beginTest() {
+ manager.runTests(gDecodeErrorTests, startTest);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_decode_error_crossorigin.html b/dom/media/test/test_decode_error_crossorigin.html
new file mode 100644
index 0000000000..24c1430a5b
--- /dev/null
+++ b/dom/media/test/test_decode_error_crossorigin.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Invalid formats raise decode errors with default messages for CORS cross-origin media</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+const manager = new MediaTestManager;
+let gotErrSrcNotSupported = false;
+let gotErrDecode = false;
+
+function startTest(test, token) {
+ const is = function(a, b, name) {
+ SimpleTest.is(a, b, `${test.name}: ${name}`);
+ };
+ const v = document.createElement("video");
+ manager.started(token);
+ v.addEventListener("error", event => {
+ if (v.readyState == v.HAVE_NOTHING) {
+ is(v.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED,
+ "Expected code for a load error");
+ is(v.error.message, "Failed to open media",
+ "Expected message for a load error");
+ gotErrSrcNotSupported = true;
+ } else {
+ is(v.error.code, MediaError.MEDIA_ERR_DECODE,
+ "Expected code for a decode error");
+ is(v.error.message, "Failed to decode media",
+ "Expected message for a decode error");
+ gotErrDecode = true;
+ }
+ manager.finished(token);
+ });
+
+ v.autoplay = true;
+
+ // CORS-cross-origin URL.
+ v.src = `http://example.com/tests/dom/media/test/${test.name}`;
+}
+
+gTestPrefs.push(["media.cache_size", 40000]);
+manager.onFinished = () => {
+ ok(gotErrSrcNotSupported, "At least one test led to src-not-supported");
+ ok(gotErrDecode, "At least one test led to a decode error");
+};
+manager.runTests(gErrorTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_decoder_disable.html b/dom/media/test/test_decoder_disable.html
new file mode 100644
index 0000000000..dd0d2cc51b
--- /dev/null
+++ b/dom/media/test/test_decoder_disable.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=448600
+-->
+<head>
+ <title>Test for Bug 448600</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448600">Mozilla Bug 448600</a>
+<p id="display"></p>
+
+
+<pre id="test">
+<script type="application/javascript">
+
+function filename(uri) {
+ return uri.substr(uri.lastIndexOf("/")+1);
+}
+
+function e(id) {
+ return document.getElementById(id);
+}
+
+var gLoadError = {};
+
+gLoadError.video1 = 0;
+gLoadError.video2 = 0;
+gLoadError.video3 = 0;
+
+var gErrorCount = 0;
+
+SimpleTest.waitForExplicitFinish();
+
+function finishTest() {
+ is(e('video1').currentSrc,
+ "",
+ 'video1 currentSrc should be empty when there\'s no playable typed source children');
+ is(filename(e('video2').currentSrc),
+ filename(e('video2').src),
+ 'video2 currentSrc should match src');
+ is(filename(e('video3').currentSrc),
+ filename(e('video3').src),
+ 'video3 currentSrc should match src');
+
+ is(gLoadError.video1, 2, "Expect one error per invalid source child on video1");
+ is(gLoadError.video2, 1, "Expect one error on video2");
+ is(gLoadError.video3, 1, "Expect one error on video3");
+
+ SimpleTest.finish();
+}
+
+function videoError(event, id) {
+ gLoadError[id]++;
+ gErrorCount++;
+ if (gErrorCount >= 4) {
+ finishTest();
+ }
+}
+
+</script>
+<!-- We make the resource URIs unique to ensure that they are (re)loaded with the new disable-decoder prefs. -->
+<div id="content">
+</div>
+<script>
+function makeVideos() {
+ document.getElementById('content').innerHTML = '<video id="video1" preload="metadata"><source type="video/ogg" src="320x240.ogv?decoder_disabled=1" onerror="videoError(event, \'video1\');"/><source type="audio/wave" src="r11025_u8_c1.wav?decoder_disabled=1" id=\'s2\' onerror="videoError(event, \'video1\');"/></video><video id="video2" preload="metadata" src="320x240.ogv?decoder_disabled=2" onerror="videoError(event, \'video2\');"></video><video id="video3" preload="metadata" src="r11025_u8_c1.wav?decoder_disabled=2" onerror="videoError(event, \'video3\');"></video>';
+}
+
+SpecialPowers.pushPrefEnv({"set": [["media.ogg.enabled", false], ["media.wave.enabled", false]]}, makeVideos);
+</script>
+
+</pre>
+
+</body>
+</html>
diff --git a/dom/media/test/test_defaultMuted.html b/dom/media/test/test_defaultMuted.html
new file mode 100644
index 0000000000..77bfa3d29a
--- /dev/null
+++ b/dom/media/test/test_defaultMuted.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: defaultMuted</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="../../../dom/html/test/reflect.js"></script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=706731">Mozilla Bug 706731</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <video id='v1'></video><audio id='a1'></audio>
+ <video id='v2' muted></video><audio id='a2' muted></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ reflectBoolean({
+ element: document.createElement("video"),
+ attribute: { content: "muted", idl: "defaultMuted" },
+ });
+
+ reflectBoolean({
+ element: document.createElement("audio"),
+ attribute: { content: "muted", idl: "defaultMuted" },
+ });
+
+ var v1 = document.getElementById('v1');
+ var a1 = document.getElementById('a1');
+ var v2 = document.getElementById('v2');
+ var a2 = document.getElementById('a2');
+
+ // Check that muted state correspond to the default value.
+ is(v1.muted, false, "v1.muted should be false by default");
+ is(a1.muted, false, "a1.muted should be false by default");
+ is(v2.muted, true, "v2.muted should be true by default");
+ is(a2.muted, true, "a2.muted should be true by default");
+
+ // Changing defaultMuted value should not change current muted state.
+ v1.defaultMuted = true;
+ a1.defaultMuted = true;
+ v2.defaultMuted = false;
+ a2.defaultMuted = false;
+
+ is(v1.muted, false, "v1.muted should not have changed");
+ is(a1.muted, false, "a1.muted should not have changed");
+ is(v2.muted, true, "v2.muted should not have changed");
+ is(a2.muted, true, "a2.muted should not have changed");
+
+ mediaTestCleanup();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_delay_load.html b/dom/media/test/test_delay_load.html
new file mode 100644
index 0000000000..d10812a7c1
--- /dev/null
+++ b/dom/media/test/test_delay_load.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=479711
+-->
+<head>
+ <title>Test for Bug 479711</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>
+
+ var gRegisteredElements = [];
+ var testWindows = [];
+
+ function register(v) {
+ gRegisteredElements.push(v);
+ }
+
+ function loaded() {
+ info("onload fired!");
+
+ for (var i = 0; i < gRegisteredElements.length; ++i) {
+ var v = gRegisteredElements[i];
+ ok(v.readyState >= v.HAVE_CURRENT_DATA,
+ v._name + ":" + v.id + " is not ready before onload fired (" + v.readyState + ")");
+ }
+
+ for (i=0; i<testWindows.length; ++i) {
+ testWindows[i].close();
+ }
+
+ mediaTestCleanup();
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(loaded);
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479711">Mozilla Bug 479711</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 479711 **/
+
+function createVideo(name, type, id) {
+ var v = document.createElement("video");
+ v.preload = "metadata";
+ // Make sure each video is a unique resource
+ v.src = name + "?" + id;
+ v._name = name;
+ v.id = id;
+ register(v);
+ return v;
+}
+
+var test = getPlayableVideo(gSmallTests);
+
+// Straightforward add, causing a load.
+var v = createVideo(test.name, test.type, "1");
+document.body.appendChild(v);
+
+// Load, add, then remove.
+v = createVideo(test.name, test.type, "1");
+v.load();
+document.body.appendChild(v);
+v.remove();
+
+// Load and add.
+v = createVideo(test.name, test.type, "2");
+v.load();
+document.body.appendChild(v);
+
+// Load outside of doc.
+v = createVideo(test.name, test.type, "3");
+v.load();
+
+// Open a new window for the following test. We open it here instead of in
+// the event handler to ensure that our document load event doesn't fire while
+// window.open is spinning the event loop.
+var w = window.open("", "testWindow", "width=400,height=400");
+testWindows.push(w);
+
+v = createVideo(test.name, test.type, "4");
+v.onloadstart = function(e) {
+ // Using a new window to do this is a bit annoying, but if we use an iframe here,
+ // delaying of the iframe's load event might interfere with the firing of our load event
+ // in some confusing way. So it's simpler just to use another window.
+ w.document.body.appendChild(v);
+};
+v.load(); // load started while in this document, this doc's load will block until
+ // the video's finished loading (in the other document).
+
+if (gRegisteredElements.length > 0) {
+ SimpleTest.waitForExplicitFinish();
+} else {
+ todo(false, "No types supported");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_duration_after_error.html b/dom/media/test/test_duration_after_error.html
new file mode 100644
index 0000000000..ad6bbe414f
--- /dev/null
+++ b/dom/media/test/test_duration_after_error.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of media files that should have errors</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function checkDuration(name, e, test) {
+ if (test.duration) {
+ ok(Math.abs(e.duration - test.duration) < 0.1,
+ name + " duration (" + e.duration + ") should be around " + test.duration);
+ } else {
+ ok(false, "Test doesn't include the duration field!")
+ }
+}
+
+function startTest(test, token) {
+ manager.started(token);
+
+ let v = document.createElement('video');
+ v._loadedMetadata = false;
+ let name = test.name;
+
+ v.onloadedmetadata = function() {
+ v.onloadedmetadata = null;
+ v._loadedMetadata = true;
+ ok(v._loadedMetadata , name + " has loaded metadata.");
+ }
+
+ v.onerror = function() {
+ v.onerror = null;
+ ok(v._loadedMetadata , name + " should load metadata before getting error.");
+ checkDuration(name, v, test);
+ manager.finished(token);
+ }
+
+ v.src = name;
+ document.body.appendChild(v);
+ v.play();
+}
+
+manager.runTests(gDurationTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_autoplay.html b/dom/media/test/test_eme_autoplay.html
new file mode 100644
index 0000000000..010995c095
--- /dev/null
+++ b/dom/media/test/test_eme_autoplay.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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="https://example.com:443/tests/dom/media/test/eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* import-globals-from eme.js */
+var manager = new MediaTestManager;
+
+var EMEmanifest = [
+ {
+ name:"bipbop 10s",
+ tracks: [
+ {
+ name:"video",
+ type:"video/mp4; codecs=\"avc1.4d4015\"",
+ fragments:[ "bipbop-cenc-video-10s.mp4",
+ ]
+ }
+ ],
+ keys: {
+ "7e571d037e571d037e571d037e571d11" : "7e5733337e5733337e5733337e573311",
+ },
+ sessionType:"temporary",
+ sessionCount:1,
+ duration:10.01
+ },
+];
+
+function startTest(test, token)
+{
+ manager.started(token);
+
+ let v = document.createElement("video");
+ v.controls = true;
+ v.autoplay = true;
+
+ document.body.appendChild(v);
+
+ var eventCounts = { play: 0, playing: 0};
+ function ForbiddenEvents(e) {
+ var video = e.target;
+ ok(video.readyState >= video.HAVE_FUTURE_DATA, "Must not have received event too early");
+ is(eventCounts[e.type], 0, "event should have only be fired once");
+ eventCounts[e.type]++;
+ }
+ // Log events for debugging.
+ var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause", "durationchange", "seeking", "seeked"];
+ function logEvent(e) {
+ info("got " + e.type + " event");
+ }
+ events.forEach(function(e) {
+ v.addEventListener(e, logEvent);
+ });
+ v.addEventListener("play", ForbiddenEvents);
+ v.addEventListener("playing", ForbiddenEvents);
+
+ var gotWaitingForKey = 0;
+
+ let waitForKey = new EMEPromise;
+ v.addEventListener("waitingforkey", function() {
+ gotWaitingForKey += 1;
+ waitForKey.resolve();
+ });
+
+ v.addEventListener("loadedmetadata", function() {
+ ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v),
+ TimeStamp(token) + " isEncrypted should be true");
+ is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content");
+ });
+
+ let finish = new EMEPromise;
+ v.addEventListener("playing", function() {
+ ok(true, TimeStamp(token) + " got playing event");
+ // We expect only one waitingForKey as we delay until all sessions are ready.
+ // I.e. one waitingForKey should be fired, after which, we process all sessions,
+ // so it should not be possible to be blocked by a key after that point.
+ ok(gotWaitingForKey == 1, "Expected number 1 wait, got: " + gotWaitingForKey);
+
+ finish.resolve();
+ });
+
+ Promise.all([
+ LoadInitData(v, test, token),
+ CreateAndSetMediaKeys(v, test, token),
+ LoadTest(test, v, token, false /* do not call endOfStream */),
+ waitForKey.promise])
+ .then(values => {
+ let initData = values[0];
+ return ProcessInitData(v, test, token, initData);
+ })
+ .then(sessions => {
+ Log(token, "Updated all sessions, loading complete");
+ finish.promise.then(() => CloseSessions(v, sessions));
+ return finish.promise;
+ })
+ .catch(reason => ok(false, reason))
+ .then(() => manager.finished(token));
+}
+
+function beginTest() {
+ manager.runTests(EMEmanifest, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_canvas_blocked.html b/dom/media/test/test_eme_canvas_blocked.html
new file mode 100644
index 0000000000..714fceafb7
--- /dev/null
+++ b/dom/media/test/test_eme_canvas_blocked.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+var manager = new MediaTestManager;
+
+function startTest(test, token)
+{
+ manager.started(token);
+
+ let v = document.createElement("video");
+ v.preload = "auto"; // Required due to "canplay" not firing for MSE unless we do this.
+
+ var p1 = new EMEPromise;
+ v.addEventListener("loadeddata", function(ev) {
+ var video = ev.target;
+ var canvas = document.createElement("canvas");
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+ document.body.appendChild(canvas);
+ var ctx = canvas.getContext("2d");
+ var threwError = false;
+ try {
+ ctx.drawImage(video, 0, 0);
+ } catch (ex) {
+ threwError = true;
+ }
+ ok(threwError, TimeStamp(token) + " - Should throw an error when trying to draw EME video to canvas.");
+ p1.resolve();
+ });
+
+ let p2 = SetupEME(v, test, token);
+
+ Promise.all([p1.promise, p2])
+ .catch(reason => ok(false, reason))
+ .then(() => {
+ CleanUpMedia(v);
+ manager.finished(token);
+ });
+}
+
+function beginTest() {
+ manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_createMediaKeys_iframes.html b/dom/media/test/test_eme_createMediaKeys_iframes.html
new file mode 100644
index 0000000000..2309fd76f4
--- /dev/null
+++ b/dom/media/test/test_eme_createMediaKeys_iframes.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test creation of MediaKeys in iframes</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">
+// Helper functions.
+
+// We take navigator explicitly as an argument to avoid ambiguity in fetching
+// it. This is to avoid issues with the following
+// ```
+// iframe.contentWindow.createMediaKeys = createMediaKeys;
+// await iframe.contentWindow.createMediaKeys();
+// ```
+// If we don't pass a navigator, and just use `navigator` in the function, this
+// ends up being equivalent to
+// ```
+// iframe.contentWindow.createMediaKeys = createMediaKeys;
+// await iframe.contentWindow.createMediaKeys(window.navigator);
+// ```
+// i.e. the function will use the navigator from the global window for the top
+// browsing context, not the iframe's. This would result in the tests not
+// correctly testing within the iframe.
+async function createMediaKeys(aNavigator) {
+ const clearKeyOptions = [
+ {
+ initDataTypes: ["webm"],
+ videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }],
+ },
+ ];
+
+ let access = await aNavigator.requestMediaKeySystemAccess(
+ "org.w3.clearkey",
+ clearKeyOptions
+ );
+
+ return access.createMediaKeys();
+}
+// End helper functions.
+
+// Setup for the tests.
+add_task(async () => {
+ info("Setting up EME prefs");
+ // Wrap EME pref setup in a promise so it plays nice with add_task.
+ return new Promise(r => {
+ SetupEMEPref(r);
+ });
+});
+
+// These tests check that the following work using different iframe combinations
+// - navigator.requestMediaKeySystem(...) successfully grants access.
+// - the resulting MediaKeySystemAccess object's createMediaKeys() creates
+// MediaKeys as expected.
+
+// Same origin iframe, using src attribute, wait for onload.
+add_task(async () => {
+ info(
+ "Starting same origin iframe, using src attribute, wait for onload test"
+ );
+ let iframe = document.createElement("iframe");
+ let iframeLoadPromise = new Promise(r => {
+ iframe.onload = r;
+ });
+ iframe.src = "file_eme_createMediaKeys.html";
+ document.body.appendChild(iframe);
+ await iframeLoadPromise;
+ info("iframe loaded");
+
+ // Setup our handler for when the iframe messages to tell us if it
+ // created MediaKeys or not.
+ let iframeMessagePromise = new Promise(r => {
+ window.onmessage = message => {
+ is(
+ message.data,
+ "successCreatingMediaKeys",
+ "iframe should have posted us a message saying keys were successfully created"
+ );
+ r();
+ };
+ });
+ // Post a message to the iframe to ask it to try and create media keys.
+ iframe.contentWindow.postMessage("", "*");
+ // Wait until we've got a message back from our iframe.
+ await iframeMessagePromise;
+});
+
+// Same origin iframe, call via JS, wait for onload.
+add_task(async () => {
+ info("Starting same origin iframe, call via JS, wait for onload test");
+ let iframe = document.createElement("iframe");
+ let iframeLoadPromise = new Promise(r => {
+ iframe.onload = r;
+ });
+ iframe.src = ""; // No src iframes are same origin.
+ document.body.appendChild(iframe);
+ await iframeLoadPromise;
+ info("iframe loaded");
+
+ try {
+ iframe.contentWindow.createMediaKeys = createMediaKeys;
+ let mediaKeys = await iframe.contentWindow.createMediaKeys(
+ iframe.contentWindow.navigator
+ );
+ ok(mediaKeys, "Should get media keys");
+ } catch (e) {
+ ok(
+ false,
+ `Should not get any errors while trying to get media keys, got ${e}`
+ );
+ }
+});
+
+// Same origin iframe, call via JS, *do not* wait for onload.
+//
+// Note, sites shouldn't do this, because
+// https://bugzilla.mozilla.org/show_bug.cgi?id=543435
+// means not waiting for onload results in weird behavior, however
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1675360
+// shows sites doing this in the wild because historically this worked in
+// Firefox.
+//
+// Breaking this test case isn't necessarily against any specifications
+// I'm (bryce) aware of, but it will probably break site compat, so be really
+// sure you want to before doing so.
+add_task(async () => {
+ info(
+ "Starting same origin iframe, call via JS, *do not* wait for onload test"
+ );
+ let iframe = document.createElement("iframe");
+ let iframeLoadPromise = new Promise(r => {
+ iframe.onload = r;
+ });
+ iframe.src = ""; // No src iframes are same origin.
+ document.body.appendChild(iframe);
+ info("iframe appended (we're not waiting for load)");
+
+ try {
+ iframe.contentWindow.createMediaKeys = createMediaKeys;
+ let mediaKeys = await iframe.contentWindow.createMediaKeys(
+ iframe.contentWindow.navigator
+ );
+ ok(mediaKeys, "Should get media keys");
+
+ // We await the load to see if they keys persist through the load.
+ // This could fail if gecko internally associates the keys with the
+ // about:blank page that is replaced by the load.
+ await iframeLoadPromise;
+ ok(mediaKeys, "Media keys should still exist after the load");
+ } catch (e) {
+ ok(
+ false,
+ `Should not get any errors while trying to get media keys, got ${e}`
+ );
+ }
+});
+
+// Different origin iframe, using src attribute, wait for onload
+add_task(async () => {
+ info(
+ "Starting different origin iframe, using src attribute, wait for onload test"
+ );
+ let iframe = document.createElement("iframe");
+ let iframeLoadPromise = new Promise(r => {
+ iframe.onload = r;
+ });
+ // Make our iframe cross origin (see build/pgo/server-locations.txt for more
+ // info the url used).
+ iframe.src =
+ "https://w3c-test.org:443/tests/dom/media/test/file_eme_createMediaKeys.html";
+ iframe.allow = "encrypted-media";
+ document.body.appendChild(iframe);
+ await iframeLoadPromise;
+ info("iframe loaded");
+
+ // Setup our handler for when the iframe messages to tell us if it
+ // created MediaKeys or not.
+ let iframeMessagePromise = new Promise(r => {
+ window.onmessage = message => {
+ is(
+ message.data,
+ "successCreatingMediaKeys",
+ "iframe should have posted us a message saying keys were successfully created"
+ );
+ r();
+ };
+ });
+ // Post a message to the iframe to ask it to try and create media keys.
+ iframe.contentWindow.postMessage("", "*");
+ // Wait until we've got a message back from our iframe.
+ await iframeMessagePromise;
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_detach_media_keys.html b/dom/media/test/test_eme_detach_media_keys.html
new file mode 100644
index 0000000000..aad035db14
--- /dev/null
+++ b/dom/media/test/test_eme_detach_media_keys.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+<video id="v" controls></video>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function createAndSet() {
+ return new Promise(function(resolve, reject) {
+ var m;
+ navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig)
+ .then(function (access) {
+ return access.createMediaKeys();
+ })
+ .then(function (mediaKeys) {
+ m = mediaKeys;
+ return document.getElementById("v").setMediaKeys(mediaKeys);
+ })
+ .then(function() {
+ resolve(m);
+ });
+ }
+)}
+
+var m1,m2;
+
+// Test that if we create and set two MediaKeys on one video element,
+// that if the first MediaKeys we set on the media elemnt is still usable
+// after the second MediaKeys has been set on the media element.
+SetupEMEPref(() => {
+ createAndSet().then((m) => {
+ m1 = m; // Stash MediaKeys.
+ return createAndSet();
+ })
+ .then((m) => {
+ m2 = m;
+ is(document.getElementById("v").mediaKeys, m2, "Should have set MediaKeys on media element");
+ ok(document.getElementById("v").mediaKeys != m1, "First MediaKeys should no longer be set on media element");
+ var s = m1.createSession("temporary");
+ return s.generateRequest("webm", StringToArrayBuffer(atob('YAYeAX5Hfod+V9ANHtANHg==')));
+ })
+ .then(() => {
+ ok(true, "Was able to generateRequest using second CDM");
+ SimpleTest.finish();
+ }, () => {
+ ok(false, "Was *NOT* able to generateRequest using second CDM");
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html b/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html
new file mode 100644
index 0000000000..0e379a0e5a
--- /dev/null
+++ b/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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="https://example.com:443/tests/dom/media/test/eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<video id="v" controls></video>
+<script class="testbody" type="text/javascript">
+/* import-globals-from eme.js */
+var manager = new MediaTestManager;
+
+var EMEmanifest = [
+ {
+ name:"bipbop 10s",
+ tracks: [
+ {
+ name:"video",
+ type:"video/mp4; codecs=\"avc1.4d4015\"",
+ fragments:[ "bipbop-cenc-video-10s.mp4",
+ ]
+ }
+ ],
+ keys: {
+ "7e571d037e571d037e571d037e571d11" : "7e5733337e5733337e5733337e573311",
+ },
+ sessionType:"temporary",
+ sessionCount:1,
+ duration:10.01
+ },
+];
+
+function sleep(time) {
+ return new Promise((resolve) => setTimeout(resolve, time));
+}
+
+// To check if playback can be blocked and resumed correctly after
+// detaching original mediakeys and reattach it back.
+function startTest(test, token)
+{
+ manager.started(token);
+
+ var mk_ori;
+ let finish = new EMEPromise;
+
+ let v = document.getElementById("v");
+ let sessions = [];
+ function onSessionCreated(session) {
+ sessions.push(session);
+ }
+
+ function closeSessions() {
+ let p = new EMEPromise;
+ Promise.all(sessions.map(s => s.close()))
+ .then(p.resolve, p.reject);
+ return p.promise;
+ }
+
+ function setMediaKeysToElement(mk, solve, reject) {
+ v.setMediaKeys(mk).then(solve, reject);
+ }
+
+ function ReattachOriMediaKeys() {
+ function onOriMediaKeysSetOK() {
+ ok(true, TimeStamp(token) + " (ENCRYPTED) Set original MediaKeys back OK!");
+ }
+ function onOriMediaKeysSetFailed() {
+ ok(false, " Failed to set original mediakeys back.");
+ }
+
+ function onCanPlayAgain(ev) {
+ Promise.all([closeSessions()])
+ .then(() => {
+ ok(true, " (ENCRYPTED) Playback can be resumed.");
+ manager.finished(token);
+ }, () => {
+ ok(false, TimeStamp(token) + " Sessions are closed incorrectly.");
+ manager.finished(token);
+ });
+ }
+
+ once(v, "canplay", onCanPlayAgain);
+ setMediaKeysToElement(mk_ori, onOriMediaKeysSetOK, onOriMediaKeysSetFailed)
+ }
+
+ function triggerSeek() {
+ v.currentTime = v.duration / 2;
+ }
+
+ function onCanPlay(ev) {
+ function onSetMediaKeysToNullOK() {
+ ok(true, TimeStamp(token) + " Set MediaKeys to null. OK!");
+
+ triggerSeek();
+
+ SimpleTest.requestFlakyTimeout("To reattach mediakeys back again in 5s.");
+ sleep(5000).then(ReattachOriMediaKeys);
+ }
+ function onSetMediaKeysToNullFailed() {
+ ok(false, TimeStamp(token) + " Set MediaKeys to null. FAILED!");
+ }
+
+ SimpleTest.requestFlakyTimeout("To detach mediakeys after receiving 'canplay' event in 2s");
+ sleep(2000).then(() => {
+ setMediaKeysToElement(null, onSetMediaKeysToNullOK, onSetMediaKeysToNullFailed);
+ });
+ }
+
+ once(v, "canplay", onCanPlay);
+
+ var p1 = LoadInitData(v, test, token);
+ var p2 = CreateAndSetMediaKeys(v, test, token);
+ var p3 = LoadTest(test, v, token);
+ Promise.all([p1, p2, p3])
+ .then(values => {
+ let initData = values[0];
+ // stash the mediakeys
+ mk_ori = v.mediaKeys;
+ initData.map(ev => {
+ let session = v.mediaKeys.createSession();
+ onSessionCreated(session);
+ MakeRequest(test, token, ev, session);
+ });
+ })
+ .then(() => {
+ return finish.promise;
+ })
+ .catch(reason => ok(false, reason))
+}
+
+function beginTest() {
+ manager.runTests(EMEmanifest, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_getstatusforpolicy.html b/dom/media/test/test_eme_getstatusforpolicy.html
new file mode 100644
index 0000000000..fd2c4b11be
--- /dev/null
+++ b/dom/media/test/test_eme_getstatusforpolicy.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+<video id="v" controls></video>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function createMediaKeysAndSet() {
+ return navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig)
+ .then(function (access) {
+ return access.createMediaKeys();
+ })
+ .then(function (mediaKeys) {
+ document.getElementById("v").setMediaKeys(mediaKeys);
+ return mediaKeys;
+ });
+}
+
+function test() {
+ SetupEMEPref(() => {
+ createMediaKeysAndSet()
+ .then((m) => {
+ let video = document.getElementById("v");
+ is(video.mediaKeys, m, "Should have set MediaKeys on media element");
+ // getStatusForPolicy() is not suppored by ClearKey key system.
+ // The promise will always be rejected with NotSupportedError.
+ return video.mediaKeys.getStatusForPolicy({minHdcpVersion: "hdcp-2.0"});
+ })
+ .then((mediaKeyStatus) => {
+ ok(false, "Promise of getStatusForPolicy should not be resolved with clearkey key system");
+ })
+ // Promise rejected with NotSupportedError as expected.
+ .catch(reason => is("NotSupportedError", reason.name,
+ "Promise should be rejected with NotSupportedError."))
+ .then(() => SimpleTest.finish());
+ });
+}
+
+SpecialPowers.pushPrefEnv({"set":
+ [
+ ["media.eme.hdcp-policy-check.enabled", true],
+ ]
+ }, test);
+
+</script>
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/media/test/test_eme_initDataTypes.html b/dom/media/test/test_eme_initDataTypes.html
new file mode 100644
index 0000000000..5dfb873138
--- /dev/null
+++ b/dom/media/test/test_eme_initDataTypes.html
@@ -0,0 +1,133 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+
+var tests = [
+ {
+ name: "One keyId",
+ initDataType: 'keyids',
+ initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}',
+ expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}',
+ sessionType: 'temporary',
+ expectPass: true,
+ },
+ {
+ name: "Two keyIds",
+ initDataType: 'keyids',
+ initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
+ expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}',
+ sessionType: 'temporary',
+ expectPass: true,
+ },
+ {
+ name: "Two keyIds, temporary session",
+ initDataType: 'keyids',
+ initData: '{"type":"temporary", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
+ expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}',
+ sessionType: 'temporary',
+ expectPass: true,
+ },
+ {
+ name: "Two keyIds, persistent session, type before kids",
+ initDataType: 'keyids',
+ initData: '{"type":"persistent-license", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
+ expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"persistent-license"}',
+ sessionType: 'persistent-license',
+ expectPass: false,
+ },
+ {
+ name: "Invalid keyId",
+ initDataType: 'keyids',
+ initData: '{"kids":["0"]}',
+ sessionType: 'temporary',
+ expectPass: false,
+ },
+ {
+ name: "Empty keyId",
+ initDataType: 'keyids',
+ initData: '{"kids":[""]}',
+ sessionType: 'temporary',
+ expectPass: false,
+ },
+ {
+ name: "Invalid initData",
+ initDataType: 'keyids',
+ initData: 'invalid initData',
+ sessionType: 'temporary',
+ expectPass: false,
+ },
+ {
+ name: "'webm' initDataType",
+ initDataType: 'webm',
+ initData: 'YAYeAX5Hfod+V9ANHtANHg==',
+ expectedRequest: '{"kids":["YAYeAX5Hfod-V9ANHtANHg"],"type":"temporary"}',
+ sessionType: 'temporary',
+ expectPass: true,
+ },
+ {
+ name: "'webm' initDataType with non 16 byte keyid",
+ initDataType: 'webm',
+ initData: 'YAYeAX5Hfod',
+ expectedRequest: '{\"kids\":[\"YAYeAX5Hfoc\"],\"type\":\"temporary\"}',
+ sessionType: 'temporary',
+ expectPass: true,
+ },
+];
+
+function PrepareInitData(initDataType, initData)
+{
+ if (initDataType == "keyids") {
+ return new TextEncoder().encode(initData);
+ } else if (initDataType == "webm") {
+ return StringToArrayBuffer(atob(initData));
+ }
+}
+
+function Test(test) {
+ return new Promise(function(resolve, reject) {
+ var configs = [{
+ initDataTypes: [test.initDataType],
+ videoCapabilities: [{contentType: 'video/mp4' }],
+ }];
+ navigator.requestMediaKeySystemAccess('org.w3.clearkey', configs)
+ .then((access) => access.createMediaKeys())
+ .then((mediaKeys) => {
+ var session = mediaKeys.createSession(test.sessionType);
+ session.addEventListener("message", function(event) {
+ is(event.messageType, "license-request", "'" + test.name + "' MediaKeyMessage type should be license-request.");
+ var text = new TextDecoder().decode(event.message);
+ is(text, test.expectedRequest, "'" + test.name + "' got expected response.");
+ is(text == test.expectedRequest, test.expectPass,
+ "'" + test.name + "' expected to " + (test.expectPass ? "pass" : "fail"));
+ resolve();
+ });
+ var initData = PrepareInitData(test.initDataType, test.initData);
+ return session.generateRequest(test.initDataType, initData);
+ }
+ ).catch((x) => {
+ ok(!test.expectPass, "'" + test.name + "' expected to fail.");
+ resolve();
+ });
+ });
+}
+
+function beginTest() {
+ Promise.all(tests.map(Test)).then(function() { SimpleTest.finish(); });
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_missing_pssh.html b/dom/media/test/test_eme_missing_pssh.html
new file mode 100644
index 0000000000..d39a7c9821
--- /dev/null
+++ b/dom/media/test/test_eme_missing_pssh.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test Encrypted Media Extensions</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>
+ <audio controls id="audio"></audio>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+ // Tests that a fragmented MP4 file without a PSSH, but with valid encrypted
+ // tracks with valid TENC boxes, is able to load with EME.
+ // We setup MSE before starting up EME, so that we exercise the "waiting for
+ // cdm" step in the MediaDecoderStateMachine.
+
+ SimpleTest.waitForExplicitFinish();
+
+ var pssh = [
+ 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh')
+ 0x01, 0x00, 0x00, 0x00, // Full box header (version = 1, flags = 0)
+ 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID
+ 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+ 0x00, 0x00, 0x00, 0x01, // KID_count (1)
+ 0x2f, 0xef, 0x8a, 0xd8, 0x12, 0xdf, 0x42, 0x97,
+ 0x83, 0xe9, 0xbf, 0x6e, 0x5e, 0x49, 0x3e, 0x53,
+ 0x00, 0x00, 0x00, 0x00 // Size of Data (0)
+ ];
+
+ var audio = document.getElementById("audio");
+
+ function LoadEME() {
+ var options = [{
+ initDataType: 'cenc',
+ audioType: 'audio/mp4; codecs="mp4a.40.2"',
+ }];
+ navigator.requestMediaKeySystemAccess("org.w3.clearkey", options)
+ .then((keySystemAccess) => {
+ return keySystemAccess.createMediaKeys();
+ }, bail("Failed to request key system access."))
+
+ .then((mediaKeys) => {
+ audio.setMediaKeys(mediaKeys);
+ var session = mediaKeys.createSession();
+ once(session, "message", (message) => {
+ is(message.messageType, 'license-request', "Expected a license-request");
+ var license = new TextEncoder().encode(JSON.stringify({
+ 'keys': [{
+ 'kty':'oct',
+ 'kid':'L--K2BLfQpeD6b9uXkk-Uw',
+ 'k':HexToBase64('7f412f0575f44f718259beef56ec7771')
+ }],
+ 'type': 'temporary'
+ }));
+ session.update(license);
+ });
+ session.generateRequest('cenc', new Uint8Array(pssh));
+ });
+ }
+
+ function DownloadMedia(url, type, mediaSource) {
+ return new Promise(function(resolve, reject) {
+ var sourceBuffer = mediaSource.addSourceBuffer(type);
+ fetchWithXHR(url, (response) => {
+ once(sourceBuffer, "updateend", resolve);
+ sourceBuffer.appendBuffer(new Uint8Array(response));
+ });
+ });
+ }
+
+ function LoadMSE() {
+ var ms = new MediaSource();
+ audio.src = URL.createObjectURL(ms);
+
+ once(ms, "sourceopen", ()=>{
+ DownloadMedia('short-audio-fragmented-cenc-without-pssh.mp4', 'audio/mp4; codecs="mp4a.40.2"', ms)
+ .then(() => { ms.endOfStream(); LoadEME();});
+ });
+
+ audio.addEventListener("loadeddata", SimpleTest.finish);
+ }
+
+ SetupEMEPref(LoadMSE);
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/test/test_eme_non_mse_fails.html b/dom/media/test/test_eme_non_mse_fails.html
new file mode 100644
index 0000000000..efd87b0a95
--- /dev/null
+++ b/dom/media/test/test_eme_non_mse_fails.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1131392 - Test that EME does not work for non-MSE media</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">
+/* import-globals-from eme.js */
+var manager = new MediaTestManager;
+
+function DoSetMediaKeys(v, test, token)
+{
+ var options = [{
+ initDataTypes: ["cenc"],
+ audioCapabilities: [{contentType: test.audioType}],
+ videoCapabilities: [{contentType: test.videoType}],
+ }];
+
+ return navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, options)
+
+ .then(function(keySystemAccess) {
+ return keySystemAccess.createMediaKeys();
+ })
+
+ .catch(function() {
+ ok(false, token + " was not expecting failure (yet)");
+ })
+
+ .then(function(mediaKeys) {
+ return v.setMediaKeys(mediaKeys);
+ });
+}
+
+function TestSetMediaKeys(test, token)
+{
+ manager.started(token);
+
+ var v = document.createElement("video");
+
+ v.addEventListener("encrypted", function() {
+ ok(false, token + " should not fire encrypted event");
+ });
+
+ var loadedMetadata = false;
+ v.addEventListener("loadedmetadata", function() {
+ loadedMetadata = true;
+ });
+
+ v.addEventListener("error", function() {
+ ok(true, token + " expected error event");
+ ok(loadedMetadata, token + " expected loadedmetadata to have fired");
+ manager.finished(token);
+ });
+
+ v.src = test.name;
+}
+
+function TestSetSrc(test, token)
+{
+ manager.started(token);
+
+ var v = document.createElement("video");
+ v.addEventListener("error", function(err) {
+ ok(true, token + " got error setting src on video element, as expected");
+ manager.finished(token);
+ });
+
+ DoSetMediaKeys(v, test, token)
+
+ .then(function() {
+ v.src = test.name;
+ })
+
+ .catch(function() {
+ ok(false, token + " got error setting media keys");
+ });
+}
+
+function startTest(test, token)
+{
+ TestSetMediaKeys(test, token + "_setMediaKeys");
+ TestSetSrc(test, token + "_setSrc");
+}
+
+function beginTest() {
+ manager.runTests(gEMENonMSEFailTests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_playback.html b/dom/media/test/test_eme_playback.html
new file mode 100644
index 0000000000..dc6092e486
--- /dev/null
+++ b/dom/media/test/test_eme_playback.html
@@ -0,0 +1,193 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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="https://example.com:443/tests/dom/media/test/eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* import-globals-from eme.js */
+var manager = new MediaTestManager;
+
+function ArrayBuffersEqual(a, b) {
+ if (a.byteLength != b.byteLength) {
+ return false;
+ }
+ var ua = new Uint8Array(a);
+ var ub = new Uint8Array(b);
+ for (var i = 0; i < ua.length; i++) {
+ if (ua[i] != ub[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function KeysChangeFunc(session, keys, token) {
+ session.keyIdsReceived = [];
+ for (var keyid in keys) {
+ Log(token, "Set " + keyid + " to false in session[" + session.sessionId + "].keyIdsReceived");
+ session.keyIdsReceived[keyid] = false;
+ }
+ return function(ev) {
+ var s = ev.target;
+ s.gotKeysChanged = true;
+
+ var keyList = [];
+ var valueList = [];
+ var map = s.keyStatuses;
+
+ // Test that accessing keys not known to the CDM has expected behaviour.
+ var absentKey = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
+ 0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c]);
+ is(map.has(absentKey), false, "Shouldn't have a key that's not in the media");
+ is(map.get(absentKey), undefined, "Unknown keys should undefined status");
+
+ // Verify known keys have expected status.
+ for (let [key, val] of map.entries()) {
+ is(key.constructor, ArrayBuffer, "keyId should be ArrayBuffer");
+ ok(map.has(key), "MediaKeyStatusMap.has() should work.");
+ is(map.get(key), val, "MediaKeyStatusMap.get() should work.");
+ keyList.push(key);
+ valueList.push(val);
+ is(val, "usable", token + ": key status should be usable");
+ var kid = Base64ToHex(window.btoa(ArrayBufferToString(key)));
+ ok(kid in s.keyIdsReceived, TimeStamp(token) + " session[" + s.sessionId + "].keyIdsReceived contained " + kid + " as expected.");
+ s.keyIdsReceived[kid] = true;
+ }
+
+ var index = 0;
+ for (var keyId of map.keys()) {
+ ok(ArrayBuffersEqual(keyId, keyList[index]), "MediaKeyStatusMap.keys() should correspond to entries");
+ index++;
+ }
+ index = 0;
+ for (let val of map.values()) {
+ is(val, valueList[index], "MediaKeyStatusMap.values() should correspond to entries");
+ index++;
+ }
+ }
+}
+
+function startTest(test, token)
+{
+ manager.started(token);
+
+ var sessions = [];
+
+ function onSessionCreated(session) {
+ sessions.push(session);
+ session.addEventListener("keystatuseschange", KeysChangeFunc(session, test.keys, token));
+
+ session.numKeystatuseschangeEvents = 0;
+ session.numOnkeystatuseschangeEvents = 0;
+
+ session.addEventListener("keystatuseschange", function() {
+ session.numKeystatuseschangeEvents += 1;
+ });
+ session.onkeystatuseschange = function() {
+ session.numOnkeystatuseschangeEvents += 1;
+ };
+
+ session.numMessageEvents = 0;
+ session.numOnMessageEvents = 0;
+ session.addEventListener("message", function() {
+ session.numMessageEvents += 1;
+ });
+ session.onmessage = function() {
+ session.numOnMessageEvents += 1;
+ };
+ }
+
+ let v = document.createElement("video");
+ document.body.appendChild(v);
+
+ var gotEncrypted = 0;
+ let finish = new EMEPromise;
+
+ v.addEventListener("encrypted", function(ev) {
+ gotEncrypted += 1;
+ });
+
+ v.addEventListener("loadedmetadata", function() {
+ ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v),
+ TimeStamp(token) + " isEncrypted should be true");
+ is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content");
+ });
+
+ v.addEventListener("ended", function(ev) {
+ ok(true, TimeStamp(token) + " got ended event");
+
+ is(gotEncrypted, test.sessionCount,
+ TimeStamp(token) + " encrypted events expected: " + test.sessionCount
+ + ", actual: " + gotEncrypted);
+
+ ok(Math.abs(test.duration - v.duration) < 0.1,
+ TimeStamp(token) + " Duration of video should be corrrect");
+ ok(Math.abs(test.duration - v.currentTime) < 0.1,
+ TimeStamp(token) + " Current time should be same as duration");
+
+ // Verify all sessions had all keys went sent to the CDM usable, and thus
+ // that we received keystatuseschange event(s).
+ is(sessions.length, test.sessionCount, TimeStamp(token) + " should have "
+ + test.sessionCount
+ + " session" + (test.sessionCount === 1 ? "" : "s"));
+ var keyIdsReceived = [];
+ for (var keyid in test.keys) { keyIdsReceived[keyid] = false; }
+ for (var i = 0; i < sessions.length; i++) {
+ var session = sessions[i];
+ ok(session.gotKeysChanged,
+ TimeStamp(token) + " session[" + session.sessionId
+ + "] should have received at least one keychange event");
+ for (let kid in session.keyIdsReceived) {
+ Log(token, "session[" + session.sessionId + "] key " + kid + " = " + (session.keyIdsReceived[kid] ? "true" : "false"));
+ if (session.keyIdsReceived[kid]) { keyIdsReceived[kid] = true; }
+ }
+ ok(session.numKeystatuseschangeEvents > 0, TimeStamp(token) + " should get key status changes");
+ is(session.numKeystatuseschangeEvents, session.numOnkeystatuseschangeEvents,
+ TimeStamp(token) + " should have as many keystatuseschange event listener calls as event handler calls.");
+
+ ok(session.numMessageEvents > 0, TimeStamp(token) + " should get message events");
+ is(session.numMessageEvents, session.numOnMessageEvents,
+ TimeStamp(token) + " should have as many message event listener calls as event handler calls.");
+ }
+ for (let kid in keyIdsReceived) {
+ ok(keyIdsReceived[kid], TimeStamp(token) + " key with id " + kid + " was usable as expected");
+ }
+
+ CloseSessions(v, sessions).then(finish.resolve, finish.reject);
+ });
+
+ Promise.all([
+ LoadInitData(v, test, token),
+ CreateAndSetMediaKeys(v, test, token),
+ LoadTest(test, v, token)])
+ .then(values => {
+ v.play();
+ let initData = values[0];
+ initData.map(ev => {
+ let session = v.mediaKeys.createSession();
+ onSessionCreated(session);
+ MakeRequest(test, token, ev, session);
+ });
+ return finish.promise;
+ })
+ .catch(reason => ok(false, reason))
+ .then(() => manager.finished(token));
+}
+
+function beginTest() {
+ manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_pssh_in_moof.html b/dom/media/test/test_eme_pssh_in_moof.html
new file mode 100644
index 0000000000..d1965be844
--- /dev/null
+++ b/dom/media/test/test_eme_pssh_in_moof.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+
+ <head>
+ <title>Test Encrypted Media Extensions</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>
+ <script class="testbody" type="text/javascript">
+ let manager = new MediaTestManager;
+
+ let psshTests = [
+ { name: "bear-640x360-cenc-key-rotation",
+ tracks : [
+ {
+ name: "video",
+ type: "video/mp4; codecs=\"avc1.64000d\"",
+ fragments: ["bear-640x360-v_frag-cenc-key_rotation.mp4"],
+ initDatas: 6
+ },
+ {
+ name: "audio",
+ type: "audio/mp4; codecs=\"mp4a.40.2\"",
+ fragments: ["bear-640x360-a_frag-cenc-key_rotation.mp4"],
+ initDatas: 7
+ }
+ ],
+ keys : {
+ "30313233343536373839303132333435" :
+ "ebdd62f16814d27b68ef122afce4ae3c"
+ }
+ },
+ { name: "bipbop-clearkey-keyrotation-clear-lead",
+ tracks : [
+ {
+ name: "video",
+ type: "video/mp4; codecs=\"avc1.4d4015\"",
+ fragments: ["bipbop-clearkey-keyrotation-clear-lead-video.mp4"],
+ initDatas: 3
+ },
+ {
+ name: "audio",
+ type: "audio/mp4; codecs=\"mp4a.40.2\"",
+ fragments: ["bipbop-clearkey-keyrotation-clear-lead-audio.mp4"],
+ initDatas: 3
+ }
+ ],
+ keys : {
+ "00112233445566778899aabbccddeeff" :
+ "00112233445566778899aabbccddeeff",
+ "112233445566778899aabbccddeeff00" :
+ "112233445566778899aabbccddeeff00"
+ }
+ }
+ ];
+
+ // Specialized create media keys function, since the one in eme.js relies
+ // on listening for encrypted events, and we want to manage those
+ // ourselves within this test.
+ async function createAndSetMediaKeys(video, test, token) {
+ function streamType(type) {
+ var x = test.tracks.find(o => o.name == type);
+ return x ? x.type : undefined;
+ }
+
+ var configuration = { initDataTypes: ["cenc"] };
+ if (streamType("video")) {
+ configuration.videoCapabilities = [{contentType: streamType("video")}];
+ }
+ if (streamType("audio")) {
+ configuration.audioCapabilities = [{contentType: streamType("audio")}];
+ }
+ let keySystemAccess = await navigator.requestMediaKeySystemAccess(
+ "org.w3.clearkey", [configuration]);
+ let mediaKeys = await keySystemAccess.createMediaKeys();
+ return video.setMediaKeys(mediaKeys);
+ }
+
+ function startTest(test, token) {
+ manager.started(token);
+ let video = document.createElement("video");
+ document.body.appendChild(video);
+
+ let encryptedEventCount = 0;
+
+ let initDatas = new Map();
+
+ function handleEncrypted(event) {
+ // We get one 'encrypted' event for every run of contiguous PSSH boxes
+ // in each stream. Note that some of the PSSH boxes contained in the
+ // "bear" streams aren't in the Common Open PSSH box format, so our
+ // ClearKey CDM will reject those license requests. Some of the init
+ // data is also repeated.
+ encryptedEventCount++;
+
+ let hexStr = StringToHex(new TextDecoder().decode(event.initData));
+ if (initDatas.has(hexStr)) {
+ // Already have a session for this.
+ return;
+ }
+
+ let initData = new Uint8Array(event.initData);
+ is(event.initDataType, "cenc", "'encrypted' event should have 'cenc' " +
+ "initDataType");
+ Log(token, "encrypted event => len=" + initData.length + " " + hexStr);
+ let session = event.target.mediaKeys.createSession();
+ initDatas.set(hexStr, session);
+ session.addEventListener("message", e => {
+ e.target.update(
+ GenerateClearKeyLicense(e.message, test.keys));
+ });
+
+ session.generateRequest(event.initDataType, event.initData);
+ }
+
+ video.addEventListener("encrypted", handleEncrypted);
+
+ video.addEventListener("ended", () => {
+ let expectedEncryptedEvents =
+ test.tracks.reduce((sum, track) => sum += track.initDatas, 0);
+ is(encryptedEventCount, expectedEncryptedEvents,
+ "Should get one 'encrypted' event per run of contiguous PSSH " +
+ "boxes in media.");
+ manager.finished(token);
+ });
+
+ video.autoplay = true;
+
+ createAndSetMediaKeys(video, test, token)
+ .then(() => LoadTest(test, video, token))
+ }
+
+ manager.runTests(psshTests, startTest);
+
+ </script>
+ </body>
+</html>
diff --git a/dom/media/test/test_eme_requestKeySystemAccess.html b/dom/media/test/test_eme_requestKeySystemAccess.html
new file mode 100644
index 0000000000..59c7ef566d
--- /dev/null
+++ b/dom/media/test/test_eme_requestKeySystemAccess.html
@@ -0,0 +1,480 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+
+const SUPPORTED_LABEL = "pass label";
+
+function ValidateConfig(name, expected, observed) {
+ info("ValidateConfig " + name);
+ info("expected cfg=" + JSON.stringify(expected));
+ info("observed cfg=" + JSON.stringify(observed));
+
+ is(observed.label, expected.label, name + " label should match");
+ if (expected.initDataTypes) {
+ ok(expected.initDataTypes.every((element, index, array) => observed.initDataTypes.includes(element)), name + " initDataTypes should match.");
+ }
+
+ if (expected.audioCapabilities) {
+ ok(expected.audioCapabilities.length == 1, "Test function can only handle one capability.");
+ ok(observed.audioCapabilities.length == 1, "Test function can only handle one capability.");
+ is(observed.audioCapabilities[0].contentType, expected.audioCapabilities[0].contentType, name + " audioCapabilities should match.");
+ }
+ if (typeof expected.videoCapabilities !== 'undefined') {
+ info("expected.videoCapabilities=" + expected.videoCapabilities);
+ dump("expected.videoCapabilities=" + expected.videoCapabilities + "\n");
+ ok(expected.videoCapabilities.length == 1, "Test function can only handle one capability.");
+ ok(observed.videoCapabilities.length == 1, "Test function can only handle one capability.");
+ is(observed.videoCapabilities[0].contentType, expected.videoCapabilities[0].contentType, name + " videoCapabilities should match.");
+ }
+ if (expected.sessionTypes) {
+ is(expected.sessionTypes.length, observed.sessionTypes.length, "Should have expected number of sessionTypes");
+ for (var i = 0; i < expected.sessionTypes.length; i++) {
+ is(expected[i], observed[i], "Session type " + i + " should match");
+ }
+ }
+}
+
+function Test(test) {
+ var name = "'" + test.name + "'";
+ return new Promise(function(resolve, reject) {
+ var p;
+ if (test.options) {
+ var keySystem = (test.keySystem !== undefined) ? test.keySystem : CLEARKEY_KEYSYSTEM;
+ p = navigator.requestMediaKeySystemAccess(keySystem, test.options);
+ } else {
+ p = navigator.requestMediaKeySystemAccess(keySystem);
+ }
+ p.then(
+ function(keySystemAccess) {
+ ok(test.shouldPass, name + " passed and was expected to " + (test.shouldPass ? "pass" : "fail"));
+ is(keySystemAccess.keySystem, CLEARKEY_KEYSYSTEM, "CDM keySystem should be in MediaKeySystemAccess.keySystem");
+ ValidateConfig(name, test.expectedConfig, keySystemAccess.getConfiguration());
+ resolve();
+ },
+ function(ex) {
+ if (test.shouldPass) {
+ info(name + " failed: " + ex);
+ }
+ ok(!test.shouldPass, name + " failed and was expected to " + (test.shouldPass ? "pass" : "fail"));
+ resolve();
+ });
+ });
+}
+
+var tests = [
+ {
+ name: 'Empty keySystem string',
+ keySystem: '',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Empty options specified',
+ options: [ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Undefined options',
+ shouldPass: false,
+ },
+ {
+ name: 'Basic MP4 cenc',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4'}],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4'}],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Invalid keysystem failure',
+ keySystem: 'bogusKeySystem',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Invalid initDataType',
+ options: [
+ {
+ initDataTypes: ['bogus'],
+ audioCapabilities: [{contentType: 'audio/mp4'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Valid initDataType after invalid',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['bogus', 'invalid', 'cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Invalid videoType',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/bogus'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Invalid distinctiveIdentifier fails',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ distinctiveIdentifier: 'bogus',
+ persistentState: 'bogus',
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'distinctiveIdentifier is prohibited for ClearKey',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ distinctiveIdentifier: 'required',
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Invalid persistentState fails',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ persistentState: 'bogus',
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Invalid robustness unsupported',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4', robustness: 'very much so'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Unexpected config entry should be ignored',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ unexpectedEntry: 'this should be ignored',
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Invalid option followed by valid',
+ options: [
+ {
+ label: "this config should not be supported",
+ initDataTypes: ['bogus'],
+ },
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Persistent-license should not be supported by ClearKey',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ sessionTypes: ['persistent-license'],
+ persistentState: 'optional',
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'Persistent-usage-record should not be supported by ClearKey',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4'}],
+ sessionTypes: ['persistent-usage-record'],
+ persistentState: 'optional',
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'MP4 audio container',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'MP4 audio container with AAC-LC',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'MP4 audio container with invalid codecs',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4; codecs="bogus"'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'MP4 audio container with mp3 is unsupported',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4; codecs="mp3"'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'MP4 video container type with an mp3 codec is unsupported',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="mp3"'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'MP4 audio container type with a video codec is unsupported',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ audioCapabilities: [{contentType: 'audio/mp4; codecs="avc1.42E01E"'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'MP4 video container with constrained baseline h.264',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'MP4 video container with invalid codecs',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="bogus"'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'MP4 video container with both audio and video codec type in videoType',
+ options: [
+ {
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E,mp4a.40.2"'}],
+ }
+ ],
+ shouldPass: false,
+ },
+ {
+ name: 'MP4 audio and video type both specified',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
+ audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['cenc'],
+ videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
+ audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Basic WebM video',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ videoCapabilities: [{contentType: 'video/webm'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ videoCapabilities: [{contentType: 'video/webm'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Basic WebM audio',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ audioCapabilities: [{contentType: 'audio/webm'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ audioCapabilities: [{contentType: 'audio/webm'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Webm with Vorbis audio and VP8 video.',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
+ audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
+ audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Webm with Vorbis audio and VP9 video.',
+ options: [
+ {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}],
+ audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+ }
+ ],
+ expectedConfig: {
+ label: SUPPORTED_LABEL,
+ initDataTypes: ['webm'],
+ videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}],
+ audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
+ },
+ shouldPass: true,
+ },
+ {
+ name: 'Webm with bogus video.',
+ options: [
+ {
+ initDataTypes: ['webm'],
+ videoCapabilities: [{contentType: 'video/webm;codecs="bogus"'}],
+ }
+ ],
+ shouldPass: false,
+ },
+];
+
+function beginTest() {
+ Promise.all(tests.map(Test)).then(function() { SimpleTest.finish(); });
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_requestMediaKeySystemAccess_with_app_approval.html b/dom/media/test/test_eme_requestMediaKeySystemAccess_with_app_approval.html
new file mode 100644
index 0000000000..6bafb768c1
--- /dev/null
+++ b/dom/media/test/test_eme_requestMediaKeySystemAccess_with_app_approval.html
@@ -0,0 +1,209 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions access can be gated by application</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">
+
+// These test cases should be used to make a request to
+// requestMediaKeySystemAccess and have the following members:
+// name: a name describing the test.
+// askAppApproval: used to set prefs such so that Gecko will ask for app
+// approval for EME if true, or not if false.
+// appApproves: used to set prefs to simulate app approval of permission
+// request, true if the app approves the request, false if not.
+// expectedKeySystemAccess: true if we expect to be granted key system access,
+// false if not.
+const testCases = [
+ {
+ name: "Don't check for app approval",
+ askAppApproval: false,
+ expectedKeySystemAccess: true,
+ },
+ {
+ name: "Check for app approval and app denies request",
+ askAppApproval: true,
+ appApproves: false,
+ expectedKeySystemAccess: false,
+ },
+ {
+ name: "Check for app approval and app allows request",
+ askAppApproval: true,
+ appApproves: true,
+ expectedKeySystemAccess: true,
+ },
+];
+
+// Options for requestMediaKeySystemAccess that are expected to work.
+const options = [{
+ initDataTypes: ['webm'],
+ audioCapabilities: [
+ { contentType: 'audio/webm; codecs="opus"' },
+ ],
+ videoCapabilities: [
+ { contentType: 'video/webm; codecs="vp8"' }
+ ]
+}];
+
+async function setTestPrefs({askAppApproval, appApproves}) {
+ if (!askAppApproval) {
+ // Test doesn't want app approval, set pref so we don't ask and unset prefs
+ // used to determine permission response as we don't need them.
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.eme.require-app-approval", false]],
+ clear: [
+ ["media.eme.require-app-approval.prompt.testing"],
+ ["media.eme.require-app-approval.prompt.testing.allow"],
+ ]
+ });
+ return;
+ }
+
+ // Test wants app approval, and will approve deny requests per appApproces
+ // value, set prefs accordingly.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.eme.require-app-approval", true],
+ ["media.eme.require-app-approval.prompt.testing", true],
+ ["media.eme.require-app-approval.prompt.testing.allow", appApproves],
+ ],
+ });
+}
+
+// Run a test case that makes a single requestMediaKeySystemAccess call. The
+// outcome of such a test run should depend on the test case's setting of
+// preferences controlling the eme app approval.
+async function testSingleRequest(testCase) {
+ await setTestPrefs(testCase);
+
+ try {
+ await navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, options);
+ ok(testCase.expectedKeySystemAccess,
+ `testSingleRequest ${testCase.name}: allowed media key system access.`);
+ } catch(e) {
+ is(e.name,
+ "NotSupportedError",
+ "Should get NotSupportedError when request is blocked.");
+ is(e.message,
+ "The application embedding this user agent has blocked MediaKeySystemAccess",
+ "Should get blocked error message.");
+ ok(!testCase.expectedKeySystemAccess,
+ `testSingleRequest ${testCase.name}: denied media key system access.`);
+ }
+}
+
+// Run a test case that, but using invalid arguments for
+// requestMediaKeySystemAccess. Because we expect the args to be checked
+// before requesting app approval, this test ensures that we always fail when
+// using bad args, regardless of the app approval prefs set.
+async function testRequestWithInvalidArgs(testCase) {
+ const badOptions = [{
+ initDataTypes: ['badType'],
+ audioCapabilities: [
+ { contentType: 'audio/webm; codecs="notACodec"' },
+ ],
+ videoCapabilities: [
+ { contentType: 'video/webm; codecs="notACodec"' }
+ ]
+ }];
+
+ await setTestPrefs(testCase);
+
+ // Check that calls with a bad key system fail.
+ try {
+ await navigator.requestMediaKeySystemAccess("BadKeySystem", options);
+ ok(false,
+ `testRequestWithInvalidArgs ${testCase.name}: should not get access when using bad key system.`);
+ } catch(e) {
+ is(e.name,
+ "NotSupportedError",
+ "Should get NotSupportedError using bad key system.");
+ is(e.message,
+ "Key system is unsupported",
+ "Should get not supported key system error message.");
+ }
+
+ // Check that calls with the bad options fail.
+ try {
+ await navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, badOptions);
+ ok(false,
+ `testRequestWithInvalidArgs ${testCase.name}: should not get access when using bad options.`);
+ } catch(e) {
+ is(e.name,
+ "NotSupportedError",
+ "Should get NotSupportedError using bad options.");
+ is(e.message,
+ "Key system configuration is not supported",
+ "Should get not supported config error message.");
+ }
+}
+
+// Run a test case and make multiple requests with the same case. Check that
+// all requests are resolved with the expected outcome.
+async function testMultipleRequests(testCase) {
+ // Number of requests to concurrently make.
+ const NUM_REQUESTS = 5;
+
+ await setTestPrefs(testCase);
+
+ let resolveHandler = () => {
+ ok(testCase.expectedKeySystemAccess,
+ `testMultipleRequests ${testCase.name}: allowed media key system access.`);
+ }
+
+ let rejectHandler = e => {
+ is(e.name,
+ "NotSupportedError",
+ "Should get NotSupportedError when request is blocked.");
+ is(e.message,
+ "The application embedding this user agent has blocked MediaKeySystemAccess",
+ "Should get blocked error message.");
+ ok(!testCase.expectedKeySystemAccess,
+ `testMultipleRequests ${testCase.name}: denied media key system access.`);
+ }
+
+ let accessPromises = [];
+ for(let i = 0; i < NUM_REQUESTS; i++) {
+ // Request access then chain to our resolve and reject handlers. The
+ // handlers assert test state then resolve the promise chain. Ensuring the
+ // chain is always resolved allows us to correctly await all outstanding
+ // requests -- otherwise rejects short circuit the Promise.all call below.
+ let accessPromise = navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, options)
+ .then(resolveHandler, rejectHandler);
+ accessPromises.push(accessPromise);
+ }
+ // Wait for all promises to be resolved. If not, we'll time out. Because
+ // our reject handler chains back into a resolved promise, this should wait
+ // for all requests to be serviced, even when requestMediaKeySystemAccess's
+ // promise is rejected.
+ await Promise.all(accessPromises);
+}
+
+async function beginTest() {
+ // The tests rely on prefs being set, so run them in sequence. If we run in
+ // parallel the tests break each other by overriding prefs.
+ for (const testCase of testCases) {
+ await testSingleRequest(testCase);
+ }
+ for (const testCase of testCases) {
+ await testRequestWithInvalidArgs(testCase);
+ }
+ for (const testCase of testCases) {
+ await testMultipleRequests(testCase);
+ }
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_request_notifications.html b/dom/media/test/test_eme_request_notifications.html
new file mode 100644
index 0000000000..b3c62c9cf6
--- /dev/null
+++ b/dom/media/test/test_eme_request_notifications.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+var manager = new MediaTestManager;
+
+function SetPrefs(prefs) {
+ return SpecialPowers.pushPrefEnv({"set": prefs});
+}
+
+function observe() {
+ return new Promise(function(resolve, reject) {
+ var observer = function(subject, topic, data) {
+ SpecialPowers.Services.obs.removeObserver(observer, "mediakeys-request");
+ resolve(JSON.parse(data).status);
+ };
+ SpecialPowers.Services.obs.addObserver(observer, "mediakeys-request");
+ });
+}
+
+function Test(test) {
+ var p = test.prefs ? SetPrefs(test.prefs) : Promise.resolve();
+ var name = "'" + test.keySystem + "'";
+
+ var res = observe().then((status) => {
+ is(status, test.expectedStatus, name + " expected status");
+ });
+
+ p.then(() => navigator.requestMediaKeySystemAccess(test.keySystem, gCencMediaKeySystemConfig))
+ .then((keySystemAccess) => keySystemAccess.createMediaKeys());
+
+ return res;
+}
+
+var tests = [
+ {
+ keySystem: CLEARKEY_KEYSYSTEM,
+ expectedStatus: 'cdm-created',
+ prefs: [["media.eme.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: 'api-disabled',
+ prefs: [["media.eme.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: 'cdm-disabled',
+ prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", false]]
+ },
+ {
+ keySystem: "com.widevine.alpha",
+ expectedStatus: 'cdm-not-installed',
+ prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", true]]
+ },
+ {
+ keySystem: CLEARKEY_KEYSYSTEM,
+ expectedStatus: 'cdm-created',
+ prefs: [["media.eme.enabled", true]]
+ }
+];
+
+SetupEMEPref(function() {
+ tests.reduce(function(p,c,i,array) {
+ return p.then(function() { return Test(c); });
+ }, Promise.resolve()).then(SimpleTest.finish);
+});
+
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_sample_groups_playback.html b/dom/media/test/test_eme_sample_groups_playback.html
new file mode 100644
index 0000000000..be56bff21d
--- /dev/null
+++ b/dom/media/test/test_eme_sample_groups_playback.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test Encrypted Media Extensions</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>
+ <video controls id="video"></video>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+ // Tests that files with a default key and a seperate sample keyids in the
+ // sgpd box play correctly (if the keyid from the sgpd box is not parsed
+ // or assigned to the sample we will wait indefinitely for the default
+ // key).
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Test files for samples encrypted with different media keys.
+ var gEMESampleGoupTests = [
+ {
+ name:"video with 4 keys in sgpd (sbgp in traf sgpd in stbl)",
+ track: {
+ name:"video",
+ type:"video/mp4; codecs=\"avc1.64000d\"",
+ fragments:[ "sample-encrypted-sgpdstbl-sbgptraf.mp4"
+ ]
+ },
+ keys: {
+ // "keyid" : "key"
+ "279926496a7f5d25da69f2b3b2799a7f": "5544694d47473326622665665a396b36",
+ "597669572e55547e656b56586e2f6f68": "7959493a764556786527517849756635",
+ "205b2b293a342f3d3268293e6f6f4e29": "3a4f3674376d6c48675a273464447b40",
+ "32783e367c2e4d4d6b46467b3e6b5478": "3e213f6d45584f51713d534f4b417855",
+ },
+ sessionType:"temporary",
+ sessionCount:1,
+ duration:2,
+ },
+ ],
+ test = gEMESampleGoupTests[0];
+
+ var video = document.getElementById("video");
+ video.addEventListener("encrypted", () => {
+ Log(test.name, "Recieved encrypted event");
+ });
+
+ video.addEventListener("waitingforkey", () => {
+ Log(test.name, "waitingforkey");
+ ok(false, test.name + " Video is waitingforkey, indicating that the samples are not being assigned the correct id from the sgpd box!");
+ SimpleTest.finish();
+ });
+
+ function LoadEME() {
+ var options = [{
+ initDataType: "cenc",
+ videoType: test.track.type,
+ }];
+
+ return navigator.requestMediaKeySystemAccess("org.w3.clearkey", options)
+ .then((keySystemAccess) => {
+ return keySystemAccess.createMediaKeys();
+ }, bail("Failed to request key system access."))
+
+ .then((mediaKeys) => {
+ video.setMediaKeys(mediaKeys);
+
+ var session = mediaKeys.createSession();
+ once(session, "message", (ev) => {
+ is(ev.messageType, "license-request", "Expected a license-request");
+ session.update(GenerateClearKeyLicense(ev.message, test.keys));
+ });
+
+ var json = JSON.stringify({
+ "kids":Object.keys(test.keys).map(HexToBase64)
+ });
+ var request = new TextEncoder().encode(json);
+ session.generateRequest("keyids", request)
+ .then(e => {
+ Log(test.name, "Request license success");
+ }, reason => {
+ Log("Request license failed! " + reason);
+ });
+ });
+ }
+
+ function DownloadMedia(url, type, mediaSource) {
+ return new Promise((resolve, reject) => {
+ var sourceBuffer = mediaSource.addSourceBuffer(type);
+ fetchWithXHR(url, (response) => {
+ once(sourceBuffer, "updateend", resolve);
+ sourceBuffer.appendBuffer(new Uint8Array(response));
+ });
+ });
+ }
+
+ function LoadMSE() {
+ // Only set the source of the video and download the tracks after we
+ // have set the license keys, so we don't hit the waitingforkey event
+ // unless samples are being incorrectly assigned the default key
+ // (and we can safely fail).
+ LoadEME()
+ .then(() => {
+ var ms = new MediaSource();
+ video.src = URL.createObjectURL(ms);
+
+ once(ms, "sourceopen", () => {
+ Promise.all(test.track.fragments.map(fragment => DownloadMedia(fragment, test.track.type, ms)))
+ .then(() => {
+ ms.endOfStream();
+ video.play();
+ });
+ });
+
+ once(video, "ended", SimpleTest.finish);
+ });
+ }
+
+ SetupEMEPref(LoadMSE);
+
+ </script>
+ </pre>
+</body>
+
+</html> \ No newline at end of file
diff --git a/dom/media/test/test_eme_session_callable_value.html b/dom/media/test/test_eme_session_callable_value.html
new file mode 100644
index 0000000000..24413a025d
--- /dev/null
+++ b/dom/media/test/test_eme_session_callable_value.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+
+function Test() {
+ navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig)
+ .then(access => access.createMediaKeys())
+ .then(mediaKeys => {
+ var initData = (new TextEncoder()).encode( 'this is an invalid license, and that is ok');
+ var s = mediaKeys.createSession("temporary");
+ s.generateRequest("cenc", initData); // ignore result.
+ // "update()" call should fail, because MediaKeySession is "not callable"
+ // yet, since CDM won't have had a chance to set the sessionId on MediaKeySession.
+ return s.update(initData);
+ })
+ .then(()=>{ok(false, "An exception should be thrown; MediaKeySession should be not callable."); SimpleTest.finish();},
+ ()=>{ok(true, "We expect this to fail; MediaKeySession should be not callable."); SimpleTest.finish();});
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(Test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html b/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html
new file mode 100644
index 0000000000..bb36b23a2e
--- /dev/null
+++ b/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+
+function beginTest() {
+ var video = document.createElement("video");
+
+ navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig)
+ .then(function(keySystemAccess) {
+ return keySystemAccess.createMediaKeys();
+ })
+ .then(mediaKeys => {
+ return video.setMediaKeys(mediaKeys);
+ })
+ .then(() => {
+ var ms = new MediaSource();
+ ms.addEventListener("sourceopen", ()=>{ok(true, "MediaSource should open"); SimpleTest.finish();});
+ video.addEventListener("error", ()=>{ok(false, "Shouldn't error."); SimpleTest.finish();});
+ video.src = URL.createObjectURL(ms);
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_stream_capture_blocked_case1.html b/dom/media/test/test_eme_stream_capture_blocked_case1.html
new file mode 100644
index 0000000000..228baee13a
--- /dev/null
+++ b/dom/media/test/test_eme_stream_capture_blocked_case1.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+var manager = new MediaTestManager;
+
+function startTest(test, token)
+{
+ // Case 1. setting MediaKeys on an element captured by MediaElementSource should fail.
+ var case1token = token + "_case1";
+ let v1 = document.createElement("video");
+
+ function setMediaKeys() {
+ let p = new EMEPromise;
+ CreateMediaKeys(v1, test, case1token)
+ .then(mediaKeys => {
+ v1.setMediaKeys(mediaKeys)
+ .then(() => {
+ p.reject(`${case1token} setMediaKeys shouldn't succeed.`);
+ }, () => {
+ ok(true, TimeStamp(case1token) + " setMediaKeys failed as expected.");
+ p.resolve();
+ })
+ }, p.reject);
+ return p.promise;
+ }
+
+ var context = new AudioContext();
+ context.createMediaElementSource(v1);
+ v1.addEventListener("loadeddata", function(ev) {
+ ok(false, TimeStamp(case1token) + " should never reach loadeddata, as setMediaKeys should fail");
+ });
+
+ manager.started(case1token);
+
+ Promise.all([
+ LoadTest(test, v1, case1token),
+ setMediaKeys()])
+ .catch(reason => ok(false, reason))
+ .then(() => {
+ CleanUpMedia(v1);
+ manager.finished(case1token);
+ });
+}
+
+function beginTest() {
+ manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_stream_capture_blocked_case2.html b/dom/media/test/test_eme_stream_capture_blocked_case2.html
new file mode 100644
index 0000000000..8fb8f5431b
--- /dev/null
+++ b/dom/media/test/test_eme_stream_capture_blocked_case2.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+var manager = new MediaTestManager;
+
+function startTest(test, token)
+{
+ // Case 2. creating a MediaElementSource on a media element should always succeed
+ // (no matter whether it's restricted content or not), and
+ var p1 = new EMEPromise;
+ var case2token = token + "_case2";
+ let v2 = document.createElement("video");
+
+ v2.addEventListener("loadeddata", function(ev) {
+ ok(true, case2token + " should reach loadeddata");
+ var threw = false;
+ try {
+ var context = new AudioContext();
+ context.createMediaElementSource(v2);
+ } catch (e) {
+ threw = true;
+ }
+ ok(!threw, "Should always work when creating a MediaElementSource.");
+ p1.resolve();
+ });
+
+ manager.started(case2token);
+ let p2 = SetupEME(v2, test, case2token);
+
+ Promise.all([p1.promise, p2])
+ .catch(reason => ok(false, reason))
+ .then(() => {
+ CleanUpMedia(v2);
+ manager.finished(case2token);
+ });
+}
+
+function beginTest() {
+ manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_stream_capture_blocked_case3.html b/dom/media/test/test_eme_stream_capture_blocked_case3.html
new file mode 100644
index 0000000000..5703328d40
--- /dev/null
+++ b/dom/media/test/test_eme_stream_capture_blocked_case3.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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">
+var manager = new MediaTestManager;
+
+function startTest(test, token)
+{
+ // Case 3. capturing a media element with mozCaptureStream that has a MediaKeys should fail.
+ var p1 = new EMEPromise;
+ var case3token = token + "_case3";
+ let v3 = document.createElement("video");
+
+ v3.addEventListener("loadeddata", function(ev) {
+ ok(true, TimeStamp(case3token) + " should reach loadeddata");
+ var threw = false;
+ try {
+ v3.mozCaptureStreamUntilEnded();
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, TimeStamp(case3token) + " Should throw an error calling mozCaptureStreamUntilEnded an EME video.");
+ p1.resolve();
+ });
+
+ manager.started(case3token);
+ let p2 = SetupEME(v3, test, case3token);
+
+ Promise.all([p1.promise, p2])
+ .catch(reason => ok(false, reason))
+ .then(() => {
+ CleanUpMedia(v3);
+ manager.finished(case3token);
+ });
+}
+
+function beginTest() {
+ manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_unsetMediaKeys_then_capture.html b/dom/media/test/test_eme_unsetMediaKeys_then_capture.html
new file mode 100644
index 0000000000..c3691fe71c
--- /dev/null
+++ b/dom/media/test/test_eme_unsetMediaKeys_then_capture.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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="https://example.com:443/tests/dom/media/test/eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* import-globals-from eme.js */
+var manager = new MediaTestManager;
+
+// Test that if we can capture a video frame while playing clear content after
+// removing the MediaKeys object which was used for a previous encrypted content
+// playback on the same video element
+function startTest(test, token)
+{
+ manager.started(token);
+ var sessions = [];
+ function onSessionCreated(session) {
+ sessions.push(session);
+ }
+
+ function closeSessions() {
+ let p = new EMEPromise;
+ Promise.all(sessions.map(s => s.close()))
+ .then(p.resolve, p.reject);
+ return p.promise;
+ }
+
+ let v = document.createElement("video");
+ document.body.appendChild(v);
+
+ let finish = new EMEPromise;
+
+ function onVideoEnded(ev) {
+ ok(true, TimeStamp(token) + " (ENCRYPTED) content playback ended.");
+
+ function playClearVideo() {
+ var p1 = once(v, 'loadeddata', (e) => {
+ ok(true, TimeStamp(token) + " Receiving event 'loadeddata' for (CLEAR) content.");
+ let canvasElem = document.createElement('canvas');
+ document.body.appendChild(canvasElem);
+ let ctx2d = canvasElem.getContext('2d');
+
+ var gotTypeError = false;
+ try {
+ ctx2d.drawImage(v, 0, 0);
+ } catch (ex) {
+ if (ex instanceof TypeError) {
+ gotTypeError = true;
+ }
+ }
+ ok(!gotTypeError, TimeStamp(token) + " Canvas2D context drawImage succeed.")
+ });
+ v.src = 'bipbop_225w_175kbps.mp4';
+ v.play();
+ Promise.all([p1]).then(() => {
+ manager.finished(token);
+ }, () => {
+ ok(false, TimeStamp(token) + " Something wrong.");
+ manager.finished(token);
+ });
+ }
+
+ closeSessions()
+ .then(() => {
+ v.setMediaKeys(null)
+ .then(() => {
+ ok(true, TimeStamp(token) + " Setting MediaKeys to null.");
+ playClearVideo();
+ }, () => {
+ ok(false, TimeStamp(token) + " Setting MediaKeys to null.");
+ });;
+ });
+ }
+
+ once(v, "ended", onVideoEnded);
+
+ // Create a MediaKeys object and set to HTMLMediaElement then start the playback.
+ Promise.all([
+ LoadInitData(v, test, token),
+ CreateAndSetMediaKeys(v, test, token),
+ LoadTest(test, v, token)])
+ .then(values => {
+ let initData = values[0];
+ v.play();
+ initData.map(ev => {
+ let session = v.mediaKeys.createSession();
+ onSessionCreated(session);
+ MakeRequest(test, token, ev, session);
+ });
+ })
+ .then(() => {
+ return finish.promise;
+ })
+ .catch(reason => ok(false, reason))
+}
+
+function beginTest() {
+ manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_eme_waitingforkey.html b/dom/media/test/test_eme_waitingforkey.html
new file mode 100644
index 0000000000..2506c56fd1
--- /dev/null
+++ b/dom/media/test/test_eme_waitingforkey.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Encrypted Media Extensions</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="https://example.com:443/tests/dom/media/test/eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* import-globals-from eme.js */
+var manager = new MediaTestManager;
+
+function startTest(test, token)
+{
+ manager.started(token);
+
+ let v = document.createElement("video");
+ document.body.appendChild(v);
+
+ var gotWaitingForKey = 0;
+ var gotOnwaitingforkey = 0;
+
+ let waitForKey = new EMEPromise;
+ v.addEventListener("waitingforkey", function() {
+ gotWaitingForKey += 1;
+ waitForKey.resolve();
+ });
+
+ v.onwaitingforkey = function() {
+ gotOnwaitingforkey += 1;
+ };
+
+ v.addEventListener("loadedmetadata", function() {
+ ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v),
+ TimeStamp(token) + " isEncrypted should be true");
+ is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content");
+ });
+
+ let finish = new EMEPromise;
+ v.addEventListener("ended", function() {
+ ok(true, TimeStamp(token) + " got ended event");
+ // We expect only one waitingForKey as we delay until all sessions are ready.
+ // I.e. one waitingForKey should be fired, after which, we process all sessions,
+ // so it should not be possible to be blocked by a key after that point.
+ ok(gotWaitingForKey == 1, "Expected number 1 wait, got: " + gotWaitingForKey);
+ ok(gotOnwaitingforkey == gotWaitingForKey, "Should have as many event listener calls as event handler calls, got: " + gotOnwaitingforkey);
+
+ finish.resolve();
+ });
+
+ Promise.all([
+ LoadInitData(v, test, token),
+ CreateAndSetMediaKeys(v, test, token),
+ LoadTest(test, v, token),
+ waitForKey.promise])
+ .then(values => {
+ let initData = values[0];
+ return ProcessInitData(v, test, token, initData);
+ })
+ .then(sessions => {
+ Log(token, "Updated all sessions, loading complete -> Play");
+ v.play();
+ finish.promise.then(() => CloseSessions(v, sessions));
+ return finish.promise;
+ })
+ .catch(reason => ok(false, reason))
+ .then(() => manager.finished(token));
+}
+
+function beginTest() {
+ manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_empty_resource.html b/dom/media/test/test_empty_resource.html
new file mode 100644
index 0000000000..8f65bb503d
--- /dev/null
+++ b/dom/media/test/test_empty_resource.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1094549
+-->
+<head>
+ <title>Test for Bug 1094549</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094549">Mozilla Bug 1094549</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Shorter timeout for this test should finish soon.
+SimpleTest.requestLongerTimeout(0.3);
+
+function finish(v) {
+ isnot(v.error, null, "should've got an error event");
+ SimpleTest.finish();
+}
+
+function onload() {
+ info("iframe loaded");
+ var v = SpecialPowers.wrap(document.body.getElementsByTagName("iframe")[0])
+ .contentDocument.body.getElementsByTagName("video")[0];
+
+ // Got 'error' as expected, finish the test.
+ if (v.error) {
+ finish(v);
+ return;
+ }
+
+ // Otherwise, wait for it.
+ v.onerror = function() {
+ finish(v);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+var f = document.createElement("iframe");
+// Assign a resource file with zero length and expect the error event from
+// the video element since decoding metadata will fail.
+f.src = "data:video/webm,";
+f.addEventListener("load", onload);
+document.body.appendChild(f);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_error_in_video_document.html b/dom/media/test/test_error_in_video_document.html
new file mode 100644
index 0000000000..e376ea95e3
--- /dev/null
+++ b/dom/media/test/test_error_in_video_document.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=604067
+-->
+<head>
+ <title>Test for Bug 604067</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=604067">Mozilla Bug 604067</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 604067 **/
+
+function documentVideo() {
+ return document.body.getElementsByTagName("iframe")[0]
+ .contentDocument.body.getElementsByTagName("video")[0];
+}
+
+function check() {
+ var v = documentVideo();
+
+ // Debug info for Bug 608634
+ ok(true, "iframe src=" + document.body.getElementsByTagName("iframe")[0].src);
+ is(v.readyState, v.HAVE_NOTHING, "Ready state");
+
+ isnot(v.error, null, "Error object");
+ is(v.networkState, v.NETWORK_NO_SOURCE, "Network state");
+ is(v.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, "Expected media not supported error");
+ SimpleTest.finish();
+}
+
+// Find an error test that we'd think we should be able to play (if it
+// wasn't already known to fail).
+var t = getPlayableVideo(gErrorTests);
+if (!t) {
+ todo(false, "No types supported");
+} else {
+ SimpleTest.waitForExplicitFinish();
+
+ var f = document.createElement("iframe");
+ f.src = t.name;
+ f.addEventListener("load", check);
+ document.body.appendChild(f);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_error_on_404.html b/dom/media/test/test_error_on_404.html
new file mode 100644
index 0000000000..94f7cc2ea7
--- /dev/null
+++ b/dom/media/test/test_error_on_404.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=476731
+-->
+<head>
+ <title>Test for Bug 476731</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=476731">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 476731 **/
+
+var videos = [];
+
+function FinishedLoads() {
+ if (videos.length == 0)
+ return false;
+ for (var i=0; i<videos.length; ++i) {
+ if (videos[i]._loadedData) {
+ // A video loadeddata, it should have 404'd. Signal the end of the test
+ // so the error will be reported.
+ return true;
+ }
+ if (!videos[i]._loadError) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function loadError(evt) {
+ var v = evt.target;
+ is(v._loadError, false, "Shouldn't receive multiple error events for " + v.src);
+ v._loadError = true;
+ is(v._loadStart, true, "Receive loadstart for " + v.src);
+ is(v._loadedData, false, "Shouldn't receive loadeddata for " + v.src);
+ if (FinishedLoads(videos))
+ SimpleTest.finish();
+}
+
+function loadedData(evt) {
+ evt.target._loadedData = true;
+}
+
+function loadStart(evt) {
+ evt.target._loadStart = true;
+}
+
+// Create all video objects.
+for (var i=0; i<g404Tests.length; ++i) {
+ var v = document.createElement("video");
+ v.preload = "metadata";
+ var test = g404Tests[i];
+ if (!v.canPlayType(test.type)) {
+ continue;
+ }
+ v.src = test.name;
+ v._loadedData = false;
+ v._loadStart = false;
+ v._loadError = false;
+ v.addEventListener("error", loadError);
+ v.addEventListener("loadstart", loadStart);
+ v.addEventListener("loadeddata", loadedData);
+ document.body.appendChild(v); // Will start load.
+ videos.push(v);
+}
+
+if (videos.length == 0) {
+ todo(false, "No types supported");
+} else {
+ SimpleTest.waitForExplicitFinish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_fastSeek-forwards.html b/dom/media/test/test_fastSeek-forwards.html
new file mode 100644
index 0000000000..1e50021b13
--- /dev/null
+++ b/dom/media/test/test_fastSeek-forwards.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1022913
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1022913</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1022913">Mozilla Bug 1022913</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ // Test that if we're doing a fastSeek() forwards that we don't end up
+ // seeking backwards. This can happen if the keyframe before the seek
+ // target is before the current playback position. We'd prefer to seek to
+ // the keyframe after the seek target in this case, but we don't implement
+ // this yet (bug 1026330).
+ var manager = new MediaTestManager;
+
+ var onSecondSeekComplete = function(event) {
+ var v = event.target;
+ v.removeEventListener("seeked", onSecondSeekComplete);
+ ok(v.currentTime >= v.firstSeekTarget, v.name + " seek never go backwards. time=" + v.currentTime + " firstSeekTarget=" + v.firstSeekTarget + " secondSeekTarget=" + v.secondSeekTarget);
+ manager.finished(v.token);
+ removeNodeAndSource(v);
+ };
+
+ var onFirstSeekComplete = function(event) {
+ var v = event.target;
+ v.removeEventListener("seeked", onFirstSeekComplete);
+ // Seek to 75% of the way between the start and the first keyframe
+ // using fastSeek. We then test that the currentTime doesn't drop back
+ // to the previous keyframe, currentTime should go forwards.
+ v.addEventListener("seeked", onSecondSeekComplete);
+ v.secondSeekTarget = v.keyframes[1] * 0.75;
+ v.fastSeek(v.secondSeekTarget);
+ }
+
+ var onLoadedMetadata = function(event) {
+ // Seek to the mid-point between the start and the first keyframe.
+ var v = event.target;
+ v.removeEventListener("loadedmetadata", onLoadedMetadata);
+ v.addEventListener("seeked", onFirstSeekComplete);
+ v.firstSeekTarget = v.keyframes[1] * 0.5;
+ v.currentTime = v.firstSeekTarget;
+ }
+
+ function startTest(test, token) {
+ manager.started(token);
+ let v = document.createElement("video");
+ v.src = test.name;
+ v.name = test.name;
+ v.preload = "metadata";
+ v.token = token;
+ v.target = 0;
+ v.keyframes = test.keyframes;
+ v.keyframeIndex = 0;
+ ok(v.keyframes.length >= 2, v.name + " - video should have at least two sync points");
+ v.addEventListener("loadedmetadata", onLoadedMetadata);
+ document.body.appendChild(v);
+ }
+
+ manager.runTests(gFastSeekTests, startTest);
+
+ </script>
+</body>
+</html>
diff --git a/dom/media/test/test_fastSeek.html b/dom/media/test/test_fastSeek.html
new file mode 100644
index 0000000000..e3078c6c4e
--- /dev/null
+++ b/dom/media/test/test_fastSeek.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=778077
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 778077</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=778077">Mozilla Bug 778077</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 778077 - HTMLMediaElement.fastSeek() **/
+ // Iterate through a list of keyframe timestamps, and seek to
+ // halfway between the keyframe and the keyframe after it.
+ var manager = new MediaTestManager;
+
+ function doSeek(v) {
+ // fastSeek to half way between this keyframe and the next, or if this is the last
+ // keyframe seek to halfway between this keyframe and the end of media.
+ var nextKeyFrame = (v.keyframeIndex + 1) < v.keyframes.length ? v.keyframes[v.keyframeIndex + 1] : v.duration;
+ v.target = (v.keyframes[v.keyframeIndex] + nextKeyFrame) / 2;
+ v.fastSeek(v.target);
+ ok(Math.abs(v.currentTime - v.target) < 0.01,
+ v.name + " seekTo=" + v.target + " currentTime (" + v.currentTime + ") should be close to seek target initially");
+ }
+
+ function onloadedmetadata(event) {
+ var v = event.target;
+ doSeek(v);
+ }
+
+ function onseeked(event) {
+ var v = event.target;
+ var keyframe = v.keyframes[v.keyframeIndex];
+
+ // Check that the current time ended up roughly after the keyframe.
+ // We must be a bit fuzzy here, as the decoder backend may actually
+ // seek to the audio sample prior to the keyframe.
+ ok(v.currentTime >= keyframe - 0.05,
+ v.name + " seekTo=" + v.target + " currentTime (" + v.currentTime +
+ ") should be end up roughly after keyframe (" + keyframe + ") after fastSeek");
+
+ ok(v.currentTime <= v.target,
+ v.name + " seekTo=" + v.target + " currentTime (" + v.currentTime +
+ ") should be end up less than target after fastSeek");
+
+ v.keyframeIndex++
+ if (v.keyframeIndex == v.keyframes.length) {
+ v.src = "";
+ v.remove();
+ manager.finished(v.token);
+ } else {
+ doSeek(v);
+ }
+ }
+
+ function startTest(test, token) {
+ manager.started(token);
+ let v = document.createElement("video");
+ v.src = test.name;
+ v.name = test.name;
+ v.preload = "metadata";
+ v.token = token;
+ v.target = 0;
+ v.keyframes = test.keyframes;
+ v.keyframeIndex = 0;
+ ok(v.keyframes.length > 0, v.name + " - video should have at least one sync point");
+ v.addEventListener("loadedmetadata", onloadedmetadata);
+ v.addEventListener("seeked", onseeked);
+ document.body.appendChild(v);
+ }
+
+ manager.runTests(gFastSeekTests, startTest);
+
+ </script>
+</body>
+</html>
diff --git a/dom/media/test/test_fragment_noplay.html b/dom/media/test/test_fragment_noplay.html
new file mode 100644
index 0000000000..6a1119f342
--- /dev/null
+++ b/dom/media/test/test_fragment_noplay.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: fragment tests</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="fragment_noplay.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+// Fragment parameters to try
+var gFragmentParams = [
+ // W3C Media fragment tests
+ // http://www.w3.org/2008/WebVideo/Fragments/TC/ua-test-cases
+ { fragment: "#t=banana", start: null, end: null }, // TC0027-UA
+ { fragment: "#t=3,banana", start: null, end: null }, // TC0028-UA
+ { fragment: "#t=banana,7", start: null, end: null }, // TC0029-UA
+ { fragment: "#t='3'", start: null, end: null }, // TC0030-UA
+ { fragment: "#t=3-7", start: null, end: null }, // TC0031-UA
+ { fragment: "#t=3:7", start: null, end: null }, // TC0032-UA
+ { fragment: "#t=3,7,9", start: null, end: null }, // TC0033-UA
+ { fragment: "#t%3D3", start: null, end: null }, // TC0034-UA
+ { fragment: "#%74=3", start: 3, end: null }, // TC0035-UA
+ { fragment: "#t=%33", start: 3, end: null }, // TC0036-UA
+ { fragment: "#t=3%2C7", start: 3, end: 7 }, // TC0037-UA
+ { fragment: "#t=%6Ept:3", start: 3, end: null }, // TC0038-UA
+ { fragment: "#t=npt%3A3", start: 3, end: null }, // TC0039-UA
+ { fragment: "#t=-1,3", start: null, end: null }, // TC0044-UA
+ { fragment: "#t=3&", start: 3, end: null }, // TC0051-UA
+ { fragment: "#u=12&t=3", start: 3, end: null }, // TC0052-UA
+ { fragment: "#t=foo:7&t=npt:3", start: 3, end: null }, // TC0053-UA
+ { fragment: "#&&=&=tom&jerry=&t=3&t=meow:0#", start: 3, end: null }, // TC0054-UA
+ { fragment: "#t=7&t=3", start: 3, end: null }, // TC0055-UA
+ { fragment: "#T=3,7", start: null, end: null }, // TC0058-UA
+ { fragment: "#t=", start: null, end: null }, // TC0061-UA
+ { fragment: "#t=.", start: null, end: null }, // TC0062-UA
+ { fragment: "#t=.0", start: null, end: null }, // TC0063-UA
+ { fragment: "#t=0s", start: null, end: null }, // TC0064-UA
+ { fragment: "#t=,0s", start: null, end: null }, // TC0065-UA
+ { fragment: "#t=0s,0s", start: null, end: null }, // TC0066-UA
+ { fragment: "#t=00:00:00s", start: null, end: null }, // TC0067-UA
+ { fragment: "#t=s", start: null, end: null }, // TC0068-UA
+ { fragment: "#t=npt:", start: null, end: null }, // TC0069-UA
+ { fragment: "#t=1e-1:", start: null, end: null }, // TC0070-UA
+ { fragment: "#t=00:00:01.1e-1", start: null, end: null }, // TC0071-UA
+ { fragment: "#t=3.", start: 3, end: null }, // TC0072-UA
+ { fragment: "#t=0:0:0", start: null, end: null }, // TC0073-UA
+ { fragment: "#t=0:00:60", start: null, end: null }, // TC0074-UA
+ { fragment: "#t=0:01:60", start: null, end: null }, // TC0075-UA
+ { fragment: "#t=0:60:00", start: null, end: null }, // TC0076-UA
+ { fragment: "#t=0:000:000", start: null, end: null }, // TC0077-UA
+ { fragment: "#t=00:00:03,00:00:07", start: 3, end: 7 }, // TC0078-UA
+ { fragment: "#t=3,00:00:07", start: 3, end: 7 }, // TC0079-UA
+ { fragment: "#t=00:00.", start: null, end: null }, // TC0080-UA
+ { fragment: "#t=0:00:00.", start: null, end: null }, // TC0081-UA
+ { fragment: "#t=0:00:10e-1", start: null, end: null }, // TC0082-UA
+ { fragment: "#t=0:00:60.000", start: null, end: null }, // TC0083-UA
+ { fragment: "#t=0:60:00.000", start: null, end: null }, // TC0084-UA
+ { fragment: "#t=3,7&t=foo", start: 3, end: 7 }, // TC0085-UA
+ { fragment: "#foo&t=3,7", start: 3, end: 7 }, // TC0086-UA
+ { fragment: "#t=3,7&foo", start: 3, end: 7 }, // TC0087-UA
+ { fragment: "#t=3,7&&", start: 3, end: 7 }, // TC0088-UA
+ { fragment: "#&t=3,7", start: 3, end: 7 }, // TC0089-UA
+ { fragment: "#&&t=3,7", start: 3, end: 7 }, // TC0090-UA
+ { fragment: "#&t=3,7&", start: 3, end: 7 }, // TC0091-UA
+ { fragment: "#t%3d10", start: null, end: null }, // TC0092-UA
+ { fragment: "#t=10%26", start: null, end: null }, // TC0093-UA
+ { fragment: "#&t=3,7,", start: null, end: null } // TC0094-UA
+];
+
+function createTestArray() {
+ var tests = [];
+ var tmpVid = document.createElement("video");
+
+ for (var testNum=0; testNum<gFragmentTests.length; testNum++) {
+ var test = gFragmentTests[testNum];
+ if (!tmpVid.canPlayType(test.type)) {
+ continue;
+ }
+
+ for (var fragNum=0; fragNum<gFragmentParams.length; fragNum++) {
+ var p = gFragmentParams[fragNum];
+ var t = {};
+ t.name = test.name + p.fragment;
+ t.type = test.type;
+ t.duration = test.duration;
+ t.start = p.start;
+ t.end = p.end;
+ tests.push(t);
+ }
+ }
+ return tests;
+}
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ manager.started(token);
+ video.preload = "metadata";
+ video.src = test.name;
+ video.token = token;
+ video.controls = true;
+ document.body.appendChild(video);
+ var name = test.name + " fragment test";
+ var localIs = function(n) { return function(a, b, msg) {
+ is(a, b, n + ": " + msg);
+ }}(name);
+ var localOk = function(n) { return function(a, msg) {
+ ok(a, n + ": " + msg);
+ }}(name);
+ var localFinish = function(v, m) { return function() {
+ removeNodeAndSource(v);
+ m.finished(v.token);
+ }}(video, manager);
+ window.test_fragment_noplay(video, test.start, test.end, localIs, localOk, localFinish);
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_fragment_play.html b/dom/media/test/test_fragment_play.html
new file mode 100644
index 0000000000..eab422bab1
--- /dev/null
+++ b/dom/media/test/test_fragment_play.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="fragment_play.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+PARALLEL_TESTS = 1;
+var manager = new MediaTestManager;
+
+// Fragment parameters to try. These tests
+// try playing the video. Tests for other fragment
+// formats are in test_fragment_noplay.html.
+var gFragmentParams = [
+ { fragment: "", start: null, end: null },
+ { fragment: "#t=,", start: null, end: null },
+ { fragment: "#t=3,3", start: null, end: null },
+ { fragment: "#t=7,3", start: null, end: null },
+ { fragment: "#t=7,15", start: 7, end: null },
+ { fragment: "#t=15,20", start: 9.287982, end: null },
+ { fragment: "#t=5", start: 5, end: null },
+ { fragment: "#t=5.5", start: 5.5, end: null },
+ { fragment: "#t=5,", start: null, end: null },
+ { fragment: "#t=,5", start: 0, end: 5, todo: "See bugs 682141 and 720248" },
+ { fragment: "#t=2.5,5.5", start: 2.5, end: 5.5, todo: "See bugs 682141 and 720248" },
+ { fragment: "#t=1,2.5", start: 1, end: 2.5, todo: "See bugs 682141 and 720248" },
+ { fragment: "#t=,15", start: 0, end: null }
+];
+
+function createTestArray() {
+ var tests = [];
+ var tmpVid = document.createElement("video");
+
+ for (var testNum=0; testNum<gFragmentTests.length; testNum++) {
+ var test = gFragmentTests[testNum];
+ if (!tmpVid.canPlayType(test.type)) {
+ continue;
+ }
+
+ for (var fragNum=0; fragNum<gFragmentParams.length; fragNum++) {
+ var p = gFragmentParams[fragNum];
+ var t = {};
+ t.name = test.name + p.fragment;
+ t.type = test.type;
+ t.duration = test.duration;
+ t.start = p.start;
+ t.end = p.end;
+ t.todo = p.todo;
+ tests.push(t);
+ }
+ }
+ return tests;
+}
+
+function startTest(test, token) {
+ if (test.todo) {
+ todo(false, test.todo);
+ return;
+ }
+ var video = document.createElement('video');
+ manager.started(token);
+ video.preload = "metadata";
+ video.src = test.name;
+ video.token = token;
+ video.controls = true;
+ document.body.appendChild(video);
+ var name = test.name + " fragment test";
+ var localIs = function(n) { return function(a, b, msg) {
+ is(a, b, n + ": " + msg);
+ }}(name);
+ var localOk = function(n) { return function(a, msg) {
+ ok(a, n + ": " + msg);
+ }}(name);
+ var localFinish = function(v, m) { return function() {
+ removeNodeAndSource(v);
+ m.finished(v.token);
+ }}(video, manager);
+ window.test_fragment_play(video, test.start, test.end, localIs, localOk, localFinish);
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_hls_player_independency.html b/dom/media/test/test_hls_player_independency.html
new file mode 100644
index 0000000000..cea5c140ed
--- /dev/null
+++ b/dom/media/test/test_hls_player_independency.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of 2 HLS video at the same page </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>
+</head>
+<body>
+ <div id='player1'>
+ <video id='player4x3' controls autoplay>
+ </video>
+ </div>
+ <p> 4x3 basic stream<span>
+ <span>
+ <div height = 10>
+ <span>
+ <div id='player2'>
+ <video id='player16x9' controls autoplay>
+ </video>
+ </div>
+ <p> 16x9 basic stream<span>
+
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var v4x3 = document.getElementById('player4x3');
+ var v16x9 = document.getElementById('player16x9');
+
+ var p1 = once(v4x3, 'ended', function onended(e) {
+ is(v4x3.videoWidth, 400, "4x3 content, the width should be 400.");
+ is(v4x3.videoHeight, 300, "4x3 content, the height should be 300.");
+ });
+
+ var p2 = once(v16x9, 'ended', function onended(e) {
+ is(v16x9.videoWidth, 416, "16x9 content, the width should be 416.");
+ is(v16x9.videoHeight, 234, "16x9 content, the height should be 234.");
+ });
+
+ v4x3.src = serverUrl + "/bipbop_4x3_single.m3u8";
+ v16x9.src = serverUrl + "/bipbop_16x9_single.m3u8";
+ Promise.all([p1, p2]).then(() => {
+ SimpleTest.finish();
+ });
+}
+
+startTest();
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_imagecapture.html b/dom/media/test/test_imagecapture.html
new file mode 100644
index 0000000000..d61dd358bb
--- /dev/null
+++ b/dom/media/test/test_imagecapture.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1041393
+-->
+<head>
+ <meta charset="utf-8">
+ <title>ImageCapture tests</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="gUM_support.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1041393">ImageCapture tests</a>
+<script type="application/javascript">
+
+// Check if the callback returns even no JS reference on it.
+async function gcTest(track) {
+ const repeat = 100;
+ const promises = [];
+ for (let i = 0; i < repeat; ++i) {
+ const imageCapture = new ImageCapture(track);
+ promises.push(new Promise((resolve, reject) => {
+ imageCapture.onphoto = resolve;
+ imageCapture.onerror = () =>
+ reject(new Error(`takePhoto gcTest failure for capture ${i}`));
+ }));
+ imageCapture.takePhoto();
+ }
+ info("Call gc ");
+ SpecialPowers.gc();
+ await Promise.all(promises);
+}
+
+// Continue calling takePhoto() in rapid succession.
+async function rapidTest(track) {
+ const repeat = 100;
+ const imageCapture = new ImageCapture(track);
+ // "error" can fire synchronously in `takePhoto`
+ const errorPromise = new Promise(r => imageCapture.onerror = r);
+ for (let i = 0; i < repeat; ++i) {
+ imageCapture.takePhoto();
+ }
+ for (let i = 0; i < repeat; ++i) {
+ await Promise.race([
+ new Promise(r => imageCapture.onphoto = r),
+ errorPromise.then(() => Promise.reject(new Error("Capture failed"))),
+ ]);
+ }
+}
+
+// Check if the blob is decodable.
+async function blobTest(track) {
+ const imageCapture = new ImageCapture(track);
+ // "error" can fire synchronously in `takePhoto`
+ const errorPromise = new Promise(r => imageCapture.onerror = r);
+ imageCapture.takePhoto();
+ const blob = await Promise.race([
+ new Promise(r => imageCapture.onphoto = r),
+ errorPromise.then(() => Promise.reject(new Error("Capture failed"))),
+ ]);
+
+ const img = new Image();
+ img.src = URL.createObjectURL(blob.data);
+ await new Promise((resolve, reject) => {
+ img.onload = resolve;
+ img.onerror = () => reject(new Error("Decode failed"));
+ });
+}
+
+// It should return an error event after disabling video track.
+async function trackTest(track) {
+ const imageCapture = new ImageCapture(track);
+ track.enabled = false;
+ try {
+ const errorPromise = new Promise(r => imageCapture.onerror = r);
+ imageCapture.takePhoto();
+ const error = await Promise.race([
+ errorPromise,
+ new Promise(r => imageCapture.onphoto = r).then(
+ () => Promise.reject(new Error("Disabled video track should fail"))),
+ ]);
+ is(error.imageCaptureError.code, error.imageCaptureError.PHOTO_ERROR,
+ "Expected error code")
+ } finally {
+ track.enabled = true;
+ }
+}
+
+async function init() {
+ // Use loopback camera if available, otherwise fake.
+ // MediaTrackGraph will be the backend of ImageCapture.
+ await setupGetUserMediaTestPrefs();
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ return stream.getVideoTracks()[0];
+}
+
+async function start() {
+ try {
+ const track = await init();
+ info("ImageCapture track disable test.");
+ await trackTest(track);
+ info("ImageCapture blob test.");
+ await blobTest(track);
+ info("ImageCapture rapid takePhoto() test.");
+ await rapidTest(track);
+ info("ImageCapture multiple instances test.");
+ await gcTest(track);
+ } catch (e) {
+ ok(false, "Unexpected error during test: " + e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.requestCompleteLog();
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.imagecapture.enabled", true],
+ ["media.navigator.permission.disabled", true],
+ ],
+}, start);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_info_leak.html b/dom/media/test/test_info_leak.html
new file mode 100644
index 0000000000..41b01c74c1
--- /dev/null
+++ b/dom/media/test/test_info_leak.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478957
+-->
+<head>
+ <title>Test for Bug 478957</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478957">Mozilla Bug 478957</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<div id="log" style="font-size: small;"></div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 478957 **/
+
+// Tests whether we leak events and state change info when loading stuff from local files from a webserver.
+
+var manager = new MediaTestManager;
+
+var gEventTypes = [ 'loadstart', 'progress', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'play', 'pause', 'loadedmetadata', 'loadeddata', 'waiting', 'playing', 'canplay', 'canplaythrough', 'seeking', 'seeked', 'timeupdate', 'ended', 'ratechange', 'durationchange', 'volumechange' ];
+
+var gExpectedEvents = ['loadstart', 'error'];
+
+function createTestArray() {
+ var tests = [];
+ var tmpVid = document.createElement("video");
+
+ return makeInfoLeakTests().then(infoLeakTests => {
+ for (var testNum=0; testNum < infoLeakTests.length; testNum++) {
+ var test = infoLeakTests[testNum];
+ if (!tmpVid.canPlayType(test.type)) {
+ continue;
+ }
+
+ var t = {};
+ t.name = test.src;
+ t.type = test.type;
+
+ tests.push(t);
+ }
+ return tests;
+ })
+}
+
+function log(msg) {
+ info(msg);
+ var l = document.getElementById('log');
+ // eslint-disable-next-line no-unsanitized/property
+ l.innerHTML += msg + "<br>";
+}
+
+function finish(v) {
+ log("finish: " + v.name);
+ clearInterval(v.checkStateInterval);
+
+ for (var i=0; i<gEventTypes.length; i++) {
+ v.removeEventListener(gEventTypes[i], listener);
+ }
+ removeNodeAndSource(v);
+
+ manager.finished(v.token);
+ v = null;
+}
+
+function listener(evt) {
+ var v = evt.target;
+ log(filename(v.name) + ': got ' + evt.type);
+
+ // On slow machines like B2G emulator, progress timer could time out before
+ // receiving any HTTP notification. We will ignore the 'stalled' event to
+ // pass the tests.
+ if (evt.type == 'stalled') {
+ return;
+ }
+
+ ok(v.eventNum < gExpectedEvents.length, filename(v.name) + " Too many events received");
+ var expected = (v.eventNum < gExpectedEvents.length) ? gExpectedEvents[v.eventNum] : "NoEvent";
+ is(evt.type, expected, filename(v.name) + " Events received in wrong order");
+ v.eventNum++;
+ if (v.eventNum == gExpectedEvents.length) {
+ // In one second, move onto the next test. This give a chance for any
+ // other events to come in. Note: we don't expect any events to come
+ // in, unless we've leaked some info, and 1 second should be enough time
+ // for the leak to show up.
+ setTimeout(function() {finish(v);}, 1000);
+ }
+}
+
+function createMedia(type, src, token) {
+ var tag = getMajorMimeType(type);
+ var v = document.createElement(tag);
+ for (var i=0; i<gEventTypes.length; i++) {
+ v.addEventListener(gEventTypes[i], listener);
+ }
+ v.preload = "metadata";
+ v.src = src;
+ v.name = src;
+ document.body.appendChild(v);
+ v.eventNum = 0;
+ v.token = token;
+ setTimeout(
+ function() {
+ v.checkStateInterval = setInterval(function(){checkState(v);},1);
+ }, 0);
+}
+
+// Define our own ok() and is() functions. The mochitest ones take ages constructing the log
+// of all the passes, so only report failures.
+function test_ok(b, msg) {
+ if (!b) {
+ log("FAILED test_ok: " + msg);
+ ok(b, msg);
+ }
+}
+
+function test_is(a, b, msg) {
+ if (a != b) {
+ log("FAILED test_is: " + msg);
+ is(a,b,msg);
+ }
+}
+
+function filename(uri) {
+ return uri.substr(uri.lastIndexOf("/")+1);
+}
+
+function checkState(v) {
+ test_ok(v.networkState <= HTMLMediaElement.NETWORK_LOADING ||
+ v.networkState == HTMLMediaElement.NETWORK_NO_SOURCE,
+ "NetworkState of " + v.networkState + " was leaked.");
+ test_ok(v.readyState == HTMLMediaElement.HAVE_NOTHING,
+ "Ready state of " + v.readyState + " was leaked");
+ test_is(v.seeking, false, "Seeking leaked");
+ test_is(v.currentTime, 0, "Leaked currentTime");
+ test_ok(isNaN(v.duration), "Leaked duration");
+ test_is(v.paused, true, "Paused leaked");
+ test_is(v.ended, false, "Ended leaked");
+ test_is(v.autoplay, false, "Autoplay leaked");
+ test_is(v.controls, false, "Controls leaked");
+ test_is(v.muted, false, "muted leaked");
+ test_ok(v.error==null || v.error.code==MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED,
+ "Error code should not exist or be SRC_NOT_SUPPORTED. v.error=" +
+ (v.error ? v.error.code : "null"));
+ test_ok(filename(v.currentSrc) == filename(v.name) ||
+ v.networkState == HTMLMediaElement.NETWORK_NO_SOURCE,
+ "currentSrc should match candidate uri, if we've got a valid source");
+}
+
+
+function startTest(test, token) {
+ manager.started(token);
+ log("Testing: " + test.type + " @ " + test.name);
+ createMedia(test.type, test.name, token);
+}
+
+SimpleTest.waitForExplicitFinish();
+createTestArray().then(testArray => {
+ manager.runTests(testArray, startTest);
+});
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/media/test/test_invalid_reject.html b/dom/media/test/test_invalid_reject.html
new file mode 100644
index 0000000000..583847fe12
--- /dev/null
+++ b/dom/media/test/test_invalid_reject.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>Test rejection of invalid files</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ manager.started(token);
+
+ // Set up event handlers. Seeing any of these is a failure.
+ function badEvent(type) { return function(e) {
+ ok(false, test.name + " should not fire '" + type + "' event");
+ }};
+ var events = [
+ 'loadeddata', 'load',
+ 'canplay', 'canplaythrough',
+ 'playing'
+ ];
+ events.forEach( function(e) {
+ v.addEventListener(e, badEvent(e));
+ });
+
+ // Seeing a decoder error is a success.
+ v.addEventListener("error", function onerror(e) {
+ if (v.readyState == v.HAVE_NOTHING) {
+ is(v.error.code, v.error.MEDIA_ERR_SRC_NOT_SUPPORTED,
+ "decoder should reject " + test.name);
+ } else {
+ is(v.error.code, v.error.MEDIA_ERR_DECODE,
+ "decoder should reject " + test.name);
+ }
+ v.removeEventListener('error', onerror);
+ manager.finished(token);
+ });
+
+ // Now try to load and play the file, which should result in the
+ // error event handler above being called, terminating the test.
+ document.body.appendChild(v);
+ v.src = test.name;
+ v.play();
+}
+
+manager.runTests(gInvalidTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_invalid_reject_play.html b/dom/media/test/test_invalid_reject_play.html
new file mode 100644
index 0000000000..3e658f94b8
--- /dev/null
+++ b/dom/media/test/test_invalid_reject_play.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8" />
+ <title>Test rejection of invalid files during playback</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ manager.started(token);
+
+ // Seeing a decoder error is a success.
+ v.addEventListener("error", function onerror(e) {
+ is(v.error.code, v.error.MEDIA_ERR_DECODE,
+ "decoder should reject " + test.name);
+ v.removeEventListener("error", onerror);
+ manager.finished(token);
+ });
+
+ v.addEventListener("ended", function onended(e) {
+ ok(false, "decoder should have rejected file before playback ended");
+ v.removeEventListener("ended", onended);
+ manager.finished(token);
+ });
+
+ document.body.appendChild(v);
+ v.src = test.name;
+ v.play();
+}
+
+manager.runTests(gInvalidPlayTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_invalid_seek.html b/dom/media/test/test_invalid_seek.html
new file mode 100644
index 0000000000..8aa514b977
--- /dev/null
+++ b/dom/media/test/test_invalid_seek.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: invalide seek test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<video id='v'></video>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// http://www.whatwg.org/specs/web-apps/current-work/#dom-media-seek
+// If the media element's readyState is HAVE_NOTHING, then the user agent
+// must raise an InvalidStateError exception.
+var v = document.getElementById('v');
+var passed = false;
+
+ok(v.readyState == HTMLMediaElement.HAVE_NOTHING,
+ "Invalid ready state in media element");
+
+try {
+ v.seek(1);
+}
+catch(e) {
+ passed = true;
+}
+
+ok(passed, "Video did not raise error during invalid seek");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_load.html b/dom/media/test/test_load.html
new file mode 100644
index 0000000000..de8fd63948
--- /dev/null
+++ b/dom/media/test/test_load.html
@@ -0,0 +1,217 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=479859
+-->
+<head>
+ <title>Test for Bug 479859</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="manifest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479859">Mozilla Bug 479859</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript">
+
+function log(msg) {
+ //document.getElementById('log').innerHTML += "<p>" + msg + "</p>";
+}
+
+// We don't track: progress, canplay, canplaythrough and stalled events,
+// as these can be delivered out of order, and/or multiple times.
+var gEventTypes = [ 'loadstart', 'abort', 'error', 'emptied', 'play',
+ 'pause', 'loadedmetadata', 'loadeddata', 'waiting', 'playing', 'seeking',
+ 'seeked', 'timeupdate', 'ended', 'ratechange', 'durationchange', 'volumechange' ];
+
+var gEventNum = 0;
+var gTestNum = 0;
+var gTestFileNum = 0;
+var gExpectedEvents = null;
+var gTest = null;
+var gTestName = "?";
+
+function listener(evt) {
+ log('event ' + evt.type);
+ ok(gEventNum < gExpectedEvents.length, gTestName+" - corrent number of events received");
+ var expected = (gEventNum < gExpectedEvents.length) ? gExpectedEvents[gEventNum] : "NoEvent";
+ is(evt.type, expected, gTestName+" - events received in order");
+ gEventNum++;
+ if (gEventNum == gExpectedEvents.length) {
+ setTimeout(nextTest, 0);
+ }
+}
+
+function source_error(evt) {
+ log('event source_error');
+ ok(evt.type == "error", "Should only get error events here");
+ ok(gEventNum < gExpectedEvents.length, gTestName+" - corrent number of events received");
+ var expected = (gEventNum < gExpectedEvents.length) ? gExpectedEvents[gEventNum] : "NoEvent";
+ is("source_error", expected, gTestName+" - events received in order");
+ gEventNum++;
+ if (gEventNum == gExpectedEvents.length) {
+ setTimeout(nextTest, 0);
+ }
+}
+
+var gMedia = null;
+
+function createMedia(tag) {
+ gMedia = document.createElement(tag);
+ gMedia.preload = "metadata";
+ for (var i=0; i<gEventTypes.length; i++) {
+ gMedia.addEventListener(gEventTypes[i], listener);
+ }
+}
+
+function addSource(src, type) {
+ var s = document.createElement("source");
+ s.addEventListener("error", source_error);
+ s.src = src;
+ s.type = type;
+ gMedia.appendChild(s);
+ return s;
+}
+
+function prependSource(src, type) {
+ var s = document.createElement("source");
+ s.addEventListener("error", source_error);
+ s.src = src;
+ s.type = type;
+ gMedia.insertBefore(s, gMedia.firstChild);
+ return s;
+}
+
+var gTests = [
+ {
+ // Test 0: adding video to doc, then setting src should load implicitly.
+ create(src, type) {
+ document.body.appendChild(gMedia);
+ gMedia.src = src;
+ },
+ expectedEvents: ['loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
+ }, {
+ // Test 1: adding video to doc, then adding source.
+ create(src, type) {
+ document.body.appendChild(gMedia);
+ addSource(src, type);
+ },
+ expectedEvents: ['loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
+ },{
+ // Test 2: video with multiple source, the first of which are bad, we should load the last,
+ // and receive error events for failed loads on the source children.
+ create(src, type) {
+ document.body.appendChild(gMedia);
+ addSource("404a", type);
+ addSource("404b", type);
+ addSource(src, type);
+ },
+ expectedEvents: ['loadstart', 'source_error', 'source_error', 'durationchange', 'loadedmetadata', 'loadeddata']
+ }, {
+ // Test 3: video with bad src, good <source>, ensure that <source> aren't used.
+ create(src, type) {
+ gMedia.src = "404a";
+ addSource(src, type);
+ document.body.appendChild(gMedia);
+ },
+ expectedEvents: ['loadstart', 'error']
+ }, {
+ // Test 4: video with only bad source, loading, then adding a good source
+ // - should resume load.
+ create(src, type) {
+ addSource("404a", type);
+ var s2 = addSource("404b", type);
+ s2.addEventListener("error",
+ function(e) {
+ // Should awaken waiting load, causing successful load.
+ addSource(src, type);
+ });
+ document.body.appendChild(gMedia);
+ },
+ expectedEvents: ['loadstart', 'source_error', 'source_error', 'durationchange', 'loadedmetadata', 'loadeddata']
+ }, {
+ // Test 5: video with only 1 bad source, let it fail to load, then prepend
+ // a good <source> to the video, it shouldn't be selected, because the
+ // "pointer" should be after the last child - the bad source.
+ prepended: false,
+ create(src, type) {
+ var prepended = false;
+ addSource("404a", type);
+ var s2 = addSource("404b", type);
+ s2.addEventListener("error",
+ function(e) {
+ // Should awaken waiting load, causing successful load.
+ if (!prepended) {
+ prependSource(src, type);
+ prepended = true;
+ }
+ });
+ document.body.appendChild(gMedia);
+ },
+ expectedEvents: ['loadstart', 'source_error', 'source_error']
+ }, {
+ // Test 6: (Bug 1165203) preload="none" then followed by an explicit
+ // call to load() should load metadata
+ create(src, type) {
+ gMedia.preload = "none";
+ gMedia.src = src;
+ document.body.appendChild(gMedia);
+ addSource(src, type);
+ gMedia.load();
+ },
+ expectedEvents: ['emptied', 'loadstart', 'durationchange', 'loadedmetadata', 'loadeddata']
+ }
+];
+
+function nextTest() {
+ if (gMedia) {
+ for (var i=0; i<gEventTypes.length; i++) {
+ gMedia.removeEventListener(gEventTypes[i], listener);
+ }
+ removeNodeAndSource(gMedia);
+ gMedia = null;
+ }
+ gEventNum = 0;
+
+ if (gTestNum == gTests.length) {
+ gTestNum = 0;
+ ++gTestFileNum;
+ if (gTestFileNum == gSmallTests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ }
+
+ var src = gSmallTests[gTestFileNum].name;
+ var type = gSmallTests[gTestFileNum].type;
+
+ var t = gTests[gTestNum];
+ gTestNum++;
+
+ createMedia(type.match(/^audio\//) ? "audio" : "video");
+ if (!gMedia.canPlayType(type)) {
+ // Unsupported type, skip to next test
+ nextTest();
+ return;
+ }
+
+ gTestName = "Test " + src + " " + (gTestNum - 1);
+ log("Starting " + gTestName);
+ gExpectedEvents = t.expectedEvents;
+
+ t.create(src, type);
+}
+
+addLoadEvent(nextTest);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+
+<div id="log" style="font-size: small"></div>
+</body>
+</html>
diff --git a/dom/media/test/test_load_candidates.html b/dom/media/test/test_load_candidates.html
new file mode 100644
index 0000000000..2bc1eb531b
--- /dev/null
+++ b/dom/media/test/test_load_candidates.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=465458
+-->
+<head>
+ <title>Test for Bug 465458</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=465458">Mozilla Bug 465458</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 465458 **/
+
+var manager = new MediaTestManager;
+
+function finish(evt) {
+ var v = evt.target;
+ is(v._error, 2, "Should have received 2 error events before loaded");
+ v._finished = true;
+ // remove error event handler, because this would otherwise
+ // cause a failure on Windows 7, see bug 1024535
+ v.onerror = null;
+ v.remove();
+ manager.finished(v.token);
+}
+
+function errorHandler(evt) {
+ evt.target.parentNode._error++;
+}
+
+var extension = {
+ "audio/wav" : "wav",
+ "audio/x-wav": "wav",
+ "video/ogg" : "ogv",
+ "audio/ogg" : "oga",
+ "video/webm" : "webm"
+};
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "auto";
+ v.onerror = function(){ok(false,"Error events on source children should not bubble");}
+ v.token = token;
+ manager.started(token);
+ v._error = 0;
+ v._finished = false;
+ v._name = test.name;
+
+ var s1 = document.createElement("source");
+ s1.type = test.type;
+ s1.src = "404." + extension[test.type];
+ s1.addEventListener("error", errorHandler);
+ v.appendChild(s1);
+
+ var s2 = document.createElement("source");
+ s2.type = test.type;
+ s2.src = "test_load_candidates.html"; // definitely an invalid media file, regardless of its actual mime type...
+ s2.addEventListener("error", errorHandler);
+ v.appendChild(s2);
+
+ var s3 = document.createElement("source");
+ s3.type = test.type;
+ s3.src = test.name;
+ v.appendChild(s3);
+
+ v.addEventListener("loadeddata", finish);
+ document.body.appendChild(v);
+}
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_load_same_resource.html b/dom/media/test/test_load_same_resource.html
new file mode 100644
index 0000000000..d3e16c88a5
--- /dev/null
+++ b/dom/media/test/test_load_same_resource.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test loading of the same resource in multiple elements</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>
+</head>
+<body>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.requestCompleteLog();
+var manager = new MediaTestManager;
+
+function checkDuration(actual, expected, name) {
+ ok(Math.abs(actual - expected) < 0.1,
+ `${name} duration: ${actual} expected: ${expected}`);
+}
+
+function cloneLoaded(event) {
+ var e = event.target;
+ ok(true, `${e.token} loaded OK`);
+ checkDuration(e.duration, e._expectedDuration, e.token);
+ removeNodeAndSource(e);
+ manager.finished(e.token);
+}
+
+function tryClone(e) {
+ var clone = e.cloneNode(false);
+ clone.token = `${e.token}(cloned)`;
+ manager.started(clone.token);
+ manager.finished(e.token);
+
+ // Log events for debugging.
+ var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause"];
+ function logEvent(evt) {
+ var event = evt.target;
+ info(`${event.token} got ${evt.type}`);
+ }
+ events.forEach(function(event) {
+ clone.addEventListener(event, logEvent);
+ });
+
+
+ checkDuration(e.duration, e._expectedDuration, e.token);
+ clone._expectedDuration = e._expectedDuration;
+
+ clone.addEventListener("loadeddata", cloneLoaded, {once: true});
+ clone.addEventListener("loadstart", function(evt) {
+ info(`${evt.target.token} starts loading.`);
+ // Since there is only one H264 decoder instance, we have to delete the
+ // decoder of the original element for the cloned element to load. However,
+ // we can't delete the decoder too early otherwise cloning decoder will
+ // fail to kick in. We wait for 'loadstart' event of the cloned element to
+ // know when the decoder is already cloned and we can delete the decoder of
+ // the original element.
+ removeNodeAndSource(e);
+ }, {once: true});
+}
+
+// This test checks that loading the same URI twice in different elements at the same time
+// uses the same resource without doing another network fetch. One of the gCloneTests
+// uses dynamic_resource.sjs to return one resource on the first fetch and a different resource
+// on the second fetch. These resources have different lengths, so if the cloned element
+// does a network fetch it will get a resource with the wrong length and we get a test
+// failure.
+
+async function initTest(test, token) {
+ var e = document.createElement("video");
+ e.preload = "auto";
+ e.src = test.name;
+ e._expectedDuration = test.duration;
+ ok(true, `Trying to load ${test.name}, duration=${test.duration}`);
+ e.token = token;
+ manager.started(token);
+
+ // Since 320x240.ogv is less than 32KB, we need to wait for the
+ // 'suspend' event to ensure the partial block is flushed to the cache
+ // otherwise the cloned resource will create a new channel when it
+ // has no data to read at position 0. The new channel will download
+ // a different file than the original resource and fail the duration
+ // test.
+ let p1 = once(e, "loadeddata");
+ let p2 = once(e, "suspend");
+ await p1;
+ await p2;
+ tryClone(e);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {"set": [
+ ["logging.MediaDecoder", "Debug"],
+ ["logging.nsMediaElement", "Debug"],
+ ["logging.MediaCache", "Debug"],
+ ]},
+ manager.runTests.bind(manager, gCloneTests, initTest));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_load_source.html b/dom/media/test/test_load_source.html
new file mode 100644
index 0000000000..95a925b61f
--- /dev/null
+++ b/dom/media/test/test_load_source.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=534571
+-->
+<head>
+ <title>Test for Bug 534571</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534571">Mozilla Bug 534571</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 534571 **/
+
+// Test that when we load a video from a source child and then change the
+// source's src attribute and load again, that the subsequent loads work.
+
+var v = null;
+var s = null;
+
+function finish(event) {
+ ok(true, "Should have played both videos");
+ SimpleTest.finish();
+}
+
+var first = null;
+var second = null;
+
+function ended(event) {
+ s.type = second.type;
+ s.src = second.name;
+ v.removeEventListener("ended", ended);
+ v.addEventListener("ended", finish);
+ v.load();
+}
+
+// Find 2 videos we can play.
+v = document.createElement('video');
+for (var i=0; i<gPlayTests.length; i++) {
+ if (!v.canPlayType(gPlayTests[i].type))
+ continue;
+ if (!first) {
+ first = gPlayTests[i];
+ } else if (!second) {
+ second = gPlayTests[i];
+ break;
+ }
+}
+
+if (first && second) {
+ s = document.createElement('source');
+ s.type = first.type;
+ s.src = first.name;
+ v.appendChild(s);
+ v.autoplay = true;
+ v.addEventListener("ended", ended);
+ document.body.appendChild(v);
+ SimpleTest.waitForExplicitFinish();
+} else {
+ todo(false, "Need at least two media of supported types for this test!");
+}
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_load_source_empty_type.html b/dom/media/test/test_load_source_empty_type.html
new file mode 100644
index 0000000000..9ceee9af46
--- /dev/null
+++ b/dom/media/test/test_load_source_empty_type.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Load resource from source element with empty type</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="manifest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<video id="type"><source src="gizmo.mp4" type></video>
+<video id="type-and-equal"><source src="gizmo.mp4" type=></video>
+<video id="type-and-equal-quotation-marks"><source src="gizmo.mp4" type=""></video>
+<script class="testbody" type="text/javascript">
+/**
+ * This test is used to ensure that media element can start loading when its
+ * child source element with empty type property. We would test following forms,
+ * `type`, `type=` and `type=""`.
+ */
+SimpleTest.waitForExplicitFinish();
+
+const videos = document.getElementsByTagName("video");
+const videoNum = videos.length;
+let loadedElementsNum = 0;
+
+for (let video of videos) {
+ video.addEventListener("loadeddata",
+ () => {
+ ok(true, `loaded element '${video.id}'`);
+ if (++loadedElementsNum == videoNum) {
+ SimpleTest.finish();
+ }
+ }, { once: true});
+}
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_loop.html b/dom/media/test/test_loop.html
new file mode 100644
index 0000000000..943059edad
--- /dev/null
+++ b/dom/media/test/test_loop.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test looping support</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ manager.started(token);
+ var v = document.createElement('video');
+ v.token = token;
+ v.src = test.name;
+ v.name = test.name;
+ v.playCount = 0;
+ v.seekingCount = 0;
+ v.seekedCount = 0;
+ v.loop = true;
+
+ v.addEventListener("play", function (e) {
+ e.target.playCount += 1;
+ ok(e.target.playCount == 1, "Should get exactly one play event.");
+ });
+
+ v.addEventListener("seeking", function (e) {
+ e.target.seekingCount += 1;
+ });
+
+ v.addEventListener("seeked", function (e) {
+ e.target.seekedCount += 1;
+ if (e.target.seekedCount == 2) {
+ ok(e.target.seekingCount == 2, "Expect matched pairs of seeking/seeked events.");
+ e.target.loop = false;
+ }
+ });
+
+ v.addEventListener("ended", function (e) {
+ ok(!e.target.loop, "Shouldn't get ended event while looping.");
+ removeNodeAndSource(e.target);
+ manager.finished(e.target.token);
+ });
+
+ document.body.appendChild(v);
+ v.play();
+}
+
+manager.runTests(gSmallTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_looping_eventsOrder.html b/dom/media/test/test_looping_eventsOrder.html
new file mode 100644
index 0000000000..7d070de72f
--- /dev/null
+++ b/dom/media/test/test_looping_eventsOrder.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Looping events order</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>
+</head>
+<script type="text/javascript">
+/**
+ * This test is used to ensure the events order when media is looping back to
+ * the start position. We should see events in following order.
+ * 'seeking' -> 'timeupdate' -> 'seeked'.
+ */
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ { name:"small-shot.ogg", type:"audio/ogg" },
+ { name:"seek-short.webm", type:"video/webm" }
+];
+
+async function testTimeupdateChanged({name, type}) {
+ info(`- start testPlay for name=${name} -`);
+ const element = document.createElement(getMajorMimeType(type));
+ element.src = name;
+ element.loop = true;
+
+ await once(element, "canplay");
+ ok(await element.play().then(() => true, () => false), `start playing ${name}`);
+
+ let gotTimeUpdated = false;
+ await once(element, "seeking");
+ element.addEventListener("timeupdate", function() {
+ gotTimeUpdated = true;
+ }, {once: true});
+ await once(element, "seeked");
+ ok(gotTimeUpdated, "Got timeupdate between seeking and seeked.");
+
+ removeNodeAndSource(element);
+}
+
+(async function startTest() {
+ for (let test of tests) {
+ await testTimeupdateChanged(test);
+ }
+ SimpleTest.finish();
+})();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_media_selection.html b/dom/media/test/test_media_selection.html
new file mode 100644
index 0000000000..42f5a9bd43
--- /dev/null
+++ b/dom/media/test/test_media_selection.html
@@ -0,0 +1,142 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: media selection</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function maketest(attach_media, name, type, check_metadata) {
+ return function (token) {
+ var e = document.createElement('video');
+ e.preload = "metadata";
+ token = name + "-" + token;
+ manager.started(token);
+ var errorRun = false;
+ if (check_metadata) {
+ e.addEventListener('loadedmetadata', function () {
+ ok(e.readyState >= HTMLMediaElement.HAVE_METADATA,
+ 'test ' + token + ' readyState ' + e.readyState + ' expected >= ' + HTMLMediaElement.HAVE_METADATA);
+ is(e.currentSrc.substring(e.currentSrc.length - name.length), name, 'test ' + token);
+ // The load can go idle due to cache size limits
+ ok(e.networkState >= HTMLMediaElement.NETWORK_IDLE,
+ 'test ' + token + ' networkState = ' + e.networkState + ' expected >= ' + HTMLMediaElement.NETWORK_IDLE);
+ check_metadata(e);
+ removeNodeAndSource(e);
+ manager.finished(token);
+ });
+ } else {
+ e.addEventListener('error', function onerror(event) {
+ is(errorRun, false, "error handler should run once only!");
+ errorRun = true;
+ is(e.readyState, HTMLMediaElement.HAVE_NOTHING,
+ 'test ' + token + ' readyState should be HAVE_NOTHING when load fails.');
+ e.removeEventListener('error', onerror, true);
+ removeNodeAndSource(e);
+ manager.finished(token);
+ }, true);
+ }
+ attach_media(e, name, type);
+ }
+}
+
+function set_src(element, name, type) {
+ element.src = name;
+ document.body.appendChild(element);
+}
+
+function add_source(element, name, type) {
+ do_add_source(element, name, type);
+ document.body.appendChild(element);
+}
+
+function do_add_source(element, name, type) {
+ var source = document.createElement('source');
+ if (type) {
+ source.type = type;
+ }
+ source.src = name;
+ element.appendChild(source);
+}
+
+function add_sources_last(element, name, type) {
+ do_add_source(element, name, 'unsupported/type');
+ do_add_source(element, name, type);
+ document.body.appendChild(element);
+}
+
+function add_sources_first(element, name, type) {
+ do_add_source(element, name, type);
+ do_add_source(element, name, 'unsupported/type');
+ document.body.appendChild(element);
+}
+
+function late_add_sources_last(element, name, type) {
+ document.body.appendChild(element);
+ do_add_source(element, name, 'unsupported/type');
+ do_add_source(element, name, type);
+}
+
+function late_add_sources_first(element, name, type) {
+ document.body.appendChild(element);
+ do_add_source(element, name, type);
+ do_add_source(element, name, 'unsupported/type');
+}
+
+var nextTest = 0;
+var subtests = [
+ maketest(add_source, 'unknown.raw', 'bogus/type', null)
+];
+
+var tmpVid = document.createElement('video');
+
+for (var i = 0; i < gSmallTests.length; ++i) {
+ var test = gSmallTests[i];
+ var src = test.name;
+ var type = test.type;
+
+ if (!tmpVid.canPlayType(type))
+ continue;
+
+ // The following nested function hack is to ensure that 'test' is correctly
+ // captured in the closure and we don't end up getting the value 'test'
+ // had in the last iteration of the loop. I blame Brendan.
+ var check = function(t) { return function (e) {
+ checkMetadata(t.name, e, t);
+ }}(test);
+
+ var otherType = type.match(/^video\//) ? "audio/x-wav" : "video/ogg";
+ subtests.push(maketest(set_src, src, null, check),
+ maketest(add_source, src, null, check),
+ maketest(add_source, src, type, check),
+ maketest(add_sources_last, src, null, check),
+ maketest(add_sources_first, src, type, check),
+
+ // type hint matches a decoder, actual type matches different decoder
+ maketest(add_source, src, otherType, check),
+ maketest(add_source, 'unknown.raw', type, null),
+
+ // should not start loading, type excludes it from media candiate list
+ maketest(add_source, src, 'bogus/type', null),
+
+ // element doesn't notice source children attached later, needs bug 462455 fixed
+ maketest(late_add_sources_last, src, type, check),
+ maketest(late_add_sources_first, src, type, check));
+}
+
+function startTest(t, token) {
+ t(token);
+}
+
+manager.runTests(subtests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_media_sniffer.html b/dom/media/test/test_media_sniffer.html
new file mode 100644
index 0000000000..68e2bba8af
--- /dev/null
+++ b/dom/media/test/test_media_sniffer.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: sniffing</title>
+ <meta charset='utf-8'>
+ <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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+function finish_test(element) {
+ element.removeEventListener("error", onerror);
+ removeNodeAndSource(element);
+ manager.finished(element.token);
+}
+
+function onmetadataloaded(e) {
+ var t = e.target;
+ ++t.srcIndex;
+ ok(true, "The media loads when loaded via " + t.src);
+ if (t.srcIndex < t.srcList.length) {
+ t.src = t.srcList[t.srcIndex];
+ } else {
+ finish_test(t);
+ }
+}
+
+function onerror(e) {
+ var t = e.target;
+ t.removeEventListener('error', onerror);
+ ok(false, "The media could not be loaded." + t.src + "\n");
+ finish_test(t);
+}
+
+function startTest(test, token) {
+ var elemType = /^audio/.test(test.type) ? "audio" : "video";
+ var element = document.createElement(elemType);
+ // This .sjs file serve the media file without Content-Type header, or with a
+ // specific Content-Type header.
+ var baseSrc = 'contentType.sjs?file=' + test.name;
+ element.srcList = [
+ baseSrc + "&nomime",
+ baseSrc + "&type=application/octet-stream",
+ baseSrc + "&type=audio/wav",
+ baseSrc + "&type=text/html",
+ baseSrc + "&type=absolute_nonsense"
+ ];
+ element.srcIndex = 0;
+ element.src = element.srcList[element.srcIndex];
+ element.token = token;
+ element.controls = true;
+ element.preload = "metadata";
+ document.body.appendChild(element);
+ manager.started(token);
+ element.addEventListener("loadedmetadata", onmetadataloaded);
+ element.addEventListener("error", onerror);
+}
+
+manager.runTests(gSnifferTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediacapabilities_resistfingerprinting.html b/dom/media/test/test_mediacapabilities_resistfingerprinting.html
new file mode 100644
index 0000000000..1f07d1c707
--- /dev/null
+++ b/dom/media/test/test_mediacapabilities_resistfingerprinting.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1369309</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="manifest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1461454">Mozilla Bug 1461454</a>
+<a target="_blank" href="https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/13543">Tor Issue 13543</a>
+
+<script type="application/javascript">
+async function testWhetherSpoofed(resistFingerprinting) {
+
+ var unsupportedVideoConfiguration = {
+ contentType: 'video/bogus',
+ width: 800,
+ height: 600,
+ bitrate: 3000,
+ framerate: 24,
+ };
+ var supportedVideoConfiguration = {
+ contentType: 'video/webm; codecs="vp09.00.10.08"',
+ width: 800,
+ height: 600,
+ bitrate: 3000,
+ framerate: 24,
+ };
+
+ await SpecialPowers.pushPrefEnv({
+ "set": [
+ ["privacy.resistFingerprinting", resistFingerprinting]
+ ],
+ });
+
+ var result;
+
+ result = await navigator.mediaCapabilities.decodingInfo({
+ type: 'file',
+ video: unsupportedVideoConfiguration
+ });
+ is(result.supported, false, "video/bogus should be unsupported.");
+ is(result.smooth, false, "smooth is false when unsupported.");
+ is(result.powerEfficient, false, "powerEfficient is false when unsupported.");
+
+ result = await navigator.mediaCapabilities.decodingInfo({
+ type: 'file',
+ video: supportedVideoConfiguration
+ });
+ is(result.supported, true, "'video/webm; codecs=\"vp09.00.10.08\"' should be supported.");
+ if (resistFingerprinting) {
+ is(result.smooth, true, "smooth should be spoofed to true in RFP mode.");
+ is(result.powerEfficient, false, "powerEfficient should be spoofed to false in RFP mode.");
+ }
+}
+
+async function start() {
+ await testWhetherSpoofed(true);
+ await testWhetherSpoofed(false);
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+start();
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_avoid_recursion.html b/dom/media/test/test_mediarecorder_avoid_recursion.html
new file mode 100644
index 0000000000..0c733b9fc2
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_avoid_recursion.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+ <title>MediaRecorder infinite recursion with requestData() calls in "dataavailable" event</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="gUM_support.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897776">Mozill
+a Bug 897776</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+async function startTest() {
+ try {
+ await setupGetUserMediaTestPrefs();
+ let stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ let mediaRecorder = new MediaRecorder(stream);
+ let count = 0;
+ mediaRecorder.onerror = function () {
+ ok(false, 'Unexpected onerror callback fired');
+ SimpleTest.finish();
+ };
+ mediaRecorder.onwarning = function () {
+ ok(false, 'Unexpected onwarning callback fired');
+ };
+ mediaRecorder.ondataavailable = function (e) {
+ ++count;
+ info("got ondataavailable data size = " + e.data.size);
+ // no more requestData() to prevent busy main thread from starving
+ // the encoding thread
+ if (count == 30) {
+ info("track.stop");
+ stream.getTracks()[0].stop();
+ } else if (count < 30 && mediaRecorder.state == 'recording') {
+ info("requestData again");
+ mediaRecorder.requestData();
+ }
+ };
+ mediaRecorder.onstop = function () {
+ ok(true, "requestData within ondataavailable successfully avoided infinite recursion");
+ SimpleTest.finish();
+ };
+ mediaRecorder.start();
+ info("mediaRecorder start");
+ mediaRecorder.requestData();
+ info("mediaRecorder requestData");
+ } catch (e) {
+ ok(false, 'Unexpected error fired with: ' + e);
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/media/test/test_mediarecorder_bitrate.html b/dom/media/test/test_mediarecorder_bitrate.html
new file mode 100644
index 0000000000..693b27e3bf
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_bitrate.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Bitrate</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+var results = [];
+
+/**
+ * Starts a test on every media recorder file included to check that
+ * the bitrate control works
+ */
+function startTest(test, token) {
+ manager.started(token);
+ runTest(test, token, 1000000);
+ runTest(test, token, 100000);
+}
+
+function runTest(test, token, bitrate) {
+ var element = document.createElement('video');
+
+ element.token = token;
+
+ element.src = test.name;
+ element.preload = "metadata";
+ element.onloadedmetadata = function () {
+ info("loadedmetadata");
+ const stream = element.mozCaptureStreamUntilEnded();
+ element.onloadedmetadata = null;
+ element.play();
+
+ const mediaRecorder = new MediaRecorder(stream, {videoBitsPerSecond: bitrate});
+ mediaRecorder.start();
+ is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
+ is(mediaRecorder.stream, stream,
+ 'Media recorder stream = element stream at the start of recording');
+
+ var onStopFired = false;
+ var onDataAvailableFired = false;
+ var encoded_size = 0;
+
+ mediaRecorder.onerror = function () {
+ ok(false, 'Unexpected onerror callback fired');
+ };
+
+ mediaRecorder.onwarning = function () {
+ ok(false, 'Unexpected onwarning callback fired');
+ };
+
+ // This handler verifies that only a single onstop event handler is fired.
+ mediaRecorder.onstop = function () {
+ if (onStopFired) {
+ ok(false, 'onstop unexpectedly fired more than once');
+ } else {
+ onStopFired = true;
+
+ // ondataavailable should always fire before onstop
+ if (onDataAvailableFired) {
+ ok(true, 'onstop fired after ondataavailable');
+ info("test " + test.name + " encoded@" + bitrate + "=" + encoded_size);
+ if (results[test.name]) {
+ var big, small, temp;
+ big = {};
+ big.bitrate = bitrate;
+ big.size = encoded_size;
+ small = results[test.name];
+ // Don't assume the order that these will finish in
+ if (results[test.name].bitrate > bitrate) {
+ temp = big;
+ big = small;
+ small = temp;
+ }
+ // Ensure there is a big enough difference in the encoded
+ // sizes
+ ok(small.size*1.25 < big.size,
+ test.name + ' encoded@' + big.bitrate + '=' + big.size +
+ ' > encoded@' + small.bitrate + '=' + small.size);
+ manager.finished(token);
+ } else {
+ results[test.name] = {};
+ results[test.name].bitrate = bitrate;
+ results[test.name].size = encoded_size;
+ }
+ } else {
+ ok(false, 'onstop fired without an ondataavailable event first');
+ }
+ }
+ };
+
+ // This handler verifies that only a single ondataavailable event handler
+ // is fired with the blob generated having greater than zero size
+ // and a correct mime type.
+ mediaRecorder.ondataavailable = function (evt) {
+ if (onDataAvailableFired) {
+ ok(false, 'ondataavailable unexpectedly fired more than once');
+ } else {
+ onDataAvailableFired = true;
+
+ ok(evt instanceof BlobEvent,
+ 'Events fired from ondataavailable should be BlobEvent');
+ is(evt.type, 'dataavailable',
+ 'Event type should dataavailable');
+ ok(evt.data.size > 0,
+ 'Blob data received should be greater than zero');
+ encoded_size = evt.data.size;
+
+ // onstop should not have fired before ondataavailable
+ if (onStopFired) {
+ ok(false, 'ondataavailable unexpectedly fired later than onstop');
+ manager.finished(token);
+ }
+ }
+ };
+ };
+}
+
+manager.runTests(gMediaRecorderVideoTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_creation.html b/dom/media/test/test_mediarecorder_creation.html
new file mode 100644
index 0000000000..cd23cb7a96
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_creation.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Creation</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+/**
+ * Starts a test on every media recorder file included to check that
+ * a media recorder object created with a stream derived from a media
+ * element with that file produces the correct starting attribute values.
+ */
+function startTest(test, token) {
+ var element = document.createElement('audio');
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.stream = element.mozCaptureStreamUntilEnded();
+
+ var mediaRecorder = new MediaRecorder(element.stream);
+
+ is(mediaRecorder.stream, element.stream,
+ 'Stream should be provided stream on creation');
+ is(mediaRecorder.mimeType, '',
+ 'mimeType should be an empty string on creation');
+ is(mediaRecorder.state, 'inactive',
+ 'state should be inactive on creation');
+
+ manager.finished(token);
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_creation_fail.html b/dom/media/test/test_mediarecorder_creation_fail.html
new file mode 100644
index 0000000000..f411c2d3e1
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_creation_fail.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record with media.ogg.enabled = false</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function testThrows(stream, options) {
+ try {
+ new MediaRecorder(stream, options);
+ return false;
+ } catch(e) {
+ return e.name;
+ }
+}
+(async () => {
+ SimpleTest.waitForExplicitFinish();
+ await SpecialPowers.pushPrefEnv({set: [
+ ["media.ogg.enabled", false],
+ ["media.encoder.webm.enabled", false],
+ ]});
+ const stream = new AudioContext().createMediaStreamDestination().stream;
+ is(testThrows(stream, {mimeType: "audio/ogg"}), "NotSupportedError",
+ "Creating an ogg recorder without ogg support throws");
+ is(testThrows(stream, {mimeType: "audio/webm"}), "NotSupportedError",
+ "Creating a webm recorder without webm support throws");
+ is(testThrows(stream, {mimeType: "video/webm"}), "NotSupportedError",
+ "Creating a webm recorder without webm support throws");
+ is(testThrows(stream, {mimeType: "apa/bepa"}), "NotSupportedError",
+ "Creating a recorder for a bogus mime type throws");
+ is(testThrows(stream, {}), false,
+ "Creating a default recorder never throws, even without container support");
+ const recorder = new MediaRecorder(stream);
+ try {
+ recorder.start();
+ ok(false, "Starting a recorder without container support should throw");
+ } catch(e) {
+ is(e.name, "NotSupportedError",
+ "Starting a recorder without container support throws");
+ }
+ SimpleTest.finish();
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_fires_start_event_once_when_erroring.html b/dom/media/test/test_mediarecorder_fires_start_event_once_when_erroring.html
new file mode 100644
index 0000000000..537e1dbb47
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_fires_start_event_once_when_erroring.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1395022 - MediaRecorder fires start event when erroring.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1395022">Mozilla Bug 1395022</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function startTest() {
+ let audioContext = new AudioContext();
+ let destination1 = audioContext.createMediaStreamDestination();
+ let oscilator = audioContext.createOscillator();
+ oscilator.connect(destination1);
+ oscilator.start();
+
+ let destination2 = audioContext.createMediaStreamDestination();
+
+ let rec = new MediaRecorder(destination1.stream);
+
+ let numStartEvents = 0;
+
+ rec.onstart = () => {
+ numStartEvents += 1;
+ is(numStartEvents, 1, "One start event should be fired by the recorder");
+ // Trigger an error in the recorder
+ destination1.stream.addTrack(destination2.stream.getTracks()[0]);
+ };
+
+ rec.onerror = () => {
+ is(numStartEvents, 1, "One start event should have been fired by the recorder");
+ SimpleTest.finish();
+ };
+
+ rec.start();
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</head>
+</html> \ No newline at end of file
diff --git a/dom/media/test/test_mediarecorder_onerror_pause.html b/dom/media/test/test_mediarecorder_onerror_pause.html
new file mode 100644
index 0000000000..55ee5fb535
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_onerror_pause.html
@@ -0,0 +1,107 @@
+<html>
+<head>
+ <title>Bug 957439 - Media Recording - Assertion fail at Pause if unsupported input stream.</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="gUM_support.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957439">Mozilla Bug 957439</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function unexpected(e) {
+ ok(false, `Got unexpected ${e.type} event`);
+}
+
+
+async function startTest() {
+ // also do general checks on mimetype support for audio-only
+ ok(MediaRecorder.isTypeSupported("audio/ogg"),
+ 'Should support audio/ogg');
+ ok(MediaRecorder.isTypeSupported('audio/ogg; codecs=opus'),
+ 'Should support audio/ogg+opus');
+ ok(!MediaRecorder.isTypeSupported('audio/ogg; codecs=foobar'),
+ 'Should not support audio/ogg + unknown_codec');
+ ok(MediaRecorder.isTypeSupported("video/webm"),
+ 'Should support video/webm');
+ ok(!MediaRecorder.isTypeSupported("video/mp4"),
+ 'Should not support video/mp4');
+
+ try {
+ await setupGetUserMediaTestPrefs();
+ const expectedMimeType = 'video/webm; codecs="vp8, opus"';
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+ const [audioTrack] = stream.getAudioTracks();
+
+ // Expected event sequence should be:
+ // 1. start
+ // 2. error (from removed track)
+ // 3. dataavailable
+ // 4. stop
+ const mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream, 'Stream should be provided on creation');
+
+ mediaRecorder.onstart = unexpected;
+ mediaRecorder.onerror = unexpected;
+ mediaRecorder.ondataavailable = unexpected;
+ mediaRecorder.onstop = unexpected;
+
+ mediaRecorder.start();
+ is(mediaRecorder.state, 'recording', 'state should be recording');
+ is(mediaRecorder.mimeType, '', 'mimetype should be unset');
+
+ await new Promise(r => mediaRecorder.onstart = r);
+ mediaRecorder.onstart = unexpected;
+ ok(true, 'start event fired');
+ is(mediaRecorder.mimeType, expectedMimeType, 'mimetype should be set');
+
+ // Trigger an error
+ stream.removeTrack(audioTrack);
+
+ const err = await new Promise(r => mediaRecorder.onerror = r);
+ mediaRecorder.onerror = unexpected;
+ ok(true, 'error event fired');
+ is(err.error.name, 'InvalidModificationError',
+ 'Error name should be InvalidModificationError.');
+ ok(err.error.stack.includes('test_mediarecorder_onerror_pause.html'),
+ 'Events fired from onerror should include an error with a stack trace indicating ' +
+ 'an error in this test');
+ is(mediaRecorder.mimeType, '', 'mimetype should be unset');
+ is(mediaRecorder.state, 'inactive', 'state is inactive');
+
+ try {
+ mediaRecorder.pause();
+ ok(false, 'pause should fire an exception if called on an inactive recorder');
+ } catch(e) {
+ ok(e instanceof DOMException, 'pause should fire an exception ' +
+ 'if called on an inactive recorder');
+ is(e.name, 'InvalidStateError', 'Exception name should be InvalidStateError');
+ }
+
+ const evt = await new Promise(r => mediaRecorder.ondataavailable = r);
+ mediaRecorder.ondataavailable = unexpected;
+ ok(true, 'dataavailable event fired');
+ isnot(evt.data.size, 0, 'data size should not be zero');
+ ok(evt instanceof BlobEvent,
+ 'Events fired from ondataavailable should be BlobEvent');
+ is(evt.data.type, expectedMimeType, 'blob mimeType is set');
+
+ await new Promise(r => mediaRecorder.onstop = r);
+ mediaRecorder.onstop = unexpected;
+ ok(true, 'onstop event fired');
+ is(mediaRecorder.state, 'inactive', 'state should be inactive');
+ } catch (err) {
+ ok(false, `Unexpected error fired with: ${err}`);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+window.onload = startTest;
+
+</script>
+</head>
+</html>
diff --git a/dom/media/test/test_mediarecorder_pause_resume_video.html b/dom/media/test/test_mediarecorder_pause_resume_video.html
new file mode 100644
index 0000000000..a9b3d6a034
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_pause_resume_video.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording doesn't record during pause</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+ <canvas id="video-src-canvas"></canvas>
+ <video id="recorded-video"></video>
+</div>
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ // Setup canvas and take a stream from it
+ let canvas = document.getElementById("video-src-canvas");
+
+ let canvas_size = 100;
+ let new_canvas_size = 50;
+
+ canvas.width = canvas.height = canvas_size;
+
+ let helper = new CaptureStreamTestHelper2D(100, 100);
+ helper.drawColor(canvas, helper.red);
+
+ let canvasStream = canvas.captureStream();
+ // Canvas set up
+
+ // Check values for events
+ let numDataAvailabledRaised = 0;
+ // Recorded data that will be playback.
+ let blob;
+
+ let mediaRecorder = new MediaRecorder(canvasStream);
+ is(mediaRecorder.stream, canvasStream,
+ "Media recorder stream = canvas stream at the start of recording");
+
+ mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired");
+
+ mediaRecorder.onerror = () => ok(false, "Recording failed");
+
+ mediaRecorder.ondataavailable = ev => {
+ info("Got 'dataavailable' event");
+ ++numDataAvailabledRaised;
+ // Save recorded data for playback
+ blob = ev.data;
+ };
+
+ mediaRecorder.onstart = () => {
+ info("Got 'start' event");
+ // We just want one frame encoded before we pause
+ mediaRecorder.pause();
+ // We may rewrite this once we settle Bug 1363915, could listen for pause event instead
+ is(mediaRecorder.state, 'paused', 'Media recorder should be paused');
+
+ // Wait a while, then while paused draw blue at another size, then again
+ // green at the original size and resume.
+ let numberOfPaintsSincePause = 0;
+ window.requestAnimationFrame(function draw() {
+ numberOfPaintsSincePause++;
+ if (numberOfPaintsSincePause == 8) {
+ canvas.width = canvas.height = new_canvas_size;
+ helper.drawColor(canvas, helper.blue);
+ } else if (numberOfPaintsSincePause == 60) {
+ canvas.width = canvas.height = canvas_size;
+ helper.drawColor(canvas, helper.green);
+ } else if (numberOfPaintsSincePause == 68) {
+ // Waited 8 draws since changing canvas to green, should be safe to resume
+ mediaRecorder.resume();
+ } else if (numberOfPaintsSincePause > 120) {
+ mediaRecorder.stop();
+ return; // Early return, we don't want to request any more animation frames
+ }
+ window.requestAnimationFrame(draw);
+ });
+ };
+
+ mediaRecorder.onstop = () => {
+ info("Got 'stop' event");
+ is(mediaRecorder.state, 'inactive', 'Media recorder should be incative after stop');
+ is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event");
+
+ ok(blob, "Should have gotten a data blob");
+ let video = document.getElementById("recorded-video");
+ video.id = "recorded-video";
+ video.src = URL.createObjectURL(blob);
+ // Setup a check to make sure we don't play back any blue
+ let checkVideoHasNoBlue = () => {
+ if(helper.isPixel(helper.getPixel(video), helper.blue, 128)) {
+ ok(false, "Video should have no blue frames");
+ // Remove handler so we don't spam the log
+ video.ontimeupdate = null;
+ }
+ };
+ video.ontimeupdate = checkVideoHasNoBlue;
+ video.onerror = () => {
+ ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
+ SimpleTest.finish();
+ };
+ video.onended = () => {
+ ok(helper.isPixel(helper.getPixel(video), helper.green, 128), "Last frame should be green");
+ SimpleTest.finish();
+ };
+ // The video will resize once it loads its metadata, only listen for resizes after that
+ video.onloadedmetadata = () => {
+ ok(video.videoWidth === canvas_size && video.videoHeight === canvas_size,
+ "video element should be same size as canvas once metadata is loaded");
+ // We shouldn't have any resize events once the video is loaded
+ video.onresize = () => {
+ ok(false, "Should not have any resize events!");
+ };
+ };
+
+ video.play();
+ };
+
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder should be recording");
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_playback_can_repeat.html b/dom/media/test/test_mediarecorder_playback_can_repeat.html
new file mode 100644
index 0000000000..4cf2735088
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_playback_can_repeat.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording creates videos that can playback more than once</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+ <canvas id="video-src-canvas"></canvas>
+ <video id="recorded-video" loop></video>
+</div>
+<script class="testbody" type="text/javascript">
+
+(async function() {
+ try {
+ SimpleTest.waitForExplicitFinish();
+ await startTest();
+ } catch(e) {
+ ok(false, `Got error msg '${e}'`);
+ } finally {
+ SimpleTest.finish();
+ }
+})();
+
+async function startTest() {
+ let canvas = document.getElementById("video-src-canvas");
+
+ let canvas_size = 100;
+ canvas.width = canvas.height = canvas_size;
+ let helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size);
+ helper.drawColor(canvas, helper.red);
+
+ let stream = canvas.captureStream();
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the beginning of recording");
+ new Promise(r => mediaRecorder.onerror = err => r(err)).then(
+ err => Promise.reject(`MediaRecorder: error unexpectedly fired. Code ${err.name}`));
+
+ mediaRecorder.start();
+ await new Promise(r => mediaRecorder.onstart = r);
+ info("Media recorder started");
+ // Feed some more data into the recorder so the output has a non trivial duration.
+ // This avoids the case where we have only 1 frame in the output, which breaks
+ // looping (this is expected as a WebM with 1 video frame has no duration).
+ let counter = 0;
+ let draw = () => {
+ counter++;
+ helper.drawColor(canvas, helper.blue);
+ if(counter > 2) {
+ mediaRecorder.stop();
+ return;
+ }
+ requestAnimationFrame(draw);
+ };
+ requestAnimationFrame(draw);
+
+ // When recorder is stopped get recorded data.
+ const data = await new Promise(r => mediaRecorder.ondataavailable = ev => r(ev));
+ info(`Media recorder get availiable data`);
+ const blob = data.data;
+
+ await new Promise(r => mediaRecorder.onstop = r);
+ info("Media recorder stopped");
+ ok(blob, "Should have gotten a data blob");
+ const video = document.getElementById("recorded-video");
+ new Promise(r => video.onerror = err => r(err)).then(
+ err => Promise.reject(`Video: error unexpectedly fired. Code ${err.code}`));
+ video.src = URL.createObjectURL(blob);
+ video.play();
+ await new Promise(r => video.onplaying = r);
+ for (let idx = 0; idx < 2; idx++) {
+ await new Promise(r => video.onseeking = r);
+ ok(true, `waiting until video finishes seeking`);
+ await new Promise(r => video.onseeked = r);
+ ok(true, "video finished seeked");
+ }
+ video.pause();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_principals.html b/dom/media/test/test_mediarecorder_principals.html
new file mode 100644
index 0000000000..00dcaa5a5b
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_principals.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=489415
+-->
+<head>
+ <title>Test for MediaRecorder Reaction to Principal Change</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+</head>
+<body>
+<div>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018299">Test for MediaRecorder Principal Handling</a>
+</div>
+
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+let throwOutside = e => setTimeout(() => { throw e; });
+
+// Loading data from a resource that changes origins while streaming should
+// be detected by the media cache and result in a null principal so that the
+// MediaRecorder usages below fail.
+
+// This test relies on midflight-redirect.sjs returning the the first quarter of
+// the resource as a byte range response, and then hanging up, and when Firefox
+// requests the remainder midflight-redirect.sjs serves a redirect to another origin.
+
+async function testPrincipals(resource) {
+ if (!resource) {
+ todo(false, "No types supported");
+ return;
+ }
+ await testPrincipals1(resource);
+ await testPrincipals2(resource);
+}
+
+function makeVideo() {
+ let video = document.createElement("video");
+ video.preload = "metadata";
+ video.controls = true;
+ document.body.appendChild(video);
+ return video;
+}
+
+// First test: Load file from same-origin first, then get redirected to
+// another origin before attempting to record stream.
+async function testPrincipals1(resource) {
+ let video = makeVideo();
+ video.src =
+ "http://mochi.test:8888/tests/dom/media/test/midflight-redirect.sjs" +
+ "?resource=" + resource.name + "&type=" + resource.type;
+
+ let errorBarrier = once(video, "error");
+ // Wait for the video to load to metadata. We can then start capturing.
+ // Must also handle the download bursting and hitting the error before we
+ // reach loadedmetadata. Normally we reach loadedmetadata first, but
+ // rarely we hit the redirect first.
+ await Promise.race([once(video, "loadedmetadata"), errorBarrier]);
+
+ let rec = new MediaRecorder(video.mozCaptureStreamUntilEnded());
+ video.play();
+
+ // Wait until we hit a playback error. This means our download has hit the redirect.
+ await errorBarrier;
+
+ // Try to record, it should be blocked with a security error.
+ try {
+ rec.start();
+ ok(false, "mediaRecorder.start() must throw SecurityError, but didn't throw at all");
+ } catch (ex) {
+ is(ex.name, "SecurityError", "mediaRecorder.start() must throw SecurityError");
+ }
+ removeNodeAndSource(video);
+}
+
+// Second test: Load file from same-origin first, but record ASAP, before
+// getting redirected to another origin.
+async function testPrincipals2(resource) {
+ let video = makeVideo();
+ video.src =
+ "http://mochi.test:8888/tests/dom/media/test/midflight-redirect.sjs" +
+ "?resource=" + resource.name + "&type=" + resource.type;
+
+ // Wait for the video to load to metadata. We can then start capturing.
+ // Must also handle the download bursting and hitting the error before we
+ // reach loadedmetadata. Normally we reach loadedmetadata first, but
+ // rarely we hit the redirect first.
+ await Promise.race([once(video, "loadedmetadata"), once(video, "error")]);
+
+ let ended = false;
+ once(video, "ended", () => ended = true);
+
+ // Start capturing. It should work.
+ let rec;
+ let errorBarrier;
+ try {
+ rec = new MediaRecorder(video.mozCaptureStreamUntilEnded());
+ errorBarrier = nextEvent(rec, "error");
+ rec.start();
+ ok(true, "mediaRecorder.start() should not throw here, and didn't");
+ } catch (ex) {
+ ok(false, "mediaRecorder.start() unexpectedly threw " + ex.name + " (" + ex.message + ")");
+ }
+
+ // Play the video, this should result in a SecurityError on the recorder.
+ let hasStopped = once(rec, "stop");
+ video.play();
+ let error = (await errorBarrier).error;
+ is(error.name, "SecurityError", "mediaRecorder.onerror must fire SecurityError");
+ ok(error.stack.includes('test_mediarecorder_principals.html'),
+ 'Events fired from onerror should include an error with a stack trace indicating ' +
+ 'an error in this test');
+ is(ended, false, "Playback should not have reached end");
+ await hasStopped;
+ is(ended, false, "Playback should definitely not have reached end");
+
+ removeNodeAndSource(video);
+}
+
+testPrincipals({ name:"pixel_aspect_ratio.mp4", type:"video/mp4", duration:28 })
+ .catch(e => throwOutside(e))
+ .then(() => SimpleTest.finish());
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html
new file mode 100644
index 0000000000..99f57f181e
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record AudioContext with four channels</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>
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(4, 80920, context.sampleRate);
+ for (var i = 0; i < 80920; ++i) {
+ for(var j = 0; j < 4; ++j) {
+ buffer.getChannelData(j)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate);
+ }
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.loop = true;
+ var dest = context.createMediaStreamDestination();
+ var stopTriggered = false;
+ var onstopTriggered = false;
+ dest.channelCount = 4;
+ var expectedMimeType = 'audio/ogg; codecs=opus';
+ source.channelCountMode = 'explicit';
+ source.connect(dest);
+ var elem = document.createElement('audio');
+ elem.srcObject = dest.stream;
+ source.start(0);
+ elem.play();
+ let mMediaRecorder = new MediaRecorder(dest.stream);
+ mMediaRecorder.onwarning = function() {
+ ok(false, 'onwarning unexpectedly fired');
+ };
+
+ mMediaRecorder.onerror = function() {
+ ok(false, 'onerror unexpectedly fired');
+ };
+
+ mMediaRecorder.onstop = function() {
+ ok(true, 'onstop fired successfully');
+ is(mMediaRecorder.state, 'inactive', 'check recording status is inactive');
+ onstopTriggered = true;
+ SimpleTest.finish();
+ };
+ mMediaRecorder.ondataavailable = function (e) {
+ ok(e.data.size > 0, 'check blob has data');
+ is(e.data.type, expectedMimeType, 'blob should have expected mimetype');
+ if (!stopTriggered) {
+ is(mMediaRecorder.mimeType, expectedMimeType, 'recorder should have expected mimetype');
+ mMediaRecorder.stop();
+ is(mMediaRecorder.mimeType, '', 'recorder should have reset its mimetype');
+ stopTriggered = true;
+ } else if (onstopTriggered) {
+ ok(false, 'ondataavailable should come before onstop event');
+ }
+ };
+ try {
+ mMediaRecorder.start(1000);
+ is('recording', mMediaRecorder.state, "check record state recording");
+ } catch (e) {
+ ok(false, 'Can t record audio context');
+ }
+}
+
+startTest();
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_addtracked_stream.html b/dom/media/test/test_mediarecorder_record_addtracked_stream.html
new file mode 100644
index 0000000000..046bf768ce
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_addtracked_stream.html
@@ -0,0 +1,184 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder recording a constructed MediaStream</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script src="/tests/dom/media/webrtc/tests/mochitests/head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+</div>
+<script>
+SimpleTest.waitForExplicitFinish();
+runTestWhenReady(async () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = canvas.height = 100;
+ document.getElementById("content").appendChild(canvas);
+
+ const helper = new CaptureStreamTestHelper2D(100, 100);
+ helper.drawColor(canvas, helper.red);
+
+ const audioCtx = new AudioContext();
+ const osc = audioCtx.createOscillator();
+ osc.frequency.value = 1000;
+ osc.start();
+ const dest = audioCtx.createMediaStreamDestination();
+ osc.connect(dest);
+
+ const stream = dest.stream;
+
+ // Timeouts are experienced intermittently in Linux due to no sound in the
+ // destination. As a workaround wait for the source sound to arrive.
+ const sourceAnalyser = new AudioStreamAnalyser(audioCtx, stream);
+ const sourceAudioReady = sourceAnalyser.waitForAnalysisSuccess(array => {
+ const freq = osc.frequency.value;
+ const lowerFreq = freq / 2;
+ const upperFreq = freq + 1000;
+ const lowerAmp = array[sourceAnalyser.binIndexForFrequency(lowerFreq)];
+ const freqAmp = array[sourceAnalyser.binIndexForFrequency(freq)];
+ const upperAmp = array[sourceAnalyser.binIndexForFrequency(upperFreq)];
+ info("Analysing source audio. "
+ + lowerFreq + ": " + lowerAmp + ", "
+ + freq + ": " + freqAmp + ", "
+ + upperFreq + ": " + upperAmp);
+ return lowerAmp < 50 && freqAmp > 200 && upperAmp < 50;
+ });
+ await sourceAudioReady;
+ info("Source Audio content ok");
+
+ canvas.captureStream(0).getVideoTracks().forEach(t => stream.addTrack(t));
+
+ const blobs = [];
+
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = constructed stream at the start of recording");
+
+
+ mediaRecorder.ondataavailable = evt => {
+ info("ondataavailable fired");
+
+ is(mediaRecorder.state, "inactive", "Blob received after stopping");
+ is(blobs.length, 0, "This is the first and only blob");
+ ok(evt instanceof BlobEvent,
+ "Events fired from ondataavailable should be BlobEvent");
+ is(evt.type, "dataavailable",
+ "Event type should dataavailable");
+ ok(evt.data.size >= 0,
+ "Blob data size received is greater than or equal to zero");
+
+ blobs.push(evt.data);
+ };
+
+ const stopped = haveEvent(mediaRecorder, "stop", wait(5000, new Error("Timeout")));
+ const stoppedNoErrors = Promise.all([
+ stopped,
+ haveNoEvent(mediaRecorder, "warning", stopped),
+ haveNoEvent(mediaRecorder, "error", stopped)
+ ]);
+
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder should be recording");
+
+ await haveEvent(mediaRecorder, "start", wait(5000, new Error("Timeout")));
+ info("onstart fired");
+
+ // The recording can be too short to cause any checks with
+ // waitForAnalysisSuccess(). Waiting a bit here solves this.
+ await wait(500);
+
+ is(mediaRecorder.state, "recording",
+ "Media recorder is recording before being stopped");
+ mediaRecorder.stop();
+ is(mediaRecorder.state, "inactive",
+ "Media recorder is inactive after being stopped");
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = constructed stream post recording");
+
+ await stoppedNoErrors;
+ info("Got 'stop' event");
+
+ ok(blobs.length == 1, "Should have gotten one data blob");
+
+ // Clean up recording sources
+ osc.stop();
+ stream.getTracks().forEach(t => t.stop());
+
+ // Sanity check the recording
+ const video = document.createElement("video");
+ document.getElementById("content").appendChild(video);
+ video.id = "recorded-video";
+
+ const blob = new Blob(blobs);
+ ok(blob.size > 0, "Recorded blob should contain data");
+
+ video.src = URL.createObjectURL(blob);
+ video.preload = "metadata";
+
+ info("Waiting for metadata to be preloaded");
+
+ await haveEvent(video, "loadedmetadata", wait(5000, new Error("Timeout")));
+ info("Playback of recording loaded metadata");
+
+ const recordingStream = video.mozCaptureStream();
+ is(recordingStream.getVideoTracks().length, 1,
+ "Recording should have one video track");
+ is(recordingStream.getAudioTracks().length, 1,
+ "Recording should have one audio track");
+
+ const ended = haveEvent(video, "ended", wait(5000, new Error("Timeout")));
+ const endedNoError = Promise.all([
+ ended,
+ haveNoEvent(video, "error", ended),
+ ]);
+
+ const analyser = new AudioStreamAnalyser(audioCtx, recordingStream);
+ const audioReady = analyser.waitForAnalysisSuccess(array => {
+ const freq = osc.frequency.value;
+ const lowerFreq = freq / 2;
+ const upperFreq = freq + 1000;
+ const lowerAmp = array[analyser.binIndexForFrequency(lowerFreq)];
+ const freqAmp = array[analyser.binIndexForFrequency(freq)];
+ const upperAmp = array[analyser.binIndexForFrequency(upperFreq)];
+ info("Analysing audio. "
+ + lowerFreq + ": " + lowerAmp + ", "
+ + freq + ": " + freqAmp + ", "
+ + upperFreq + ": " + upperAmp);
+ return lowerAmp < 50 && freqAmp > 200 && upperAmp < 50;
+ }, endedNoError.then(() => new Error("Audio check failed")));
+
+ const videoReady = helper.pixelMustBecome(
+ video, helper.red, {
+ threshold: 128,
+ infoString: "Should become red",
+ cancelPromise: endedNoError.then(() => new Error("Video check failed")),
+ });
+
+ video.play();
+
+ try {
+ await endedNoError;
+ } finally {
+ analyser.disconnect();
+ let url = video.src;
+ video.src = "";
+ URL.revokeObjectURL(url);
+ }
+
+ info("Playback of recording ended without error");
+
+ await audioReady;
+ info("Audio content ok");
+
+ await videoReady;
+ info("Video content ok");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_audiocontext.html b/dom/media/test/test_mediarecorder_record_audiocontext.html
new file mode 100644
index 0000000000..686faaeb6b
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_audiocontext.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record AudioContext</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>
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(1, 80920, context.sampleRate);
+ for (var i = 0; i < 80920; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.loop = true;
+
+ var dest = context.createMediaStreamDestination();
+ source.connect(dest);
+ var elem = document.createElement('audio');
+ elem.srcObject = dest.stream;
+ source.start(0);
+ elem.play();
+ let mMediaRecorder = new MediaRecorder(dest.stream);
+ mMediaRecorder.onwarning = function() {
+ ok(false, 'onwarning unexpectedly fired');
+ };
+
+ mMediaRecorder.onerror = function() {
+ ok(false, 'onerror unexpectedly fired');
+ };
+
+ mMediaRecorder.onstop = function() {
+ ok(true, 'onstop fired successfully');
+ is(mMediaRecorder.state, 'inactive', 'check recording status is inactive');
+ SimpleTest.finish();
+ };
+ mMediaRecorder.ondataavailable = function (e) {
+ if (mMediaRecorder.state == 'recording') {
+ is(mMediaRecorder.mimeType, 'audio/ogg; codecs=opus', 'Expected MediaRecorder mimetype');
+ is(e.data.type, 'audio/ogg; codecs=opus', 'Expected Blob mimetype');
+ ok(e.data.size > 0, 'check blob has data');
+ mMediaRecorder.stop();
+ }
+ };
+ try {
+ mMediaRecorder.start(1000);
+ is('recording', mMediaRecorder.state, "check record state recording");
+ } catch (e) {
+ ok(false, 'Can t record audio context');
+ }
+}
+
+startTest();
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_audiocontext_mlk.html b/dom/media/test/test_mediarecorder_record_audiocontext_mlk.html
new file mode 100644
index 0000000000..4128702aef
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_audiocontext_mlk.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>capture for possible memory leak when record AudioContext</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=973765">Mozill
+a Bug 973765</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ // This test case want to capture the memory leak if exit the browser after running those script.
+ var ac = new window.AudioContext();
+ var destStream = ac.createMediaStreamDestination().stream;
+ var recorder = new MediaRecorder(destStream);
+ recorder.start(1000);
+ is(recorder.state, 'recording', 'Media recorder should be recording');
+ is(recorder.stream, destStream,
+ 'Media recorder stream = element stream at the start of recording');
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_audionode.html b/dom/media/test/test_mediarecorder_record_audionode.html
new file mode 100644
index 0000000000..8a57437b81
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_audionode.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record AudioContext Node</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968109">Mozilla Bug 968109</a>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function setUpSource(contextType, nodeType) {
+ // Use contextType to choose offline or real-time context.
+ const context = contextType == "offline"?
+ new OfflineAudioContext(2 , 80920, 44100) : new AudioContext();
+ const buffer = context.createBuffer(2, 80920, context.sampleRate);
+ for (let i = 0; i < 80920; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate);
+ buffer.getChannelData(1)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ const source = context.createBufferSource();
+ source.buffer = buffer;
+ source.loop = true;
+
+ source.start(0);
+
+ // nodeType decides which node in graph should be the source of recording.
+ let node;
+ if (nodeType == "source") {
+ node = source;
+ } else if (nodeType == "splitter") {
+ const splitter = context.createChannelSplitter();
+ source.connect(splitter);
+ node = splitter;
+ } else if (nodeType == "destination") {
+ source.connect(context.destination);
+ node = context.destination;
+ }
+ // Explicitly start offline context.
+ if (contextType == "offline") {
+ context.startRendering();
+ }
+
+ return node;
+}
+
+async function testRecord(source, mimeType) {
+ const isOffline = source.context instanceof OfflineAudioContext;
+ const recorder = new MediaRecorder(source, 0, {mimeType});
+ is(recorder.mimeType, mimeType, "Mime type is set");
+ const extendedMimeType = `${mimeType || "audio/ogg"}; codecs=opus`;
+
+ recorder.onwarning = () => ok(false, "should not fire onwarning");
+ recorder.onerror = () => ok(false, "should not fire onerror");
+ if (!isOffline) {
+ recorder.onstop = () => ok(false, "should not fire stop yet");
+ }
+
+ recorder.start(1000);
+ is("recording", recorder.state, "state should become recording after calling start()");
+ is(recorder.mimeType, mimeType, "Mime type is not changed by start()");
+
+ await new Promise(r => recorder.onstart = r);
+ is(recorder.mimeType, extendedMimeType, "Mime type is fully defined");
+
+ const chunks = [];
+ let {data} = await new Promise(r => recorder.ondataavailable = r);
+ if (!isOffline) {
+ is(recorder.state, "recording", "Expected to still be recording");
+ }
+ is(data.type, extendedMimeType, "Blob has fully defined mimetype");
+ isnot(data.size, 0, "should get data and its length should be > 0");
+ chunks.push(data);
+
+ if (isOffline) {
+ await new Promise(r => recorder.onstop = r);
+ is(recorder.state, "inactive", "Offline context should end by itself");
+ } else {
+ is(recorder.state, "recording", "Expected to still be recording");
+ recorder.stop();
+ ({data} = await new Promise(r => recorder.ondataavailable = r));
+ is(recorder.state, "inactive", "Expected to be inactive after last blob");
+ isnot(data.size, 0, "should get data and its length should be > 0");
+ chunks.push(data);
+
+ await new Promise(r => recorder.onstop = r);
+ is(recorder.state, "inactive", "state should remain inactive after stop event");
+ }
+ return new Blob(chunks, {type: chunks[0].type});
+}
+
+addLoadEvent(async () => {
+ const src = setUpSource();
+ let didThrow = false;
+ try {
+ new MediaRecorder(src);
+ } catch (e) {
+ didThrow = true;
+ }
+ ok(didThrow, "MediaRecorder(AudioNode) should be hidden behind a pref");
+
+ await SpecialPowers.pushPrefEnv({set: [
+ ["media.recorder.audio_node.enabled", true],
+ ]});
+
+ // Test with various context and source node types.
+ for (const mimeType of [
+ "audio/ogg",
+ "audio/webm",
+ "video/webm",
+ "",
+ ]) {
+ for (const {context, node} of [
+ {context: "", node: "source"},
+ {context: "", node: "splitter"},
+ {context: "offline", node: "destination"},
+ ]) {
+ info(`Testing recording ${context || "regular"} context and ${node} ` +
+ `node with mimeType '${mimeType}'`);
+ await testRecord(setUpSource(context, node), mimeType);
+ }
+ }
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_canvas_captureStream.html b/dom/media/test/test_mediarecorder_record_canvas_captureStream.html
new file mode 100644
index 0000000000..0b6cd6dbb5
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_canvas_captureStream.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording canvas stream</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+</div>
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var canvas = document.createElement("canvas");
+ canvas.width = canvas.height = 100;
+ document.getElementById("content").appendChild(canvas);
+
+ var helper = new CaptureStreamTestHelper2D(100, 100);
+ helper.drawColor(canvas, helper.red);
+
+ var stream = canvas.captureStream(0);
+
+ var blob;
+
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the start of recording");
+
+ mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired");
+
+ mediaRecorder.onerror = () => ok(false, "Recording failed");
+
+ mediaRecorder.ondataavailable = ev => {
+ is(blob, undefined, "Should only get one dataavailable event");
+ blob = ev.data;
+ };
+
+ mediaRecorder.onstart = () => {
+ info("Got 'start' event");
+ // We just want one frame encoded, to see that the recorder produces something readable.
+ mediaRecorder.stop();
+ };
+
+ mediaRecorder.onstop = () => {
+ info("Got 'stop' event");
+ ok(blob, "Should have gotten a data blob");
+
+ var video = document.createElement("video");
+ video.id = "recorded-video";
+ video.src = URL.createObjectURL(blob);
+ video.play();
+ video.onerror = err => {
+ ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
+ SimpleTest.finish();
+ };
+ document.getElementById("content").appendChild(video);
+ helper.pixelMustBecome(video, helper.red, {
+ threshold: 128,
+ infoString: "Should become red"
+ }).then(SimpleTest.finish);
+ };
+
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder should be recording");
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_changing_video_resolution.html b/dom/media/test/test_mediarecorder_record_changing_video_resolution.html
new file mode 100644
index 0000000000..d6354ee5a1
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_changing_video_resolution.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording canvas stream that dynamically changes resolution</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+</div>
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ let canvas = document.createElement("canvas");
+ const resolution_change = [
+ {width: 100, height: 100, color: "red"},
+ {width: 150, height: 150, color: "blue"},
+ {width: 100, height: 100, color: "red"},
+ ];
+ canvas.width = resolution_change[0].width;
+ canvas.height = resolution_change[0].height;
+
+ let ctx = canvas.getContext("2d");
+ ctx.fillStyle = resolution_change[0].color;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // The recorded stream coming from canvas.
+ let stream = canvas.captureStream();
+
+ // Check values for events
+ let numDataAvailabledRaised = 0;
+ let numResizeRaised = 0;
+ // Recorded data that will be playback.
+ let blob;
+
+ // Media recorder for VP8 and canvas stream.
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the start of recording");
+
+ // Not expected events.
+ mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired");
+ mediaRecorder.onerror = err => {
+ ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name);
+ SimpleTest.finish();
+ };
+
+ // When recorder is stopped get recorded data.
+ mediaRecorder.ondataavailable = ev => {
+ info("Got 'dataavailable' event");
+ ++numDataAvailabledRaised;
+ is(blob, undefined, "Should only get one dataavailable event");
+ // Save recorded data for playback
+ blob = ev.data;
+ };
+
+ mediaRecorder.onstart = () => {
+ info('onstart fired successfully');
+ };
+
+ mediaRecorder.onstop = () => {
+ info("Got 'stop' event");
+ is(numDataAvailabledRaised, 1, "Should have gotten 1 dataavailable event");
+ // Playback stream and verify resolution changes.
+ ok(blob, "Should have gotten a data blob");
+
+ let video = document.createElement("video");
+ video.id = "recorded-video";
+ video.src = URL.createObjectURL(blob);
+ video.preload = "metadata";
+
+ video.onerror = err => {
+ ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
+ SimpleTest.finish();
+ };
+
+ // Check that the encoded frames have the correct sizes.
+ video.onresize = function() {
+ if (numResizeRaised < resolution_change.length) {
+ is(video.videoWidth, resolution_change[numResizeRaised].width,
+ "onresize width should be as expected");
+ is(video.videoHeight, resolution_change[numResizeRaised].height,
+ "onresize height should be as expected");
+ } else {
+ ok(false, "Got more resize events than expected");
+ }
+ ++numResizeRaised;
+ };
+
+ video.onloadedmetadata = function() {
+ info("loadedmetadata");
+ seekThroughFrames();
+ };
+
+ video.onended = function() {
+ is(numResizeRaised, resolution_change.length, "Expected number of resize events");
+ SimpleTest.finish();
+ // This shouldn't be needed, however video.ended may not be set after
+ // seeking to the final frame. This can result in seekToNextFrame being
+ // called again by seekThroughFrames and onended being invoked again,
+ // resulting in multiple finish() calls.
+ // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1386489
+ video.onended = null;
+ };
+
+ document.getElementById("content").appendChild(video);
+
+ function seekThroughFrames() {
+ info("Seeking to next frame");
+ video.seekToNextFrame()
+ .then(() => {
+ info("Seeking to next frame finished. width=" + video.videoWidth
+ + ", height=" + video.videoHeight);
+
+ if (video.ended) {
+ return;
+ }
+
+ // After seeking finished we queue the next seek task on the event
+ // loop so it gets in the same queue as the "resize" events.
+ setTimeout(seekThroughFrames, 0);
+ })
+ .catch(error => {
+ ok(false, "seekToNextFrame rejected: " + error);
+ });
+ }
+ };
+
+ // Start here by stream recorder.
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder should be recording");
+ requestAnimationFrame(draw);
+
+ // Change resolution in every frame
+ // Stop recorder on last frame
+ let countFrames = 0;
+ let previous_time = performance.now();
+ function draw(timestamp) {
+ if (timestamp - previous_time < 100) {
+ requestAnimationFrame(draw);
+ return;
+ }
+ previous_time = timestamp;
+
+ if (countFrames == resolution_change.length) {
+ mediaRecorder.stop();
+ return;
+ }
+
+ canvas.width = resolution_change[countFrames].width;
+ canvas.height = resolution_change[countFrames].height;
+ ctx.fillStyle = resolution_change[countFrames].color;
+ // Resize and draw canvas
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ // Register draw to be called on next rendering
+ requestAnimationFrame(draw);
+ countFrames++;
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.seekToNextFrame.enabled", true ],
+ ["media.video-queue.send-to-compositor-size", 1]
+ ]
+ }, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_downsize_resolution.html b/dom/media/test/test_mediarecorder_record_downsize_resolution.html
new file mode 100644
index 0000000000..f9422a3897
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_downsize_resolution.html
@@ -0,0 +1,148 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording canvas dynamically changes to greater resolution</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+</div>
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var canvas = document.createElement("canvas");
+ var canvas_size = 100;
+ var new_canvas_size = 50;
+ canvas.width = canvas.height = canvas_size;
+
+ var helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size);
+ helper.drawColor(canvas, helper.red);
+
+ // The recorded stream coming from canvas.
+ var stream = canvas.captureStream();
+
+ // Check values for events
+ var numDataAvailabledRaised = 0;
+ var numResizeRaised = 0;
+ // Recorded data that will be playback.
+ var blob;
+
+ // Media recorder for VP8 and canvas stream.
+ var mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the beginning of recording");
+
+ // Not expected events.
+ mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired");
+ mediaRecorder.onerror = err => {
+ ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name);
+ SimpleTest.finish();
+ }
+
+ // When recorder is stopped get recorded data.
+ mediaRecorder.ondataavailable = ev => {
+ info("Got 'dataavailable' event");
+ ++numDataAvailabledRaised;
+ is(blob, undefined, "On dataavailable event blob is undefined");
+ // Save recorded data for playback
+ blob = ev.data;
+ };
+
+ mediaRecorder.onstart = () => {
+ info('onstart fired successfully');
+ };
+
+ mediaRecorder.onstop = () => {
+ info("Got 'stop' event");
+ is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event");
+
+ // Playback stream and verify resolution changes.
+ ok(blob, "Should have gotten a data blob");
+ var video = document.createElement("video");
+ video.id = "recorded-video";
+ video.src = URL.createObjectURL(blob);
+ video.onerror = err => {
+ ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
+ SimpleTest.finish();
+ };
+
+ // Check here that resize is correct in the playback stream.
+ video.onresize = function() {
+ ++numResizeRaised;
+ if (numResizeRaised == 1) {
+ is(this.videoWidth, canvas_size, "1st resize event original width");
+ is(this.videoHeight, canvas_size, "1st resize event original height ");
+ } else if (numResizeRaised == 2) {
+ is(this.videoWidth, new_canvas_size, "2nd resize event new width");
+ is(this.videoHeight, new_canvas_size, "2nd resize event new height");
+ } else {
+ ok(false, "Only 2 numResizeRaised events are expected");
+ }
+ };
+
+ video.onended = () => {
+ is(numResizeRaised, 2, "Expected 2 resize event");
+ };
+ document.getElementById("content").appendChild(video);
+ video.play();
+
+ // Check last color
+ helper.pixelMustBecome(video, helper.red, {
+ threshold: 128,
+ infoString: "Should become red",
+ }).then(() => {
+ video.onresize = {};
+ video.onended = {};
+ SimpleTest.finish();
+ });
+ };
+
+ // Start here by stream recorder.
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder started");
+ requestAnimationFrame(draw);
+
+ // Change resolution every 100ms
+ var countFrames=0;
+ var previous_time = performance.now();
+ function draw(timestamp) {
+ if (timestamp - previous_time < 100) {
+ requestAnimationFrame(draw);
+ return;
+ }
+ previous_time = timestamp;
+
+ var size = 0;
+ var color = "";
+ if (countFrames < 1) {
+ // Initial size
+ size = canvas_size;
+ color = helper.blue;
+ } else if (countFrames < 2) {
+ // upsize
+ size = new_canvas_size;
+ color = helper.red;
+ } else {
+ // Stop recoredr on last frame
+ mediaRecorder.stop();
+ return;
+ }
+ // Resize and draw canvas
+ canvas.width = canvas.height = size;
+ helper.drawColor(canvas, color);
+ // Register next draw on every call
+ requestAnimationFrame(draw);
+ countFrames++;
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_getdata_afterstart.html b/dom/media/test/test_mediarecorder_record_getdata_afterstart.html
new file mode 100644
index 0000000000..3b181ed8db
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_getdata_afterstart.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 951008 Test MediaRecorder Record has start event</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>
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+
+ var element = document.createElement('audio');
+ var hasonstart = false;
+ var hasondataavailable = false;
+ var mMediaRecorder;
+
+ element.token = token;
+ manager.started(token);
+ element.src = test.name;
+ element.test = test;
+ element.stream = element.mozCaptureStream();
+
+ mMediaRecorder = new MediaRecorder(element.stream);
+ is(mMediaRecorder.mimeType, '', 'Expected MediaRecorder mimetype');
+ mMediaRecorder.onwarning = function() {
+ ok(false, 'onwarning unexpectedly fired');
+ };
+
+ mMediaRecorder.onerror = function() {
+ ok(false, 'onerror unexpectedly fired');
+ };
+
+ mMediaRecorder.onstart = function() {
+ info('onstart fired successfully');
+ hasonstart = true;
+ is(mMediaRecorder.mimeType, 'audio/ogg; codecs=opus',
+ "MediaRecorder mimetype as expected");
+ mMediaRecorder.requestData();
+ };
+
+ mMediaRecorder.onstop = function() {
+ info('onstop fired successfully');
+ ok(hasondataavailable, "should have ondataavailable before onstop");
+ is(mMediaRecorder.state, 'inactive', 'check recording status is inactive');
+ SimpleTest.finish();
+ };
+
+ mMediaRecorder.ondataavailable = function (e) {
+ info('ondataavailable fired successfully');
+ if (mMediaRecorder.state == 'recording') {
+ hasondataavailable = true;
+ ok(hasonstart, "should have had start event first");
+ is(e.data.type, mMediaRecorder.mimeType,
+ "blob's mimeType matches the recorder's");
+ mMediaRecorder.stop();
+ }
+ };
+
+ // Start recording once metadata are parsed.
+ element.onloadedmetadata = function() {
+ element.oncanplaythrough = null;
+ mMediaRecorder.start(0);
+ is(mMediaRecorder.state, 'recording', 'Media recorder should be recording');
+ is(mMediaRecorder.stream, element.stream,
+ 'Media recorder stream = element stream at the start of recording');
+ };
+
+ element.play();
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html b/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html
new file mode 100644
index 0000000000..961a9644b2
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_gum_video_timeslice.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record gUM video with Timeslice</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="gUM_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<div id="content" style="display: none">
+</div>
+<script class="testbody" type="text/javascript">
+
+async function startTest() {
+ try {
+ await setupGetUserMediaTestPrefs();
+ let stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+ let dataAvailableCount = 0;
+ let onDataAvailableFirst = false;
+ const expectedMimeType = 'video/webm; codecs="vp8, opus"';
+
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ 'Media recorder stream = element stream at the start of recording');
+ mediaRecorder.onwarning = function() {
+ ok(false, 'onwarning unexpectedly fired');
+ };
+
+ mediaRecorder.onerror = function() {
+ ok(false, 'onerror unexpectedly fired');
+ };
+
+ mediaRecorder.onstop = function() {
+ ok(false, 'Unexpected onstop callback fired');
+ };
+
+ mediaRecorder.onstart = function() {
+ is(mediaRecorder.mimeType, expectedMimeType, 'Expected mime type');
+ };
+
+ mediaRecorder.ondataavailable = function (evt) {
+ info('ondataavailable fired');
+ dataAvailableCount++;
+
+ ok(evt instanceof BlobEvent,
+ 'Events fired from ondataavailable should be BlobEvent');
+ is(evt.type, 'dataavailable',
+ 'Event type should dataavailable');
+ ok(evt.data.size >= 0,
+ 'Blob data size ' + evt.data.size + ' received is greater than or equal to zero');
+ is(evt.data.type, expectedMimeType, 'Expected blob mime type');
+
+ // We'll stop recording upon the 1st blob being received
+ if (dataAvailableCount === 1) {
+ mediaRecorder.onstop = function (event) {
+ info('onstop fired');
+
+ if (!onDataAvailableFirst) {
+ ok(false, 'onstop unexpectedly fired before ondataavailable');
+ }
+
+ ok(true, 'onstop fired successfully');
+ is(mediaRecorder.state, 'inactive',
+ 'check recording status is inactive');
+ SimpleTest.finish();
+ };
+
+ mediaRecorder.stop();
+ is(mediaRecorder.state, 'inactive',
+ 'Media recorder is inactive after being stopped');
+
+ } else if (dataAvailableCount === 2) {
+ // Ensure we've received at least two ondataavailable events before
+ // onstop
+ onDataAvailableFirst = true;
+ }
+ };
+
+ mediaRecorder.start(250);
+ is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
+ is(mediaRecorder.mimeType, '', 'Expected mime type');
+ } catch (err) {
+ ok(false, 'Unexpected error fired with: ' + err);
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_gum_video_timeslice_mixed.html b/dom/media/test/test_mediarecorder_record_gum_video_timeslice_mixed.html
new file mode 100644
index 0000000000..af607280f6
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_gum_video_timeslice_mixed.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record gUM video with Timeslice, and playback of mixed memory and file blobs</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="gUM_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="text/javascript">
+function unexpected({type}) {
+ ok(false, `${type} unexpectedly fired`);
+}
+
+(async () => {
+ SimpleTest.waitForExplicitFinish();
+ let blobUrl = null;
+ let stream = null;
+ try {
+ // This is the memory limit per blob. If a blob is larger than this,
+ // MediaRecorder will put it in a file. For this test we need to get at
+ // least one blob under, and one blob over the limit.
+ const memoryLimit = 3000;
+ await SpecialPowers.pushPrefEnv({set: [
+ ["media.recorder.max_memory", memoryLimit],
+ ]});
+ // We always use fake devices since the loopback ones don't make enough
+ // pixels change per frame to make the encoded frames large enough.
+ await pushGetUserMediaTestPrefs({fakeAudio: true, fakeVideo: true});
+ stream = await navigator.mediaDevices.getUserMedia(
+ {audio: true, video: true});
+ const blobs = [];
+
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = element stream at the start of recording");
+ mediaRecorder.start();
+ mediaRecorder.addEventListener("warning", unexpected);
+ mediaRecorder.addEventListener("error", unexpected);
+ mediaRecorder.addEventListener("stop", unexpected);
+ await new Promise(r => mediaRecorder.onstart = r);
+
+ for (let hasMemory = false; !hasMemory;) {
+ mediaRecorder.requestData();
+ const {data} = await new Promise(r => mediaRecorder.ondataavailable = r);
+ blobs.push(data);
+ ok(data.size < memoryLimit, "Blob should be small enough at start");
+ hasMemory = data.size > 0 && data.size < memoryLimit;
+ info(`Blob is ${data.size} bytes.${hasMemory ? " In memory." : ""}`);
+ }
+ info("Got a memory blob");
+
+ SimpleTest.requestFlakyTimeout("Wait for file blob");
+ for (let hasFile = false, waitTimeMs = 500; !hasFile; waitTimeMs *= 4) {
+ info(`Waiting ${waitTimeMs/1000} seconds for file blob`);
+ await new Promise(r => setTimeout(r, waitTimeMs));
+ mediaRecorder.requestData();
+ const {data} = await new Promise(r => mediaRecorder.ondataavailable = r);
+ blobs.push(data);
+ hasFile = data.size > memoryLimit;
+ info(`Blob is ${data.size} bytes. In ${hasFile ? "file" : "memory"}.`);
+ }
+ info("Got a file blob");
+
+ mediaRecorder.stop();
+ const {data} = await new Promise(r => mediaRecorder.ondataavailable = r);
+ blobs.push(data);
+ mediaRecorder.removeEventListener("stop", unexpected);
+ await new Promise(r => mediaRecorder.onstop = r);
+
+ const video = document.createElement("video");
+ const blob = new Blob(blobs);
+ blobUrl = URL.createObjectURL(blob);
+ video.src = blobUrl;
+ info(`Starting playback. Blob-size=${blob.size}`);
+ video.play();
+
+ await Promise.race([
+ new Promise(res => video.onended = res),
+ new Promise((res, rej) => video.onerror = () => rej(video.error.message)),
+ ]);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ if (stream) {
+ for (const t of stream.getTracks()) {
+ t.stop();
+ }
+ }
+ if (blobUrl) {
+ URL.revokeObjectURL(blobUrl);
+ }
+ SimpleTest.finish();
+ }
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_immediate_stop.html b/dom/media/test/test_mediarecorder_record_immediate_stop.html
new file mode 100644
index 0000000000..8ed7c321a0
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_immediate_stop.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Immediate Stop</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+/**
+ * Stops the media recorder immediately after starting the recorder. This test
+ * verifies whether the media recorder can handle this scenario nicely. The
+ * return blob size should be greater than zero, but its duration would be zero
+ * length when play.
+ */
+function startTest(test, token) {
+ var element = document.createElement('audio');
+ var expectedMimeType = test.type;
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.stream = element.mozCaptureStreamUntilEnded();
+
+ var mediaRecorder =
+ new MediaRecorder(element.stream, {mimeType: expectedMimeType});
+ var onStopFired = false;
+ var onDataAvailableFired = false;
+
+ mediaRecorder.onerror = function () {
+ ok(false, 'Unexpected onerror callback fired');
+ };
+
+ mediaRecorder.onwarning = function () {
+ ok(false, 'Unexpected onwarning callback fired');
+ };
+
+ // This handler verifies that only a single onstop event handler is fired.
+ mediaRecorder.onstop = function () {
+ if (onStopFired) {
+ ok(false, 'onstop unexpectedly fired more than once');
+ } else {
+ onStopFired = true;
+
+ // ondataavailable should always fire before onstop
+ if (onDataAvailableFired) {
+ manager.finished(token);
+ } else {
+ ok(false, 'onstop fired without an ondataavailable event first');
+ }
+ }
+ };
+
+ // This handler verifies that only a single ondataavailable event handler
+ // is fired with the blob generated having greater than zero size
+ // and a correct mime type.
+ mediaRecorder.ondataavailable = function (evt) {
+ if (onDataAvailableFired) {
+ ok(false, 'ondataavailable unexpectedly fired more than once');
+ } else {
+ onDataAvailableFired = true;
+
+ ok(evt instanceof BlobEvent,
+ 'Events fired from ondataavailable should be BlobEvent');
+ is(evt.type, 'dataavailable',
+ 'Event type should dataavailable');
+
+ // The initialization of encoder can be cancelled.
+ // On some platforms, the stop method may run after media stream track
+ // available, so the blob can contain the header data.
+ is(evt.data.type, expectedMimeType,
+ 'Blob data received and should have mime type');
+ is(mediaRecorder.mimeType, expectedMimeType,
+ 'Media Recorder mime type in ondataavailable = ' + expectedMimeType);
+ ok(evt.data.size >= 0, 'Blob size can not be negative');
+
+ // onstop should not have fired before ondataavailable
+ if (onStopFired) {
+ ok(false, 'ondataavailable unexpectedly fired later than onstop');
+ manager.finished(token);
+ }
+ }
+ };
+
+ // This handler completes a start and stop of recording and verifies
+ // respective media recorder state.
+ element.onloadedmetadata = function () {
+ element.onloadedmetadata = null;
+ element.play();
+ mediaRecorder.start();
+ is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
+ is(mediaRecorder.stream, element.stream,
+ 'Media recorder stream = element stream at the start of recording');
+
+ mediaRecorder.stop();
+ is(mediaRecorder.state, 'inactive',
+ 'Media recorder is inactive after being stopped');
+ is(mediaRecorder.stream, element.stream,
+ 'Media recorder stream = element stream post recording');
+ };
+
+ element.preload = "metadata";
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_no_timeslice.html b/dom/media/test/test_mediarecorder_record_no_timeslice.html
new file mode 100644
index 0000000000..6ed148a108
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_no_timeslice.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record No Timeslice</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+/**
+ * Starts a test on every media recorder file included to check that a
+ * stream derived from the file can be recorded with no time slice provided.
+ */
+function startTest(test, token) {
+ var element = document.createElement('audio');
+ var expectedMimeType = test.type;
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.stream = element.mozCaptureStreamUntilEnded();
+
+ var mediaRecorder =
+ new MediaRecorder(element.stream, {mimeType: expectedMimeType});
+ var onStopFired = false;
+ var onDataAvailableFired = false;
+
+ mediaRecorder.onerror = function () {
+ ok(false, 'Unexpected onerror callback fired');
+ };
+
+ mediaRecorder.onwarning = function () {
+ ok(false, 'Unexpected onwarning callback fired');
+ };
+
+ // This handler verifies that only a single onstop event handler is fired.
+ mediaRecorder.onstop = function () {
+ if (onStopFired) {
+ ok(false, 'onstop unexpectedly fired more than once');
+ } else {
+ onStopFired = true;
+
+ // ondataavailable should always fire before onstop
+ if (onDataAvailableFired) {
+ ok(true, 'onstop fired after ondataavailable');
+ manager.finished(token);
+ } else {
+ ok(false, 'onstop fired without an ondataavailable event first');
+ }
+ }
+ };
+
+ // This handler verifies that only a single ondataavailable event handler
+ // is fired with the blob generated having greater than zero size
+ // and a correct mime type.
+ mediaRecorder.ondataavailable = function (evt) {
+ if (onDataAvailableFired) {
+ ok(false, 'ondataavailable unexpectedly fired more than once');
+ } else {
+ onDataAvailableFired = true;
+
+ ok(evt instanceof BlobEvent,
+ 'Events fired from ondataavailable should be BlobEvent');
+ is(evt.type, 'dataavailable',
+ 'Event type should dataavailable');
+ ok(evt.data.size > 0,
+ 'Blob data received should be greater than zero');
+ is(evt.data.type, expectedMimeType,
+ 'Blob data received should have type = ' + expectedMimeType);
+
+ is(mediaRecorder.mimeType, expectedMimeType,
+ 'Mime type in ondataavailable = ' + expectedMimeType);
+
+ // onstop should not have fired before ondataavailable
+ if (onStopFired) {
+ ok(false, 'ondataavailable unexpectedly fired later than onstop');
+ manager.finished(token);
+ }
+ }
+ };
+
+ element.preload = "metadata";
+
+ element.onloadedmetadata = function () {
+ element.onloadedmetadata = null;
+ mediaRecorder.start();
+ is(mediaRecorder.state, 'recording',
+ 'Media recorder should be recording');
+ is(mediaRecorder.stream, element.stream,
+ 'Media recorder stream = element stream at the start of recording');
+
+ element.play();
+ }
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_session.html b/dom/media/test/test_mediarecorder_record_session.html
new file mode 100644
index 0000000000..88795d82d0
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_session.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=909670
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Media Recoder recording session</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var element = document.createElement('audio');
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.stream = element.mozCaptureStream();
+
+ var mStopCount = 0;
+ // Start and stop recording session three times continuously.
+ var mExpectStopCount = 3;
+ var mediaRecorder = new MediaRecorder(element.stream);
+
+ // Stop callback.
+ // Suppose to receive mExpectStopCount
+ mediaRecorder.onstop = function stopCallback() {
+ mStopCount++;
+
+ info("MediaRecorder.onstop callback: (" + mStopCount + ")");
+
+ if (mExpectStopCount === mStopCount)
+ {
+ manager.finished(token);
+ }
+ }
+
+ // data avaliable.
+ mediaRecorder.ondataavailable = function(evt) {}
+
+ mediaRecorder.onerror = function(err) {
+ ok(false, 'Unexpected error fired with:' + err);
+ }
+
+ mediaRecorder.onwarning = function() {
+ ok(false, 'Unexpected warning fired');
+ }
+
+ element.preload = "metadata";
+
+ element.onloadedmetadata = function () {
+ element.onloadedmetadata = null;
+ element.play();
+ for (var i = 0; i < mExpectStopCount; i++) {
+ mediaRecorder.start(1000);
+ mediaRecorder.stop();
+ }
+ }
+
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_startstopstart.html b/dom/media/test/test_mediarecorder_record_startstopstart.html
new file mode 100644
index 0000000000..b4cc62c709
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_startstopstart.html
@@ -0,0 +1,75 @@
+ <!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder crash on sequence start stop start method</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content" style="display: none">
+</div>
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var ac = new window.AudioContext();
+ var dest = ac.createMediaStreamDestination();
+ var recorder = new MediaRecorder(dest.stream);
+ var stopCount = 0;
+ var dataavailable = 0;
+ var expectedMimeType = 'audio/ogg; codecs=opus';
+ recorder.onstop = function (e) {
+ info('onstop fired');
+ is(recorder.stream, dest.stream,
+ 'Media recorder stream = element stream post recording');
+ stopCount++;
+ if (stopCount == 2) {
+ if (dataavailable >= 2) {
+ SimpleTest.finish();
+ } else {
+ ok(false, 'Should have at least two dataavailable events');
+ }
+ }
+ }
+ recorder.ondataavailable = function (evt) {
+ info('ondataavailable fired');
+ ok(evt instanceof BlobEvent,
+ 'Events fired from ondataavailable should be BlobEvent');
+ is(evt.type, 'dataavailable',
+ 'Event type should dataavailable');
+ // If script runs slower, it may generate header data in blob from encoder
+ if (evt.data.size > 0) {
+ info('blob size = ' + evt.data.size);
+ is(evt.data.type, expectedMimeType,
+ 'Blob data received should have type = ' + expectedMimeType);
+ }
+ dataavailable++;
+ }
+ recorder.onerror = function (e) {
+ ok(false, 'it should execute normally without exception');
+ }
+ recorder.onwarning = function() {
+ ok(false, 'onwarning unexpectedly fired');
+ };
+
+ recorder.start(2000);
+ is(recorder.state, 'recording', 'Media recorder should be recording');
+ recorder.stop();
+ is(recorder.state, 'inactive', 'check recording status is inactive');
+ recorder.start(10000); // This bug would crash on this line without this fix.
+ is(recorder.state, 'recording', 'check recording status is recording');
+ // Simulate delay stop, only delay stop no no stop can trigger crash.
+ setTimeout(function() {
+ recorder.stop();
+ is(recorder.state, 'inactive','check recording status is recording');
+ }, 1000);
+}
+
+SimpleTest.requestFlakyTimeout("untriaged");
+startTest();
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/media/test/test_mediarecorder_record_timeslice.html b/dom/media/test/test_mediarecorder_record_timeslice.html
new file mode 100644
index 0000000000..3e547e77b4
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_timeslice.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Record Timeslice</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+/**
+ * Starts a test on every media recorder file included to check that a stream
+ * derived from the file can be recorded with a timeslice provided
+ */
+function startTest(test, token) {
+ var element = document.createElement('audio');
+ var expectedMimeType = test.type;
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.preload = "auto";
+
+ // Set up MediaRecorder once loadedmetadata fires and tracks are available.
+ element.onloadedmetadata = function() {
+ element.onloadedmetadata = null;
+
+ const stream = element.mozCaptureStream();
+ const mediaRecorder =
+ new MediaRecorder(stream, {mimeType: expectedMimeType});
+
+ mediaRecorder.onerror = function () {
+ ok(false, 'Unexpected onerror callback fired');
+ };
+
+ mediaRecorder.onwarning = function () {
+ ok(false, 'Unexpected onwarning callback fired');
+ };
+
+ mediaRecorder.onstop = function () {
+ ok(false, 'Unexpected onstop callback fired');
+ };
+
+ var dataAvailableCount = 0;
+ var onDataAvailableFirst = false;
+
+ // This handler fires every 250ms to generate a blob.
+ mediaRecorder.ondataavailable = function (evt) {
+ info('ondataavailable fired');
+ dataAvailableCount++;
+
+ ok(evt instanceof BlobEvent,
+ 'Events fired from ondataavailable should be BlobEvent');
+ is(evt.type, 'dataavailable',
+ 'Event type should dataavailable');
+ ok(evt.data.size >= 0,
+ 'Blob data size received is greater than or equal to zero');
+
+ is(evt.data.type, expectedMimeType,
+ 'Blob data received should have type = ' + expectedMimeType);
+ is(mediaRecorder.mimeType, expectedMimeType,
+ 'Mime type in ondataavailable = ' + mediaRecorder.mimeType);
+
+ // We'll stop recording upon the 1st blob being received
+ if (dataAvailableCount === 1) {
+ mediaRecorder.onstop = function (event) {
+ info('onstop fired');
+
+ if (!onDataAvailableFirst) {
+ ok(false, 'onstop unexpectedly fired before ondataavailable');
+ }
+ element.pause();
+ manager.finished(token);
+ };
+
+ mediaRecorder.stop();
+ is(mediaRecorder.state, 'inactive',
+ 'Media recorder is inactive after being stopped');
+ is(mediaRecorder.stream, stream,
+ 'Media recorder stream = element stream post recording');
+
+ } else if (dataAvailableCount === 2) {
+ // Ensure we've received at least two ondataavailable events before onstop
+ onDataAvailableFirst = true;
+ }
+ };
+
+ mediaRecorder.start(1);
+ element.play();
+ is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
+ is(mediaRecorder.stream, stream,
+ 'Media recorder stream = element stream at the start of recording');
+ };
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_record_upsize_resolution.html b/dom/media/test/test_mediarecorder_record_upsize_resolution.html
new file mode 100644
index 0000000000..d02fd08e44
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_record_upsize_resolution.html
@@ -0,0 +1,148 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording canvas dynamically changes to greater resolution</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+</div>
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var canvas = document.createElement("canvas");
+ var canvas_size = 100;
+ var new_canvas_size = 150;
+ canvas.width = canvas.height = canvas_size;
+
+ var helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size);
+ helper.drawColor(canvas, helper.red);
+
+ // The recorded stream coming from canvas.
+ var stream = canvas.captureStream();
+
+ // Check values for events
+ var numDataAvailabledRaised = 0;
+ var numResizeRaised = 0;
+ // Recorded data that will be playback.
+ var blob;
+
+ // Media recorder for VP8 and canvas stream.
+ var mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the beginning of recording");
+
+ // Not expected events.
+ mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired");
+ mediaRecorder.onerror = err => {
+ ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name);
+ SimpleTest.finish();
+ }
+
+ // When recorder is stopped get recorded data.
+ mediaRecorder.ondataavailable = ev => {
+ info("Got 'dataavailable' event");
+ ++numDataAvailabledRaised;
+ is(blob, undefined, "On dataavailable event blob is undefined");
+ // Save recorded data for playback
+ blob = ev.data;
+ };
+
+ mediaRecorder.onstart = () => {
+ info('onstart fired successfully');
+ };
+
+ mediaRecorder.onstop = () => {
+ info("Got 'stop' event");
+ is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event");
+
+ // Playback stream and verify resolution changes.
+ ok(blob, "Should have gotten a data blob");
+ var video = document.createElement("video");
+ video.id = "recorded-video";
+ video.src = URL.createObjectURL(blob);
+ video.onerror = err => {
+ ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
+ SimpleTest.finish();
+ };
+
+ // Check here that resize is correct in the playback stream.
+ video.onresize = function() {
+ ++numResizeRaised;
+ if (numResizeRaised == 1) {
+ is(this.videoWidth, canvas_size, "1st resize event original width");
+ is(this.videoHeight, canvas_size, "1st resize event original height ");
+ } else if (numResizeRaised == 2) {
+ is(this.videoWidth, new_canvas_size, "2nd resize event new width");
+ is(this.videoHeight, new_canvas_size, "2nd resize event new height");
+ } else {
+ ok(false, "Only 2 numResizeRaised events are expected");
+ }
+ };
+
+ video.onended = () => {
+ is(numResizeRaised, 2, "Expected 2 resize event");
+ };
+ document.getElementById("content").appendChild(video);
+ video.play();
+
+ // Check last color
+ helper.pixelMustBecome(video, helper.red, {
+ threshold: 128,
+ infoString: "Should become red",
+ }).then(() => {
+ video.onresize = {};
+ video.onended = {};
+ SimpleTest.finish();
+ });
+ };
+
+ // Start here by stream recorder.
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder started");
+ requestAnimationFrame(draw);
+
+ // Change resolution every 100 ms
+ var countFrames=0;
+ var previous_time = performance.now();
+ function draw(timestamp) {
+ if (timestamp - previous_time < 100) {
+ requestAnimationFrame(draw);
+ return;
+ }
+ previous_time = timestamp;
+
+ var size = 0;
+ var color = "";
+ if (countFrames < 1) {
+ // Initial size
+ size = canvas_size;
+ color = helper.blue;
+ } else if (countFrames < 2) {
+ // upsize
+ size = new_canvas_size;
+ color = helper.red;
+ }else {
+ // Stop recoredr on last frame
+ mediaRecorder.stop();
+ return;
+ }
+ // Resize and draw canvas
+ canvas.width = canvas.height = size;
+ helper.drawColor(canvas, color);
+ // Register next draw on every call
+ requestAnimationFrame(draw);
+ countFrames++;
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_reload_crash.html b/dom/media/test/test_mediarecorder_reload_crash.html
new file mode 100644
index 0000000000..f6d008261f
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_reload_crash.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that reloading media recorder object</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=894348">Mozill
+a Bug 894348</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ for (let i = 0; i< 5; i++) {
+ /* eslint-disable no-undef */
+ try { o0 = document.createElement('audio') } catch(e) { }
+ try { o0.src = "sound.ogg" } catch(e) { }
+ try { (document.body || document.documentElement).appendChild(o0) } catch(e) { }
+ try { o1 = o0.mozCaptureStreamUntilEnded(); } catch(e) { }
+ try { o2 = new MediaRecorder(o1) } catch(e) { }
+ try { o2.start(0) } catch(e) { }
+ /* eslint-enable no-undef */
+ SpecialPowers.gc();
+ }
+ ok(true, "pass the crash test");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_state_event_order.html b/dom/media/test/test_mediarecorder_state_event_order.html
new file mode 100644
index 0000000000..feba055f4d
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_state_event_order.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder fires an event after changing state</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+/**
+ * The flow of test is start()=>pause()=>resume()=>stop(). In each steps,
+ * this test verifies whether each MediaRecorder methods properly change
+ * its state before firing an event. Checking the state is done in the
+ * corresponding event handlers.
+ */
+function startTest(test, token) {
+ var element = document.createElement('audio');
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.stream = element.mozCaptureStream();
+
+ var mediaRecorder = new MediaRecorder(element.stream);
+
+ mediaRecorder.onwarning = function() {
+ ok(false, 'onwarning unexpectedly fired');
+ };
+
+ mediaRecorder.onerror = function() {
+ ok(false, 'onerror unexpectedly fired');
+ };
+
+ mediaRecorder.onstart = function() {
+ info('onstart fired successfully');
+ is(mediaRecorder.state, 'recording',
+ 'Media Recorder changes state to recording before firing a start event');
+ mediaRecorder.pause();
+ };
+
+ mediaRecorder.onpause = function() {
+ info('onpause fired successfully');
+ is(mediaRecorder.state, 'paused',
+ 'Media Recorder changes state to paused before firing a pause event');
+ mediaRecorder.resume();
+ };
+
+ mediaRecorder.onresume = function() {
+ info('onresume fired successfully');
+ is(mediaRecorder.state, 'recording',
+ 'Media Recorder changes state to recording before firing a resume event');
+ mediaRecorder.stop();
+ };
+
+ mediaRecorder.onstop = function() {
+ info('onstop fired successfully');
+ is(mediaRecorder.state, 'inactive',
+ 'Media Recorder changes state to inactive before firing a stop event');
+ SimpleTest.finish();
+ };
+
+ // Call start() once metadata are parsed.
+ element.onloadedmetadata = function() {
+ element.play();
+ mediaRecorder.start();
+ is(mediaRecorder.state, 'recording', 'Media recorder should be recording');
+ is(mediaRecorder.stream, element.stream,
+ 'Media recorder stream = element stream at the start of recording');
+ };
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_state_transition.html b/dom/media/test/test_mediarecorder_state_transition.html
new file mode 100644
index 0000000000..5d6483b7de
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_state_transition.html
@@ -0,0 +1,280 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder State Transition</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+// List of operation tests for media recorder objects to verify if running
+// these operations should result in an exception or not
+var operationTests = [
+ {
+ operations: ['stop'],
+ isValid: true
+ },
+ {
+ operations: ['requestData'],
+ isValid: false
+ },
+ {
+ operations: ['pause'],
+ isValid: false
+ },
+ {
+ operations: ['resume'],
+ isValid: false
+ },
+ {
+ operations: ['start'],
+ isValid: true
+ },
+ {
+ operations: ['start'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'pause'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'start'],
+ isValid: false
+ },
+ {
+ operations: ['start', 'resume'],
+ isValid: true
+ },
+ {
+ operations: ['pause', 'start'],
+ isValid: false
+ },
+ {
+ operations: ['resume', 'start'],
+ isValid: false
+ },
+ {
+ operations: ['requestData', 'start'],
+ isValid: false
+ },
+ {
+ operations: ['stop', 'start'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'stop'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'stop'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'requestData'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'requestData'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'pause', 'stop'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause', 'start'],
+ isValid: false
+ },
+ {
+ operations: ['start', 'pause', 'pause'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause', 'requestData'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause', 'resume'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause', 'resume'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'resume', 'resume'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'resume', 'resume'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'resume', 'stop'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'resume', 'stop'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'stop', 'start'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'stop', 'start'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'stop', 'pause'],
+ isValid: false
+ },
+ {
+ operations: ['start', 'stop', 'resume'],
+ isValid: false
+ },
+ {
+ operations: ['start', 'stop', 'requestData'],
+ isValid: false
+ },
+ {
+ operations: ['start', 'stop', 'stop'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause', 'resume', 'resume'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause', 'resume', 'resume'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'pause', 'pause', 'resume'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'pause', 'pause', 'resume'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'stop', 'start', 'stop'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'stop', 'start', 'stop'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'stop', 'start', 'pause'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'stop', 'start', 'pause'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'stop', 'start', 'resume'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'stop', 'start', 'resume'],
+ isValid: true,
+ timeSlice: 200
+ },
+ {
+ operations: ['start', 'stop', 'start', 'requestData'],
+ isValid: true
+ },
+ {
+ operations: ['start', 'stop', 'start', 'requestData'],
+ isValid: true,
+ timeSlice: 200
+ },
+];
+
+/**
+ * Runs through each available state transition test by running all
+ * available operations on a media recorder object. Then, we report
+ * back if the test was expected through an exception or not.
+ *
+ * @param {MediaStream} testStream the media stream used for media recorder
+ * operation tests
+ */
+function runStateTransitionTests(testStream) {
+ for (const operationTest of operationTests) {
+ var mediaRecorder = new MediaRecorder(testStream);
+ var operationsString = operationTest.operations.toString();
+
+ try {
+ for (const operation of operationTest.operations) {
+ if (operationTest.timeSlice && operation === 'start') {
+ operationsString += ' with timeslice ' + operationTest.timeSlice;
+ mediaRecorder[operation](operationTest.timeSlice);
+ } else {
+ mediaRecorder[operation]();
+ }
+ }
+
+ ok(operationTest.isValid, `${operationsString} should succeed`);
+ } catch (err) {
+ if (operationTest.isValid) {
+ ok(false, `${operationsString} failed unexpectedly with ${err.name}`);
+ } else {
+ is(err.name, "InvalidStateError",
+ `${operationsString} expected to fail with InvalidStateError`);
+ }
+ }
+ }
+}
+
+/**
+ * Starts a test on every media recorder file included to check that various
+ * state transition flows that can happen in the media recorder object throw
+ * exceptions when they are expected to and vice versa.
+ */
+function startTest(test, token) {
+ var element = document.createElement('audio');
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.stream = element.mozCaptureStream();
+
+ element.oncanplaythrough = function () {
+ element.oncanplaythrough = null;
+ runStateTransitionTests(element.stream);
+ manager.finished(token);
+ };
+
+ element.play();
+}
+
+manager.runTests(gMediaRecorderTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediarecorder_webm_support.html b/dom/media/test/test_mediarecorder_webm_support.html
new file mode 100644
index 0000000000..6b115ee33a
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_webm_support.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media Recording - test WebM MIME support</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ok(MediaRecorder.isTypeSupported('audio/webm'),
+ 'Should support audio/webm');
+ok(MediaRecorder.isTypeSupported('AUDIO/WEBM'),
+ 'Should support audio/webm, upper case');
+ok(MediaRecorder.isTypeSupported('AuDiO/wEbM'),
+ 'Should support audio/webm, mixed case');
+
+ok(MediaRecorder.isTypeSupported('audio/webm;codecs=opus'),
+ 'Should support audio/webm;codecs=opus');
+ok(MediaRecorder.isTypeSupported('AUDIO/WEBM;CODECS=opus'),
+ 'Should support audio/webm;codecs=opus, upper case');
+ok(MediaRecorder.isTypeSupported('AuDiO/wEbM;cOdEcS=opus'),
+ 'Should support audio/webm;codecs=opus, mixed case');
+
+ok(MediaRecorder.isTypeSupported('video/webm'),
+ 'Should support video/webm');
+ok(MediaRecorder.isTypeSupported('VIDEO/WEBM'),
+ 'Should support video/webm, upper case');
+ok(MediaRecorder.isTypeSupported('vIdEo/WeBm'),
+ 'Should support video/webm, mixed case');
+
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8"'),
+ 'Should support video/webm; codecs="vp8"');
+ok(MediaRecorder.isTypeSupported('VIDEO/WEBM; CODECS="vp8"'),
+ 'Should support video/webm; codecs="vp8", upper case');
+ok(MediaRecorder.isTypeSupported('vIdEo/WeBm; CoDeCs="vp8"'),
+ 'Should support video/webm; codecs="vp8", mixed case');
+
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8.0"'),
+ 'Should support video/webm; codecs="vp8.0"');
+ok(MediaRecorder.isTypeSupported('VIDEO/WEBM; CODECS="vp8.0"'),
+ 'Should support video/webm; codecs="vp8.0", upper case');
+ok(MediaRecorder.isTypeSupported('vIdEo/WeBm; CoDeCs="vp8.0"'),
+ 'Should support video/webm; codecs="vp8.0", mixed case');
+
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp8, vorbis"'),
+ 'Should not support video/webm + vp8/vorbis');
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, vorbis"'),
+ 'Should not support video/webm + vp9/vorbis');
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8, opus"'),
+ 'Should support video/webm + vp8/opus');
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, opus"'),
+ 'Should not support video/webm + vp9/opus');
+</script>
+</head>
+</html>
diff --git a/dom/media/test/test_mediatrack_consuming_mediaresource.html b/dom/media/test/test_mediatrack_consuming_mediaresource.html
new file mode 100644
index 0000000000..515df5c053
--- /dev/null
+++ b/dom/media/test/test_mediatrack_consuming_mediaresource.html
@@ -0,0 +1,198 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test track interfaces when consuming media resources</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const manager = new MediaTestManager;
+
+function startTest(test, token) {
+ const elemType = getMajorMimeType(test.type);
+ const element = document.createElement(elemType);
+
+ let audioOnchange = 0;
+ let audioOnaddtrack = 0;
+ let audioOnremovetrack = 0;
+ let videoOnchange = 0;
+ let videoOnaddtrack = 0;
+ let videoOnremovetrack = 0;
+ let isPlaying = false;
+
+ isnot(element.audioTracks, undefined,
+ 'HTMLMediaElement::AudioTracks() property should be available.');
+ isnot(element.videoTracks, undefined,
+ 'HTMLMediaElement::VideoTracks() property should be available.');
+
+ element.audioTracks.onaddtrack = function(e) {
+ audioOnaddtrack++;
+ }
+
+ element.audioTracks.onremovetrack = function(e) {
+ audioOnremovetrack++;
+ }
+
+ element.audioTracks.onchange = function(e) {
+ audioOnchange++;
+ }
+
+ element.videoTracks.onaddtrack = function(e) {
+ videoOnaddtrack++;
+ }
+
+ element.videoTracks.onremovetrack = function(e) {
+ videoOnremovetrack++;
+ }
+
+ element.videoTracks.onchange = function(e) {
+ videoOnchange++;
+ }
+
+ function checkTrackNotRemoved() {
+ is(audioOnremovetrack, 0, 'Should have no calls of onremovetrack on audioTracks.');
+ is(videoOnremovetrack, 0, 'Should have no calls of onremovetrack on videoTracks.');
+ if (isPlaying) {
+ is(element.audioTracks.length, test.hasAudio ? 1 : 0,
+ 'Expected length of audioTracks.');
+ is(element.videoTracks.length, test.hasVideo ? 1 : 0,
+ 'Expected length of videoTracks.');
+ }
+ }
+
+ function checkTrackRemoved() {
+ is(element.audioTracks.length, 0, 'The length of audioTracks should be 0.');
+ is(element.videoTracks.length, 0, 'The length of videoTracks should be 0.');
+ if (isPlaying) {
+ is(audioOnremovetrack, test.hasAudio ? 1 : 0,
+ 'Expected calls of onremovetrack on audioTracks.');
+ is(videoOnremovetrack, test.hasVideo ? 1 : 0,
+ 'Expected calls of onremovetrack on videoTracks.');
+ }
+ }
+
+ function onended() {
+ ok(true, 'Event ended is expected to be fired on element.');
+ checkTrackNotRemoved();
+ element.onended = null;
+ element.onplaying = null;
+ element.onpause = null;
+ element.src = "";
+ is(element.audioTracks.length, 0, 'audioTracks have been forgotten');
+ is(element.videoTracks.length, 0, 'videoTracks have been forgotten');
+ is(audioOnremovetrack, 0, 'No audio removetrack events yet');
+ is(videoOnremovetrack, 0, 'No video removetrack events yet');
+ setTimeout(() => {
+ checkTrackRemoved();
+ manager.finished(element.token);
+ }, 100);
+ }
+
+ function checkTrackAdded() {
+ isPlaying = true;
+ if (test.hasAudio) {
+ is(audioOnaddtrack, 1, 'Calls of onaddtrack on audioTracks should be 1.');
+ is(element.audioTracks.length, 1, 'The length of audioTracks should be 1.');
+ ok(element.audioTracks[0].enabled, 'Audio track should be enabled as default.');
+ }
+ if (test.hasVideo) {
+ is(videoOnaddtrack, 1, 'Calls of onaddtrack on videoTracks should be 1.');
+ is(element.videoTracks.length, 1, 'The length of videoTracks should be 1.');
+ is(element.videoTracks.selectedIndex, 0,
+ 'The first video track is set selected as default.');
+ }
+ }
+
+ function setTrackEnabled(enabled) {
+ if (test.hasAudio) {
+ element.audioTracks[0].enabled = enabled;
+ }
+ if (test.hasVideo) {
+ element.videoTracks[0].selected = enabled;
+ }
+ }
+
+ function checkTrackChanged(calls, enabled) {
+ if (test.hasAudio) {
+ is(audioOnchange, calls, 'Calls of onchange on audioTracks should be '+calls);
+ is(element.audioTracks[0].enabled, enabled,
+ 'Enabled value of the audio track should be ' +enabled);
+ }
+ if (test.hasVideo) {
+ is(videoOnchange, calls, 'Calls of onchange on videoTracks should be '+calls);
+ is(element.videoTracks[0].selected, enabled,
+ 'Selected value of the video track should be ' +enabled);
+ var index = enabled ? 0 : -1;
+ is(element.videoTracks.selectedIndex, index,
+ 'SelectedIndex of video tracks should be ' +index);
+ }
+ }
+
+ function onpause() {
+ element.onpause = null;
+ if (element.ended) {
+ return;
+ }
+ if (steps == 1) {
+ setTrackEnabled(false);
+ element.onplaying = onplaying;
+ element.play();
+ steps++;
+ } else if (steps == 2) {
+ setTrackEnabled(true);
+ element.onplaying = onplaying;
+ element.play();
+ steps++;
+ }
+ }
+
+ function onplaying() {
+ element.onplaying = null;
+ if (element.ended) {
+ return;
+ }
+ if (steps == 1) {
+ element.onpause = onpause;
+ element.pause();
+ checkTrackAdded();
+ } else if (steps == 2) {
+ element.onpause = onpause;
+ element.pause();
+ checkTrackChanged(1, false);
+ } else if (steps == 3) {
+ checkTrackChanged(2, true);
+ }
+ }
+
+ var steps = 0;
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.onplaying = onplaying;
+ element.onended = onended;
+ element.play();
+ steps++;
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.track.enabled", true]
+ ]
+ },
+ function() {
+ manager.runTests(gTrackTests, startTest);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediatrack_consuming_mediastream.html b/dom/media/test/test_mediatrack_consuming_mediastream.html
new file mode 100644
index 0000000000..b930ca4fdc
--- /dev/null
+++ b/dom/media/test/test_mediatrack_consuming_mediastream.html
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test track interfaces when consuming a MediaStream from gUM</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="gUM_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+async function startTest() {
+ let steps = 0;
+ let audioOnchange = 0;
+ let audioOnaddtrack = 0;
+ let videoOnchange = 0;
+ let videoOnaddtrack = 0;
+ let isPlaying = false;
+ let element = document.createElement("video");
+ let stream;
+ try {
+ await setupGetUserMediaTestPrefs();
+ stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+ } catch (err) {
+ ok(false, 'Unexpected error fired with: ' + err);
+ SimpleTest.finish();
+ return;
+ }
+
+ element.audioTracks.onaddtrack = function(e) {
+ audioOnaddtrack++;
+ };
+
+ element.audioTracks.onchange = function(e) {
+ audioOnchange++;
+ };
+
+ element.videoTracks.onaddtrack = function(e) {
+ videoOnaddtrack++;
+ };
+
+ element.videoTracks.onchange = function(e) {
+ videoOnchange++;
+ };
+
+ function checkTrackRemoved() {
+ if (isPlaying) {
+ is(element.audioTracks.length, 0, 'The length of audioTracks should be 0.');
+ is(element.videoTracks.length, 0, 'The length of videoTracks should be 0.');
+ }
+ }
+
+ element.onended = function() {
+ ok(true, 'Event ended is expected to be fired on element.');
+ checkTrackRemoved();
+ element.onended = null;
+ element.onplaying = null;
+ element.onpause = null;
+ SimpleTest.finish();
+ }
+
+ function checkTrackAdded() {
+ isPlaying = true;
+ is(audioOnaddtrack, 1, 'Calls of onaddtrack on audioTracks should be 1.');
+ is(element.audioTracks.length, 1, 'The length of audioTracks should be 1.');
+ ok(element.audioTracks[0].enabled, 'Audio track should be enabled as default.');
+ is(videoOnaddtrack, 1, 'Calls of onaddtrack on videoTracks should be 1.');
+ is(element.videoTracks.length, 1, 'The length of videoTracks should be 1.');
+ is(element.videoTracks.selectedIndex, 0,
+ 'The first video track is set selected as default.');
+ }
+
+ function setTrackEnabled(enabled) {
+ element.audioTracks[0].enabled = enabled;
+ element.videoTracks[0].selected = enabled;
+ }
+
+ function checkTrackChanged(calls, enabled) {
+ is(audioOnchange, calls, 'Calls of onchange on audioTracks should be '+calls);
+ is(element.audioTracks[0].enabled, enabled,
+ 'Enabled value of the audio track should be ' +enabled);
+ is(videoOnchange, calls, 'Calls of onchange on videoTracks should be '+calls);
+ is(element.videoTracks[0].selected, enabled,
+ 'Selected value of the video track should be ' +enabled);
+ var index = enabled ? 0 : -1;
+ is(element.videoTracks.selectedIndex, index,
+ 'SelectedIndex of video tracks should be ' +index);
+ }
+
+ function onpause() {
+ element.onpause = null;
+ if (element.ended) {
+ return;
+ }
+ if (steps == 1) {
+ setTrackEnabled(false);
+ element.onplaying = onplaying;
+ element.play();
+ steps++;
+ } else if (steps == 2) {
+ setTrackEnabled(true);
+ element.onplaying = onplaying;
+ element.play();
+ steps++;
+ }
+ }
+
+ function onplaying() {
+ element.onplaying = null;
+ if (element.ended) {
+ return;
+ }
+ if (steps == 1) {
+ element.onpause = onpause;
+ element.pause();
+ checkTrackAdded();
+ } else if (steps == 2) {
+ element.onpause = onpause;
+ element.pause();
+ checkTrackChanged(1, false);
+ } else if (steps == 3) {
+ checkTrackChanged(2, true);
+ stream.getTracks().forEach(t => t.stop());
+ }
+ }
+
+ element.onplaying = onplaying;
+ element.srcObject = stream;
+
+ steps++;
+ await element.play();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.track.enabled", true]
+ ]
+ }, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediatrack_events.html b/dom/media/test/test_mediatrack_events.html
new file mode 100644
index 0000000000..5eae94f804
--- /dev/null
+++ b/dom/media/test/test_mediatrack_events.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test events of media track interfaces</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="gUM_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+async function startTest() {
+ let steps = 0;
+ let element = document.createElement("video");
+ let stream;
+ try {
+ await setupGetUserMediaTestPrefs();
+ stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+ } catch (err) {
+ ok(false, 'Unexpected error fired with: ' + err);
+ SimpleTest.finish();
+ return;
+ }
+
+ function verifyEvent(e, type) {
+ is(e.type, type, "Event type should be " + type);
+ ok(e.isTrusted, "Event should be trusted.");
+ ok(!e.bubbles, "Event shouldn't bubble.");
+ ok(!e.cancelable, "Event shouldn't be cancelable.");
+ }
+
+ element.audioTracks.onaddtrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent");
+ ok(true, 'onaddtrack is expected to be called from audioTracks.');
+ verifyEvent(e, "addtrack");
+ };
+
+ element.audioTracks.onremovetrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent");
+ ok(true, 'onremovetrack is expected to be called from audioTracks.');
+ verifyEvent(e, "removetrack");
+ };
+
+ element.audioTracks.onchange = function(e) {
+ ok(e instanceof window.Event, "Event fired from onchange should be a simple event.");
+ ok(true, 'onchange is expected to be called from audioTracks.');
+ verifyEvent(e, "change");
+ };
+
+ element.videoTracks.onaddtrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onaddtrack should be a TrackEvent");
+ ok(true, 'onaddtrack is expected to be called from videoTracks.');
+ verifyEvent(e, "addtrack");
+ };
+
+ element.videoTracks.onremovetrack = function(e) {
+ ok(e instanceof TrackEvent, "Event fired from onremovetrack should be a TrackEvent");
+ ok(true, 'onremovetrack is expected to be called from videoTracks.');
+ verifyEvent(e, "removetrack");
+ };
+
+ element.videoTracks.onchange = function(e) {
+ ok(e instanceof window.Event, "Event fired from onchange should be a simple event.");
+ ok(true, 'onchange is expected to be called from videoTracks.');
+ verifyEvent(e, "change");
+ };
+
+ element.onended = function() {
+ ok(true, 'Event ended is expected to be fired on element.');
+ element.onended = null;
+ element.onplaying = null;
+ element.onpause = null;
+ //This helps to prevent these events from firing after SimpleTest.finish()
+ //on B2G ICS Emulator, but not sure they have been run at all, then
+ element.audioTracks.onremovetrack = null;
+ element.audioTracks.onaddtrack = null;
+ element.audioTracks.onchange = null;
+ element.videoTracks.onremovetrack = null;
+ element.videoTracks.onaddtrack = null;
+ element.videoTracks.onchange = null;
+ SimpleTest.finish();
+ }
+
+ function onpause() {
+ element.onpause = null;
+ if (element.ended) {
+ return;
+ }
+ if (steps == 1) {
+ element.audioTracks[0].enabled = false;
+ element.videoTracks[0].selected = false;
+ element.onplaying = onplaying;
+ element.play();
+ steps++;
+ }
+ }
+
+ function onplaying() {
+ element.onplaying = null;
+ if (element.ended) {
+ return;
+ }
+ if (steps == 1) {
+ element.onpause = onpause;
+ element.pause();
+ } else if (steps == 2) {
+ stream.getTracks().forEach(t => t.stop());
+ }
+ }
+
+ element.onplaying = onplaying;
+ element.srcObject = stream;
+
+ isnot(element.audioTracks, undefined,
+ 'HTMLMediaElement::AudioTracks() property should be available.');
+ isnot(element.videoTracks, undefined,
+ 'HTMLMediaElement::VideoTracks() property should be available.');
+
+ steps++;
+ await element.play();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.track.enabled", true]
+ ]
+ }, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediatrack_parsing_ogg.html b/dom/media/test/test_mediatrack_parsing_ogg.html
new file mode 100644
index 0000000000..aabd40e2a3
--- /dev/null
+++ b/dom/media/test/test_mediatrack_parsing_ogg.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test events of media track interfaces</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function localCheckMetadata(msg, e) {
+ ok(msg in gOggTrackInfoResults, "File: " + msg + " is in pre-parsed gOggTrackInfoResults list");
+ var r = gOggTrackInfoResults[msg];
+
+ var hasExpectedAudio = r && r.hasOwnProperty("audio_id");
+ var hasExpectedVideo = r && r.hasOwnProperty("video_id");
+
+ var hasParsedAudio = e.audioTracks.length >= 1;
+ var hasParsedVideo = e.videoTracks.length >= 1;
+
+ ok(!(hasExpectedAudio ^ hasParsedAudio), "Check availability of expected/parsed audio");
+ ok(!(hasExpectedVideo ^ hasParsedVideo), "Check availability of expected/parsed video");
+ if (hasParsedAudio) {
+ is(e.audioTracks.length, 1, "The length of audio track should be 1");
+ is(e.audioTracks[0].id, r.audio_id, "File: " + msg + ", Audio track id");
+ is(e.audioTracks[0].kind, r.audio_kind, "File: " + msg + ", Audio track kind");
+ is(e.audioTracks[0].language, r.audio_language, "File: " + msg + ", Audio track language");
+ is(e.audioTracks[0].label, r.audio_label, "File: " + msg + ", Audio track label");
+ }
+ if (hasParsedVideo) {
+ is(e.videoTracks.length, 1, "The length of video track should be 1");
+ is(e.videoTracks[0].id, r.video_id, "File: " + msg + ", Video track id");
+ is(e.videoTracks[0].kind, r.video_kind, "File: " + msg + ", Video track kind");
+ is(e.videoTracks[0].language, r.video_language, "File: " + msg + ", Video track language");
+ is(e.videoTracks[0].label, r.video_label, "File: " + msg + ", Video track label");
+ }
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "metadata";
+ v.token = token;
+ manager.started(token);
+
+ v.src = test.name;
+ v.name = test.name;
+
+ v.onloadedmetadata = function(evt) {
+ localCheckMetadata(evt.target.name, evt.target);
+ evt.target.finished = true;
+ evt.target.onloadedmetadata = null;
+ removeNodeAndSource(evt.target);
+ manager.finished(evt.target.token);
+ };
+
+ document.body.appendChild(v);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["media.track.enabled", true]]},
+ function() {
+ manager.runTests(gMultitrackInfoOggPlayList, startTest);
+ }
+);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mediatrack_replay_from_end.html b/dom/media/test/test_mediatrack_replay_from_end.html
new file mode 100644
index 0000000000..16b0cbeb97
--- /dev/null
+++ b/dom/media/test/test_mediatrack_replay_from_end.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test media tracks if replay after playback has ended</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const manager = new MediaTestManager;
+
+function startTest(test, token) {
+ // Scenario to test:
+ // 1. Audio tracks and video tracks should be added to the track list when
+ // metadata has loaded, and all tracks should remain even after we seek to
+ // the end.
+ // 2. No tracks should be added back to the list if we replay from the end,
+ // and no tracks should be removed from the list after we seek to the end.
+ // 3. After seek to the middle from end of playback, all tracks should remain
+ // in the list if we play from here, and no tracks should be removed from
+ // the list after we seek to the end.
+ // 4. Unsetting the media element's src attribute should remove all tracks.
+
+ const elemType = getMajorMimeType(test.type);
+ const element = document.createElement(elemType);
+
+ let audioOnaddtrack = 0;
+ let audioOnremovetrack = 0;
+ let videoOnaddtrack = 0;
+ let videoOnremovetrack = 0;
+ let isPlaying = false;
+ let steps = 0;
+
+ element.audioTracks.onaddtrack = function(e) {
+ audioOnaddtrack++;
+ }
+
+ element.audioTracks.onremovetrack = function(e) {
+ audioOnremovetrack++;
+ }
+
+ element.videoTracks.onaddtrack = function(e) {
+ videoOnaddtrack++;
+ }
+
+ element.videoTracks.onremovetrack = function(e) {
+ videoOnremovetrack++;
+ }
+
+ function testExpectedAddtrack(expectedCalls) {
+ if (test.hasAudio) {
+ is(audioOnaddtrack, expectedCalls,
+ 'Calls of onaddtrack on audioTracks should be '+expectedCalls+' times.');
+ }
+ if (test.hasVideo) {
+ is(videoOnaddtrack, expectedCalls,
+ 'Calls of onaddtrack on videoTracks should be '+expectedCalls+' times.');
+ }
+ }
+
+ function testExpectedRemovetrack(expectedCalls) {
+ if (test.hasAudio) {
+ is(audioOnremovetrack, expectedCalls,
+ 'Calls of onremovetrack on audioTracks should be '+expectedCalls+' times.');
+ }
+ if (test.hasVideo) {
+ is(videoOnremovetrack, expectedCalls,
+ 'Calls of onremovetrack on videoTracks should be '+expectedCalls+' times.');
+ }
+ }
+
+ function finishTesting() {
+ element.onpause = null;
+ element.onseeked = null;
+ element.onplaying = null;
+ element.onended = null;
+ manager.finished(element.token);
+ }
+
+ function onended() {
+ if (isPlaying) {
+ switch(steps) {
+ case 1:
+ testExpectedAddtrack(1);
+ testExpectedRemovetrack(0);
+ element.onplaying = onplaying;
+ element.play();
+ steps++;
+ break;
+ case 2:
+ testExpectedAddtrack(1);
+ testExpectedRemovetrack(0);
+ element.currentTime = element.duration * 0.5;
+ element.onplaying = onplaying;
+ element.play();
+ steps++;
+ break;
+ case 3:
+ testExpectedAddtrack(1);
+ testExpectedRemovetrack(0);
+ element.src = "";
+ setTimeout(() => {
+ testExpectedAddtrack(1);
+ testExpectedRemovetrack(1);
+ finishTesting();
+ }, 0);
+ break;
+ }
+ } else {
+ ok(true, 'Finish the test anyway if ended is fired before other events.');
+ finishTesting();
+ }
+ }
+
+ function seekToEnd() {
+ element.onpause = null;
+ element.currentTime = element.duration * 1.1;
+ }
+
+ function onseeked() {
+ element.onseeked = null;
+ element.onpause = seekToEnd;
+ element.pause();
+ }
+
+ function onplaying() {
+ isPlaying = true;
+ element.onplaying = null;
+ element.onseeked = onseeked;
+ }
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ element.onplaying = onplaying;
+ element.onended = onended;
+ element.play();
+ steps++;
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.track.enabled", true]
+ ]
+ },
+ function() {
+ manager.runTests(gTrackTests, startTest);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_metadata.html b/dom/media/test/test_metadata.html
new file mode 100644
index 0000000000..08f28f5f47
--- /dev/null
+++ b/dom/media/test/test_metadata.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test returning metadata from media files with mozGetMetadata()</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>
+</head>
+<body>
+<pre id="test">
+<div id="output"></div>
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var a = document.createElement('audio');
+ a.preload = "metadata";
+ a.token = token;
+ manager.started(token);
+
+ a.src = test.name;
+ a.name = test.name;
+
+ // Tags should not be available immediately.
+ var exception_fired = false;
+ try {
+ a.mozGetMetadata();
+ } catch (e) {
+ is(e.name, 'InvalidStateError',
+ "early mozGetMetadata() should throw InvalidStateError");
+ exception_fired = true;
+ }
+ ok(exception_fired,
+ "mozGetMetadata() should throw an exception before HAVE_METADATA");
+
+ // Wait until metadata has loaded.
+ a.addEventListener('loadedmetadata', function() {
+ // read decoded tags
+ let tags = a.mozGetMetadata();
+ ok(tags, "mozGetMetadata() should return a truthy value");
+ // Dump them out.
+ var d = document.getElementById('output');
+ var html = '<table>\n';
+ html += '<caption><p>Called getMozMetadata()'
+ html += ' on '+test.name+'</p></caption>\n';
+ html += '<tr><th>tag</th>';
+ html += '<th>decoded value</th><th>expected value</th></tr>\n';
+ for (let tag in tags) {
+ html += '<tr><td>'+tag+'</td>';
+ html += '<td>'+tags[tag]+'</td>';
+ html += '<td>'+test.tags[tag]+'</td>';
+ html += '</tr>\n';
+ }
+ if (!Object.keys(tags).length) {
+ html += '<tr><td colspan=3 align=center><em>no tags</em></td></tr>\n';
+ }
+ html += '</table>\n';
+ var div = document.createElement('div');
+ // eslint-disable-next-line no-unsanitized/property
+ div.innerHTML = html;
+ d.appendChild(div);
+ // Verify decoded tag values.
+ for (let tag in tags) {
+ is(tags[tag], test.tags[tag], "Tag '"+tag+"' should match");
+ }
+ // Verify expected tag values
+ for (let tag in test.tags) {
+ is(tags[tag], test.tags[tag], "Tag '"+tag+"' should match");
+ }
+ removeNodeAndSource(a);
+ manager.finished(token);
+ });
+}
+
+manager.runTests(gMetadataTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_midflight_redirect_blocked.html b/dom/media/test/test_midflight_redirect_blocked.html
new file mode 100644
index 0000000000..55b96ccd38
--- /dev/null
+++ b/dom/media/test/test_midflight_redirect_blocked.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test mid-flight cross site redirects are blocked</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+ </head>
+ <body>
+ <pre id='test'>
+ <script class="testbody" type='application/javascript'>
+
+ function testIfLoadsToMetadata(test, useCors) {
+ return new Promise(function(resolve, reject) {
+ var elemType = getMajorMimeType(test.type);
+ var element = document.createElement(elemType);
+
+ if (useCors) {
+ element.crossOrigin = "anonymous";
+ }
+
+ // Log events for debugging.
+ [
+ "suspend", "play", "canplay", "canplaythrough", "loadstart",
+ "loadedmetadata", "loadeddata", "playing", "ended", "error",
+ "stalled", "emptied", "abort", "waiting", "pause"
+ ].forEach((eventName) => {
+ element.addEventListener(eventName, (event)=> {
+ info(test.name + " " + event.type);
+ });
+ });
+
+ element.addEventListener("loadedmetadata", ()=>{
+ resolve(true);
+ removeNodeAndSource(element);
+ });
+
+ element.addEventListener("error", ()=>{
+ resolve(false);
+ removeNodeAndSource(element);
+ });
+
+ // Note: request redirect before the end of metadata, otherwise we won't
+ // error before metadata has loaded if mixed origins are allowed.
+ element.src = "midflight-redirect.sjs?resource=" + test.name
+ + (useCors ? "&cors" : "")
+ + "&type=" + test.type
+ + "&redirectAt=200";
+ element.preload = "metadata";
+ document.body.appendChild(element);
+ element.load()
+ });
+ }
+
+ let v = document.createElement("video");
+ const testCases = gSmallTests.filter(t => v.canPlayType(t.type));
+
+ async function testMediaLoad(expectedToLoad, message, useCors) {
+ for (let test of testCases) {
+ let loaded = await testIfLoadsToMetadata(test, useCors);
+ is(loaded, expectedToLoad, test.name + " " + message);
+ }
+ }
+
+ async function runTest() {
+ try {
+ SimpleTest.info("Allowing midflight redirects...");
+ await SpecialPowers.pushPrefEnv({'set': [["media.block-midflight-redirects", false]]});
+
+ SimpleTest.info("Test that all media plays...");
+ await testMediaLoad(true, "expected to load", false);
+
+ SimpleTest.info("Blocking midflight redirects...");
+ await SpecialPowers.pushPrefEnv({'set': [["media.block-midflight-redirects", true]]});
+
+ SimpleTest.info("Test that all media no longer play...");
+ await testMediaLoad(false, "expected to be blocked", false);
+
+ SimpleTest.info("Test that all media play if CORS used...");
+ await testMediaLoad(true, "expected to play with CORS", true);
+ } catch (e) {
+ info("Exception " + e.message);
+ ok(false, "Threw exception " + e.message);
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(runTest);
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/test/test_mixed_principals.html b/dom/media/test/test_mixed_principals.html
new file mode 100644
index 0000000000..c00d65b983
--- /dev/null
+++ b/dom/media/test/test_mixed_principals.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=489415
+-->
+<head>
+ <title>Test for Bug 489415</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+ <style>
+ video {
+ width: 40%;
+ border: solid black 1px;
+ }
+ </style>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=489415">Mozilla Bug 489415</a>
+ <p id="display"></p>
+ <pre id="test">
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({ set: p });
+ var count = 0;
+
+ function canReadBack(video) {
+ var c = document.createElement("canvas");
+ var ctx = c.getContext("2d");
+ ctx.drawImage(video, 0, 0);
+ try {
+ c.toDataURL();
+ return true;
+ } catch (ex) {
+ return false;
+ }
+ }
+
+ function runTest(origin, shouldReadBackOnLoad) {
+ return new Promise(function (resolve, reject) {
+ // Load will redirect mid-flight, which will be detected and should error,
+ // and we should no longer be able to readback.
+ var video = document.createElement("video");
+ video.preload = "metadata";
+ video.controls = true;
+ var url = "http://" + origin + "/tests/dom/media/test/midflight-redirect.sjs"
+ + "?resource=pixel_aspect_ratio.mp4&type=video/mp4";
+ SimpleTest.info("Loading from " + url);
+ video.src = url;
+ document.body.appendChild(video);
+
+ once(video, "loadeddata", () => {
+ is(canReadBack(video), shouldReadBackOnLoad, "Should be able to readback");
+ video.play();
+ });
+
+ once(video, "error", () => {
+ is(canReadBack(video), false, "Should not be able to readback after error");
+ removeNodeAndSource(video);
+ resolve();
+ });
+
+ once(video, "ended", () => {
+ ok(false, "Should not be able to playback to end, we should have errored!");
+ removeNodeAndSource(video);
+ resolve();
+ });
+
+ });
+ }
+
+ Promise.all([
+ runTest("mochi.test:8888", true),
+ runTest("example.org", false),
+ ]).then(SimpleTest.finish);
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mozHasAudio.html b/dom/media/test/test_mozHasAudio.html
new file mode 100644
index 0000000000..c25873c786
--- /dev/null
+++ b/dom/media/test/test_mozHasAudio.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of media files that should play OK</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function onloadedmetadata(e) {
+ var t = e.target;
+ is(t.mozHasAudio, t.hasAudio, "The element reports the wrong audio property." + t.token);
+ manager.finished(t.token);
+}
+
+function startTest(test, token) {
+ var elemType = /^audio/.test(test.type) ? "audio" : "video";
+ var element = document.createElement(elemType);
+ element.preload = "auto";
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.name = test.name;
+ element.hasAudio = elemType == "video" ? test.hasAudio : undefined;
+ element.addEventListener("loadedmetadata", onloadedmetadata);
+
+ element.load();
+}
+
+manager.runTests(gTrackTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_mp3_with_multiple_ID3v2.html b/dom/media/test/test_mp3_with_multiple_ID3v2.html
new file mode 100644
index 0000000000..1f2f946520
--- /dev/null
+++ b/dom/media/test/test_mp3_with_multiple_ID3v2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Play mp3 file with multiple ID3v2 tags</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript">
+
+add_task(async function testPlayMP3WithMultipleID3Tags() {
+ info(`adjust cache size`);
+ await SpecialPowers.pushPrefEnv(
+ // The second ID3v2 header is huge (4622361 bytes) so the first audio samle
+ // in this file is in the position 4945370, so we have to extend the size of
+ // the cache.
+ {"set": [["media.cache_size", 5000000]]}
+ );
+
+ info(`create audio and wait its loading`);
+ let audio = document.createElement('audio');
+ audio.src = "multi_id3v2.mp3";
+ document.body.appendChild(audio);
+ await new Promise(r => audio.onloadeddata = r);
+ ok(true, `finishing loading data.`)
+});
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/media/test/test_multiple_mediastreamtracks.html b/dom/media/test/test_multiple_mediastreamtracks.html
new file mode 100644
index 0000000000..fb28d8652e
--- /dev/null
+++ b/dom/media/test/test_multiple_mediastreamtracks.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the ability of MediaStream with multiple MediaStreamTracks</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="gUM_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+async function startTest() {
+ try {
+ await setupGetUserMediaTestPrefs();
+ let orgStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+ let a = orgStream.getAudioTracks()[0];
+ let v = orgStream.getVideoTracks()[0];
+ let stream = new MediaStream([a, a, a, a, v, v, v].map(track => track.clone()));
+ let element = document.createElement("video");
+
+ element.onloadedmetadata = function() {
+ is(stream.getAudioTracks().length, 4, 'Length of audio tracks should be 4.');
+ is(stream.getVideoTracks().length, 3, 'Length of vudio tracks should be 3.');
+ SimpleTest.finish();
+ };
+
+ element.srcObject = stream;
+ element.play();
+ } catch (err) {
+ ok(false, 'Unexpected error fired with: ' + err);
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.track.enabled", true]
+ ]
+ }, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_networkState.html b/dom/media/test/test_networkState.html
new file mode 100644
index 0000000000..8001ca514c
--- /dev/null
+++ b/dom/media/test/test_networkState.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: networkState</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>
+</head>
+<body onunload="mediaTestCleanup();">
+<video id='v1'></video><audio id='a1'></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+"use strict";
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+var passed = "truthy";
+
+try {
+ v1.networkState = 0;
+} catch (e) {
+ passed = !passed;
+}
+try {
+ a1.networkState = 0;
+} catch (e) {
+ passed = !passed;
+}
+ok(passed === true,
+ "Setting networkState throws in strict mode (readonly attribute)");
+</script>
+
+<script class="testbody" type="text/javascript">
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+var passed = false;
+
+var oldv1ns = v1.networkState, olda1ns = a1.networkState;
+try {
+ v1.networkState = 0;
+ a1.networkState = 0;
+ passed = v1.networkState === oldv1ns && a1.networkState === olda1ns;
+} catch (e) { }
+ok(passed, "Should not be able to modify networkState (readonly attribute)");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_new_audio.html b/dom/media/test/test_new_audio.html
new file mode 100644
index 0000000000..e1f8964f73
--- /dev/null
+++ b/dom/media/test/test_new_audio.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=528566
+-->
+<head>
+ <title>Test for Bug 528566</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=528566">Mozilla Bug 528566</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 528566 **/
+
+var manager = new MediaTestManager;
+
+var player = new Audio();
+
+function startTest(test, token) {
+ if (!player.canPlayType(test.type)) {
+ return;
+ }
+ manager.started(token);
+ var a = new Audio(test.name);
+ a.autoplay = true;
+ document.body.appendChild(a);
+ a.addEventListener("ended",
+ function(e){
+ ok(true, "[" + a.src + "]We should get to the end. Oh look we did.");
+ a.remove();
+ manager.finished(token);
+ });
+}
+
+manager.runTests(gAudioTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_no_load_event.html b/dom/media/test/test_no_load_event.html
new file mode 100644
index 0000000000..1e2717d983
--- /dev/null
+++ b/dom/media/test/test_no_load_event.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=715469
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 715469</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>
+</head>
+<body onload="start();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=715469">Mozilla Bug 715469</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 715469 **/
+
+var gotLoadEvent = false;
+
+function start() {
+ var resource = getPlayableVideo(gSmallTests);
+ if (resource == null) {
+ todo(false, "No types supported");
+ } else {
+ SimpleTest.waitForExplicitFinish();
+ var v = document.createElement("video");
+ v.src = resource.name;
+
+ v.addEventListener("load", function() {
+ gotLoadEvent = true;
+ });
+
+ v.addEventListener("ended", finished);
+ document.body.appendChild(v);
+ v.play();
+ }
+}
+
+function finished() {
+ is(gotLoadEvent, false, "Should not receive a load on the video element");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html b/dom/media/test/test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html
new file mode 100644
index 0000000000..f1b8cc8b15
--- /dev/null
+++ b/dom/media/test/test_not_reset_playbackRate_when_removing_nonloaded_media_from_document.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Do not reset playback rate when removing non-loaded media from a document</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="manifest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+/**
+ * When removing media from a document, it should only trigger internal pause,
+ * instead of the pause method, which would trigger loading process and reset
+ * the media's playback rate for non-loaded media.
+ */
+async function startTest() {
+ info(`create a media and append it to a document`);
+ const audio = document.createElement("audio");
+ document.body.appendChild(audio);
+
+ info(`change audio's playbackRate and remove it from a document`);
+ const expectedRate = 0.1;
+ audio.playbackRate = expectedRate;
+ await once(audio, "ratechange");
+ is(audio.playbackRate, expectedRate,
+ `${audio.playbackRate} is equal to ${expectedRate}`);
+ audio.remove();
+
+ info(`queue a macrotask to check if the playback rate is still unchanged`);
+ setTimeout(() => {
+ // If we unexpectedly reset the playback rate, it would happen in a
+ // microtask when removing media from a document [1] (Await a stable state),
+ // which would always be run before the macrotask.
+ // [1] https://html.spec.whatwg.org/#playing-the-media-resource:remove-an-element-from-a-document
+ is(audio.playbackRate, expectedRate,
+ `${audio.playbackRate} is equal to ${expectedRate}`);
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+onload = startTest;
+
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_paused.html b/dom/media/test/test_paused.html
new file mode 100644
index 0000000000..17af4d3898
--- /dev/null
+++ b/dom/media/test/test_paused.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: paused</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>
+</head>
+<body>
+<video id='v1'></video><audio id='a1'></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+ok(v1.paused, "v1.paused must initially be true");
+ok(a1.paused, "a1.paused must initially be true");
+mediaTestCleanup();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_paused_after_ended.html b/dom/media/test/test_paused_after_ended.html
new file mode 100644
index 0000000000..83f27a921f
--- /dev/null
+++ b/dom/media/test/test_paused_after_ended.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: paused</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function ended(evt) {
+ var v = evt.target;
+ v.removeEventListener("ended", ended);
+ is(v.gotPause, true, "We should have received a \"pause\" event.")
+ is(v.paused, true, v._name + " must be paused after end");
+ manager.finished(v.token);
+ removeNodeAndSource(v);
+}
+
+function pause(evt) {
+ var v = evt.target;
+ v.removeEventListener("pause", pause);
+ v.gotPause = true;
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ document.body.appendChild(v);
+ v.token = token;
+ manager.started(v.token);
+ v.src = test.name;
+ v._name = test.name;
+ v._finished = false;
+ v.load();
+ is(v.paused, true, v._name + " must be paused at start");
+
+ v.play();
+ is(v.paused, false, v._name + " must not be paused after play");
+
+ v.addEventListener("pause", pause);
+ v.addEventListener("ended", ended);
+}
+
+manager.runTests(gPausedAfterEndedTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_play_events.html b/dom/media/test/test_play_events.html
new file mode 100644
index 0000000000..44b819b488
--- /dev/null
+++ b/dom/media/test/test_play_events.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+
+var manager = new MediaTestManager;
+
+var tokens = {
+ 0: ["play"],
+ "play": ["canplay"],
+ "canplay": ["playing"],
+ "playing": ["canplay", "canplaythrough"],
+ "canplaythrough": ["canplay", "canplaythrough"]
+};
+
+function gotPlayEvent(event) {
+ var v = event.target;
+ ok(tokens[v._state].includes(event.type),
+ "Check expected event got " + event.type + " at " + v._state + " for " + v.src +
+ " tokens["+v._state+"]=" + tokens[v._state] +
+ " tokens["+v._state+"].indexOf(event.type)=" + tokens[v._state].indexOf(event.type));
+ v._state = event.type;
+}
+
+function ended(event) {
+ var v = event.target;
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function initTest(test, token) {
+ var v = document.createElement('video');
+ v.token = token;
+ manager.started(token);
+ v._state = 0;
+
+ ["play", "canplay", "playing", "canplaythrough"].forEach(function (e) {
+ v.addEventListener(e, gotPlayEvent);
+ });
+
+ v.addEventListener("ended", ended);
+
+ v.src = test.name;
+ document.body.appendChild(v); // Causes load.
+ v.play();
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_play_events_2.html b/dom/media/test/test_play_events_2.html
new file mode 100644
index 0000000000..022bb5373a
--- /dev/null
+++ b/dom/media/test/test_play_events_2.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: play() method via DOM 0 handlers</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+var manager = new MediaTestManager;
+
+var tokens = {
+ 0: ["play"],
+ "play": ["canplay"],
+ "canplay": ["playing"],
+ "playing": ["canplay", "canplaythrough"],
+ "canplaythrough": ["canplay", "canplaythrough"]
+};
+
+function gotPlayEvent(event) {
+ var v = event.target;
+ ok(tokens[v._state].includes(event.type),
+ "Check expected event got " + event.type + " at " + v._state + " for " + v.src);
+ v._state = event.type;
+}
+
+function ended(event) {
+ var v = event.target;
+ v._finished = true;
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.token = token;
+ manager.started(token);
+ v._state = 0;
+ v._finished = false;
+
+ ["play", "canplay", "playing", "canplaythrough"].forEach(function (e) {
+ v["on" + e] = gotPlayEvent;
+ });
+
+ v.onended = ended;
+
+ v.src = test.name;
+ document.body.appendChild(v); // Causes load.
+ v.play();
+}
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_play_promise_1.html b/dom/media/test/test_play_promise_1.html
new file mode 100644
index 0000000000..ce4e287f34
--- /dev/null
+++ b/dom/media/test/test_play_promise_1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playBeforeCanPlay
+// Case: invoke play() on an element that doesn't have enough data
+// Expected result: resolve the promise
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.src = test.name;
+ ok(element.readyState == HTMLMediaElement.HAVE_NOTHING);
+
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_10.html b/dom/media/test/test_play_promise_10.html
new file mode 100644
index 0000000000..1c0096986c
--- /dev/null
+++ b/dom/media/test/test_play_promise_10.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: loadRejectsPendingPromises
+// Case: invoke load() on an element with pending promises.
+// Expected result: reject all the pending promises with AbortError DOM exception.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.play().then(
+ (result) => {
+ ok(false, `${token} is resolved with ${result}.`);
+ },
+ (error) => {
+ if (error.name == "AbortError") {
+ ok(true, `${token} is rejected with ${error.name}.`);
+ } else {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ }
+ ).then( () => { manager.finished(token); } );
+ element.load();
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_11.html b/dom/media/test/test_play_promise_11.html
new file mode 100644
index 0000000000..a59baf5657
--- /dev/null
+++ b/dom/media/test/test_play_promise_11.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: newSrcRejectPendingPromises
+// Case: change src of an element with pending promises.
+// Expected result: reject all the pending promises.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.play().then(
+ (result) => {
+ ok(false, `${token} is resolved with ${result}.`);
+ },
+ (error) => {
+ if (error.name == "AbortError") {
+ ok(true, `${token} is rejected with ${error.name}.`);
+ } else {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ }
+ ).then( () => { manager.finished(token); } );
+ element.src = test.name;
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_12.html b/dom/media/test/test_play_promise_12.html
new file mode 100644
index 0000000000..50972885eb
--- /dev/null
+++ b/dom/media/test/test_play_promise_12.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: pausePlayAfterPlaybackStarted
+// Case: invoke pause() and then play() on an element that is already playing.
+// Expected result: resolve the promise with undefined.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.preload = "auto";
+ element.src = test.name;
+ element.play();
+ once(element, "playing").then(() => {
+ element.pause();
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_13.html b/dom/media/test/test_play_promise_13.html
new file mode 100644
index 0000000000..aacab88895
--- /dev/null
+++ b/dom/media/test/test_play_promise_13.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: loadAlgorithmDoesNotCancelTasks
+// Case: re-invoke the load() on an element which had dispatched a task to resolve a promise.
+// Expected result: the already dispatched promise should still be resolved with undefined.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.preload = "auto";
+ element.src = test.name;
+
+ // We must wait for "canplay" event; otherwise, invoke play() will lead to a
+ // pending promise and will then be rejected by the following load().
+ once(element, "canplay").then(() => {
+ // The play() promise will be queued to be resolved immediately, which means
+ // the promise is not in the pending list.
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+ element.src = test.name; // Re-invoke the load algorithm again.
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_14.html b/dom/media/test/test_play_promise_14.html
new file mode 100644
index 0000000000..066138ecad
--- /dev/null
+++ b/dom/media/test/test_play_promise_14.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: loadAlgorithmKeepPromisesPendingWhenNotPausing
+// Case: step1: create an element with its paused member to be fause and networkState to be NETWORK_EMPTY.
+// stpe2: invoke load() on the element and the load() leaves the promise pending.
+// Expected result: the pending promise should finally be resolved.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ // Invoke play() -> (1) the promise will be left pending.
+ // (2) invoke load() -> (1) set the networkState to be NETWORK_NO_SOURCE.
+ // (2) queue a task to run resouce selection algorithm.
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+
+ once(element, "play").then(() => {
+ // The resouce selection algorithm has been done.
+ // -> set the networkState to be NETWORK_EMPTY because there is no valid resource to load.
+ ok(element.networkState == HTMLMediaElement.NETWORK_EMPTY);
+
+ // Invoke load() again and since the networkState is NETWORK_EMPTY, the load() does not reject the pending promise.
+ // The load() will queue a task to run resouce selection algorithm which will change the readyState and finally resolve the pending promise.
+ element.src = test.name;
+
+ // Since the networkState is NETWORK_EMPTY, the load() does not set paused to be true.
+ ok(!element.paused, `loadAlgorithmKeepPromisesPendingWhenNotPausing(${token}).paused should be false.`);
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_15.html b/dom/media/test/test_play_promise_15.html
new file mode 100644
index 0000000000..b8bce48651
--- /dev/null
+++ b/dom/media/test/test_play_promise_15.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: loadAlgorithmRejectPromisesWhenPausing
+// Case: step1: create an element with its paused member to be fause and networkState to be NETWORK_NO_SOURCE.
+// stpe2: invoke load() on the element and the load() rejects the pending promise.
+// Expected result: reject the pending promise with AbortError DOM exception.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ // Invoke play() -> (1) the promise will be left pending.
+ // (2) invoke load() -> (1) set the networkState to be NETWORK_NO_SOURCE.
+ // (2) queue a task to run resouce selection algorithm.
+ element.play().then(
+ (result) => {
+ ok(false, `${token} is resolved with ${result}.`);
+ },
+ (error) => {
+ if (error.name == "AbortError") {
+ ok(true, `${token} is rejected with ${error.name}.`);
+ } else {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ }
+ ).then( () => { manager.finished(token); } );
+
+ ok(element.networkState == HTMLMediaElement.NETWORK_NO_SOURCE);
+
+ // Invoke load() again and since the networkState is NETWORK_NO_SOURCE, the load() rejects the pending promise.
+ element.src = test.name;
+
+ // Since the networkState is not NETWORK_EMPTY, the load() sets paused to be true.
+ ok(element.paused, `loadAlgorithmRejectPromisesWhenPausing(${token}).paused should be true.`);
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_16.html b/dom/media/test/test_play_promise_16.html
new file mode 100644
index 0000000000..a643e7fff8
--- /dev/null
+++ b/dom/media/test/test_play_promise_16.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: loadAlgorithmResolveOrdering
+// Case: invoke load() on an element should resolve pending promises in order.
+// Expected result: the pending promises are resolved in order.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.preload = "auto";
+ element.src = test.name;
+ once(element, "canplay").then(() => {
+ let firstPromiseResolved = false;
+
+ // play
+ element.play().then(
+ () => { firstPromiseResolved = true; },
+ () => { ok(false, `loadAlgorithmResolveOrdering(${token}) should not be rejected.`); }
+ );
+
+ // play again
+ element.play().then(
+ () => { ok(firstPromiseResolved, `loadAlgorithmResolveOrdering(${token}), the first play should already be resolved.`); },
+ () => { ok(false, `loadAlgorithmResolveOrdering(${token}) should not be rejected.`); }
+ ).then( () => { manager.finished(token); } );
+
+ // triger load
+ element.src = test.name;
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_17.html b/dom/media/test/test_play_promise_17.html
new file mode 100644
index 0000000000..5e5eb71fd5
--- /dev/null
+++ b/dom/media/test/test_play_promise_17.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: loadAlgorithmRejectOrdering
+// Case: invoke load() on an element should reject pending promises in order.
+// Expected result: the pending promises are rejected in order.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ let firstPromiseRejected = false;
+
+ // play
+ element.play().then(
+ () => { ok(false, `loadAlgorithmRejectOrdering(${token}) should not be resolved.`); },
+ () => { firstPromiseRejected = true; }
+ );
+
+ // play again
+ element.play().then(
+ () => { ok(false, `loadAlgorithmRejectOrdering(${token}) should not be resolved.`); },
+ () => { ok(firstPromiseRejected, `loadAlgorithmRejectOrdering(${token}), the first play should already be rejected.`); }
+ ).then( () => { manager.finished(token); } );
+
+ // triger load
+ element.src = test.name;
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_18.html b/dom/media/test/test_play_promise_18.html
new file mode 100644
index 0000000000..e0c0837fdf
--- /dev/null
+++ b/dom/media/test/test_play_promise_18.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: 'playing' event should come before promise resolving
+// Case: invoke play() on an element which has valis source
+// Expected result: receive a 'playing' event before the promise is resolved
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.src = test.name;
+ element.receivedPlayingEvent = false;
+
+ element.addEventListener("playing", () => {
+ element.receivedPlayingEvent = true;
+ });
+
+ element.play().then(
+ (result) => {
+ if (result == undefined && element.receivedPlayingEvent) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result} and receivedPlayingEvent = ${element.receivedPlayingEvent}`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_2.html b/dom/media/test/test_play_promise_2.html
new file mode 100644
index 0000000000..c4befdbe91
--- /dev/null
+++ b/dom/media/test/test_play_promise_2.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playWhenCanPlay
+// Case: invoke play() on an element that has enough data
+// Expected result: resolve the promise
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.preload = "auto";
+ element.src = test.name;
+ once(element, "canplay").then(() => {
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_3.html b/dom/media/test/test_play_promise_3.html
new file mode 100644
index 0000000000..8ffacdae03
--- /dev/null
+++ b/dom/media/test/test_play_promise_3.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playAfterPlaybackStarted
+// Case: invoke play() on an element that is already playing
+// Expected result: resolve the promise
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.preload = "auto";
+ element.src = test.name;
+ once(element, "playing").then(() => {
+ ok(element.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA, `playAfterPlaybackStarted(${token})`);
+ ok(!element.paused, `playAfterPlaybackStarted(${token})`);
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+ });
+
+ element.play();
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_4.html b/dom/media/test/test_play_promise_4.html
new file mode 100644
index 0000000000..dfcd96e0b9
--- /dev/null
+++ b/dom/media/test/test_play_promise_4.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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="play_promise.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playNotSupportedContent
+// Case: invoke play() on an element with an unsupported content
+// Expected result: reject the promise with NotSupportedError DOM exception
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.src = getNotSupportedFile(test.name);
+ element.play().then(
+ (result) => {
+ ok(false, `${token} is resolved with ${result}.`);
+ },
+ (error) => {
+ if (error.name == "NotSupportedError") {
+ ok(true, `${token} is rejected with ${error.name}.`);
+ } else {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ }
+ ).then( () => { manager.finished(token); } );
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_5.html b/dom/media/test/test_play_promise_5.html
new file mode 100644
index 0000000000..a4c3eeb936
--- /dev/null
+++ b/dom/media/test/test_play_promise_5.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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="play_promise.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playWithErrorAlreadySet
+// Case: invoke play() on an element with MEDIA_ERR_SRC_NOT_SUPPORTED has been set
+// Expected result: reject the promise with NotSupportedError DOM exception
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.src = getNotSupportedFile(test.name);
+ once(element, "error").then(() => {
+ ok(element.error.code == MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
+ element.play().then(
+ (result) => {
+ ok(false, `${token} is resolved with ${result}.`);
+ },
+ (error) => {
+ if (error.name == "NotSupportedError") {
+ ok(true, `${token} is rejected with ${error.name}.`);
+ } else {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ }
+ ).then( () => { manager.finished(token); } );
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_6.html b/dom/media/test/test_play_promise_6.html
new file mode 100644
index 0000000000..dcbea3f108
--- /dev/null
+++ b/dom/media/test/test_play_promise_6.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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="play_promise.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playSwitchToValidSrcAfterError
+// Case: invoke play() on an element which had its source changed (to a valid source) after suffering from an error
+// Expected result: resolve the promise with undefined
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.src = getNotSupportedFile(test.name);
+ once(element, "error").then(() => {
+ ok(element.error.code == MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
+ element.src = test.name;
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_7.html b/dom/media/test/test_play_promise_7.html
new file mode 100644
index 0000000000..1abbe42f33
--- /dev/null
+++ b/dom/media/test/test_play_promise_7.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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="play_promise.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playSwitchToInvalidSrcAfterError
+// Case: invoke play() on an element which had its source changed (to a invalid source) after suffering from an error
+// Expected result: reject the promise with NotSupportedError DOM exception
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.src = getNotSupportedFile(test.name);
+ once(element, "error").then(() => {
+ ok(element.error.code == MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
+ element.src = getNotSupportedFile(test.name);
+ ok(element.error == null);
+ ok(element.readyState == HTMLMediaElement.HAVE_NOTHING);
+ element.play().then(
+ (result) => {
+ ok(false, `${token} is resolved with ${result}.`);
+ },
+ (error) => {
+ if (error.name == "NotSupportedError") {
+ ok(true, `${token} is rejected with ${error.name}.`);
+ } else {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ }
+ ).then( () => { manager.finished(token); } );
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_8.html b/dom/media/test/test_play_promise_8.html
new file mode 100644
index 0000000000..d61e12c9fe
--- /dev/null
+++ b/dom/media/test/test_play_promise_8.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playAndPauseWhenCanplay
+// Case: invlke play() and then pause() on an element that already has enough data to play
+// Expected result: resolve the promise
+// Note: the pause() doesn't cancel the play() because it was alredy been queued to be resolved.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.preload = "auto";
+ element.src = test.name;
+ once(element, "canplay").then(() => {
+ element.play().then(
+ (result) => {
+ if (result == undefined) {
+ ok(true, `${token} is resolved with ${result}.`);
+ } else {
+ ok(false, `${token} is resolved with ${result}.`);
+ }
+ },
+ (error) => {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ ).then( () => { manager.finished(token); } );
+ ok(!element.paused, `playAndPauseWhenCanplay(${token}) element should not be paused.`);
+ element.pause();
+ ok(element.paused, `playAndPauseWhenCanplay(${token}) element should be paused.`);
+ });
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_promise_9.html b/dom/media/test/test_play_promise_9.html
new file mode 100644
index 0000000000..c3a3856515
--- /dev/null
+++ b/dom/media/test/test_play_promise_9.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: promise-based play() method</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+// Name: playAndPauseBeforeCanPlay
+// Case: invlke play() and then pause() on an element that deoen't have enough data to play.
+// Expected result: reject the promise with AbortError DOM exception.
+// Note: the pause() cancels the play() because the promise is still pending.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ ok(element.readyState == HTMLMediaElement.HAVE_NOTHING);
+ element.play().then(
+ (result) => {
+ ok(false, `${token} is resolved with ${result}.`);
+ },
+ (error) => {
+ if (error.name == "AbortError") {
+ ok(true, `${token} is rejected with ${error.name}.`);
+ } else {
+ ok(false, `${token} is rejected with ${error.name}.`);
+ }
+ }
+ ).then( () => { manager.finished(token); } );
+ ok(!element.paused, `playAndPauseBeforeCanPlay(${token}) element should not be paused.`);
+ element.pause();
+ ok(element.paused, `playAndPauseBeforeCanPlay(${token}) element should be paused.`);
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_play_twice.html b/dom/media/test/test_play_twice.html
new file mode 100644
index 0000000000..e94f28a031
--- /dev/null
+++ b/dom/media/test/test_play_twice.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of media files that should play OK</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ video.token = token;
+ manager.started(token);
+ video.src = test.name;
+ video.name = test.name;
+ video.playingCount = 0;
+ video._playedOnce = false;
+
+ var check = function(t, v) { return function() {
+ checkMetadata(t.name, v, test);
+ }}(test, video);
+
+ var noLoad = function(t, v) { return function() {
+ ok(false, t.name + " should not fire 'load' event");
+ }}(test, video);
+
+ function finish(v) {
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+ }
+
+ function mayFinish(v) {
+ if (v.seenEnded && v.seenSuspend) {
+ finish(v);
+ }
+ }
+
+ var checkEnded = function(t, v) { return function() {
+ if (t.duration) {
+ ok(Math.abs(v.currentTime - t.duration) < 0.1,
+ t.name + " current time at end: " + v.currentTime);
+ }
+
+ is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState");
+ ok(v.ended, t.name + " checking playback has ended");
+ ok(v.playingCount > 0, "Expect at least one playing event");
+ v.playingCount = 0;
+
+ if (v._playedOnce) {
+ v.seenEnded = true;
+ mayFinish(v);
+ } else {
+ v._playedOnce = true;
+ v.play();
+ }
+ }}(test, video);
+
+ var checkSuspended = function(t, v) { return function() {
+ if (v.seenSuspend) {
+ return;
+ }
+
+ v.seenSuspend = true;
+ ok(true, t.name + " got suspend");
+ mayFinish(v);
+ }}(test, video);
+
+ var checkPlaying = function(t, v) { return function() {
+ is(t.name, v.name, "Should be testing file we think we're testing...");
+ v.playingCount++;
+ }}(test, video);
+
+ video.addEventListener("load", noLoad);
+ video.addEventListener("loadedmetadata", check);
+ video.addEventListener("playing", checkPlaying);
+
+ // We should get "ended" and "suspend" events for every resource
+ video.addEventListener("ended", checkEnded);
+ video.addEventListener("suspend", checkSuspended);
+
+ document.body.appendChild(video);
+ video.play();
+}
+
+manager.runTests(gReplayTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_playback.html b/dom/media/test/test_playback.html
new file mode 100644
index 0000000000..5e28861e93
--- /dev/null
+++ b/dom/media/test/test_playback.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of media files that should play OK</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ video.preload = "metadata";
+ video.token = token;
+ video.prevTime = 0;
+ video.seenEnded = false;
+ video.seenSuspend = false;
+
+ var handler = {
+ "ontimeout": function() {
+ Log(token, "timed out: ended=" + video.seenEnded + ", suspend=" + video.seenSuspend);
+ }
+ };
+ manager.started(token, handler);
+
+ video.src = test.name;
+ video.name = test.name;
+
+ var check = function(t, v) { return function() {
+ is(t.name, v.name, t.name + ": Name should match #1");
+ checkMetadata(t.name, v, t);
+ }}(test, video);
+
+ var noLoad = function(t, v) { return function() {
+ ok(false, t.name + " should not fire 'load' event");
+ }}(test, video);
+
+ var noError = function(t, v) { return function() {
+ ok(false, t.name + " should not fire 'error' event " + v.error.message);
+ }}(test, video);
+
+ var finish = function() {
+ video.finished = true;
+ video.removeEventListener("timeupdate", timeUpdate);
+ removeNodeAndSource(video);
+ manager.finished(video.token);
+ }
+
+ // We should get "ended" and "suspend" events to finish the test.
+ var mayFinish = function() {
+ if (video.seenEnded && video.seenSuspend) {
+ finish();
+ }
+ }
+
+ var checkEnded = function(t, v) { return function() {
+ is(t.name, v.name, t.name + ": Name should match #2");
+ checkMetadata(t.name, v, test);
+ is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState");
+ ok(v.ended, t.name + " checking playback has ended");
+ ok(!v.finished, t.name + " shouldn't be finished");
+ ok(!v.seenEnded, t.name + " shouldn't be ended");
+
+ v.seenEnded = true;
+ mayFinish();
+ }}(test, video);
+
+ var checkSuspended = function(t, v) { return function() {
+ if (v.seenSuspend) {
+ return;
+ }
+ is(t.name, v.name, t.name + ": Name should match #3");
+
+ v.seenSuspend = true;
+ mayFinish();
+ }}(test, video);
+
+ var timeUpdate = function(t, v) { return function() {
+ if (v.prevTime > v.currentTime) {
+ ok(false, t.name + " time should run forwards: p=" +
+ v.prevTime + " c=" + v.currentTime);
+ }
+ v.prevTime = v.currentTime;
+ }}(test, video);
+
+ video.addEventListener("load", noLoad);
+ video.addEventListener("error", noError);
+ video.addEventListener("loadedmetadata", check);
+ video.addEventListener("timeupdate", timeUpdate);
+
+ // We should get "ended" and "suspend" events for every resource
+ video.addEventListener("ended", checkEnded);
+ video.addEventListener("suspend", checkSuspended);
+
+ document.body.appendChild(video);
+ video.play();
+}
+
+manager.runTests(gPlayTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_playback_errors.html b/dom/media/test/test_playback_errors.html
new file mode 100644
index 0000000000..7b3f046099
--- /dev/null
+++ b/dom/media/test/test_playback_errors.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of media files that should have errors</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ manager.started(token);
+ video._errorCount = 0;
+ video._ignore = false;
+ function endedTest(v) {
+ if (v._ignore)
+ return;
+ v._ignore = true;
+ v.remove();
+ manager.finished(token);
+ }
+ var checkError = function(t, v) { return function(evt) {
+ v._errorCount++;
+ is(v._errorCount, 1, t.name + " only one error fired");
+ endedTest(v);
+ }}(test, video);
+ var checkEnded = function(t, v) { return function() {
+ ok(false, t.name + " successfully played");
+ endedTest(v);
+ }}(test, video);
+ video.addEventListener("error", checkError);
+ video.addEventListener("ended", checkEnded);
+ video.src = test.name;
+ document.body.appendChild(video);
+ video.play();
+}
+
+manager.runTests(gErrorTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_playback_hls.html b/dom/media/test/test_playback_hls.html
new file mode 100644
index 0000000000..7d5c777fc1
--- /dev/null
+++ b/dom/media/test/test_playback_hls.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of HLS with simple m3u8 that should play OK</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ video.preload = "metadata";
+ video.token = token;
+ video.prevTime = 0;
+ video.seenEnded = false;
+
+ var handler = {
+ "ontimeout": function() {
+ Log(token, "timed out: ended=" + video.seenEnded);
+ }
+ };
+ manager.started(token, handler);
+
+ video.src = test.name;
+ video.name = test.name;
+
+ var check = function(t, v) { return function() {
+ is(t.name, v.name, t.name + ": Name should match #1");
+ checkMetadata(t.name, v, t);
+ }}(test, video);
+
+ var noLoad = function(t, v) { return function() {
+ ok(false, t.name + " should not fire 'load' event");
+ }}(test, video);
+
+ var finish = function() {
+ video.finished = true;
+ video.removeEventListener("timeupdate", timeUpdate);
+ removeNodeAndSource(video);
+ manager.finished(video.token);
+ }
+
+ // We should get "ended" events to finish the test.
+ var mayFinish = function() {
+ if (video.seenEnded) {
+ finish();
+ }
+ }
+
+ var checkEnded = function(t, v) { return function() {
+ is(t.name, v.name, t.name + ": Name should match #2");
+ checkMetadata(t.name, v, test);
+ is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState");
+ ok(v.ended, t.name + " checking playback has ended");
+ ok(!v.finished, t.name + " shouldn't be finished");
+ ok(!v.seenEnded, t.name + " shouldn't be ended");
+
+ v.seenEnded = true;
+ mayFinish();
+ }}(test, video);
+
+ var timeUpdate = function(t, v) { return function() {
+ if (v.prevTime > v.currentTime) {
+ ok(false, t.name + " time should run forwards: p=" +
+ v.prevTime + " c=" + v.currentTime);
+ }
+ v.prevTime = v.currentTime;
+ }}(test, video);
+
+ video.addEventListener("load", noLoad);
+ video.addEventListener("loadedmetadata", check);
+ video.addEventListener("timeupdate", timeUpdate);
+
+ // We should get "ended" events for the hls resource
+ video.addEventListener("ended", checkEnded);
+
+ document.body.appendChild(video);
+ video.play();
+}
+
+manager.runTests(gHLSTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_playback_rate.html b/dom/media/test/test_playback_rate.html
new file mode 100644
index 0000000000..a704b9a1ba
--- /dev/null
+++ b/dom/media/test/test_playback_rate.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for the playbackRate property </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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type='application/javascript'>
+
+let manager = new MediaTestManager;
+
+function rangeCheck(lhs, rhs, threshold) {
+ var diff = Math.abs(lhs - rhs);
+ if (diff < threshold) {
+ return true;
+ }
+ return false;
+}
+
+function checkPlaybackRate(wallclock, media, expected, threshold) {
+ if (rangeCheck(media / wallclock, expected, threshold)) {
+ return true;
+ }
+ return false;
+}
+
+// Those value are expected to match those at the top of HTMLMediaElement.cpp.
+let VERY_SLOW_RATE = 1 / 32,
+ SLOW_RATE = 1 / 16,
+ FAST_RATE = 16,
+ VERY_FAST_RATE = 20,
+ NULL_RATE = 0.0;
+
+function ontimeupdate(e) {
+ var t = e.target;
+ // Skip short files for SoundTouch doesn't work well on small number of samples.
+ if (t.gotEnded || t.duration < 2) {
+ return;
+ }
+ t.testedForSlowdown = true;
+ if (t.currentTime > t.duration / 2) {
+ t.oldCurrentTime = t.currentTime;
+ t.timestamp = Date.now();
+ var delta = t.oldCurrentTime,
+ delta_wallclock = (t.timestamp - t.startTimestamp - t.bufferingTime) / 1000;
+
+ t.mozPreservesPitch = false;
+ is(t.mozPreservesPitch, false, t.name + ": If we disable the pitch preservation, it should appear as such.");
+
+ t.bufferingTime = 0;
+
+ is(t.playbackRate, SLOW_RATE, t.name + ": The playback rate shoud be "+SLOW_RATE+".");
+ ok(checkPlaybackRate(delta_wallclock, delta, SLOW_RATE, 0.25), t.name + ": We are effectively slowing down playback. (" + delta_wallclock + ", " + delta + ")");
+ t.removeEventListener("timeupdate", ontimeupdate);
+ t.addEventListener("pause", onpaused);
+ t.playbackRate = NULL_RATE;
+ t.oldCurrentTime = t.currentTime;
+ setTimeout(function() {
+ afterNullPlaybackRate(e);
+ }, 100);
+ }
+}
+
+function onpaused(e) {
+ var t = e.target;
+ t.pausedReceived = true;
+}
+
+function afterNullPlaybackRate(e) {
+ var t = e.target;
+
+ // skip if we have received 'ended' event or 'ended' event is pending.
+ if (t.gotEnded || t.ended) {
+ return;
+ }
+
+ t.testedForNull = true;
+
+ ok(t.currentTime == t.oldCurrentTime, t.name + ": Current time should not change when playbackRate is null (" + t.currentTime + " " + t.oldCurrentTime + ").");
+ ok(!t.paused, t.name + ": The element should not be in paused state.");
+ t.removeEventListener("paused", onpaused);
+ is(t.pausedReceived, undefined, t.name + ": Paused event should not have been received.");
+ t.timestamp = Date.now();
+ t.oldCurrentTime = t.currentTime;
+ t.playbackRate = VERY_FAST_RATE;
+ is(t.playbackRate, VERY_FAST_RATE, t.name + ": Playback rate should be clamped to " + VERY_FAST_RATE + ".");
+}
+
+function onended(e) {
+ var t = e.target;
+ t.gotEnded = true;
+
+ t.bufferingTime = 0;
+ // If we got "ended" too early, skip these tests.
+ if (t.testedForSlowdown && t.testedForNull) {
+ is(t.playbackRate, FAST_RATE, t.name + ": The playback rate should still be "+FAST_RATE+".");
+ ok(!t.muted, t.name + ": The audio should be muted when playing at high speed, but should not appear as such.");
+ is(t.currentTime, t.duration, t.name + ": Current time should be equal to the duration (not change by playback rate).");
+ }
+ finish_test(t);
+}
+
+function onratechange(e) {
+ if (!e.target.ratechangecount) {
+ e.target.ratechangecount = 0;
+ }
+ e.target.ratechangecount++;
+}
+
+function finish_test(element) {
+ removeNodeAndSource(element);
+ manager.finished(element.token);
+}
+
+// These two functions handle the case when the playback pauses for buffering. It
+// adjusts the timestamps to be accurate. Despite the fact that the web servers
+// is supposed to be on the same machine, buffering pauses can occur (rarely,
+// but still).
+function onplaying(e) {
+ var t = e.target;
+ if (t.bufferingTimestamp != undefined) {
+ t.bufferingTime += (Date.now() - t.bufferingTimestamp);
+ t.bufferingTimestamp = undefined;
+ }
+}
+
+function onwaiting(e) {
+ var t = e.target;
+ t.bufferingTimestamp = Date.now();
+}
+
+function onvolumechange(e) {
+ ok(false, e.target.name + ": We should not receive a volumechange event when changing the playback rate.");
+}
+
+function startTest(test, token) {
+ let elemType = /^audio/.test(test.type) ? "audio" : "video";
+ let element = document.createElement(elemType);
+ element.src = test.name;
+ element.name = test.name;
+ element.preload = "metadata";
+ element.token = token;
+ element.controls = true;
+ element.bufferingTime = 0;
+ document.body.appendChild(element);
+ element.addEventListener("ratechange", onratechange);
+ element.addEventListener("timeupdate", ontimeupdate);
+ element.addEventListener("ended", onended);
+ element.addEventListener("waiting", onwaiting);
+ element.addEventListener("playing", onplaying);
+ element.addEventListener("volumechange", onvolumechange);
+ manager.started(token);
+ element.startTimestamp = Date.now();
+ is(element.mozPreservesPitch, true, test.name + ": Pitch preservation should be enabled by default.");
+ element.addEventListener("loadedmetadata", function() {
+ is(element.playbackRate, 1.0, test.name + ": playbackRate should be initially 1.0");
+ is(element.defaultPlaybackRate, 1.0, test.name + ": defaultPlaybackRate should be initially 1.0");
+ element.playbackRate = VERY_SLOW_RATE;
+ is(element.playbackRate, VERY_SLOW_RATE, test.name + ": PlaybackRate should be " + VERY_SLOW_RATE + ".");
+ element.play();
+ element.playbackRate = SLOW_RATE;
+ });
+}
+
+manager.runTests(gPlayedTests, startTest);
+
+</script>
+</pre>
+<div id="elements">
+</div>
+</body>
+</html>
diff --git a/dom/media/test/test_playback_rate_playpause.html b/dom/media/test/test_playback_rate_playpause.html
new file mode 100644
index 0000000000..2f3e6d8b9a
--- /dev/null
+++ b/dom/media/test/test_playback_rate_playpause.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that the playbackRate property is not reset when resuming the playback</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type='application/javascript'>
+
+let manager = new MediaTestManager;
+
+function ontimeupdate(e) {
+ var t = e.target;
+ if (t.currentTime != 0.0) {
+ dump(t.token + " t.currentTime != 0.0.\n");
+ t.removeEventListener("timeupdate", ontimeupdate);
+ t.pause();
+ is(t.playbackRate, 0.5, "PlaybackRate should not have changed after pause.");
+ } else {
+ dump(t.token + " t.currentTime == 0.0.\n");
+ }
+}
+
+function onpaused(e) {
+ var t = e.target;
+ dump(t.token + " onpaused.\n");
+ t.play();
+ is(t.playbackRate, 0.5, "PlaybackRate should not have changed after resuming playback.");
+ finish_test(t);
+}
+
+function finish_test(element) {
+ dump(element.token + " finish_test.\n");
+ removeNodeAndSource(element);
+ manager.finished(element.token);
+}
+
+function startTest(test, token) {
+ let elemType = /^audio/.test(test.type) ? "audio" : "video";
+ let element = document.createElement(elemType);
+ element.src = test.name;
+ element.token = token;
+ element.controls = true;
+ element.playbackRate = 0.5;
+ element.preload = "metadata";
+ element.addEventListener("timeupdate", ontimeupdate);
+ element.addEventListener("pause", onpaused);
+ element.addEventListener("loadedmetadata", function() {
+ dump(element.token + " loadedmetadata\n");
+ element.play();
+ });
+ document.body.appendChild(element);
+ manager.started(token);
+}
+
+manager.runTests(gPlayedTests, startTest);
+
+</script>
+</pre>
+<div id="elements">
+</div>
+</body>
+</html>
diff --git a/dom/media/test/test_playback_reactivate.html b/dom/media/test/test_playback_reactivate.html
new file mode 100644
index 0000000000..dec5330ce5
--- /dev/null
+++ b/dom/media/test/test_playback_reactivate.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback with dormant of media files that should play OK</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/* This testcase wants to test a video element's playback is not break
+ by dormant.
+ When the metadata is loaded, we remove the video element to trigger dormant.
+ Then set a timer to append the video element back and play it.
+ Test pass if the video plays to the end.
+*/
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ video.preload = "metadata";
+ video.token = token;
+
+ var handler = {
+ "ontimeout": function() {
+ Log(token, "timed out: ended=" + video.seenEnded + ", suspend=" + video.seenSuspend);
+ }
+ };
+ manager.started(token, handler);
+
+ video.src = test.name;
+ video.name = test.name;
+
+ var check = function(t, v) { return function() {
+ is(t.name, v.name, t.name + ": Name should match #1");
+ Log(v.token, "removeChild: " + v.name);
+ document.body.removeChild(v);
+ var appendAndPlayElement = function() {
+ Log(v.token, "appendChild: " + v.name);
+ document.body.appendChild(v);
+ Log(v.token, "Element play: " + v.name);
+ v.play();
+ }
+ setTimeout(appendAndPlayElement, 2000);
+ }}(test, video);
+
+ var finish = function() {
+ video.finished = true;
+ removeNodeAndSource(video);
+ manager.finished(video.token);
+ }
+
+ var checkEnded = function(t, v) { return function() {
+ is(t.name, v.name, t.name + ": Name should match #2");
+ checkMetadata(t.name, v, t);
+ is(v.readyState, v.HAVE_CURRENT_DATA, t.name + " checking readyState");
+ ok(v.ended, t.name + " checking playback has ended");
+
+ finish();
+ }}(test, video);
+
+
+ video.addEventListener("loadedmetadata", check);
+ video.addEventListener("ended", checkEnded);
+
+ document.body.appendChild(video);
+}
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_played.html b/dom/media/test/test_played.html
new file mode 100644
index 0000000000..e8b4906c2c
--- /dev/null
+++ b/dom/media/test/test_played.html
@@ -0,0 +1,245 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test played member for media elements</title>
+<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+<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>
+</head>
+<body>
+<pre id='test'>
+<script class="testbody" type='application/javascript'>
+
+let manager = new MediaTestManager;
+
+function finish_test(element) {
+ removeNodeAndSource(element);
+ manager.finished(element.token);
+}
+
+// Check that a file has been played in its entirety.
+function check_full_file_played(element) {
+ element.addEventListener('ended', (function(e) {
+ let interval_count = e.target.played.length;
+ is(interval_count, 1, element.token + ": played.length must be 1");
+ is(element.played.start(0), 0, element.token + ": start time shall be 0");
+ is(element.played.end(0), e.target.duration, element.token + ": end time shall be duration");
+ finish_test(e.target);
+ }));
+}
+
+var tests = [
+// Without playing, check that player.played.length == 0.
+{
+ setup(element) {
+ element.addEventListener("loadedmetadata", function() {
+ is(element.played.length, 0, element.token + ": initial played.length equals zero");
+ finish_test(element);
+ });
+ },
+ name: "test1"
+},
+// Play the file, test the range we have.
+{
+ setup(element) {
+ check_full_file_played(element);
+ element.play();
+ },
+ name: "test2"
+},
+
+// Play the second half of the file, pause, play
+// an check we have only one range.
+{
+ setup (element) {
+ element.onended = function (e) {
+ var t = e.target;
+ t.onended = null;
+ check_full_file_played(t);
+ t.pause();
+ t.currentTime = 0;
+ t.play();
+ };
+ element.addEventListener("loadedmetadata", function() {
+ element.currentTime = element.duration / 2;
+ element.play();
+ });
+ },
+ name: "test3"
+},
+
+// Play the first half of the file, seek back, while
+// continuing to play. We shall have only one range.
+{
+ setup (element) {
+ let onTimeUpdate = function() {
+ if (element.currentTime > element.duration / 2) {
+ info(element.token + ": currentTime=" + element.currentTime + ", duration=" + element.duration);
+ element.removeEventListener("timeupdate", onTimeUpdate);
+ element.pause();
+ var oldEndRange = element.played.end(0);
+ element.currentTime = element.duration / 4;
+ is(element.played.end(0), oldEndRange,
+ element.token + ": When seeking back, |played| should not be changed");
+ element.play();
+ }
+ }
+ element.addEventListener("timeupdate", onTimeUpdate);
+ check_full_file_played(element);
+ element.play();
+ },
+ name: "test4"
+},
+
+// Play and seek to have two ranges, and check that, as well a
+// boundaries.
+{
+ setup (element) {
+ let seekTarget = 0;
+ let onTimeUpdate = function() {
+ if (element.currentTime > element.duration / 2) {
+ info(element.token + ": currentTime=" + element.currentTime + ", duration=" + element.duration);
+ element.removeEventListener("timeupdate", onTimeUpdate);
+ element.pause();
+ // Remember seek target for later comparison since duration may change
+ // during playback.
+ seekTarget = element.currentTime = element.duration / 10;
+ element.currentTime = seekTarget;
+ element.play();
+ }
+ }
+
+ element.addEventListener("loadedmetadata", function() {
+ element.addEventListener("timeupdate", onTimeUpdate);
+ });
+
+
+ element.addEventListener("ended", (function() {
+ if(element.played.length > 1) {
+ is(element.played.length, 2, element.token + ": element.played.length == 2");
+ is(element.played.start(1), seekTarget, element.token + ": we should have seeked forward by one tenth of the duration");
+ is(element.played.end(1), element.duration, element.token + ": end of second range shall be the total duration");
+ }
+ is(element.played.start(0), 0, element.token + ": start of first range shall be 0");
+ finish_test(element);
+ }));
+
+ element.play();
+ },
+ name: "test5"
+},
+
+// Play to create two ranges, in the reverse order. check that they are sorted.
+{
+ setup (element) {
+ function end() {
+ element.pause();
+ let p = element.played;
+ ok(p.length >= 1, element.token + ": There should be at least one range=" + p.length);
+ is(p.start(0), seekTarget, element.token + ": Start of first range should be the sixth of the duration");
+ ok(p.end(p.length - 1) > 5 * element.duration / 6, element.token + ": End of last range should be greater that five times the sixth of the duration");
+ finish_test(element);
+ }
+
+ let seekTarget = 0;
+ function pauseseekrestart() {
+ element.pause();
+ // Remember seek target for later comparison since duration may change
+ // during playback.
+ seekTarget = element.duration / 6;
+ element.currentTime = seekTarget;
+ element.play();
+ }
+
+ function onTimeUpdate_pauseseekrestart() {
+ if (element.currentTime > 5 * element.duration / 6) {
+ element.removeEventListener("timeupdate", onTimeUpdate_pauseseekrestart);
+ pauseseekrestart();
+ element.addEventListener("timeupdate", onTimeUpdate_end);
+ }
+ }
+
+ function onTimeUpdate_end() {
+ if (element.currentTime > 3 * element.duration / 6) {
+ element.removeEventListener("timeupdate", onTimeUpdate_end);
+ end();
+ }
+ }
+
+ element.addEventListener("timeupdate", onTimeUpdate_pauseseekrestart);
+
+ element.addEventListener('loadedmetadata', function() {
+ element.currentTime = 4 * element.duration / 6;
+ element.play();
+ });
+ },
+ name: "test6"
+},
+// Seek repeatedly without playing. No range should appear.
+{
+ setup(element) {
+ let index = 1;
+
+ element.addEventListener('seeked', function() {
+ index++;
+ element.currentTime = index * element.duration / 5;
+ is(element.played.length, 0, element.token + ": played.length should be 0");
+ if (index == 5) {
+ finish_test(element);
+ }
+ });
+
+ element.addEventListener('loadedmetadata', function() {
+ element.currentTime = element.duration / 5;
+ });
+ },
+ name: "test7"
+}
+];
+
+function createTestArray() {
+ var A = [];
+ for (var i=0; i<tests.length; i++) {
+ for (var k=0; k<gPlayedTests.length; k++) {
+ var t = {};
+ t.setup = tests[i].setup;
+ t.name = tests[i].name + "-" + gPlayedTests[k].name;
+ t.type = gPlayedTests[k].type;
+ t.src = gPlayedTests[k].name;
+ A.push(t);
+ }
+ }
+ return A;
+}
+
+function startTest(test, token) {
+ var elemType = getMajorMimeType(test.type);
+ var element = document.createElement(elemType);
+ element.src = test.src;
+ element.token = token;
+ element.preload = "metadata";
+ test.setup(element);
+ manager.started(token);
+
+ // Log events for debugging.
+ var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause"];
+ function logEvent(e) {
+ var v = e.target;
+ info(v.token + ": got " + e.type);
+ }
+ events.forEach(function(e) {
+ element.addEventListener(e, logEvent);
+ });
+
+}
+
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_preload_actions.html b/dom/media/test/test_preload_actions.html
new file mode 100644
index 0000000000..a9de54988c
--- /dev/null
+++ b/dom/media/test/test_preload_actions.html
@@ -0,0 +1,582 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548523
+-->
+<head>
+ <title>Test for Bug 548523</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=548523">Mozilla Bug 548523</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<!-- <button onClick="SimpleTest.finish();">Finish</button> -->
+<div>Tests complete: <span id="log" style="font-size: small;"></span></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 548523 **/
+
+SimpleTest.requestCompleteLog();
+var manager = new MediaTestManager;
+
+manager.onFinished = function() {
+ is(gotLoadEvent, true, "Should not have delayed the load event indefinitely");
+};
+
+var test = getPlayableVideo(gSeekTests);
+var baseName = test.name;
+var gTest = test;
+var bogusSrc = "bogus.duh";
+var bogusType = "video/bogus";
+var gotLoadEvent = false;
+var finished = false;
+
+addLoadEvent(function() {gotLoadEvent=true;});
+
+function log(m) {
+ var l = document.getElementById("log");
+ // eslint-disable-next-line no-unsanitized/property
+ l.innerHTML += m;
+}
+
+function maybeFinish(v, n) {
+ if (v._finished) {
+ return;
+ }
+ v._finished = true;
+ log(n + ",");
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function filename(uri) {
+ return uri.substr(uri.lastIndexOf("/")+1);
+}
+
+// Every test must have a setup(v) function, and must call maybeFinish() when test is complete.
+var tests = [
+ {
+ // 1. Add preload:none video with src to document. Load should halt at NETWORK_IDLE and HAVE_NOTHING,
+ // after receiving a suspend event. Should not receive loaded events until after we call load().
+ // Note the suspend event is explictly sent by our "stop the load" code, but other tests can't rely
+ // on it for the preload:metadata case, as there can be multiple suspend events when loading metadata.
+ suspend(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(1) Must get loadstart.");
+ is(v._gotLoadedMetaData, false, "(1) Must not get loadedmetadata.");
+ is(v.readyState, v.HAVE_NOTHING, "(1) ReadyState must be HAVE_NOTHING");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(1) NetworkState must be NETWORK_IDLE");
+ maybeFinish(v, 1);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("suspend", this.suspend);
+ v.src = test.name;
+ document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
+ },
+
+ name: "test1",
+ },
+ {
+ // 2. Add preload:metadata video with src to document. Should halt with NETWORK_IDLE, HAVE_CURRENT_DATA
+ // after suspend event and after loadedmetadata.
+ loadeddata(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(2) Must get loadstart.");
+ is(v._gotLoadedMetaData, true, "(2) Must get loadedmetadata.");
+ ok(v.readyState >= v.HAVE_CURRENT_DATA, "(2) ReadyState must be >= HAVE_CURRENT_DATA");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(2) NetworkState must be NETWORK_IDLE");
+ maybeFinish(v, 2);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "metadata";
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadeddata", this.loadeddata);
+ v.src = test.name;
+ document.body.appendChild(v); // Causes implicit load, which will be halted after
+ // metadata due to preload:metadata.
+ },
+
+ name: "test2",
+ },
+ {
+ // 3. Add preload:auto to document. Should receive canplaythrough eventually.
+ canplaythrough(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(3) Must get loadstart.");
+ is(v._gotLoadedMetaData, true, "(3) Must get loadedmetadata.");
+ maybeFinish(v, 3);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "auto";
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("canplaythrough", this.canplaythrough);
+ v.src = test.name; // Causes implicit load.
+ document.body.appendChild(v);
+ },
+
+ name: "test3",
+ },
+ {
+ // 4. Add preload:none video to document. Call play(), should load then play through.
+ suspend(e) {
+ var v = e.target;
+ if (v._gotSuspend) {
+ return; // We can receive multiple suspend events, like the one after download completes.
+ }
+ v._gotSuspend = true;
+ is(v._gotLoadStart, true, "(4) Must get loadstart.");
+ is(v._gotLoadedMetaData, false, "(4) Must not get loadedmetadata.");
+ is(v.readyState, v.HAVE_NOTHING, "(4) ReadyState must be HAVE_NOTHING");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(4) NetworkState must be NETWORK_IDLE");
+ v.play(); // Should load and play through.
+ },
+
+ ended(e) {
+ ok(true, "(4) Got playback ended");
+ maybeFinish(e.target, 4);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v._gotSuspend = false;
+ v.preload = "none";
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("suspend", this.suspend);
+ v.addEventListener("ended", this.ended);
+ v.src = test.name;
+ document.body.appendChild(v);
+ },
+
+ name: "test4",
+ },
+ {
+ // 5. preload:none video without resource, add to document, will implicitly start a
+ // preload:none load. Add a src, it shouldn't load.
+ suspend(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(5) Must get loadstart.");
+ is(v._gotLoadedMetaData, false, "(5) Must not get loadedmetadata.");
+ is(v.readyState, v.HAVE_NOTHING, "(5) ReadyState must be HAVE_NOTHING");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(5) NetworkState must be NETWORK_IDLE");
+ maybeFinish(v, 5);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("suspend", this.suspend);
+ document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
+ v.src = test.name; // Load should start, and halt at preload:none.
+ },
+
+ name: "test5",
+ },
+ {
+ // 6. preload:none video without resource, add to document, will implicitly start a
+ // preload:none load. Add a source, it shouldn't load.
+ suspend(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(6) Must get loadstart.");
+ is(v._gotLoadedMetaData, false, "(6) Must not get loadedmetadata.");
+ is(v.readyState, v.HAVE_NOTHING, "(6) ReadyState must be HAVE_NOTHING");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(6) NetworkState must be NETWORK_IDLE");
+ maybeFinish(v, 6);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("suspend", this.suspend);
+ document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
+ var s = document.createElement("source");
+ s.src = test.name;
+ s.type = test.type;
+ v.appendChild(s); // Load should start, and halt at preload:none.
+ },
+
+ name: "test6",
+ },
+ {
+ // 7. create a preload:none document with multiple sources, the first of which is invalid.
+ // Add to document, then play. It should load and play through the second source.
+ suspend(e) {
+ var v = e.target;
+ if (v._gotSuspend)
+ return; // We can receive multiple suspend events, like the one after download completes.
+ v._gotSuspend = true;
+ is(v._gotLoadStart, true, "(7) Must get loadstart.");
+ is(v._gotLoadedMetaData, false, "(7) Must not get loadedmetadata.");
+ is(v.readyState, v.HAVE_NOTHING, "(7) ReadyState must be HAVE_NOTHING");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(7) NetworkState must be NETWORK_IDLE");
+ v.play(); // Should load and play through.
+ },
+
+ ended(e) {
+ ok(true, "(7) Got playback ended");
+ var v = e.target;
+ is(v._gotErrorEvent, true, "(7) Should get error event from first source load failure");
+ maybeFinish(v, 7);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v._gotErrorEvent = false;
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("suspend", this.suspend);
+ v.addEventListener("ended", this.ended);
+ var s1 = document.createElement("source");
+ s1.src = "not-a-real-file.404"
+ s1.type = test.type;
+ s1.addEventListener("error", function(e){v._gotErrorEvent = true;});
+ v.appendChild(s1);
+ var s2 = document.createElement("source");
+ s2.src = test.name;
+ s2.type = test.type;
+ v.appendChild(s2);
+ document.body.appendChild(v); // Causes implicit load, which will be halt at preload:none on the second resource.
+ },
+
+ name: "test7",
+ },
+ {
+ // 8. Change preload value from none to metadata should cause metadata to be loaded.
+ loadeddata(e) {
+ var v = e.target;
+ is(v._gotLoadedMetaData, true, "(8) Must get loadedmetadata.");
+ ok(v.readyState >= v.HAVE_CURRENT_DATA, "(8) ReadyState must be >= HAVE_CURRENT_DATA on suspend.");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(8) NetworkState must be NETWORK_IDLE when load is halted");
+ maybeFinish(v, 8);
+ },
+
+ setup(v) {
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v.addEventListener("loadstart", function(e){v.preload = "metadata";});
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadeddata", this.loadeddata);
+ v.src = test.name; // Causes implicit load.
+ document.body.appendChild(v);
+ },
+
+ name: "test8",
+ },
+ /*{
+ // 9. Change preload value from metadata to auto should cause entire media to be loaded.
+ // For some reason we don't always receive the canplaythrough event, particuarly on this test.
+ // We've disabled this test until bug 568402 is fixed.
+ canplaythrough:
+ function(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(9) Must get loadstart.");
+ is(v._gotLoadedMetaData, true, "(9) Must get loadedmetadata.");
+ maybeFinish(v, 9);
+ },
+
+ setup:
+ function(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "metadata";
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+ v.addEventListener("loadeddata", function(){v.preload = "auto"}, false);
+ v.addEventListener("canplaythrough", this.canplaythrough, false);
+ v.src = test.name; // Causes implicit load.
+ document.body.appendChild(v);
+ },
+ },*/
+ {
+ // 10. Change preload value from none to auto should cause entire media to be loaded.
+ canplaythrough(e) {
+ var v = e.target;
+ is(v._gotLoadedMetaData, true, "(10) Must get loadedmetadata.");
+ maybeFinish(v, 10);
+ },
+
+ setup(v) {
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v.addEventListener("loadstart", function(e){v.preload = "auto";});
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("canplaythrough", this.canplaythrough);
+ v.src = test.name; // Causes implicit load.
+ document.body.appendChild(v);
+ },
+
+ name: "test10",
+ },
+ {
+ // 11. Change preload value from none to metadata should cause metadata to load.
+ loadeddata(e) {
+ var v = e.target;
+ is(v._gotLoadedMetaData, true, "(11) Must get loadedmetadata.");
+ ok(v.readyState >= v.HAVE_CURRENT_DATA, "(11) ReadyState must be >= HAVE_CURRENT_DATA.");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(11) NetworkState must be NETWORK_IDLE.");
+ maybeFinish(v, 11);
+ },
+
+ setup(v) {
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v.addEventListener("loadstart", function(e){v.preload = "metadata";});
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadeddata", this.loadeddata);
+ v.src = test.name; // Causes implicit load.
+ document.body.appendChild(v);
+ },
+
+ name: "test11",
+ },
+ {
+ // 13. Change preload value from auto to none after specifying a src
+ // should load according to preload none, no buffering should have taken place
+ suspend(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(13) Must get loadstart.");
+ is(v._gotLoadedMetaData, false, "(13) Must not get loadedmetadata.");
+ is(v.readyState, v.HAVE_NOTHING, "(13) ReadyState must be HAVE_NOTHING");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(13) NetworkState must be NETWORK_IDLE");
+ maybeFinish(v, 13);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "auto";
+ v.src = test.name;
+ v.preload = "none";
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("suspend", this.suspend);
+ document.body.appendChild(v); // Causes implicit load, should load according to preload none
+ document.createElement("source");
+ },
+
+ name: "test13",
+ },
+ {
+ // 14. Add preload:metadata video with src to document. Play(), should play through.
+ loadeddata(e) {
+ var v = e.target;
+ is(v._gotLoadStart, true, "(14) Must get loadstart.");
+ is(v._gotLoadedMetaData, true, "(14) Must get loadedmetadata.");
+ ok(v.readyState >= v.HAVE_CURRENT_DATA, "(14) ReadyState must be >= HAVE_CURRENT_DATA");
+ // bug 962949
+ // is(v.networkState, v.NETWORK_IDLE, "(14) NetworkState must be NETWORK_IDLE");
+ v.play();
+ },
+
+ ended(e) {
+ ok(true, "(14) Got playback ended");
+ var v = e.target;
+ maybeFinish(v, 14);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "metadata";
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("ended", this.ended);
+ v.addEventListener("loadeddata", this.loadeddata);
+ v.src = test.name;
+ document.body.appendChild(v); // Causes implicit load, which will be halted after
+ // metadata due to preload:metadata.
+ },
+
+ name: "test14",
+ },
+ {
+ // 15. Autoplay should override preload:none.
+ ended(e) {
+ ok(true, "(15) Got playback ended.");
+ var v = e.target;
+ maybeFinish(v, 15);
+ },
+
+ setup(v) {
+ v._gotLoadStart = false;
+ v._gotLoadedMetaData = false;
+ v.preload = "none";
+ v.autoplay = true;
+ v.addEventListener("loadstart", function(e){v._gotLoadStart = true;});
+ v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;});
+ v.addEventListener("ended", this.ended);
+ v.src = test.name; // Causes implicit load.
+ document.body.appendChild(v);
+
+ // Log events for debugging.
+ var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause"];
+ function logEvent(e) {
+ info(e.target.token + ": got " + e.type);
+ }
+ events.forEach(function(e) {
+ v.addEventListener(e, logEvent);
+ });
+ },
+
+ name: "test15",
+ },
+ {
+ // 16. Autoplay should override preload:metadata.
+ ended(e) {
+ ok(true, "(16) Got playback ended.");
+ var v = e.target;
+ maybeFinish(v, 16);
+ },
+
+ setup(v) {
+ v.preload = "metadata";
+ v.autoplay = true;
+ v.addEventListener("ended", this.ended);
+ v.src = test.name; // Causes implicit load.
+ document.body.appendChild(v);
+ },
+
+ name: "test16",
+ },
+ {
+ // 17. On a preload:none video, adding autoplay should disable preload none, i.e. don't break autoplay!
+ ended(e) {
+ ok(true, "(17) Got playback ended.");
+ var v = e.target;
+ maybeFinish(v, 17);
+ },
+
+ setup(v) {
+ v.addEventListener("ended", this.ended);
+ v.preload = "none";
+ document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
+ v.autoplay = true;
+ v.src = test.name;
+ },
+
+ name: "test17",
+ },
+ {
+ // 18. On a preload='none' video, call play() before load algorithms's sync
+ // has run, the play() call should override preload='none'.
+ ended(e) {
+ ok(true, "(18) Got playback ended.");
+ var v = e.target;
+ maybeFinish(v, 18);
+ },
+
+ setup(v) {
+ v.addEventListener("ended", this.ended);
+ v.preload = "none";
+ v.src = test.name; // Schedules async section to continue load algorithm.
+ document.body.appendChild(v);
+ v.play(); // Should cause preload:none to be overridden.
+ },
+
+ name: "test18",
+ },
+ {
+ // 19. Set preload='auto' on first video source then switching preload='none' and swapping the video source to another.
+ // The second video should not start playing as it's preload state has been changed to 'none' from 'auto'
+ setup(v) {
+ v.preload = "auto";
+ v.src = test.name;
+ // add a listener for when the video has loaded, so we know preload auto has worked
+ v.onloadedmetadata = function() {
+ is(v.preload, "auto", "(19) preload is initially auto");
+ // set preload state to none and switch video sources
+ v.preload="none";
+ v.src = test.name + "?asdf";
+
+ v.onloadedmetadata = function() {
+ ok(false, "(19) 'loadedmetadata' shouldn't fire when preload is none");
+ }
+
+ var ontimeout = function() {
+ v.removeEventListener("suspend", onsuspend);
+ ok(false, "(19) 'suspend' should've fired");
+ maybeFinish(v, 19);
+ }
+ var cancel = setTimeout(ontimeout, 10000);
+
+ var onsuspend = function() {
+ v.removeEventListener("suspend", onsuspend);
+ clearTimeout(cancel);
+ is(v.readyState, 0, "(19) no buffering has taken place");
+ maybeFinish(v, 19);
+ }
+ v.addEventListener("suspend", onsuspend);
+ }
+ document.body.appendChild(v);
+ },
+
+ name: "test19",
+ }
+];
+
+var iterationCount = 0;
+function startTest(t, token) {
+ if (t == tests[0]) {
+ ++iterationCount;
+ info("iterationCount=" + iterationCount);
+ }
+ if (iterationCount == 2) {
+ // Do this series of tests on logically different resources
+ t.name = baseName + "?" + Math.floor(Math.random()*100000);
+ }
+ var v = document.createElement("video");
+ v.token = token;
+ t.setup(v);
+ manager.started(token);
+}
+
+var twiceTests = tests.concat(tests);
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest);
+function beginTest() {
+ manager.runTests(twiceTests, startTest);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_preload_attribute.html b/dom/media/test/test_preload_attribute.html
new file mode 100644
index 0000000000..1e415035c5
--- /dev/null
+++ b/dom/media/test/test_preload_attribute.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=479863
+-->
+<head>
+ <title>Test for Bug 479863</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479863">Mozilla Bug 479863</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<video id='v1'></video><audio id='a1'></audio>
+<video id='v2' preload="auto"></video><audio id='a2' preload="auto"></audio>
+
+<pre id="test">
+<script type="application/javascript">
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+var v2 = document.getElementById('v2');
+var a2 = document.getElementById('a2');
+is(v1.getAttribute("preload"), null, "video preload via getAttribute should be null by default");
+is(a1.getAttribute("preload"), null, "video preload via getAttribute should be null by default");
+is(v1.preload, "", "v1.preload should be empty by default");
+is(a1.preload, "", "a1.preload should be empty by default");
+is(v2.preload, "auto", "v2.preload should be auto");
+is(a2.preload, "auto", "a2.preload should be auto");
+
+v1.preload = "auto";
+a1.preload = "auto";
+is(v1.preload, "auto", "video.preload should be auto");
+is(a1.preload, "auto", "audio.preload should be auto");
+is(v1.getAttribute("preload"), "auto", "video preload attribute should be auto");
+is(a1.getAttribute("preload"), "auto", "video preload attribute should be auto");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_preload_suspend.html b/dom/media/test/test_preload_suspend.html
new file mode 100644
index 0000000000..7f1146360f
--- /dev/null
+++ b/dom/media/test/test_preload_suspend.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 479863</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+function checkSuspendCount(evt) {
+ var v = evt.target;
+ ++v.suspendCount;
+ is(v.networkState, v.NETWORK_IDLE, v.name + " got suspended, count=" + v.suspendCount);
+ if (v.suspendCount == v.expectedSuspendCount) {
+ removeNodeAndSource(v);
+ manager.finished(v.name);
+ }
+ if (v.suspendCount > v.expectedSuspendCount) {
+ ok(false, v.name + " got too many suspend events");
+ }
+}
+
+var tests = [
+ {
+ name: 'v1',
+ preload: 'none',
+ expectedSuspendCount: 2,
+ onsuspend(evt) {
+ checkSuspendCount(evt);
+ if (evt.target.suspendCount == 1) {
+ evt.target.preload = 'auto';
+ }
+ }
+ },
+ {
+ name: 'v2',
+ preload: 'auto',
+ expectedSuspendCount: 1,
+ onsuspend: checkSuspendCount
+ },
+ {
+ name: 'v3',
+ preload: 'none',
+ autoplay: true,
+ expectedSuspendCount: 1,
+ onsuspend: checkSuspendCount
+ },
+ {
+ name: 'v4',
+ preload: 'none',
+ expectedSuspendCount: 2,
+ onsuspend(evt) {
+ checkSuspendCount(evt);
+ if (evt.target.suspendCount == 1) {
+ evt.target.play();
+ }
+ }
+ },
+ // disable v5 since media element doesn't support 'load' event anymore.
+ /*{
+ name: 'v5',
+ preload: 'none',
+ expectedSuspendCount: 2,
+ onsuspend: function(evt) {
+ checkSuspendCount(evt);
+ if (evt.target.suspendCount == 1) {
+ evt.target.currentTime = 0.1;
+ }
+ }
+ },*/
+ {
+ name: 'v6',
+ preload: 'none',
+ expectedSuspendCount: 2,
+ onsuspend(evt) {
+ checkSuspendCount(evt);
+ if (evt.target.suspendCount == 1) {
+ evt.target.autoplay = true;
+ }
+ }
+ }
+];
+
+function startTest(test, token) {
+ var v = document.createElement("video");
+ v.name = test.name;
+ var key = Math.random();
+ v.src = "seek.ogv?key=" + key + "&id=" + v.name;
+ v.preload = test.preload;
+ v.suspendCount = 0;
+ v.expectedSuspendCount = test.expectedSuspendCount;
+ if (test.autoplay) {
+ v.autoplay = true;
+ }
+ v.onsuspend = test.onsuspend;
+ document.body.appendChild(v);
+ manager.started(v.name);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, function() {
+ manager.runTests(tests, startTest);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_preserve_playbackrate_after_ui_play.html b/dom/media/test/test_preserve_playbackrate_after_ui_play.html
new file mode 100644
index 0000000000..98bdd66675
--- /dev/null
+++ b/dom/media/test/test_preserve_playbackrate_after_ui_play.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title> Bug 1013933 - preserve playbackRate after clicking play button </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content">
+ <video width="320" height="240" id="video" controls mozNoDynamicControls preload="auto"></video>
+</div>
+
+<script type="text/javascript">
+/*
+ * Positions of the UI elements, relative to the upper-left corner of the
+ * <video> box.
+ */
+const videoHeight = 240;
+const playButtonWidth = 28;
+const playButtonHeight = 28;
+const playButtonCenterX = 0 + Math.round(playButtonWidth / 2);
+const playButtonCenterY = videoHeight - Math.round(playButtonHeight / 2);
+
+var expectedPlaybackRate = 0.5
+
+function runTest() {
+ var video = document.getElementById("video");
+ video.src = "audio.wav";
+ video.loop = true;
+ video.playbackRate = expectedPlaybackRate;
+
+ video.oncanplaythrough = function() {
+ video.oncanplaythrough = null;
+ is(video.paused, true, "video is not playing yet.");
+ is(video.playbackRate, expectedPlaybackRate,
+ "playbackRate is correct before clicking play button.");
+
+ // Click the play button
+ synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { });
+ };
+
+ video.onplay = function() {
+ video.onplay = null;
+ is(video.paused, false, "video starts playing.");
+ is(video.playbackRate, expectedPlaybackRate,
+ "playbackRate is correct after clicking play button.");
+ video.pause();
+ SimpleTest.finish();
+ };
+}
+
+window.addEventListener("load", runTest);
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_progress.html b/dom/media/test/test_progress.html
new file mode 100644
index 0000000000..b958b2b335
--- /dev/null
+++ b/dom/media/test/test_progress.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: progress events</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function do_progress(e) {
+ var v = e.target;
+ ok(!v._finished, "Check no progress events after completed for " + v._name);
+}
+
+function do_ended(e) {
+ var v = e.target;
+ ok(!v._finished, "Only one ended event for " + v._name);
+ v._finished = true;
+ v.removeEventListener("ended", do_ended);
+ v.removeEventListener("progress", do_progress);
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function startTest(test, token) {
+ var type = /^video/.test(test.type) ? "video" : "audio";
+ var v = document.createElement(type);
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v.autoplay = true;
+ v._name = test.name;
+ v._finished = false;
+ v.addEventListener("ended", do_ended);
+ v.addEventListener("progress", do_progress);
+ document.body.appendChild(v);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest);
+function beginTest() {
+ manager.runTests(gProgressTests, startTest);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_reactivate.html b/dom/media/test/test_reactivate.html
new file mode 100644
index 0000000000..43675f7771
--- /dev/null
+++ b/dom/media/test/test_reactivate.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test reactivation of a media element from a dead document</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>
+</head>
+<body>
+
+<iframe id="frame" src="reactivate_helper.html"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var elements;
+
+function playElement(e) {
+ // All elements played out, finish the test case.
+ if (!e) {
+ SimpleTest.finish();
+ return;
+ }
+
+ e.play();
+ info("Element play: " + e._name);
+ var reviveElement = function() {
+ document.body.appendChild(e);
+ e.onended = function() {
+ info("Element ended: " + e._name);
+ removeNodeAndSource(e);
+ // Play next element.
+ playElement(elements.pop());
+ }
+ }
+ setTimeout(reviveElement, 2000);
+}
+
+function loadedAll(elementList) {
+ elements = elementList;
+
+ // Log events for debugging.
+ var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause"];
+ function logEvent(e) {
+ info(e.target._name + ": got " + e.type);
+ }
+ elementList.forEach(function(element) {
+ events.forEach(function(evt) {
+ element.addEventListener(evt, logEvent);
+ });
+ });
+
+ // Blow away the subframe
+ document.body.removeChild(document.getElementById("frame"));
+
+ // Play elements one by one to avoid hitting bug 847903 on MacOSX.
+ playElement(elements.pop());
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_readyState.html b/dom/media/test/test_readyState.html
new file mode 100644
index 0000000000..17c363c503
--- /dev/null
+++ b/dom/media/test/test_readyState.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: readyState</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<video id='v1'></video><audio id='a1'></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+"use strict";
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+var passed = "truthy";
+
+is(v1.readyState, 0);
+is(a1.readyState, 0);
+
+try {
+ v1.readyState = 0;
+} catch (e) {
+ passed = !passed;
+}
+try {
+ a1.readyState = 0;
+} catch (e) {
+ passed = !passed;
+}
+ok(passed === true,
+ "Setting readyState throws in strict mode (readonly attribute)");
+</script>
+
+<script class="testbody" type="text/javascript">
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+var passed = false;
+
+is(v1.readyState, 0);
+is(a1.readyState, 0);
+
+try {
+ v1.readyState = 1;
+ a1.readyState = 1;
+ passed = v1.readyState === 0 && a1.readyState === 0;
+} catch(e) { }
+ok(passed, "Should not be able to set readyState (readonly attribute)");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_referer.html b/dom/media/test/test_referer.html
new file mode 100644
index 0000000000..2561e72b6a
--- /dev/null
+++ b/dom/media/test/test_referer.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=584480
+-->
+<head>
+ <title>Test for Bug 584480</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=584480">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+var media = [];
+
+function checkComplete() {
+ for (var i=0; i<media.length; ++i) {
+ if (!media[i]._complete) {
+ return;
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+function removeNode(v) {
+ v.removeEventListener("error", loadError);
+ v.removeEventListener("loadedmetadata", loadedMetadata);
+ v.remove();
+ v.src = "";
+}
+
+function loadError(evt) {
+ // If no referer is sent then the sjs returns an error
+ ok(false, "check referer is sent with media request");
+ evt.target._complete = true;
+ checkComplete();
+ removeNode(evt.target);
+}
+
+function loadedMetadata(evt) {
+ // If a referer is sent then the sjs returns a valid media
+ ok(true, "check referer is sent with media request");
+ evt.target._complete = true;
+ checkComplete();
+ removeNode(evt.target);
+}
+
+// Create all media objects.
+for (var i=0; i<gSmallTests.length; ++i) {
+ var test = gSmallTests[i];
+ var type;
+ if (/^video/.test(test.type)) {
+ type = "video"
+ } else {
+ type = "audio";
+ }
+ var v = document.createElement(type);
+ if (!v.canPlayType(test.type)) {
+ continue;
+ }
+ // ensure metadata is loaded for default preload is none on b2g
+ v.preload = "metadata";
+ v.autoplay = "true";
+ v._complete = false;
+ v.addEventListener("error", loadError);
+ v.addEventListener("loadedmetadata", loadedMetadata);
+ v.src = 'referer.sjs?name=' + test.name + '&type=' + test.type;
+ document.body.appendChild(v); // Will start load.
+ media.push(v);
+}
+
+if (media.length == 0) {
+ todo(false, "No types supported");
+} else {
+ SimpleTest.waitForExplicitFinish();
+}
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/media/test/test_replay_metadata.html b/dom/media/test/test_replay_metadata.html
new file mode 100644
index 0000000000..dab022885f
--- /dev/null
+++ b/dom/media/test/test_replay_metadata.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=467972
+-->
+<head>
+ <title>Test for Bug 467972</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=467972">Mozilla Bug 467972</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Test for Bug 467972. Tests that when we play to end, seek to 0, and play again, that we don't
+// send/receive multiple loadeddata and loadedmetadata events.
+
+var manager = new MediaTestManager;
+
+function seekStarted(evt) {
+ var v = evt.target;
+ v._gotSeekStarted = true;
+}
+
+function seekEnded(evt) {
+ var v = evt.target;
+ v._gotSeekEnded = true;
+ v.play();
+}
+
+function loadedData(evt) {
+ var v = evt.target;
+ v._loadedDataCount++;
+ ok(v._loadedDataCount <= 1, "No more than 1 onloadeddata event for " + v._name);
+}
+
+function loadedMetaData(evt) {
+ var v = evt.target;
+ v._loadedMetaDataCount++;
+ ok(v._loadedMetaDataCount <= 1, "No more than 1 onloadedmetadata event for " + v._name);
+ checkMetadata(v._name, v, v._test);
+ v.play();
+}
+
+function playing(evt) {
+ evt.target._playingCount++;
+}
+
+function removeNodeAndListener(n) {
+ n.removeEventListener("loadedmetadata", loadedMetaData);
+ n.removeEventListener("ended", playbackEnded);
+ n.removeEventListener("playing", playing);
+ n.removeEventListener("loadeddata", loadedData);
+ n.removeEventListener("seeking", seekStarted);
+ n.removeEventListener("seeked", seekEnded);
+ removeNodeAndSource(n);
+}
+
+function playbackEnded(evt) {
+ var v = evt.target;
+ v._endCount++;
+ ok(v.currentTime >= v.duration-0.1 && v.currentTime <= v.duration + 0.1,
+ "CurrentTime (" + v.currentTime + ") should be around " + v.duration
+ + " for " + v._name);
+ if (!v._playedOnce) {
+ v.currentTime = 0;
+ ok(v.seeking, "seeking should be true for " + v._name);
+ ok(!v.ended, "ended shouldn't be true once seeking has begun for " + v._name);
+ v._playedOnce = true;
+ } else {
+ ok(v._gotSeekEnded, "Should have received seekended for " + v._name);
+ ok(v._gotSeekStarted, "Should have received seekstarted for " + v._name);
+ is(v._loadedDataCount, 1, "Should have 1 onloadeddata event for " + v._name);
+ is(v._loadedMetaDataCount, 1, "Should have 1 onloadedmetadata event for " + v._name);
+ is(v._endCount, 2, "Should have received two ended events for " + v._name);
+ ok(v._playingCount > 0, "Should have at least one playing event for " + v._name);
+ v._finished = true;
+ removeNodeAndListener(v);
+ manager.finished(v.token);
+ }
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "auto";
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._name = test.name;
+ v._playedOnce = false;
+ v._gotSeekEnded = false;
+ v._gotSeekStarted = false;
+ v._loadedDataCount = 0;
+ v._loadedMetaDataCount = 0;
+ v._playingCount = 0;
+ v._endCount = 0;
+ v._test = test;
+ v._finished = false;
+ v.addEventListener("loadedmetadata", loadedMetaData);
+ v.addEventListener("ended", playbackEnded);
+ v.addEventListener("playing", playing);
+ v.addEventListener("loadeddata", loadedData);
+ v.addEventListener("seeking", seekStarted);
+ v.addEventListener("seeked", seekEnded);
+ document.body.appendChild(v);
+}
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_reset_events_async.html b/dom/media/test/test_reset_events_async.html
new file mode 100644
index 0000000000..482ec55986
--- /dev/null
+++ b/dom/media/test/test_reset_events_async.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=975270
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="manifest.js"></script>
+ <script type="application/javascript">
+
+ /** Test for Bug 975270 **/
+ // Test that 'emptied' and 'abort' events are fired asynchronously when re-starting
+ // media load.
+ SimpleTest.waitForExplicitFinish();
+
+ var a = document.createElement("audio");
+ a._abort = 0;
+ a._emptied = 0;
+ a.preload = "metadata"; // On B2G we default to preload:none.
+
+ is(a.networkState, HTMLMediaElement.NETWORK_EMPTY, "Shouldn't be loading");
+
+ a.addEventListener("abort", function(e) { a._abort++; });
+ a.addEventListener("emptied", function(e) { a._emptied++; });
+ a.addEventListener("loadedmetadata",
+ function(e) {
+ is(a._abort, 0, "Should not have received 'abort' before 'loadedmetadata");
+ is(a._emptied, 0, "Should not have received 'emptied' before 'loadedmetadata");
+
+ a.addEventListener("loadstart",
+ function() {
+ is(a._abort, 1, "Should have received 'abort' before 'loadstart");
+ is(a._emptied, 1, "Should have received 'emptied' before 'loadstart");
+ SimpleTest.finish();
+ });
+
+ a.src = "";
+ is(a._abort, 0, "Should not have received 'abort' during setting a.src=''");
+ is(a._emptied, 0, "Should not have received 'emptied' during setting a.src=''");
+ });
+
+ a.src = getPlayableAudio(gSmallTests).name;
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_reset_src.html b/dom/media/test/test_reset_src.html
new file mode 100644
index 0000000000..1912369dac
--- /dev/null
+++ b/dom/media/test/test_reset_src.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=804875
+-->
+
+<head>
+ <title>Test for bug 804875</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank"
+href="https://bugzilla.mozilla.org/show_bug.cgi?id=804875">Mozilla Bug 804875</a>
+
+<canvas></canvas>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function finish(v) {
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function onLoadedData_Audio(e) {
+ var t = e.target;
+ is(t.videoHeight, 0, t.name + ": videoHeight should be zero when there is no video.");
+ is(t.videoWidth, 0, t.name + ": videoWidth should be zero when there is no video.");
+ is(t.mozPaintedFrames, 0, t.name + ": mozPaintedFrames should be zero when there is no video.");
+ is(t.mozFrameDelay, 0, t.name + ": mozFrameDelay should be zero when there is no video.");
+ var c = document.getElementsByTagName("canvas")[0].getContext("2d");
+ try {
+ c.drawImage(t, 0, 0, t.videoHeight, t.videoWidth);
+ } catch (ex) {
+ ok(true, t.name + ": Trying to draw to a canvas should throw, since we don't have a frame anymore");
+ finish(t);
+ return;
+ }
+ ok(false, t.name + ": We should not succeed to draw a video frame on the canvas.");
+ finish(t);
+}
+
+function onTimeUpdate_Video(e) {
+ var t = e.target;
+ if (t.currentTime < t.duration / 4) {
+ return;
+ }
+ t.removeEventListener("timeupdate", onTimeUpdate_Video);
+ // There's no guarantee that a video frame composite notification reaches
+ // us before timeupdate fires.
+ ok(t.mozPaintedFrames >= 0, t.name + ": mozPaintedFrames should be positive or zero, is " + t.mozPaintedFrames + ".");
+ ok(t.mozFrameDelay >= 0, t.name + ": mozFrameDelay should be positive or zero, is " + t.mozFrameDelay + ".");
+
+ if (t._firstTime) {
+ // eslint-disable-next-line no-self-assign
+ t.src = t.src;
+ t._firstTime = false;
+ } else {
+ var source = getPlayableAudio(gPlayTests);
+ if (!source) {
+ todo("No audio file available.")
+ finish(t);
+ } else {
+ t.removeEventListener("loadeddata", onLoadedData_Video);
+ t.addEventListener("loadeddata", onLoadedData_Audio);
+ t.src = source.name;
+ }
+ }
+}
+
+function onLoadedData_Video(e) {
+ var t = e.target;
+ isnot(t.videoHeight, 0, t.name + ": We should have a videoHeight.");
+ isnot(t.videoWidth, 0, t.name + ": We should have a videoWidth.");
+ t.addEventListener("timeupdate", onTimeUpdate_Video);
+ t.play();
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ document.body.appendChild(v);
+ v._firstTime = true;
+ v.addEventListener("loadeddata", onLoadedData_Video);
+ v.src = test.name;
+ v.token = token;
+ v.name = test.name;
+ v.play();
+ manager.started(token);
+}
+
+manager.runTests(getPlayableVideos(gSmallTests.concat(gSeekTests)), startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_resolution_change.html b/dom/media/test/test_resolution_change.html
new file mode 100644
index 0000000000..403f9b505d
--- /dev/null
+++ b/dom/media/test/test_resolution_change.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test playback of files with resolution changes</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function loadedData(e) {
+ var v = e.target;
+ v.addEventListener("resize", resize);
+ v.play();
+}
+
+function resize(e) {
+ var v = e.target;
+ v.seenResolutionChange = true;
+}
+
+function ended(e) {
+ var v = e.target;
+ ok(v.seenResolutionChange, v.token + ": A resolution change should have ocurred by the end of playback");
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "metadata";
+ v.token = token;
+ v.src = test.name;
+ v.seenResolutionChange = false;
+
+ v.addEventListener("loadeddata", loadedData)
+ v.addEventListener("ended", ended);
+
+ manager.started(token);
+ document.body.appendChild(v);
+}
+
+manager.runTests(gResolutionChangeTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_resume.html b/dom/media/test/test_resume.html
new file mode 100644
index 0000000000..0e22894be1
--- /dev/null
+++ b/dom/media/test/test_resume.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: Test resume of server-dropped connections</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<audio preload="auto" id="a"></audio>
+<iframe id="f"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var key = Math.round(Math.random()*1000000000);
+var a = document.getElementById("a");
+var f = document.getElementById("f");
+
+function didEnd() {
+ ok(a.currentTime > 2.26, "Reached correct end time (got " + a.currentTime + ", expected > 2.26");
+ SimpleTest.finish();
+}
+
+function didSendCancel() {
+ a.addEventListener("ended", didEnd);
+ a.play();
+}
+
+function didSuspend() {
+ a.removeEventListener("suspend", didSuspend);
+
+ // Cache must have filled up, or something. Tell the Web server to drop
+ // our connection.
+ f.addEventListener("load", didSendCancel);
+ f.src = "cancellable_request.sjs?cancelkey=" + key;
+}
+
+if (!a.canPlayType("audio/wave")) {
+ todo(false, "Test requires support for audio/wave");
+} else {
+ a.addEventListener("suspend", didSuspend);
+ a.src = "cancellable_request.sjs?key=" + key;
+ SimpleTest.waitForExplicitFinish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seamless_looping.html b/dom/media/test/test_seamless_looping.html
new file mode 100644
index 0000000000..c9e3253a5d
--- /dev/null
+++ b/dom/media/test/test_seamless_looping.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for seamless loop of HTMLAudioElements</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>
+</head>
+<body>
+<canvas id="canvas" width="300" height="300"></canvas>
+<script type="application/javascript">
+/**
+ * This test is used to ensure every time we loop audio, the audio can loop
+ * seamlessly which means there won't have any silenece or noise between the
+ * end and the start.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+// Set DEBUG to true to add a canvas with a little drawing of what is going
+// on, and actually outputs the audio to the speakers.
+var DEBUG = true;
+var LOOPING_COUNT = 0;
+var MAX_LOOPING_COUNT = 10;
+var TONE_FREQUENCY = 440;
+
+(async function testSeamlesslooping() {
+ info(`- create looping audio element -`);
+ let audio = createAudioElement();
+
+ info(`- start audio and analyze audio wave data to ensure looping audio without any silence or noise -`);
+ await playAudioAndStartAnalyzingWaveData(audio);
+
+ info(`- test seamless looping multiples times -`);
+ for (LOOPING_COUNT = 0; LOOPING_COUNT < MAX_LOOPING_COUNT; LOOPING_COUNT++) {
+ await once(audio, "seeked");
+ info(`- the round ${LOOPING_COUNT} of the seamless looping succeeds -`);
+ }
+
+ info(`- end of seamless looping test -`);
+ SimpleTest.finish();
+})();
+
+/**
+ * Test utility functions
+ */
+function createSrcBuffer() {
+ // Generate the sine in floats, then convert, for simplicity.
+ let channels = 1;
+ let sampleRate = 44100;
+ let buffer = new Float32Array(sampleRate * channels);
+ let phase = 0;
+ const TAU = 2 * Math.PI;
+ for (let i = 0; i < buffer.length; i++) {
+ // Adjust the gain a little so we're sure it's not going to clip. This is
+ // important because we're converting to 16bit integer right after, and
+ // clipping will clearly introduce a discontinuity that will be
+ // mischaracterized as a looping click.
+ buffer[i] = Math.sin(phase) * 0.99;
+ phase += TAU * TONE_FREQUENCY / 44100;
+ if (phase > 2 * TAU) {
+ phase -= TAU;
+ }
+ }
+
+ // Make a RIFF header, it's 23 bytes
+ let buf = new Int16Array(buffer.length + 23);
+ buf[0] = 0x4952;
+ buf[1] = 0x4646;
+ buf[2] = (2 * buffer.length + 15) & 0x0000ffff;
+ buf[3] = ((2 * buffer.length + 15) & 0xffff0000) >> 16;
+ buf[4] = 0x4157;
+ buf[5] = 0x4556;
+ buf[6] = 0x6d66;
+ buf[7] = 0x2074;
+ buf[8] = 0x0012;
+ buf[9] = 0x0000;
+ buf[10] = 0x0001;
+ buf[11] = 1;
+ buf[12] = 44100 & 0x0000ffff;
+ buf[13] = (44100 & 0xffff0000) >> 16;
+ buf[14] = (2 * channels * sampleRate) & 0x0000ffff;
+ buf[15] = ((2 * channels * sampleRate) & 0xffff0000) >> 16;
+ buf[16] = 0x0004;
+ buf[17] = 0x0010;
+ buf[18] = 0x0000;
+ buf[19] = 0x6164;
+ buf[20] = 0x6174;
+ buf[21] = (2 * buffer.length) & 0x0000ffff;
+ buf[22] = ((2 * buffer.length) & 0xffff0000) >> 16;
+
+ // convert to int16 and copy.
+ for (let i = 0; i < buffer.length; i++) {
+ buf[i + 23] = Math.round(buffer[i] * (1 << 15));
+ }
+ return buf;
+}
+
+function createAudioElement() {
+ /* global audio */
+ window.audio = document.createElement("audio");
+ audio.src = URL.createObjectURL(new Blob([createSrcBuffer()],
+ { type: 'audio/wav' }));
+ audio.controls = true;
+ audio.loop = true;
+ document.body.appendChild(audio);
+ return audio;
+}
+
+async function playAudioAndStartAnalyzingWaveData(audio) {
+ createAudioWaveAnalyser(audio);
+ ok(await once(audio, "canplay").then(() => true, () => false),
+ `audio can start playing.`)
+ ok(await audio.play().then(() => true, () => false),
+ `audio started playing successfully.`);
+}
+
+function createAudioWaveAnalyser(source) {
+ /* global ac, analyser */
+ window.ac = new AudioContext();
+ window.analyser = ac.createAnalyser();
+ analyser.frequencyBuf = new Float32Array(analyser.frequencyBinCount);
+ analyser.smoothingTimeConstant = 0;
+ analyser.fftSize = 2048; // 1024 bins
+
+ let sourceNode = ac.createMediaElementSource(source);
+ sourceNode.connect(analyser);
+
+ if (DEBUG) {
+ analyser.connect(ac.destination);
+ analyser.timeDomainBuf = new Float32Array(analyser.frequencyBinCount);
+ let cvs = document.querySelector("canvas");
+ analyser.c = cvs.getContext("2d");
+ analyser.w = cvs.width;
+ analyser.h = cvs.height;
+ }
+
+ analyser.notifyAnalysis = () => {
+ if (LOOPING_COUNT >= MAX_LOOPING_COUNT) {
+ return;
+ }
+ let {frequencyBuf} = analyser;
+ analyser.getFloatFrequencyData(frequencyBuf);
+ // Let things stabilize at the beginning. See bug 1441509.
+ if (LOOPING_COUNT > 1) {
+ analyser.doAnalysis(frequencyBuf, ac.sampleRate);
+ }
+
+ if (DEBUG) {
+ let {c, w, h, timeDomainBuf} = analyser;
+ c.clearRect(0, 0, w, h);
+ analyser.getFloatTimeDomainData(timeDomainBuf);
+ for (let i = 0; i < frequencyBuf.length; i++) {
+ c.fillRect(i, h, 1, -frequencyBuf[i] + analyser.minDecibels);
+ }
+
+ for (let i = 0; i < timeDomainBuf.length; i++) {
+ c.fillRect(i, h / 2, 1, -timeDomainBuf[i] * h / 2);
+ }
+ }
+
+ requestAnimationFrame(analyser.notifyAnalysis);
+ }
+
+ analyser.doAnalysis = (buf, ctxSampleRate) => {
+ // The size of an FFT is twice the number of bins in its output.
+ let fftSize = 2 * buf.length;
+ // first find a peak where we expect one.
+ let binIndexTone = 1 + Math.round(TONE_FREQUENCY * fftSize / ctxSampleRate);
+ ok(buf[binIndexTone] > -25,
+ `Could not find a peak: ${buf[binIndexTone]} db at ${TONE_FREQUENCY}Hz`);
+
+ // check that the energy some octaves higher is very low.
+ let binIndexOutsidePeak = 1 + Math.round(TONE_FREQUENCY * 4 * buf.length / ctxSampleRate);
+ ok(buf[binIndexOutsidePeak] < -110,
+ `Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db at ${TONE_FREQUENCY * 4}Hz`);
+ }
+
+ analyser.notifyAnalysis();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-1.html b/dom/media/test/test_seek-1.html
new file mode 100644
index 0000000000..8690a475b6
--- /dev/null
+++ b/dom/media/test/test_seek-1.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 1;
+
+function test_seek1(v, seekTime, is, ok, finish) {
+
+var startPassed = false;
+var endPassed = false;
+var seekFlagStart = false;
+var seekFlagEnd = false;
+var readonly = true;
+var completed = false;
+
+function startTest() {
+ ok(!completed, "Should not be completed yet");
+ ok(!v.seeking, "seeking should default to false");
+ try {
+ v.seeking = true;
+ readonly = v.seeking === false;
+ }
+ catch(e) {
+ readonly = "threw exception: " + e;
+ }
+ is(readonly, true, "seeking should be readonly");
+
+ v.currentTime = seekTime;
+ seekFlagStart = v.seeking;
+}
+
+function seekStarted() {
+ ok(!completed, "should not be completed yet");
+ ok(Math.abs(v.currentTime - seekTime) < 0.1,
+ "Video currentTime should be around " + seekTime + ": " + v.currentTime + " (seeking)");
+ startPassed = true;
+}
+
+function seekEnded() {
+ ok(!completed, "shuld not be completed yet");
+ ok(Math.abs(v.currentTime - seekTime) < 0.1,
+ "Video currentTime should be around " + seekTime + ": " + v.currentTime + " (seeked)");
+ endPassed = true;
+ seekFlagEnd = v.seeking;
+ v.play();
+}
+
+function playbackEnded() {
+ ok(!completed, "should not be completed yet");
+
+ completed = true;
+ ok(startPassed, "seeking event");
+ ok(endPassed, "seeked event");
+ ok(seekFlagStart, "seeking flag on start should be true");
+ ok(!seekFlagEnd, "seeking flag on end should be false");
+ finish();
+}
+
+once(v, "ended", playbackEnded);
+once(v, "loadedmetadata", startTest);
+once(v, "seeking", seekStarted);
+once(v, "seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-10.html b/dom/media/test/test_seek-10.html
new file mode 100644
index 0000000000..6c02384ed4
--- /dev/null
+++ b/dom/media/test/test_seek-10.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 10;
+
+function test_seek10(v, seekTime, is, ok, finish) {
+
+// Test bug 523335 - ensure that if we close a stream while seeking, we
+// don't hang during shutdown. This test won't "fail" per se if it's regressed,
+// it will instead start to cause random hangs in the mochitest harness on
+// shutdown.
+
+function startTest() {
+ // Must be duration*0.9 rather than seekTime, else we don't hit that problem.
+ // This is probably due to the seek bisection finishing too quickly, before
+ // we can close the stream.
+ v.currentTime = v.duration * 0.9;
+}
+
+function done(evt) {
+ ok(true, "We don't acutally test anything...");
+ finish();
+}
+
+function seeking() {
+ ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime);
+ v.onerror = done;
+ v.src = "not a valid video file.";
+ v.load(); // Cause the existing stream to close.
+}
+
+v.addEventListener("loadeddata", startTest);
+v.addEventListener("seeking", seeking);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-11.html b/dom/media/test/test_seek-11.html
new file mode 100644
index 0000000000..2207f684d7
--- /dev/null
+++ b/dom/media/test/test_seek-11.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+PARALLEL_TESTS = 1;
+const SEEK_TEST_NUMBER = 11;
+
+function test_seek11(v, seekTime, is, ok, finish) {
+
+// Test for bug 476973, multiple seeks to the same position shouldn't cause problems.
+
+var seekedNonZero = false;
+var completed = false;
+var target = 0;
+
+function startTest() {
+ if (completed)
+ return;
+ target = v.duration / 2;
+ v.currentTime = target;
+ v.currentTime = target;
+ v._seekTarget = target;
+}
+
+function startSeeking() {
+ ok(v.currentTime >= v._seekTarget - 0.1,
+ "Video currentTime should be around " + v._seekTarget + ": " + v.currentTime);
+ if (!seekedNonZero) {
+ v.currentTime = target;
+ v._seekTarget = target;
+ seekedNonZero = true;
+ }
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+
+ if (v.currentTime > 0) {
+ ok(v.currentTime > target - 0.1 && v.currentTime < target + 0.1,
+ "Seek to wrong destination " + v.currentTime);
+ v.currentTime = 0.0;
+ v._seekTarget = 0.0;
+ } else {
+ ok(seekedNonZero, "Successfully seeked to nonzero");
+ ok(true, "Seek back to zero was successful");
+ completed = true;
+ finish();
+ }
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeking", startSeeking);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-12.html b/dom/media/test/test_seek-12.html
new file mode 100644
index 0000000000..28decabadc
--- /dev/null
+++ b/dom/media/test/test_seek-12.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 12;
+
+function test_seek12(v, seekTime, is, ok, finish) {
+var completed = false;
+
+function startTest() {
+ if (completed)
+ return;
+ ok(!v.seeking, "seeking should default to false");
+ v.currentTime = seekTime;
+ is(v.currentTime, seekTime, "currentTime must report seek target immediately");
+ is(v.seeking, true, "seeking flag on start should be true");
+}
+
+function seekStarted() {
+ if (completed)
+ return;
+ //is(v.currentTime, seekTime, "seeking: currentTime must be seekTime");
+ ok(Math.abs(v.currentTime - seekTime) < 0.01, "seeking: currentTime must be seekTime");
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+ completed = true;
+ //is(v.currentTime, seekTime, "seeked: currentTime must be seekTime");
+ ok(Math.abs(v.currentTime - seekTime) < 0.01, "seeked: currentTime must be seekTime");
+ is(v.seeking, false, "seeking flag on end should be false");
+ finish();
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeking", seekStarted);
+v.addEventListener("seeked", seekEnded);
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-13.html b/dom/media/test/test_seek-13.html
new file mode 100644
index 0000000000..81eda9b658
--- /dev/null
+++ b/dom/media/test/test_seek-13.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 13;
+
+function test_seek13(v, seekTime, is, ok, finish) {
+var completed = false;
+
+function startTest() {
+ if (completed)
+ return;
+ ok(!v.seeking, "seeking should default to false");
+ v.currentTime = v.duration;
+ is(v.currentTime, v.duration, "currentTime must report seek target immediately");
+ is(v.seeking, true, "seeking flag on start should be true");
+}
+
+function seekStarted() {
+ if (completed)
+ return;
+ //is(v.currentTime, v.duration, "seeking: currentTime must be duration");
+ ok(Math.abs(v.currentTime - v.duration) < 0.01,
+ "seeking: currentTime (" + v.currentTime + ") must be duration (" + v.duration + ")");
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+ //is(v.currentTime, v.duration, "seeked: currentTime must be duration");
+ ok(Math.abs(v.currentTime - v.duration) < 0.01,
+ "seeked: currentTime (" + v.currentTime + ") must be duration (" + v.duration + ")");
+ is(v.seeking, false, "seeking flag on end should be false");
+}
+
+function playbackEnded() {
+ if (completed)
+ return;
+ completed = true;
+ //is(v.currentTime, v.duration, "ended: currentTime must be duration");
+ ok(Math.abs(v.currentTime - v.duration) < 0.01,
+ "ended: currentTime (" + v.currentTime + ") must be duration (" + v.duration + ")");
+ is(v.seeking, false, "seeking flag on end should be false");
+ is(v.ended, true, "ended must be true");
+ finish();
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeking", seekStarted);
+v.addEventListener("seeked", seekEnded);
+v.addEventListener("ended", playbackEnded);
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-14.html b/dom/media/test/test_seek-14.html
new file mode 100644
index 0000000000..7e19fe3bd3
--- /dev/null
+++ b/dom/media/test/test_seek-14.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const SEEK_TEST_NUMBER = 14;
+
+function test_seek14(v, seekTime, is, ok, finish) {
+ var completed = false;
+
+ function startTest() {
+ v.play();
+ v.currentTime = v.duration;
+ }
+
+ function playbackEnded() {
+ if (completed) {
+ ok(false, "'ended' should only fire once.");
+ return;
+ }
+ completed = true;
+ // Finish the test after 700ms. We should receive only one 'ended' event.
+ setTimeout(finish, 700);
+ }
+
+ v.addEventListener("loadedmetadata", startTest);
+ v.addEventListener("ended", playbackEnded);
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-2.html b/dom/media/test/test_seek-2.html
new file mode 100644
index 0000000000..7b666df961
--- /dev/null
+++ b/dom/media/test/test_seek-2.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+PARALLEL_TESTS = 1;
+const SEEK_TEST_NUMBER = 2;
+
+function test_seek2(v, seekTime, is, ok, finish) {
+
+// Test seeking works if current time is set before video is
+// playing.
+var startPassed = false;
+var endPassed = false;
+var completed = false;
+
+function startTest() {
+ if (completed)
+ return;
+
+ v.currentTime=seekTime;
+ v.play();
+}
+
+function seekStarted() {
+ if (completed)
+ return;
+
+ ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime);
+ startPassed = true;
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+
+ endPassed = true;
+}
+
+function playbackEnded() {
+ if (completed)
+ return;
+
+ completed = true;
+ ok(startPassed, "send seeking event");
+ ok(endPassed, "send seeked event");
+ ok(v.ended, "Checking playback has ended");
+ ok(Math.abs(v.currentTime - v.duration) <= 0.1, "Checking currentTime at end: " + v.currentTime);
+ finish();
+}
+
+v.addEventListener("ended", playbackEnded);
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeking", seekStarted);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-3.html b/dom/media/test/test_seek-3.html
new file mode 100644
index 0000000000..c030f03d20
--- /dev/null
+++ b/dom/media/test/test_seek-3.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 3;
+
+function test_seek3(v, seekTime, is, ok, finish) {
+
+// Test seeking works if current time is set but video is not played.
+var completed = false;
+var gotTimeupdate = false;
+
+function startTest() {
+ if (completed)
+ return;
+
+ v.currentTime=seekTime;
+}
+
+function timeupdate() {
+ gotTimeupdate = true;
+ v.removeEventListener("timeupdate", timeupdate);
+}
+
+function seekStarted() {
+ if (completed)
+ return;
+
+ ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime);
+ v.addEventListener("timeupdate", timeupdate);
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+
+ var t = v.currentTime;
+ ok(Math.abs(t - seekTime) <= 0.1, "Video currentTime should be around " + seekTime + ": " + t);
+ ok(gotTimeupdate, "Should have got timeupdate between seeking and seekended");
+ completed = true;
+ finish();
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeking", seekStarted);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-4.html b/dom/media/test/test_seek-4.html
new file mode 100644
index 0000000000..4e5c1fee59
--- /dev/null
+++ b/dom/media/test/test_seek-4.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 4;
+
+function test_seek4(v, seekTime, is, ok, finish) {
+
+// Test for a seek, followed by another seek before the first is complete.
+var seekCount = 0;
+var completed = false;
+
+function startTest() {
+ if (completed)
+ return;
+
+ v.currentTime=seekTime;
+ v._seekTarget=seekTime;
+}
+
+function seekStarted() {
+ if (completed)
+ return;
+
+ seekCount += 1;
+
+ ok(v.currentTime >= v._seekTarget - 0.1,
+ "Video currentTime should be around " + v._seekTarget + ": " + v.currentTime);
+ if (seekCount == 1) {
+ v.currentTime=seekTime/2;
+ v._seekTarget=seekTime/2;
+ }
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+
+ if (seekCount == 2) {
+ ok(Math.abs(v.currentTime - seekTime/2) <= 0.1, "seek on target: " + v.currentTime);
+ completed = true;
+ finish();
+ }
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeking", seekStarted);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-5.html b/dom/media/test/test_seek-5.html
new file mode 100644
index 0000000000..a86477f77c
--- /dev/null
+++ b/dom/media/test/test_seek-5.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 5;
+
+function test_seek5(v, seekTime, is, ok, finish) {
+
+// Test for a seek, followed by a play before the seek completes, ensure we play at the end of the seek.
+var startPassed = false;
+var endPassed = false;
+var completed = false;
+
+function startTest() {
+ if (completed)
+ return;
+
+ v.currentTime=seekTime;
+}
+
+function seekStarted() {
+ if (completed)
+ return;
+ ok(v.currentTime >= seekTime - 0.1, "Video currentTime should be around " + seekTime + ": " + v.currentTime);
+ startPassed = true;
+ v.play();
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+ endPassed = true;
+}
+
+function playbackEnded() {
+ if (completed)
+ return;
+ ok(startPassed, "Got seeking event");
+ ok(endPassed, "Got seeked event");
+ completed = true;
+ finish();
+}
+
+v.addEventListener("ended", playbackEnded);
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeking", seekStarted);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-6.html b/dom/media/test/test_seek-6.html
new file mode 100644
index 0000000000..32554c4c2f
--- /dev/null
+++ b/dom/media/test/test_seek-6.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 6;
+
+function test_seek6(v, seekTime, is, ok, finish) {
+
+// Test for bug identified by Chris Pearce in comment 40 on
+// bug 449159.
+var seekCount = 0;
+var completed = false;
+var interval;
+
+function poll() {
+ v.currentTime;
+}
+
+function startTest() {
+ if (completed)
+ return;
+ interval = setInterval(poll, 10);
+ v.currentTime = Math.random() * v.duration;
+}
+
+function seekEnded() {
+ if (completed)
+ return;
+
+ seekCount++;
+ ok(true, "Seek " + seekCount);
+ if (seekCount == 3) {
+ clearInterval(interval);
+ completed = true;
+ finish();
+ } else {
+ v.currentTime = Math.random() * v.duration;
+ }
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-7.html b/dom/media/test/test_seek-7.html
new file mode 100644
index 0000000000..96139d6f83
--- /dev/null
+++ b/dom/media/test/test_seek-7.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 7;
+
+function test_seek7(v, seekTime, is, ok, finish) {
+
+// If a NaN is passed to currentTime, make sure this is caught
+// otherwise an infinite loop in the Ogg backend occurs.
+var completed = false;
+var thrown1 = false;
+var thrown3 = false;
+
+function startTest() {
+ if (completed)
+ return;
+
+ try {
+ v.currentTime = NaN;
+ } catch(e) {
+ thrown1 = true;
+ }
+
+ try {
+ v.currentTime = Math.random;
+ } catch(e) {
+ thrown3 = true;
+ }
+
+ completed = true;
+ ok(thrown1, "Setting currentTime to invalid value of NaN");
+ ok(thrown3, "Setting currentTime to invalid value of a function");
+ finish();
+}
+
+v.addEventListener("loadedmetadata", startTest);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-8.html b/dom/media/test/test_seek-8.html
new file mode 100644
index 0000000000..2f6e390eef
--- /dev/null
+++ b/dom/media/test/test_seek-8.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 8;
+
+function test_seek8(v, seekTime, is, ok, finish) {
+
+function startTest() {
+ v.currentTime = 1000;
+}
+
+function seekEnded() {
+ ok(Math.abs(v.currentTime - v.duration) < 0.2,
+ "currentTime " + v.currentTime + " close to " + v.duration);
+ finish();
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek-9.html b/dom/media/test/test_seek-9.html
new file mode 100644
index 0000000000..c03f6a75e3
--- /dev/null
+++ b/dom/media/test/test_seek-9.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// The data being used in these tests is specified in manifest.js.
+// The functions to build the test array and to run a specific test are in
+// seek_support.js.
+
+const SEEK_TEST_NUMBER = 9;
+
+function test_seek9(v, seekTime, is, ok, finish) {
+
+function startTest() {
+ v.currentTime = -1000;
+}
+
+function seekEnded() {
+ is(v.currentTime, 0, "currentTime clamped to 0");
+ finish();
+}
+
+v.addEventListener("loadedmetadata", startTest);
+v.addEventListener("seeked", seekEnded);
+
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seekLies.html b/dom/media/test/test_seekLies.html
new file mode 100644
index 0000000000..f3141eaabd
--- /dev/null
+++ b/dom/media/test/test_seekLies.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: server lies about range requests</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onunload="mediaTestCleanup();">
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function on_metadataloaded() {
+ var v = document.getElementById('v');
+ var d = Math.round(v.duration*1000);
+ ok(d == 4000, "Checking duration: " + d);
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<video id='v'
+ preload="metadata"
+ src='seekLies.sjs'
+ onloadedmetadata='on_metadataloaded();'></video>
+</body>
+</html>
diff --git a/dom/media/test/test_seekToNextFrame.html b/dom/media/test/test_seekToNextFrame.html
new file mode 100644
index 0000000000..755d06e622
--- /dev/null
+++ b/dom/media/test/test_seekToNextFrame.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test seekToNextFrame of media files that should play OK</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var video = document.createElement('video');
+ video.preload = "metadata";
+ video.token = token;
+ video.seenSeeking = false;
+ video.seenEnded = false;
+
+ var handler = {
+ "ontimeout": function() {
+ Log(token, "timed out: ended=" + video.seenEnded);
+ }
+ };
+ manager.started(token, handler);
+
+ video.src = test.name;
+ video.name = test.name;
+
+ function callSeekToNextFrame() {
+ video.seekToNextFrame().then(
+ () => {
+ if (!video.seenSeeking) {
+ ok(false, video.token + ": Should have already received seeking event.");
+ }
+ video.seenSeeking = false;
+ if (!video.ended) {
+ callSeekToNextFrame();
+ }
+ },
+ () => {
+ ok(false, video.token + ": seekToNextFrame() failed.");
+ }
+ );
+ }
+
+ var onLoadedmetadata = function(t, v) { return function() {
+ callSeekToNextFrame();
+ }}(test, video);
+
+ var finish = function() {
+ video.finished = true;
+ video.removeEventListener("loadedmetadata", onLoadedmetadata);
+ video.removeEventListener("seeking", onSeeking);
+ removeNodeAndSource(video);
+ manager.finished(video.token);
+ }
+
+ var onEnded = function(t, v) { return function() {
+ v.seenEnded = true;
+ finish();
+ }}(test, video);
+
+ var onSeeking = function(t, v) { return function() {
+ if (v.seenSeeking) {
+ ok(false, v.token + ": Should yet receive seeking event.");
+ }
+ v.seenSeeking = true;
+ }}(test, video);
+
+ video.addEventListener("loadedmetadata", onLoadedmetadata);
+ video.addEventListener("seeking", onSeeking);
+ video.addEventListener("ended", onEnded);
+
+ document.body.appendChild(video);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.seekToNextFrame.enabled", true ],
+ ["media.dormant-on-pause-timeout-ms", -1]
+ ]
+ },
+ function() {
+ manager.runTests(gSeekToNextFrameTests, startTest);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek_duration.html b/dom/media/test/test_seek_duration.html
new file mode 100644
index 0000000000..11ee4cb534
--- /dev/null
+++ b/dom/media/test/test_seek_duration.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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="seek_support.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * This test is used to make sure video's duration won't be changed when it
+ * reachs to the end after seeking to position where the time is very close to
+ * video's end time.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+(async function startTest()
+{
+ const video = document.createElement('video');
+ video.src = "bunny.webm";
+ document.body.appendChild(video);
+
+ const loadedMetadata = once(video, "loadedmetadata");
+ const canplay = once(video, "canplay");
+ const end = once(video, "ended");
+
+ info(`- wait for video loading metadata -`);
+ await loadedMetadata;
+ const originalDuration = video.duration;
+
+ info(`- seek video to the position which is close to end time -`);
+ // video's duration is 2.1 and the last key frame is in 2.0, we want to seek
+ // to that keyframe.
+ video.currentTime = originalDuration - 0.1;
+
+ info(`- play video until it ends -`);
+ await canplay;
+ await video.play();
+ await end;
+
+ ok(video.duration === originalDuration, `Duration shouldn't change`);
+ removeNodeAndSource(video);
+
+ SimpleTest.finish();
+})();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek_negative.html b/dom/media/test/test_seek_negative.html
new file mode 100644
index 0000000000..98f0b9c910
--- /dev/null
+++ b/dom/media/test/test_seek_negative.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seeking to a negative time with readyState HAVE_NOTHING</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function startTest(test, token) {
+ var type = getMajorMimeType(test.type);
+ var v = document.createElement(type);
+ v.token = token;
+ manager.started(token);
+
+ // Seek to negative start time.
+ v.currentTime = -123;
+ is(v.readyState, v.HAVE_NOTHING, "readyState is HAVE_NOTHING");
+ ok(!v.seeking, "can't be seeking prior src defined");
+ is(v.currentTime, -123, "currentTime is original seek time");
+
+ v.src = test.name;
+
+ // Initialize running variables.
+ v._name = test.name;
+ v._seekStarted = false;
+ v._seekCompleted = false;
+ v._metadata = false;
+
+ var events = [ "suspend", "play", "canplay", "canplaythrough", "loadstart",
+ "loadedmetadata", "loadeddata", "playing", "ended", "error",
+ "stalled", "emptied", "abort", "waiting", "pause" ];
+ function logEvent(e) {
+ var video = e.target;
+ Log(e.target.token, "got " + e.type + " with currentTime = " + video.currentTime);
+ }
+ events.forEach(function(e) {
+ v.addEventListener(e, logEvent);
+ });
+
+ once(v, "seeking", function() {
+ v._seekStarted = true;
+ ok(v.currentTime >= 0, "currentTime should be positive");
+ });
+ once(v, "seeked", function() {
+ v._seekCompleted = true;
+ ok(v.currentTime >= 0, "currentTime should be positive");
+ });
+ once(v, "loadedmetadata", function() {
+ v._metadata = true;
+ ok(v.seeking, "element is seeking once readyState is HAVE_METADATA");
+ ok(v.currentTime >= 0, "currentTime should be positive");
+ });
+ once(v, "ended", function() {
+ ok(v._seekStarted, "seek should have started");
+ ok(v._seekCompleted, "seek should have completed");
+ ok(v._metadata, "loadedmetadata fired");
+ ok(v.currentTime >= 0, "currentTime should be positive");
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+ });
+
+ v.play();
+}
+
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek_nosrc.html b/dom/media/test/test_seek_nosrc.html
new file mode 100644
index 0000000000..56461007bd
--- /dev/null
+++ b/dom/media/test/test_seek_nosrc.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seek tests</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var SEEK_TIME = 3.5;
+var seekStarted = false;
+var seekCompleted = false;
+var metadata = false;
+
+var v = document.createElement('video');
+document.body.appendChild(v);
+SimpleTest.registerCleanupFunction(function () {
+ v.remove();
+});
+
+try {
+ v.currentTime = SEEK_TIME;
+} catch (e) {
+ ok(false, "should not fire '" + e + "' event");
+}
+is(v.readyState, v.HAVE_NOTHING, "readyState is HAVE_NOTHING");
+ok(!v.seeking, "can't be seeking prior src defined");
+is(v.currentTime, SEEK_TIME, "currentTime is default playback start position");
+once(v, "seeking", function() {
+ seekStarted = true;
+});
+once(v, "seeked", function() {
+ seekCompleted = true;
+});
+once(v, "loadedmetadata", function() {
+ metadata = true;
+ ok(v.seeking, "element is seeking once readyState is HAVE_METADATA");
+});
+once(v, "ended", function() {
+ ok(seekStarted, "seek should have started");
+ ok(seekCompleted, "seek should have completed");
+ ok(metadata, "loadedmetadata fired");
+ ok(v.currentTime >= SEEK_TIME, "currentTime should be after seek time");
+ SimpleTest.finish();
+});
+
+v.src = "seek.webm";
+v.play();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek_out_of_range.html b/dom/media/test/test_seek_out_of_range.html
new file mode 100644
index 0000000000..a477ebdd19
--- /dev/null
+++ b/dom/media/test/test_seek_out_of_range.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: seeking off the end of a file</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+// Test if the ended event works correctly.
+
+async function initTest(test, token) {
+ var type = getMajorMimeType(test.type);
+ var v = document.createElement(type);
+ v.preload = "auto";
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._name = test.name;
+ document.body.appendChild(v);
+
+ await once(v, "loadedmetadata");
+ info(`${v._name}: seeking to the end of the media.`);
+ v.currentTime = 3.0 * v.duration;
+ // Wait for 'seeked' and 'ended' to be fired.
+ await Promise.all([once(v, "seeked"), once(v, "ended")]);
+ // Check currentTime is near the end of the media.
+ ok(Math.abs(v.duration - v.currentTime) < 0.1,
+ "Should be at end of media for " + v._name + " t=" + v.currentTime + " d=" + v.duration);
+ // Call play() to start playback from the beginning.
+ v.play();
+ await once(v, "ended");
+ ok(v.ended, "Checking ended set after seeking to EOF and playing for " + v._name);
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_seek_promise_bug1344357.html b/dom/media/test/test_seek_promise_bug1344357.html
new file mode 100644
index 0000000000..6c441bc1cf
--- /dev/null
+++ b/dom/media/test/test_seek_promise_bug1344357.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: bug 1344357</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>
+</head>
+<body>
+<pre id="test">
+
+<script>
+
+// This test always succeeds at runtime but should not cause any leaks at the end of mochitests.
+
+let manager = new MediaTestManager;
+
+function initTest(test, token) {
+ manager.started(token);
+
+ var win = window.open();
+ var video = win.document.createElement("video");
+ video.autoplay = true;
+ video.src = "http://example.com/tests/dom/media/test/" + test.name;
+ win.document.body.appendChild(video);
+ video.currentTime = test.duration / 2;
+ video.addEventListener("seeking", () => {
+ win.close();
+ manager.finished(token);
+ }, true);
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script> \ No newline at end of file
diff --git a/dom/media/test/test_seekable1.html b/dom/media/test/test_seekable1.html
new file mode 100644
index 0000000000..45c84b35df
--- /dev/null
+++ b/dom/media/test/test_seekable1.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test seekable member for media elements</title>
+<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+<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>
+</head>
+<body>
+<pre id='test'>
+<script class="testbody" type='application/javascript'>
+
+let manager = new MediaTestManager;
+
+function finish_test(element) {
+ if (element.parentNode)
+ element.remove();
+ element.src="";
+ manager.finished(element.token);
+}
+
+var tests = [
+// Test using a finite media stream, and a server supporting range requests
+{
+setup(element) {
+ is(element.seekable.length, 0, "seekable.length should be initialy 0.");
+ element.addEventListener("loadedmetadata", function() {
+ is(element.seekable.length, 1, "seekable.length should be 1 for a server supporting range requests.");
+
+ is(element.seekable.start(0), 0.0, "The start of the first range should be the initialTime.");
+ is(element.seekable.end(0), element.duration, "The end of the first range should be the duration.")
+ finish_test(element);
+ });
+ }
+}
+];
+
+function createTestArray() {
+ var A = [];
+ for (var k=0; k < gProgressTests.length; k++) {
+ var t = {};
+ t.setup = tests[0].setup;
+ t.name = gProgressTests[k].name;
+ t.type = gProgressTests[k].type;
+ A.push(t);
+ }
+ return A;
+}
+
+function startTest(test, token) {
+ var elemType = getMajorMimeType(test.type);
+ var element = document.createElement(elemType);
+ element.preload = "auto";
+ element.src = test.name;
+ element.token = token;
+ test.setup(element);
+ manager.started(token);
+}
+
+manager.runTests(createTestArray(), startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_source.html b/dom/media/test/test_source.html
new file mode 100644
index 0000000000..34cc9e7f8a
--- /dev/null
+++ b/dom/media/test/test_source.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: append source child</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>
+</head>
+<body>
+<video id="v1"></video>
+<audio id="a1"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var v1 = document.getElementById("v1");
+var a1 = document.getElementById("a1");
+v1.preload = "auto";
+a1.preload = "auto";
+
+is(v1.src, "", "src should be null");
+is(a1.src, "", "src should be null");
+is(v1.currentSrc, "", "currentSrc should be null");
+is(a1.currentSrc, "", "currentSrc should be null");
+is(v1.childNodes.length, 0, "should have no children");
+is(a1.childNodes.length, 0, "should have no children");
+
+function newSource(filter) {
+ var candidates = gSmallTests.filter(function(x){return filter.test(x.type);});
+ if (candidates.length > 0) {
+ var e = document.createElement("source");
+ e.type = candidates[0].type;
+ e.src = candidates[0].name;
+ return e;
+ }
+ return null
+
+}
+
+var audioLoaded = false;
+var videoLoaded = false;
+
+function loaded(e) {
+ var media = e.target;
+ ok(media.networkState > 0, "networkState should be > 0");
+ is(media.childNodes.length, 1, "should have 1 child");
+ var sourceFile = media.currentSrc.substring(media.currentSrc.lastIndexOf('/')+1);
+ var resource = media.firstChild.src.substring(media.firstChild.src.lastIndexOf('/')+1);
+ is(sourceFile, resource, "loaded wrong resource!");
+ if (media == a1)
+ audioLoaded = true;
+ else if (media == v1)
+ videoLoaded = true;
+ if (audioLoaded && videoLoaded) {
+ SimpleTest.finish();
+ }
+}
+
+v1.addEventListener('loadeddata', loaded);
+a1.addEventListener('loadeddata', loaded);
+
+var videoSource = newSource(/^video/);
+if (videoSource) {
+ v1.appendChild(videoSource);
+ v1.load();
+} else {
+ // No video backends? Don't test anything.
+ videoLoaded = true;
+}
+
+var audioSource = newSource(/^audio/);
+if (audioSource) {
+ a1.appendChild(audioSource);
+ a1.load();
+} else {
+ audioLoaded = true;
+}
+
+if (!audioLoaded && !videoLoaded) {
+ SimpleTest.waitForExplicitFinish();
+} else {
+ if (audioLoaded) {
+ todo(false, "No audio types supported");
+ }
+ if (videoLoaded) {
+ todo(false, "No video types supported");
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_source_null.html b/dom/media/test/test_source_null.html
new file mode 100644
index 0000000000..485edafd2d
--- /dev/null
+++ b/dom/media/test/test_source_null.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=752087
+-->
+<head>
+ <title>Test for Bug 752087</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=752087">Mozilla Bug 752087</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<video id="v">
+<script>
+var v = document.getElementById('v');
+v.src = null; // crashes on NULL access if not handled
+
+ok(true, "setting video.src to null didn't crash!");
+var srcPath = v.src.split('/');
+ok(srcPath.length >= 3, "src should be within dom/media/test");
+is(srcPath[srcPath.length - 1], "null", "Setting src to null is handled like 'null' string");
+</script>
+</video>
+</body>
+</html>
diff --git a/dom/media/test/test_source_write.html b/dom/media/test/test_source_write.html
new file mode 100644
index 0000000000..29fffebd10
--- /dev/null
+++ b/dom/media/test/test_source_write.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462455
+-->
+<head>
+ <title>Test for Bug 462455</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462455">Mozilla Bug 462455</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<video id="v">
+<script>
+var loadStarted = false;
+document.write('Pause parsing!');
+var v = document.getElementById('v');
+
+var resource = getPlayableVideo(gSmallTests);
+if (resource != null) {
+ var s = document.createElement("source");
+ s.src = resource.name;
+ s.type = resource.type;
+ v.appendChild(s);
+}
+
+ok(v.networkState == HTMLMediaElement.NETWORK_NO_SOURCE,
+ "We shouldn't start network load until the current task returns.");
+</script>
+</video>
+</body>
+</html>
diff --git a/dom/media/test/test_standalone.html b/dom/media/test/test_standalone.html
new file mode 100644
index 0000000000..094052847f
--- /dev/null
+++ b/dom/media/test/test_standalone.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: standalone video documents</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>
+</head>
+<body onload="doTest()">
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var iframes = [];
+
+for (let i=0; i<gSmallTests.length; ++i) {
+ var test = gSmallTests[i];
+
+ // We can't play WAV files in stand alone documents, so just don't
+ // run the test on non-video content types.
+ var tag = getMajorMimeType(test.type);
+ if (tag != "video" || !document.createElement("video").canPlayType(test.type))
+ continue;
+
+ let f = document.createElement("iframe");
+ f.src = test.name;
+ f._test = test;
+ f.id = "frame" + i;
+ iframes.push(f);
+ document.body.appendChild(f);
+}
+
+
+function filename(uri) {
+ return uri.substr(uri.lastIndexOf("/")+1);
+}
+
+function doTest()
+{
+ for (let i=0; i<iframes.length; ++i) {
+ let f = document.getElementById(iframes[i].id);
+ var v = f.contentDocument.body.firstChild;
+ is(v.tagName.toLowerCase(), "video", "Is video element");
+ var src = filename(v.currentSrc);
+ is(src, iframes[i]._test.name, "Name ("+src+") should match ("+iframes[i]._test.name+")");
+ is(v.controls, true, "Controls set (" + src + ")");
+ is(v.autoplay, true, "Autoplay set (" + src + ")");
+ }
+ SimpleTest.finish();
+}
+
+if (iframes.length == 0) {
+ todo(false, "No types supported");
+} else {
+ SimpleTest.waitForExplicitFinish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_capture_origin.html b/dom/media/test/test_streams_capture_origin.html
new file mode 100644
index 0000000000..13d5589c0d
--- /dev/null
+++ b/dom/media/test/test_streams_capture_origin.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1189506</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1189506">Mozilla Bug 1189506</a>
+<p id="display"></p>
+<video id="vin"></video>
+<video id="vout"></video>
+<video id="vout_cors" crossorigin></video>
+<canvas id="cin" width="40" height="30"></canvas>
+<canvas id="cout" width="40" height="30"></canvas>
+<canvas id="cout_cors" width="40" height="30"></canvas>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+/* global vin, vout, vout_cors, cin, cout, cout_cors */
+
+/** Test for Bug 1189506 **/
+
+SimpleTest.waitForExplicitFinish();
+
+async function start() {
+ const resource = getPlayableVideo(gSmallTests).name;
+ vin.src = "http://example.org:8000/tests/dom/media/test/" + resource;
+ vin.preload = "metadata";
+
+ await new Promise(r => vin.onloadedmetadata = r);
+ vout.srcObject = vin.mozCaptureStreamUntilEnded();
+ vout_cors.srcObject = vin.mozCaptureStreamUntilEnded();
+ vin.play();
+ vout.play();
+ vout_cors.play();
+
+ await new Promise(r => vout.onended = r);
+ is(vin.ended, vout.ended, "Source media element ends first");
+
+ const ctxin = SpecialPowers.wrap(cin.getContext("2d"));
+ ctxin.drawImage(vin, 0, 0);
+
+ {
+ info("Testing that the last frame is rendered");
+ const powerCtx = SpecialPowers.wrap(cout.getContext("2d"));
+ powerCtx.drawImage(vout, 0, 0);
+ const datain = ctxin.getImageData(0, 0, cin.width, cin.height);
+ const dataout = powerCtx.getImageData(0, 0, cout.width, cout.height);
+ for (let i = 0; i < datain.data.length; i += 4) {
+ const pixelin = datain.data.slice(i, i + 4).join(',');
+ const pixelout = dataout.data.slice(i, i + 4).join(',');
+ if (pixelin != pixelout) {
+ is(pixelout, pixelin, `Pixel #${i/4} is rendered as expected`);
+ break;
+ }
+ }
+ is(datain.data.length / 4, cin.width * cin.height,
+ "Checked expected number of pixels");
+ }
+
+ {
+ info("Testing that the principal is set");
+ const ctx = cout.getContext("2d");
+ ctx.drawImage(vout, 0, 0);
+ SimpleTest.doesThrow(() => ctx.getImageData(0, 0, cout.width, cout.height),
+ "SecurityError");
+ }
+
+ {
+ info("Testing that the crossorigin attribute is ignored for MediaStreams");
+ const ctx = cout_cors.getContext("2d");
+ ctx.drawImage(vout_cors, 0, 0);
+ SimpleTest.doesThrow(
+ () => ctx.getImageData(0, 0, cout_cors.width, cout_cors.height),
+ "SecurityError");
+ }
+}
+
+(async () => {
+ try { await start(); }
+ catch(e) { ok(false, `Rejected with ${e}`); }
+ finally { SimpleTest.finish(); }
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_element_capture.html b/dom/media/test/test_streams_element_capture.html
new file mode 100644
index 0000000000..2aa6deb587
--- /dev/null
+++ b/dom/media/test/test_streams_element_capture.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that a MediaStream captured from one element plays back in another</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+const manager = new MediaTestManager();
+
+function checkDrawImage(vout, msg) {
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+ ctx.drawImage(vout, 0, 0);
+ const imgData = ctx.getImageData(0, 0, 1, 1);
+ is(imgData.data[3], 255, msg);
+}
+
+function isGreaterThanOrEqualEps(a, b, msg) {
+ ok(a >= b, `Got ${a}, expected at least ${b}; ${msg}`);
+}
+
+async function startTest(test, token) {
+ manager.started(token);
+ const v = document.createElement('video');
+ const vout = document.createElement('video');
+
+ v.token = token;
+ v.id = "MediaDecoder";
+ vout.id = "MediaStream";
+
+ document.body.appendChild(vout);
+
+ // Log events for debugging.
+ const events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause"];
+ function logEvent(e) {
+ Log(token, `${e.target.id} got ${e.type}`);
+ }
+ for (const e of events) {
+ v.addEventListener(e, logEvent);
+ vout.addEventListener(e, logEvent);
+ };
+
+ v.src = test.name;
+ v.preload = 'metadata';
+ await new Promise(r => v.onloadedmetadata = r);
+
+ const stream = v.mozCaptureStreamUntilEnded();
+ vout.srcObject = stream;
+ is(vout.srcObject, stream,
+ `${token} set output element .srcObject correctly`);
+ // Wait for the resource fetch algorithm to have run, so that the media
+ // element is hooked up to the MediaStream and ready to go. If we don't do
+ // this, we're not guaranteed to render the very first video frame, which
+ // can make this test fail the drawImage test when a video resource only
+ // contains one frame.
+ await new Promise(r => vout.onloadstart = r);
+ v.play();
+ vout.play();
+
+ await Promise.race([
+ Promise.all([
+ new Promise(r => vout.onended = r),
+ new Promise(r => v.onended = r),
+ ]),
+ new Promise((res, rej) => vout.onerror = () => rej(new Error(vout.error.message))),
+ new Promise((res, rej) => v.onerror = () => rej(new Error(v.error.message))),
+ ]);
+
+ let duration = test.duration;
+ if (typeof(test.contentDuration) == "number") {
+ duration = test.contentDuration;
+ }
+ if (duration) {
+ isGreaterThanOrEqualEps(vout.currentTime, duration,
+ `${token} current time at end`);
+ }
+ is(vout.readyState, vout.HAVE_CURRENT_DATA,
+ `${token} checking readyState`);
+ ok(vout.ended, `${token} checking playback has ended`);
+ isnot(stream.getTracks().length, 0, `${token} results in some tracks`);
+ if (stream.getVideoTracks().length > 0) {
+ ok(test.type.match(/^video/), `${token} is a video resource`);
+ checkDrawImage(vout, `${token} checking video frame pixel has been drawn`);
+ }
+ vout.remove();
+ removeNodeAndSource(v);
+}
+
+(async () => {
+ SimpleTest.requestCompleteLog();
+ SimpleTest.waitForExplicitFinish();
+ await SpecialPowers.pushPrefEnv(
+ { "set": [
+ ["privacy.reduceTimerPrecision", false],
+ // This test exhibits bug 1543980 with RDD enabled.
+ ["media.rdd-process.enabled", false],
+ ]});
+ let tests = gPlayTests;
+ // Filter out bug1377278.webm due to bug 1541401.
+ tests = tests.filter(t => !t.name.includes("1377278"));
+
+ manager.runTests(tests, async (test, token) => {
+ try {
+ await startTest(test, token);
+ } catch(e) {
+ ok(false, `Caught exception for ${token}: ${e}`);
+ }
+ manager.finished(token);
+ });
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_element_capture_mediatrack.html b/dom/media/test/test_streams_element_capture_mediatrack.html
new file mode 100644
index 0000000000..6b4332d8af
--- /dev/null
+++ b/dom/media/test/test_streams_element_capture_mediatrack.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that a media element captureStream works when disabling MediaTracks</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+const manager = new MediaTestManager();
+
+async function startTest(test, token) {
+ manager.started(token);
+ const v = document.createElement('video');
+
+ document.body.appendChild(v);
+ v.token = token;
+ v.id = "MediaDecoder";
+
+ // Log events for debugging.
+ const events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
+ "waiting", "pause"];
+ function logEvent(e) {
+ Log(token, `${token}: ${e.target.id} got ${e.type}`);
+ }
+ for (const e of events) {
+ v.addEventListener(e, logEvent);
+ };
+
+ v.src = test.name;
+ v.preload = 'metadata';
+ await new Promise(r => v.onloadedmetadata = r);
+
+ const stream = v.mozCaptureStream();
+
+ is(stream.getAudioTracks().length, Math.min(1, v.audioTracks.length),
+ `${token}: Expected number of audio tracks`);
+ is(stream.getVideoTracks().length, Math.min(1, v.videoTracks.length),
+ `${token}: Expected number of video tracks`);
+
+ if (v.audioTracks.length) {
+ v.audioTracks[0].enabled = false;
+ const track = stream.getAudioTracks()[0];
+ await new Promise(r => track.onended = r);
+ is(track.readyState, "ended", `${token}: Audio track has ended on removal`);
+ await new Promise(r => stream.onremovetrack = r);
+ is(stream.getAudioTracks().length, 0,
+ `${token}: Audio track was removed on removetrack`);
+ }
+
+ if (v.videoTracks.length) {
+ v.videoTracks[0].selected = false;
+ const track = stream.getVideoTracks()[0];
+ await new Promise(r => track.onended = r);
+ is(track.readyState, "ended", `${token}: Video track has ended on removal`);
+ await new Promise(r => stream.onremovetrack = r);
+ is(stream.getVideoTracks().length, 0,
+ `${token}: Video track was removed on removetrack`);
+ }
+
+ stream.onaddtrack = () => ok(false, "Unexpected addtrack event");
+
+ v.play();
+
+ await Promise.race([
+ new Promise(r => v.ontimeupdate = r),
+ new Promise((res, rej) => v.onerror = () => rej(new Error(v.error.message))),
+ ]);
+
+ is(stream.getTracks().length, 0, `${token}: no tracks appeared during playback`);
+ removeNodeAndSource(v);
+}
+
+(async () => {
+ SimpleTest.requestCompleteLog();
+ SimpleTest.waitForExplicitFinish();
+ await SpecialPowers.pushPrefEnv(
+ { "set": [
+ ["media.track.enabled", true],
+ ]});
+ let tests = gPlayTests;
+ // Filter out bug1377278.webm due to bug 1541401.
+ tests = tests.filter(t => !t.name.includes("1377278"));
+
+ manager.runTests(tests, async (test, token) => {
+ try {
+ await startTest(test, token);
+ } catch(e) {
+ ok(false, `Caught exception for ${token}: ${e}`);
+ }
+ manager.finished(token);
+ });
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_element_capture_playback.html b/dom/media/test/test_streams_element_capture_playback.html
new file mode 100644
index 0000000000..ce083069ef
--- /dev/null
+++ b/dom/media/test/test_streams_element_capture_playback.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that capturing a stream doesn't stop the underlying element from firing events</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>
+</head>
+<body>
+<audio id="a"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var a = document.getElementById('a');
+var validTimeUpdate = false;
+
+function startTest() {
+ a.src = "big.wav";
+ var context = new AudioContext();
+ var node = context.createMediaElementSource(a);
+ node.connect(context.destination);
+ a.addEventListener("timeupdate", function() {
+ if (a.currentTime > 0.0 && a.currentTime < 5.0 && !validTimeUpdate) {
+ validTimeUpdate = true;
+ ok(true, "Received reasonable currentTime in a timeupdate");
+ SimpleTest.finish();
+ }
+ });
+ a.addEventListener("ended", function() {
+ if (!validTimeUpdate) {
+ ok(false, "Received reasonable currentTime in a timeupdate");
+ SimpleTest.finish();
+ }
+ });
+ a.play();
+}
+
+if (a.canPlayType("audio/wave")) {
+ startTest();
+} else {
+ todo(false, "No playable audio");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_element_capture_reset.html b/dom/media/test/test_streams_element_capture_reset.html
new file mode 100644
index 0000000000..625b0fe23f
--- /dev/null
+++ b/dom/media/test/test_streams_element_capture_reset.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that reloading, pausing and seeking in a media element that's being captured behaves as expected</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="manifest.js"></script>
+</head>
+<body>
+<video id="v"></video>
+<video id="vout"></video>
+<video id="vout_untilended"></video>
+<pre id="test">
+<script>
+const v = document.getElementById('v');
+const vout = document.getElementById('vout');
+const vout_untilended = document.getElementById('vout_untilended');
+
+function dumpEvent(event) {
+ const video = event.target;
+ info(
+ `${video.name}:${video.id} GOT EVENT ${event.type} ` +
+ `currentTime=${video.currentTime} paused=${video.paused} ` +
+ `ended=${video.ended} readyState=${video.readyState}`
+ );
+}
+
+function unexpected(event) {
+ ok(false, `${event.type} event received on ${event.target.id} unexpectedly`);
+};
+
+const events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"];
+for (const e of events) {
+ v.addEventListener(e, dumpEvent);
+ vout.addEventListener(e, dumpEvent);
+ vout_untilended.addEventListener(e, dumpEvent);
+}
+
+function isWithinEps(a, b, msg) {
+ ok(Math.abs(a - b) < 0.01,
+ "Got " + a + ", expected " + b + "; " + msg);
+}
+
+function isGreaterThanOrEqualEps(a, b, msg) {
+ ok(a >= b - 0.01,
+ "Got " + a + ", expected at least " + b + "; " + msg);
+}
+
+async function startTest(test) {
+ const seekTime = test.duration/2;
+ const contentDuration = test.contentDuration ?? test.duration;
+
+ v.src = test.name;
+ v.name = test.name;
+ vout.name = test.name;
+ vout_untilended.name = test.name;
+ v.preload = "metadata";
+ await new Promise(r => v.onloadedmetadata = r);
+
+ vout.srcObject = v.mozCaptureStream();
+ vout.play();
+
+ vout_untilended.srcObject = v.mozCaptureStreamUntilEnded();
+ vout_untilended.play();
+
+ for (const track of [
+ ...vout.srcObject.getTracks(),
+ ...vout_untilended.srcObject.getTracks(),
+ ]) {
+ ok(track.muted, `${track.kind} track ${track.id} should be muted`);
+ }
+
+ v.play();
+
+ await Promise.all([
+ ...vout.srcObject.getTracks(),
+ ...vout_untilended.srcObject.getTracks()
+ ].map(t => new Promise(r => t.onunmute = r)));
+
+ await new Promise(r => v.onended = r);
+ isGreaterThanOrEqualEps(v.currentTime, test.duration,
+ "checking v.currentTime at first 'ended' event");
+
+ await Promise.all([
+ new Promise(r => vout.onended = r),
+ new Promise(r => vout_untilended.onended = r),
+ ]);
+
+ isGreaterThanOrEqualEps(vout.currentTime, contentDuration,
+ "checking vout.currentTime at first 'ended' event");
+ ok(vout.ended, "checking vout has actually ended");
+ ok(vout_untilended.ended, "checking vout_untilended has actually ended");
+
+ vout_untilended.srcObject.onaddtrack = unexpected;
+ vout_untilended.onplaying = unexpected;
+ vout_untilended.onended = unexpected;
+
+ const voutPreSeekCurrentTime = vout.currentTime;
+ v.currentTime = seekTime;
+ await new Promise(r => v.onseeked = r);
+
+ is(v.currentTime, seekTime, "Finished seeking");
+ is(vout.currentTime, voutPreSeekCurrentTime,
+ "checking vout.currentTime has not changed after seeking");
+
+ v.play();
+ vout.play();
+
+ await new Promise(r => v.onended = r);
+ isGreaterThanOrEqualEps(v.currentTime, test.duration,
+ "checking v.currentTime at second 'ended' event");
+
+ await new Promise(r => vout.onended = r);
+ isGreaterThanOrEqualEps(vout.currentTime,
+ (test.duration - seekTime) + contentDuration,
+ "checking vout.currentTime after seeking and playing through again");
+
+ v.src = test.name + "?1";
+ vout.play();
+ await v.play();
+
+ isnot(vout.srcObject.getTracks().length, 0, "There are some output tracks");
+
+ vout.onended = unexpected;
+ vout.srcObject.onremovetrack = unexpected;
+
+ v.pause();
+ await Promise.all(
+ vout.srcObject.getTracks().map(t => new Promise(r => t.onmute = r))
+ );
+
+ for (const track of vout.srcObject.getTracks()) {
+ track.onunmute = unexpected;
+ }
+
+ v.currentTime = 0;
+ await new Promise(r => v.onseeked = r);
+
+ v.play();
+ await Promise.all(
+ vout.srcObject.getTracks().map(t => new Promise(r => t.onunmute = r))
+ );
+
+ vout.srcObject.onremovetrack = null;
+
+ await new Promise(r => v.onended = r);
+ isGreaterThanOrEqualEps(v.currentTime, test.duration,
+ "checking v.currentTime at third 'ended' event");
+
+ await new Promise(r => vout.onended = r);
+ isGreaterThanOrEqualEps(vout.currentTime,
+ (test.duration - seekTime) + contentDuration*2,
+ "checking vout.currentTime after seeking, playing through and reloading");
+}
+
+(async () => {
+ SimpleTest.waitForExplicitFinish();
+ try {
+ const testVideo = getPlayableVideo(gSmallTests);
+ if (testVideo) {
+ await startTest(testVideo);
+ } else {
+ todo(false, "No playable video");
+ }
+ } catch(e) {
+ ok(false, `Error: ${e}`);
+ } finally {
+ SimpleTest.finish();
+ }
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_element_capture_twice.html b/dom/media/test/test_streams_element_capture_twice.html
new file mode 100644
index 0000000000..0e30be1801
--- /dev/null
+++ b/dom/media/test/test_streams_element_capture_twice.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that capturing a media element, then reloading and capturing again, works</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>
+</head>
+<body>
+<video id="v"></video>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const v = document.getElementById('v');
+
+function dumpEvent(event) {
+ const video = event.target;
+ info(video.name + " GOT EVENT " + event.type +
+ " currentTime=" + video.currentTime +
+ " paused=" + video.paused +
+ " ended=" + video.ended +
+ " readyState=" + video.readyState);
+}
+
+const events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"];
+for (let i = 0; i < events.length; ++i) {
+ v.addEventListener(events[i], dumpEvent);
+}
+
+async function startTest(src) {
+ v.preload = "metadata";
+ v.src = src;
+ await new Promise(r => v.onloadedmetadata = r);
+ const s1 = v.mozCaptureStream();
+ const tracks = s1.getTracks();
+ is(tracks.length, 2, "Expected total tracks, s1, capture 1");
+ is(s1.getAudioTracks().length, 1, "Expected audio tracks, s1, capture 1");
+ is(s1.getVideoTracks().length, 1, "Expected video tracks, s1, capture 1");
+ is(s1.getAudioTracks()[0].readyState, "live", "Live audio, s1, capture 1");
+ is(s1.getVideoTracks()[0].readyState, "live", "Live video, s1, capture 1");
+
+ v.src = null;
+ for (let i = 0; i < tracks.length; ++i) {
+ await Promise.race(tracks.map(t => new Promise(r => t.onended = r)));
+ await new Promise(r => s1.onremovetrack = r);
+ }
+ is(s1.getTracks().length, 0, "Expected total tracks, s1, metadata 2");
+
+ v.src = src;
+ await new Promise(r => v.onloadedmetadata = r);
+ is(s1.getTracks().length, 2, "Expected total tracks, s1, metadata 2");
+ is(s1.getAudioTracks().length, 1, "Expected audio tracks, s1, metadata 2");
+ is(s1.getVideoTracks().length, 1, "Expected video tracks, s1, metadata 2");
+ is(s1.getAudioTracks()[0].readyState, "live", "Live audio, s1, metadata 2");
+ is(s1.getVideoTracks()[0].readyState, "live", "Live video, s1, metadata 2");
+
+ const s2 = v.mozCaptureStream();
+ is(s1.getTracks().length, 2, "Expected total tracks remains, s1, capture 2");
+ is(s2.getTracks().length, 2, "Expected total tracks, s2, capture 2");
+ is(s2.getAudioTracks().length, 1, "Expected audio tracks, s2, capture 2");
+ is(s2.getVideoTracks().length, 1, "Expected video tracks, s2, capture 2");
+ is(s2.getAudioTracks()[0].readyState, "live", "Live audio, s2, capture 2");
+ is(s2.getVideoTracks()[0].readyState, "live", "Live video, s2, capture 2");
+}
+
+(async function() {
+ try {
+ await startTest("short-video.ogv");
+ } catch(e) {
+ ok(false, `Caught error: ${e}${e.stack ? '\n' + e.stack : ''}`);
+ } finally {
+ SimpleTest.finish();
+ }
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_firstframe.html b/dom/media/test/test_streams_firstframe.html
new file mode 100644
index 0000000000..ea1dbd35c1
--- /dev/null
+++ b/dom/media/test/test_streams_firstframe.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that a non-autoplaying, non-playing element with a MediaStream source triggers canplay and shows a first frame</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="manifest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+async function runTest() {
+ const canvas = document.createElement("canvas");
+ canvas.getContext("2d");
+ const helper = new CaptureStreamTestHelper2D(100, 100);
+
+ const video = document.createElement("video");
+ document.body.appendChild(video);
+
+ video.srcObject = canvas.captureStream();
+ helper.drawColor(canvas, helper.red);
+
+ await Promise.race([
+ new Promise(r => video.oncanplay = r),
+ new Promise(r => setTimeout(r, 30000))
+ .then(() => Promise.reject(new Error("Canplay timeout"))),
+ ]);
+
+ ok(true, "Got \"canplay\"");
+ is(video.readyState, video.HAVE_ENOUGH_DATA, "Expected readyState");
+ ok(helper.isPixel(helper.getPixel(video), helper.red),
+ "First frame is rendered before playing");
+
+ helper.drawColor(canvas, helper.green);
+ await helper.pixelMustNotBecome(video, helper.green, {
+ time: 1000,
+ infoString: "Rendered first frame doesn't change on new frame from source"
+ });
+ ok(helper.isPixel(helper.getPixel(video), helper.red),
+ "First frame is still rendered");
+
+ video.play();
+ helper.drawColor(canvas, helper.blue);
+ await helper.pixelMustBecome(video, helper.blue, {
+ infoString: "New frame gets rendered when playing"
+ });
+
+ video.srcObject.getTracks().forEach(t => t.stop());
+}
+
+(async function() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("Explicit timeout reasons");
+ try {
+ await runTest();
+ } catch(e) {
+ ok(false, e);
+ }
+ SimpleTest.finish();
+})();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_gc.html b/dom/media/test/test_streams_gc.html
new file mode 100644
index 0000000000..d2ba23042b
--- /dev/null
+++ b/dom/media/test/test_streams_gc.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test garbage collection of captured stream (bug 806754)</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>
+</head>
+<body onload="doTest()">
+<audio id="a" preload="metadata"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var a = document.getElementById('a');
+a.src = getPlayableAudio(gSmallTests).name;
+
+function forceGC() {
+ SpecialPowers.gc();
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+}
+
+function doTest() {
+ a.mozCaptureStreamUntilEnded();
+
+ a.addEventListener("seeked", function() {
+ a.play();
+
+ a.addEventListener("play", function() {
+ a.addEventListener("ended", function() {
+ ok(true, "GC completed OK");
+ SimpleTest.finish();
+ });
+ });
+ });
+
+ a.currentTime = a.duration;
+ setTimeout(forceGC, 0);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_individual_pause.html b/dom/media/test/test_streams_individual_pause.html
new file mode 100644
index 0000000000..c49b563d76
--- /dev/null
+++ b/dom/media/test/test_streams_individual_pause.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1073406. Pausing a video element should not pause another playing the same stream.</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="gUM_support.js"></script>
+</head>
+<body>
+<video id="video1" autoplay></video>
+<video id="video2" autoplay></video>
+<script class="testbody" type="text/javascript">
+function getVideoImagePixelData(v) {
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+ ctx.drawImage(v, 0, 0);
+ const imgData = ctx.getImageData(canvas.width/2, canvas.height/2, 1, 1).data;
+ return "r" + imgData[0] +
+ "g" + imgData[1] +
+ "b" + imgData[2] +
+ "a" + imgData[3];
+}
+
+async function startTest() {
+ // This test expects fake devices so that the video color will change
+ // over time, explicitly request fakes.
+ await pushGetUserMediaTestPrefs({fakeAudio: true, fakeVideo: true});
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const video1 = document.getElementById('video1');
+ const video2 = document.getElementById('video2');
+
+ video1.srcObject = stream;
+ video2.srcObject = stream;
+
+ await new Promise(r => video1.onplaying = r);
+ video1.pause();
+ await new Promise(r => video1.onpause = r);
+
+ const v1PausedImageData = getVideoImagePixelData(video1);
+ const v2PausedImageData = getVideoImagePixelData(video2);
+
+ while (getVideoImagePixelData(video2) == v2PausedImageData) {
+ info("video2 has not progressed. Waiting.");
+ await new Promise(r => video2.ontimeupdate = r);
+ }
+
+ // Wait for a while to be sure video1 would have gotten a frame
+ // if it is playing.
+ for (let i = 3; i != 0; i--) {
+ await new Promise(r => video2.ontimeupdate = r);
+ }
+ info("video2 progressed OK");
+
+ isnot(video1.currentTime, video2.currentTime,
+ "v1 and v2 should not be at the same currentTime");
+ is(getVideoImagePixelData(video1), v1PausedImageData,
+ "video1 video frame should not have updated since video1 paused");
+
+ for (const t of stream.getTracks()) {
+ t.stop();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+(async function() {
+ try {
+ await startTest();
+ } catch(error) {
+ ok(false, "getUserMedia should not fail, got " + error.name);
+ } finally {
+ SimpleTest.finish();
+ }
+})();
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_srcObject.html b/dom/media/test/test_streams_srcObject.html
new file mode 100644
index 0000000000..8ea5797d14
--- /dev/null
+++ b/dom/media/test/test_streams_srcObject.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test interactions of src and srcObject</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>
+</head>
+<body onload="doTests()">
+<audio id="a1"></audio>
+<audio id="a2"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var doTest = srcObject => new Promise(resolve => {
+ var a = document.getElementById('a1');
+ a.src = getPlayableAudio(gSmallTests).name;
+ var b = new Audio();
+
+ var newSrc = a.src + "?2";
+ b.src = newSrc;
+ is(b[srcObject], null, "Initial srcObject is null");
+ var stream = a.mozCaptureStream();
+ b[srcObject] = stream;
+ is(b[srcObject], stream, "Stream set correctly");
+ try {
+ b[srcObject] = "invalid";
+ ok(false, "Setting srcObject to an invalid value should throw.");
+ } catch (e) {
+ ok(e instanceof TypeError, "Exception should be a TypeError");
+ }
+ is(b[srcObject], stream, "Stream not set to invalid value");
+ is(b.src, newSrc, "src attribute not affected by setting srcObject");
+ var step = 0;
+ b.addEventListener("loadedmetadata", function() {
+ if (step == 0) {
+ is(b.currentSrc, "", "currentSrc set to empty string while playing srcObject");
+ b[srcObject] = null;
+ is(b[srcObject], null, "Stream set to null");
+ // The resource selection algorithm will run again and choose b.src
+ } else if (step == 1) {
+ is(b.currentSrc, b.src, "currentSrc set to src now that srcObject is null");
+ resolve();
+ }
+ ++step;
+ });
+ a.play();
+ b.play();
+});
+
+var doTests = () => doTest("srcObject")
+ .catch(e => ok(false, "Unexpected error: " + e))
+ .then(() => SimpleTest.finish())
+ .catch(e => ok(false, "Coding error: " + e));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_streams_tracks.html b/dom/media/test/test_streams_tracks.html
new file mode 100644
index 0000000000..845bc36ca3
--- /dev/null
+++ b/dom/media/test/test_streams_tracks.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaStreamTrack interfaces</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+const manager = new MediaTestManager;
+
+function testTracks(tracks, hasTrack, kind, src) {
+ is(tracks.length, hasTrack ? 1 : 0, `Correct ${kind} track count for ${src}`);
+ for (const track of tracks) {
+ is(track.readyState, "live", `Track ${track.id} should still be live`);
+ is(track.kind, kind, `Correct track kind for track ${track.id} of ${src}`);
+ ok(/\{........-....-....-....-............\}/.test(track.id),
+ `id ${track.id} for ${track.kind} track of ${src} has correct form`);
+ }
+}
+
+async function startTest(test, token) {
+ try {
+ info(`Starting test of ${test.name}`);
+ const element = document.createElement("video");
+
+ element.token = token;
+ manager.started(token);
+
+ element.src = test.name;
+ element.test = test;
+ const stream = element.mozCaptureStreamUntilEnded();
+
+ element.play();
+
+ await new Promise(r => element.onloadedmetadata = r);
+
+ testTracks(stream.getAudioTracks(), test.hasAudio, "audio", test.name);
+ testTracks(stream.getVideoTracks(), test.hasVideo, "video", test.name);
+ const tracks = stream.getTracks();
+
+ await new Promise(r => element.onended = r);
+
+ for (let i = 0; i < tracks.length; ++i) {
+ await Promise.race(
+ tracks.map(t => new Promise(r => t.onended = r))
+ );
+ await new Promise(r => stream.onremovetrack = r);
+ }
+
+ testTracks(stream.getAudioTracks(), false, "audio", test.name);
+ testTracks(stream.getVideoTracks(), false, "video", test.name);
+ } catch(e) {
+ ok(false, `Caught error: ${e}`);
+ } finally {
+ manager.finished(token);
+ }
+}
+
+manager.runTests(gTrackTests, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_suspend_media_by_inactive_docshell.html b/dom/media/test/test_suspend_media_by_inactive_docshell.html
new file mode 100644
index 0000000000..7f819ca33b
--- /dev/null
+++ b/dom/media/test/test_suspend_media_by_inactive_docshell.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test suspending media by inactive docShell</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="manifest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<video id="testVideo" src="gizmo.mp4" loop></video>
+<script class="testbody" type="text/javascript">
+/**
+ * When calling `browser.suspendMediaWhenInactive`, it can set the docShell's
+ * corresponding flag that is used to suspend media when the docShell is
+ * inactive. This test is used to check if we can suspend/resume the media
+ * correctly when changing docShell's active state.
+ */
+async function startTest() {
+ const video = document.getElementById("testVideo");
+
+ info(`start video`);
+ await video.play();
+
+ info(`set docShell inactive which would suspend media`);
+ await setDocShellActive(false);
+
+ info(`set docShell active which would resume media`);
+ await setDocShellActive(true);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {"set": [["media.testing-only-events", true]]}, startTest);
+
+/**
+ * The following are test helper functions.
+ */
+function mediaSuspendedStateShouldEqualTo(expected) {
+ const video = document.getElementById("testVideo");
+ const result = SpecialPowers.wrap(video).isSuspendedByInactiveDocOrDocShell;
+ is(result, expected, `media's suspended state is correct`);
+}
+
+function setDocShellActive(isActive) {
+ const win = SpecialPowers.wrap(window);
+ const docShell = win.docShell;
+ const browsingContext = win.browsingContext;
+ // This flag is used to prevent media from playing when docShell is inactive.
+ browsingContext.top.suspendMediaWhenInactive = true;
+ browsingContext.isActive = isActive;
+ // After updating `docshell.isActive`, it would suspend/resume media and we
+ // wait suspending/resuming finishing by listening to `MozMediaSuspendChanged`
+ return new Promise(r => {
+ docShell.chromeEventHandler.addEventListener("MozMediaSuspendChanged",
+ () => {
+ mediaSuspendedStateShouldEqualTo(!isActive);
+ r();
+ }, {once : true}
+ );
+ });
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_temporary_file_blob_video_plays.html b/dom/media/test/test_temporary_file_blob_video_plays.html
new file mode 100644
index 0000000000..87f6b3c4e6
--- /dev/null
+++ b/dom/media/test/test_temporary_file_blob_video_plays.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaRecorder Recording canvas stream</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+</div>
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+ var canvas = document.createElement("canvas");
+ canvas.width = canvas.height = 100;
+ document.getElementById("content").appendChild(canvas);
+
+ var helper = new CaptureStreamTestHelper2D(100, 100);
+ helper.drawColor(canvas, helper.red);
+
+ var stream = canvas.captureStream(0);
+
+ var blob;
+
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the start of recording");
+
+ mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired");
+
+ mediaRecorder.onerror = () => ok(false, "Recording failed");
+
+ mediaRecorder.ondataavailable = ev => {
+ is(blob, undefined, "Should only get one dataavailable event");
+ blob = ev.data;
+ };
+
+ mediaRecorder.onstart = () => {
+ info("Got 'start' event");
+ // We just want one frame encoded, to see that the recorder produces something readable.
+ mediaRecorder.stop();
+ };
+
+ mediaRecorder.onstop = () => {
+ info("Got 'stop' event");
+ ok(blob, "Should have gotten a data blob");
+
+ var video = document.createElement("video");
+ video.id = "recorded-video";
+ video.src = URL.createObjectURL(blob);
+ video.play();
+ video.onerror = err => {
+ ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
+ SimpleTest.finish();
+ };
+ document.getElementById("content").appendChild(video);
+ helper.pixelMustBecome(video, helper.red, {
+ threshold: 128,
+ infoString: "Should become red",
+ }).then(SimpleTest.finish);
+ };
+
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder should be recording");
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set:[["media.recorder.max_memory", 1]]}, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_timeupdate_small_files.html b/dom/media/test/test_timeupdate_small_files.html
new file mode 100644
index 0000000000..fca35e5b71
--- /dev/null
+++ b/dom/media/test/test_timeupdate_small_files.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=495319
+-->
+
+<head>
+ <title>Bug 495319 - playing back small audio files should fire timeupdate</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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=495319">Mozilla Bug 495319</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function ended(e) {
+ var v = e.target;
+ ++v.counter.ended;
+ is(v.counter.ended, 1, v._name + " should see ended only once");
+ ok(v.counter.timeupdate > 0, v._name + " should see at least one timeupdate: " + v.currentTime);
+
+ // Rest event counters for we don't allow events after ended.
+ eventsToLog.forEach(function(event) {
+ v.counter[event] = 0;
+ });
+
+ // Finish the test after 500ms. We shouldn't receive any timeupdate events
+ // after the ended event, so this gives time for any pending timeupdate events
+ // to fire so we can ensure we don't regress behaviour.
+ setTimeout(
+ function() {
+ // Remove the event listeners before removing the video from the document.
+ // We should receive a timeupdate and pause event when we remove the element
+ // from the document (as the element is specified to behave as if pause() was
+ // invoked when it's removed from a document), and we don't want those
+ // confusing the test results.
+ v.removeEventListener("ended", ended);
+ eventsToLog.forEach(function(event) {
+ v.removeEventListener(event, logEvent);
+ });
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+ },
+ 500);
+}
+
+var eventsToLog = ["play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
+ "loadeddata", "playing", "timeupdate", "error", "stalled", "emptied", "abort",
+ "waiting", "pause"];
+
+function logEvent(event) {
+ var v = event.target;
+ ++v.counter[event.type];
+ if (v.counter.ended > 0) {
+ is(v.counter[event.type], 0, v._name + " got unexpected " + event.type + " after ended");
+ }
+}
+
+function startTest(test, token) {
+ var type = getMajorMimeType(test.type);
+ var v = document.createElement(type);
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._name = test.name;
+
+ // Keep how many events received for each event type.
+ v.counter = {};
+ eventsToLog.forEach(function(e) {
+ v.addEventListener(e, logEvent);
+ v.counter[e] = 0;
+ });
+ v.addEventListener("ended", ended);
+ v.counter.ended = 0;
+ document.body.appendChild(v);
+ v.play();
+}
+
+manager.runTests(gSmallTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_unseekable.html b/dom/media/test/test_unseekable.html
new file mode 100644
index 0000000000..e6d69f09f4
--- /dev/null
+++ b/dom/media/test/test_unseekable.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: unseekable</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/*
+
+Test that unseekable media can't be seeked. We load a media that shouldn't
+be seekable, and play through once. While playing through we repeatedly try
+to seek and check that nothing happens when we do. We also verify that the
+seekable ranges are empty.
+
+*/
+
+var manager = new MediaTestManager;
+
+var onseeking = function(event) {
+ var v = event.target;
+ v.actuallySeeked = true;
+};
+
+var onseeked = function(event) {
+ var v = event.target;
+ v.actuallySeeked = true;
+};
+
+var ontimeupdate = function(event) {
+ var v = event.target;
+
+ // Check that when we seek nothing happens.
+ var t = v.currentTime;
+ v.currentTime = v.currentTime /= 2;
+ ok(Math.abs(t - v.currentTime) < 0.01, "Current time shouldn't change when seeking in unseekable media: " + v.name);
+
+ // Check that the seekable ranges are empty.
+ is(v.seekable.length, 0, "Should have no seekable ranges in unseekable media: " + v.name);
+};
+
+var onended = function(event) {
+ var v = event.target;
+
+ // Remove the event listeners so that they can't run if there are any pending
+ // events.
+ v.removeEventListener("seeking", onseeking);
+ v.removeEventListener("seeked", onseeked);
+ v.removeEventListener("timeupdate", ontimeupdate);
+ v.removeEventListener("ended", onended);
+
+ v.src = "";
+ if (v.parentNode) {
+ v.remove();
+ }
+
+ // Verify that none of the seeks we did in timeupdate actually seeked.
+ ok(!v.actuallySeeked, "Should not be able to seek in unseekable media: " + v.name);
+
+ manager.finished(v.token);
+}
+
+function startTest(test, token) {
+ var v = document.createElement('video');
+ manager.started(token);
+ v.name = test.name;
+ v.src = test.name;
+ v.token = token;
+ v.autoplay = "true";
+
+ v.actuallySeeked = false;
+
+ v.addEventListener("seeking", onseeking);
+ v.addEventListener("seeked", onseeked);
+ v.addEventListener("timeupdate", ontimeupdate);
+ v.addEventListener("ended", onended);
+
+ document.body.appendChild(v);
+}
+
+function canPlay(candidates) {
+ var v = document.createElement("video");
+ var resources = candidates.filter(function(x){return v.canPlayType(x.type);});
+ return (resources.length > 0);
+}
+
+if (canPlay(gUnseekableTests)) {
+ manager.runTests(gUnseekableTests, startTest);
+} else {
+ todo(false, "No files of supported format to test");
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_videoDocumentTitle.html b/dom/media/test/test_videoDocumentTitle.html
new file mode 100644
index 0000000000..dd52dba26c
--- /dev/null
+++ b/dom/media/test/test_videoDocumentTitle.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=463830
+-->
+<head>
+ <title>Test for Bug 463830</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=463830">Mozilla Bug 463830</a>
+<p id="display"></p>
+<iframe id="i"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 463830 **/
+
+var gTests = [
+ { file: "320x240.ogv", title: "320x240.ogv" },
+ { file: "bug461281.ogg", title: "bug461281.ogg" },
+];
+
+var gTestNum = 0;
+
+addLoadEvent(runTest);
+
+var title;
+var i = document.getElementById("i");
+
+function runTest() {
+ if (gTestNum == gTests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ if (gTestNum == 0) {
+ i.addEventListener("load", function() {
+ is(i.contentDocument.title, title, "Doc title incorrect");
+ setTimeout(runTest, 0);
+ });
+ }
+
+ title = gTests[gTestNum].title;
+ i.src = gTests[gTestNum].file;
+ gTestNum++;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_videoPlaybackQuality_totalFrames.html b/dom/media/test/test_videoPlaybackQuality_totalFrames.html
new file mode 100644
index 0000000000..1b69a3b64f
--- /dev/null
+++ b/dom/media/test/test_videoPlaybackQuality_totalFrames.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Count the tatol frames of a video</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+var startTest = function(test, token) {
+ manager.started(token);
+ var v = document.createElement('video');
+ v.token = token;
+ document.body.appendChild(v);
+ v.src = test.name;
+
+ function ended(event) {
+ var video = event.target;
+ is(video.getVideoPlaybackQuality().totalVideoFrames, test.totalFrameCount,test.name+ " totalFrames should match!");
+ removeNodeAndSource(video);
+ manager.finished(video.token);
+ }
+ v.addEventListener("ended", ended);
+ v.play();
+};
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ "set": [
+ ["media.decoder.skip-to-next-key-frame.enabled", false],
+ ["media.av1.use-dav1d", true]
+ ]
+ },
+ function() {
+ manager.runTests(getPlayableVideos(gFrameCountTests), startTest);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_video_dimensions.html b/dom/media/test/test_video_dimensions.html
new file mode 100644
index 0000000000..4d9c2a185f
--- /dev/null
+++ b/dom/media/test/test_video_dimensions.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that a video element has set video dimensions on loadedmetadata</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>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+var startTest = function(test, token) {
+ manager.started(token);
+ var v1 = document.createElement('video');
+ var v2 = document.createElement('video');
+ var vout = document.createElement('video');
+
+ // Avoid a race for hardware resources between v1 and v2 on platforms with
+ // a hardware decoder, like B2G.
+ v1.preload = 'none';
+ v2.preload = 'none';
+
+ var numVideoElementsFinished = 0;
+
+ var ondurationchange = function(ev) {
+ var v = ev.target;
+ info(v.testName + " got durationchange");
+ v.durationchange = true;
+ };
+ var onresize = function(ev) {
+ var v = ev.target;
+ info(v.testName + " got resize");
+ ok(!v.resize, v.testName + " should only fire resize once for same size");
+ v.resize = true;
+ ok(v.durationchange, v.testName +
+ " durationchange event should have been emitted before resize");
+ is(v.videoWidth, test.width, v.testName + " width should be set on resize");
+ is(v.videoHeight, test.height, v.testName + " height should be set on resize");
+ };
+ var onloadedmetadata = function(ev) {
+ var v = ev.target;
+ info(v.testName + " got loadedmetadata");
+ ok(!v.loadedmetadata, v.testName + " should only fire loadedmetadata once");
+ v.loadedmetadata = true;
+ ok(v.resize, v.testName +
+ " resize event should have been emitted before loadedmetadata");
+
+ numVideoElementsFinished += 1;
+ if (v === v1) {
+ removeNodeAndSource(v1);
+ v2.load();
+ }
+
+ if (v === v2) {
+ vout.srcObject = v2.mozCaptureStreamUntilEnded();
+ v2.play();
+ vout.play();
+ }
+
+ if (numVideoElementsFinished === 3) {
+ removeNodeAndSource(v2);
+ removeNodeAndSource(vout);
+ manager.finished(token);
+ }
+ };
+ var setupElement = function(v, id) {
+ v.durationchange = false;
+ v.ondurationchange = ondurationchange;
+ v.resize = false;
+ v.onresize = onresize;
+ v.loadedmetadata = false;
+ v.onloadedmetadata = onloadedmetadata;
+ document.body.appendChild(v);
+ };
+
+ v1.testName = test.name;
+ v2.testName = test.name + " (Captured)";
+ vout.testName = test.name + " (Stream)";
+
+ v1.src = test.name;
+ v2.src = test.name;
+
+ setupElement(v1, "v1");
+ setupElement(v2, "v2");
+ setupElement(vout, "vout");
+
+ v1.play();
+};
+
+manager.runTests(getPlayableVideos(gSmallTests), startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_video_gzip_encoding.html b/dom/media/test/test_video_gzip_encoding.html
new file mode 100644
index 0000000000..355a245713
--- /dev/null
+++ b/dom/media/test/test_video_gzip_encoding.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1370177 gzipped mp4 with Content-Length</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>
+ </head>
+ <body>
+ <!--
+ Tests that an MP4 file served over a "Content-Encoding: gzip"
+ HTTP channel with a "Content-Length" header set to the length
+ of the compressed file works.
+ -->
+ <video id='v' src="http://mochi.test:8888/tests/dom/media/test/gzipped_mp4.sjs" controls autoplay></video>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ var v = document.getElementById('v');
+ v.addEventListener("ended", ()=>{
+ SimpleTest.finish();
+ mediaTestCleanup();
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/media/test/test_video_in_audio_element.html b/dom/media/test/test_video_in_audio_element.html
new file mode 100644
index 0000000000..a53adf9414
--- /dev/null
+++ b/dom/media/test/test_video_in_audio_element.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1060896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1060896</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="application/javascript">
+
+ /**
+ * Test for Bug 1060896; tests that loading a video inside an audio element works.
+ **/
+
+ var manager = new MediaTestManager;
+
+ function error(event) {
+ var a = event.target;
+ ok(!a.mozHasAudio, "Media must've had no active tracks to play");
+ a.removeEventListener("error", error);
+ a.removeEventListener("ended", ended);
+ removeNodeAndSource(a);
+ manager.finished(a.token);
+ }
+
+ function ended(event) {
+ var a = event.target;
+ a.removeEventListener("error", error);
+ a.removeEventListener("ended", ended);
+ removeNodeAndSource(a);
+ manager.finished(a.token);
+ }
+
+ function initTest(test, token) {
+ var a = document.createElement('audio');
+ a.token = token;
+ manager.started(token);
+ a.autoplay = true;
+
+ a.addEventListener("error", error);
+ a.addEventListener("ended", ended);
+
+ a.src = test.name;
+ }
+
+ var videos = getPlayableVideos(gSmallTests);
+ // Bug 1216012, skip the test on emulator-kk.
+ if (getAndroidVersion() == 19) {
+ todo(false, "Test disabled on emulator-kk.");
+ } else {
+ manager.runTests(videos, initTest);
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1060896">Mozilla Bug 1060896</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_video_stats_resistfingerprinting.html b/dom/media/test/test_video_stats_resistfingerprinting.html
new file mode 100644
index 0000000000..331e6a8101
--- /dev/null
+++ b/dom/media/test/test_video_stats_resistfingerprinting.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Mozilla Bug:
+https://bugzilla.mozilla.org/show_bug.cgi?id=1369309
+Tor Ticket:
+https://trac.torproject.org/projects/tor/ticket/15757
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1369309</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="manifest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=682299">Mozilla Bug 1369309</a>
+<a target="_blank" href="https://trac.torproject.org/projects/tor/ticket/15757">Tor Ticket 15757</a>
+
+<!-- The main testing script -->
+<script class="testbody" type="text/javascript">
+ var manager = new MediaTestManager;
+ const SPOOFED_FRAMES_PER_SECOND = 30;
+ const SPOOFED_DROPPED_RATIO = 0.05;
+ // Push the setting of 'privacy.resistFingerprinting' into gTestPrefs, which
+ // will be set during MediaTestManager.runTests().
+ gTestPrefs.push(
+ ["privacy.resistFingerprinting", true],
+ ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 100000],
+ ["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false],
+ // We use 240p as the target resoultion since 480p is greater than every video
+ // source in our test suite, so we need to use 240p here for allowing us to
+ // test dropped rate here.
+ ["privacy.resistFingerprinting.target_video_res", 240]
+ );
+ var testCases = [
+ { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.266, drop: false },
+ { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966, drop: false },
+ { name:"gizmo.mp4", type:"video/mp4", width:560, height:320, duration:5.56, drop: true }
+ ];
+
+ function checkStats(v, shouldDrop) {
+ // Rounding the current time to 100ms.
+ let currentTime = Math.floor(v.currentTime * 10) / 10;
+ let dropRate = 0;
+
+ if (shouldDrop) {
+ dropRate = SPOOFED_DROPPED_RATIO;
+ }
+
+ is(v.mozParsedFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND, 10),
+ "mozParsedFrames should be spoofed if fingerprinting resistance is enabled");
+ is(v.mozDecodedFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND, 10),
+ "mozDecodedFrames should be spoofed if fingerprinting resistance is enabled");
+ is(v.mozPresentedFrames,
+ parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND * (1 - dropRate), 10),
+ "mozPresentedFrames should be spoofed if fingerprinting resistance is enabled");
+ is(v.mozPaintedFrames,
+ parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND * (1 - dropRate), 10),
+ "mozPaintedFrames should be spoofed if fingerprinting resistance is enabled");
+ is(v.mozFrameDelay, 0.0,
+ "mozFrameDelay should be 0.0 if fingerprinting resistance is enabled");
+ let playbackQuality = v.getVideoPlaybackQuality();
+ is(playbackQuality.totalVideoFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND, 10),
+ "VideoPlaybackQuality.totalVideoFrames should be spoofed if fingerprinting resistance is enabled");
+ is(playbackQuality.droppedVideoFrames, parseInt(currentTime * SPOOFED_FRAMES_PER_SECOND * dropRate, 10),
+ "VideoPlaybackQuality.droppedVideoFrames should be spoofed if fingerprinting resistance is enabled");
+ }
+
+ function startTest(test, token) {
+ let v = document.createElement("video");
+ v.token = token;
+ v.src = test.name;
+ manager.started(token);
+ once(v, "ended", () => {
+ checkStats(v, test.drop);
+ removeNodeAndSource(v);
+ manager.finished(v.token);
+ });
+ v.play();
+ }
+
+ manager.runTests(testCases, startTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/media/test/test_video_to_canvas.html b/dom/media/test/test_video_to_canvas.html
new file mode 100644
index 0000000000..3267dc63f5
--- /dev/null
+++ b/dom/media/test/test_video_to_canvas.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=486646
+-->
+
+<head>
+ <title>Test for Bug 486646</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function loaded(e) {
+ var v = e.target;
+ ok(v.readyState >= v.HAVE_CURRENT_DATA,
+ "readyState must be >= HAVE_CURRENT_DATA for " + v._name);
+
+ var canvas = document.createElement("canvas");
+ canvas.width = v.videoWidth;
+ canvas.height = v.videoHeight;
+ document.body.appendChild(canvas);
+ var ctx = canvas.getContext("2d");
+ try {
+ ctx.drawImage(v, 0, 0);
+ ok(true, "Shouldn't throw exception while drawing to canvas from video for " + v._name);
+ } catch (ex) {
+ ok(false, "Shouldn't throw exception while drawing to canvas from video for " + v._name);
+ }
+
+ v._finished = true;
+ v.remove();
+ manager.finished(v.token);
+}
+
+function startTest(test, token) {
+ var type = getMajorMimeType(test.type);
+ if (type != "video")
+ return;
+
+ var v = document.createElement('video');
+ v.token = token;
+ manager.started(token);
+ v.src = test.name;
+ v._name = test.name;
+ v._finished = false;
+ v.autoplay = true;
+ v.style.display = "none";
+ v.addEventListener("ended", loaded);
+ document.body.appendChild(v);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, beginTest);
+function beginTest() {
+ manager.runTests(gSmallTests, startTest);
+}
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/media/test/test_volume.html b/dom/media/test/test_volume.html
new file mode 100644
index 0000000000..3d9e5bd91a
--- /dev/null
+++ b/dom/media/test/test_volume.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: volume attribute set</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<video id='v1'></video><audio id='a1'></audio>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function test(element, value, shouldThrow) {
+ var threw = null;
+ try {
+ element.volume = value;
+ } catch (ex) {
+ threw = ex.name;
+ }
+ is(shouldThrow, threw, "Case: " +element.id+ " setVolume=" + value);
+}
+
+
+var ids = [document.getElementById('v1'), document.getElementById('a1')];
+
+for (let i=0; i<ids.length; i++) {
+ var element = ids[i];
+ test(element, 0.0, null);
+ test(element, 1.0, null);
+ test(element, -0.1, "IndexSizeError");
+ test(element, 1.1, "IndexSizeError");
+ test(element, undefined, "TypeError");
+ test(element, NaN, "TypeError");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_vp9_superframes.html b/dom/media/test/test_vp9_superframes.html
new file mode 100644
index 0000000000..c570561b31
--- /dev/null
+++ b/dom/media/test/test_vp9_superframes.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that all VP9 frames are decoded (contains superframes)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var video = document.createElement("video");
+ video.src = "vp9-superframes.webm";
+ video.play();
+ video.addEventListener("ended", function () {
+ let vpq = video.getVideoPlaybackQuality();
+ is(vpq.totalVideoFrames, 120, "totalVideoFrames must contains 120 frames");
+ SimpleTest.finish();
+ });
+}
+
+addLoadEvent(function() {
+ test();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/test/test_wav_ended1.html b/dom/media/test/test_wav_ended1.html
new file mode 100644
index 0000000000..99f6e38243
--- /dev/null
+++ b/dom/media/test/test_wav_ended1.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Wave Media test: ended</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// Test if the ended event works correctly.
+var endPassed = false;
+var completed = false;
+
+function startTest() {
+ if (completed)
+ return;
+ var v = document.getElementById('v');
+ v.play();
+}
+
+function playbackEnded() {
+ if (completed)
+ return;
+
+ var v = document.getElementById('v');
+ completed = true;
+ ok(v.currentTime >= 0.9 && v.currentTime <= 1.1,
+ "Checking currentTime at end: " + v.currentTime);
+ ok(v.ended, "Checking playback has ended");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<audio id='v'
+ onloadedmetadata='return startTest();'
+ onended='return playbackEnded();'>
+ <source type='audio/x-wav' src='r11025_s16_c1.wav'>
+</audio>
+</body>
+</html>
diff --git a/dom/media/test/test_wav_ended2.html b/dom/media/test/test_wav_ended2.html
new file mode 100644
index 0000000000..0c172139f0
--- /dev/null
+++ b/dom/media/test/test_wav_ended2.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Wave Media test: ended and replaying</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<!-- try with autoplay and no v.play in starttest, also with both -->
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// Test if audio can be replayed after ended.
+var completed = false;
+var playingCount = 0;
+var endCount = 0;
+
+function startTest() {
+ if (completed)
+ return;
+
+ var v = document.getElementById('v');
+ v.play();
+}
+
+function playbackStarted() {
+ if (completed)
+ return;
+
+ playingCount++;
+}
+
+function playbackEnded() {
+ if (completed)
+ return;
+
+ endCount++;
+ var v = document.getElementById('v');
+ ok(v.currentTime >= 0.9 && v.currentTime <= 1.1,
+ "Checking currentTime at end: " + v.currentTime);
+ ok(v.ended, "Checking playback has ended");
+ ok(playingCount > 0, "Expect at least one playing event");
+ playingCount = 0;
+ if (endCount < 2) {
+ v.play();
+ } else {
+ ok(endCount == 2, "Check playback after ended event");
+ completed = true;
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<audio id='v'
+ onloadedmetadata='return startTest();'
+ onplaying='return playbackStarted();'
+ onended='return playbackEnded();'>
+ <source type='audio/x-wav' src='r11025_s16_c1.wav'>
+</audio>
+</body>
+</html>
diff --git a/dom/media/test/variable-channel.ogg b/dom/media/test/variable-channel.ogg
new file mode 100644
index 0000000000..77e116889c
--- /dev/null
+++ b/dom/media/test/variable-channel.ogg
Binary files differ
diff --git a/dom/media/test/variable-channel.ogg^headers^ b/dom/media/test/variable-channel.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/variable-channel.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/variable-channel.opus b/dom/media/test/variable-channel.opus
new file mode 100644
index 0000000000..76b8991167
--- /dev/null
+++ b/dom/media/test/variable-channel.opus
Binary files differ
diff --git a/dom/media/test/variable-channel.opus^headers^ b/dom/media/test/variable-channel.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/variable-channel.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/variable-preskip.opus b/dom/media/test/variable-preskip.opus
new file mode 100644
index 0000000000..a82e831ce6
--- /dev/null
+++ b/dom/media/test/variable-preskip.opus
Binary files differ
diff --git a/dom/media/test/variable-preskip.opus^headers^ b/dom/media/test/variable-preskip.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/variable-preskip.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/variable-samplerate.ogg b/dom/media/test/variable-samplerate.ogg
new file mode 100644
index 0000000000..0c2726e835
--- /dev/null
+++ b/dom/media/test/variable-samplerate.ogg
Binary files differ
diff --git a/dom/media/test/variable-samplerate.ogg^headers^ b/dom/media/test/variable-samplerate.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/variable-samplerate.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/variable-samplerate.opus b/dom/media/test/variable-samplerate.opus
new file mode 100644
index 0000000000..1d15170798
--- /dev/null
+++ b/dom/media/test/variable-samplerate.opus
Binary files differ
diff --git a/dom/media/test/variable-samplerate.opus^headers^ b/dom/media/test/variable-samplerate.opus^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/variable-samplerate.opus^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/vbr-head.mp3 b/dom/media/test/vbr-head.mp3
new file mode 100644
index 0000000000..35f4105491
--- /dev/null
+++ b/dom/media/test/vbr-head.mp3
Binary files differ
diff --git a/dom/media/test/vbr-head.mp3^headers^ b/dom/media/test/vbr-head.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/vbr-head.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/vbr.mp3 b/dom/media/test/vbr.mp3
new file mode 100644
index 0000000000..38eb376a97
--- /dev/null
+++ b/dom/media/test/vbr.mp3
Binary files differ
diff --git a/dom/media/test/vbr.mp3^headers^ b/dom/media/test/vbr.mp3^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/vbr.mp3^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/very-short.mp3 b/dom/media/test/very-short.mp3
new file mode 100644
index 0000000000..1d15dcad59
--- /dev/null
+++ b/dom/media/test/very-short.mp3
Binary files differ
diff --git a/dom/media/test/video-overhang.ogg b/dom/media/test/video-overhang.ogg
new file mode 100644
index 0000000000..e11b28fb5b
--- /dev/null
+++ b/dom/media/test/video-overhang.ogg
Binary files differ
diff --git a/dom/media/test/video-overhang.ogg^headers^ b/dom/media/test/video-overhang.ogg^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/video-overhang.ogg^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/vp9-short.webm b/dom/media/test/vp9-short.webm
new file mode 100644
index 0000000000..16d32abee3
--- /dev/null
+++ b/dom/media/test/vp9-short.webm
Binary files differ
diff --git a/dom/media/test/vp9-short.webm^headers^ b/dom/media/test/vp9-short.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/vp9-short.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/vp9-superframes.webm b/dom/media/test/vp9-superframes.webm
new file mode 100644
index 0000000000..d695e42357
--- /dev/null
+++ b/dom/media/test/vp9-superframes.webm
Binary files differ
diff --git a/dom/media/test/vp9-superframes.webm^headers^ b/dom/media/test/vp9-superframes.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/vp9-superframes.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/vp9.webm b/dom/media/test/vp9.webm
new file mode 100644
index 0000000000..221877e303
--- /dev/null
+++ b/dom/media/test/vp9.webm
Binary files differ
diff --git a/dom/media/test/vp9.webm^headers^ b/dom/media/test/vp9.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/vp9.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/vp9cake-short.webm b/dom/media/test/vp9cake-short.webm
new file mode 100644
index 0000000000..2d353d98a7
--- /dev/null
+++ b/dom/media/test/vp9cake-short.webm
Binary files differ
diff --git a/dom/media/test/vp9cake-short.webm^headers^ b/dom/media/test/vp9cake-short.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/vp9cake-short.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/vp9cake.webm b/dom/media/test/vp9cake.webm
new file mode 100644
index 0000000000..4ea70ed302
--- /dev/null
+++ b/dom/media/test/vp9cake.webm
Binary files differ
diff --git a/dom/media/test/vp9cake.webm^headers^ b/dom/media/test/vp9cake.webm^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/vp9cake.webm^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wave_metadata.wav b/dom/media/test/wave_metadata.wav
new file mode 100644
index 0000000000..5e17547c30
--- /dev/null
+++ b/dom/media/test/wave_metadata.wav
Binary files differ
diff --git a/dom/media/test/wave_metadata.wav^headers^ b/dom/media/test/wave_metadata.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wave_metadata.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wave_metadata_bad_len.wav b/dom/media/test/wave_metadata_bad_len.wav
new file mode 100644
index 0000000000..b89c4818be
--- /dev/null
+++ b/dom/media/test/wave_metadata_bad_len.wav
Binary files differ
diff --git a/dom/media/test/wave_metadata_bad_len.wav^headers^ b/dom/media/test/wave_metadata_bad_len.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wave_metadata_bad_len.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wave_metadata_bad_no_null.wav b/dom/media/test/wave_metadata_bad_no_null.wav
new file mode 100644
index 0000000000..18063048c3
--- /dev/null
+++ b/dom/media/test/wave_metadata_bad_no_null.wav
Binary files differ
diff --git a/dom/media/test/wave_metadata_bad_no_null.wav^headers^ b/dom/media/test/wave_metadata_bad_no_null.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wave_metadata_bad_no_null.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wave_metadata_bad_utf8.wav b/dom/media/test/wave_metadata_bad_utf8.wav
new file mode 100644
index 0000000000..b6f2a675b2
--- /dev/null
+++ b/dom/media/test/wave_metadata_bad_utf8.wav
Binary files differ
diff --git a/dom/media/test/wave_metadata_bad_utf8.wav^headers^ b/dom/media/test/wave_metadata_bad_utf8.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wave_metadata_bad_utf8.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wave_metadata_unknown_tag.wav b/dom/media/test/wave_metadata_unknown_tag.wav
new file mode 100644
index 0000000000..b19fb5170f
--- /dev/null
+++ b/dom/media/test/wave_metadata_unknown_tag.wav
Binary files differ
diff --git a/dom/media/test/wave_metadata_unknown_tag.wav^headers^ b/dom/media/test/wave_metadata_unknown_tag.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wave_metadata_unknown_tag.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wave_metadata_utf8.wav b/dom/media/test/wave_metadata_utf8.wav
new file mode 100644
index 0000000000..352db285bb
--- /dev/null
+++ b/dom/media/test/wave_metadata_utf8.wav
Binary files differ
diff --git a/dom/media/test/wave_metadata_utf8.wav^headers^ b/dom/media/test/wave_metadata_utf8.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wave_metadata_utf8.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wavedata_alaw.wav b/dom/media/test/wavedata_alaw.wav
new file mode 100644
index 0000000000..ef090d16e0
--- /dev/null
+++ b/dom/media/test/wavedata_alaw.wav
Binary files differ
diff --git a/dom/media/test/wavedata_alaw.wav^headers^ b/dom/media/test/wavedata_alaw.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wavedata_alaw.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wavedata_float.wav b/dom/media/test/wavedata_float.wav
new file mode 100644
index 0000000000..155f891d51
--- /dev/null
+++ b/dom/media/test/wavedata_float.wav
Binary files differ
diff --git a/dom/media/test/wavedata_float.wav^headers^ b/dom/media/test/wavedata_float.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wavedata_float.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wavedata_s16.wav b/dom/media/test/wavedata_s16.wav
new file mode 100644
index 0000000000..6a69cd78f6
--- /dev/null
+++ b/dom/media/test/wavedata_s16.wav
Binary files differ
diff --git a/dom/media/test/wavedata_s16.wav^headers^ b/dom/media/test/wavedata_s16.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wavedata_s16.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wavedata_s24.wav b/dom/media/test/wavedata_s24.wav
new file mode 100644
index 0000000000..dbdb6aac1e
--- /dev/null
+++ b/dom/media/test/wavedata_s24.wav
Binary files differ
diff --git a/dom/media/test/wavedata_s24.wav^headers^ b/dom/media/test/wavedata_s24.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wavedata_s24.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wavedata_u8.wav b/dom/media/test/wavedata_u8.wav
new file mode 100644
index 0000000000..1d895c2ce0
--- /dev/null
+++ b/dom/media/test/wavedata_u8.wav
Binary files differ
diff --git a/dom/media/test/wavedata_u8.wav^headers^ b/dom/media/test/wavedata_u8.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wavedata_u8.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/media/test/wavedata_ulaw.wav b/dom/media/test/wavedata_ulaw.wav
new file mode 100644
index 0000000000..0874face21
--- /dev/null
+++ b/dom/media/test/wavedata_ulaw.wav
Binary files differ
diff --git a/dom/media/test/wavedata_ulaw.wav^headers^ b/dom/media/test/wavedata_ulaw.wav^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/dom/media/test/wavedata_ulaw.wav^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store