summaryrefslogtreecommitdiffstats
path: root/toolkit/content/tests/widgets/test_videocontrols.html
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/tests/widgets/test_videocontrols.html')
-rw-r--r--toolkit/content/tests/widgets/test_videocontrols.html451
1 files changed, 451 insertions, 0 deletions
diff --git a/toolkit/content/tests/widgets/test_videocontrols.html b/toolkit/content/tests/widgets/test_videocontrols.html
new file mode 100644
index 0000000000..40cb1c2a09
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_videocontrols.html
@@ -0,0 +1,451 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Video controls test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+
+<div id="content">
+ <video width="320" height="240" id="video" controls mozNoDynamicControls preload="auto"></video>
+</div>
+
+<div id="host"></div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/*
+ * Positions of the UI elements, relative to the upper-left corner of the
+ * <video> box.
+ */
+const videoWidth = 320;
+const videoHeight = 240;
+const videoDuration = 3.8329999446868896;
+
+const controlBarMargin = 9;
+
+const playButtonWidth = 30;
+const playButtonHeight = 40;
+const muteButtonWidth = 30;
+const muteButtonHeight = 40;
+const positionAndDurationWidth = 75;
+const fullscreenButtonWidth = 30;
+const fullscreenButtonHeight = 40;
+const volumeSliderWidth = 48;
+const volumeSliderMarginStart = 4;
+const volumeSliderMarginEnd = 6;
+const scrubberMargin = 9;
+const scrubberWidth = videoWidth - controlBarMargin - playButtonWidth - scrubberMargin * 2 - positionAndDurationWidth - muteButtonWidth - volumeSliderMarginStart - volumeSliderWidth - volumeSliderMarginEnd - fullscreenButtonWidth - controlBarMargin;
+const scrubberHeight = 40;
+
+// Play button is on the bottom-left
+const playButtonCenterX = 0 + Math.round(playButtonWidth / 2);
+const playButtonCenterY = videoHeight - Math.round(playButtonHeight / 2);
+// Mute button is on the bottom-right before the full screen button and volume slider
+const muteButtonCenterX = videoWidth - Math.round(muteButtonWidth / 2) - volumeSliderWidth - fullscreenButtonWidth - controlBarMargin;
+const muteButtonCenterY = videoHeight - Math.round(muteButtonHeight / 2);
+// Fullscreen button is on the bottom-right at the far end
+const fullscreenButtonCenterX = videoWidth - Math.round(fullscreenButtonWidth / 2) - controlBarMargin;
+const fullscreenButtonCenterY = videoHeight - Math.round(fullscreenButtonHeight / 2);
+// Scrubber bar is between the play and mute buttons. We don't need it's
+// X center, just the offset of its box.
+const scrubberOffsetX = controlBarMargin + playButtonWidth + scrubberMargin;
+const scrubberCenterY = videoHeight - Math.round(scrubberHeight / 2);
+
+const video = document.getElementById("video");
+
+let expectingEvents;
+let expectingEventPromise;
+
+async function isMuteButtonMuted() {
+ const muteButton = getElementWithinVideo(video, "muteButton");
+ await new Promise(SimpleTest.executeSoon);
+ return muteButton.getAttribute("muted") === "true";
+}
+
+async function isVolumeSliderShowingCorrectVolume(expectedVolume) {
+ const volumeControl = getElementWithinVideo(video, "volumeControl");
+ await new Promise(SimpleTest.executeSoon);
+ is(+volumeControl.value, expectedVolume * 100, "volume slider should match expected volume");
+}
+
+function forceReframe() {
+ // Setting display then getting the bounding rect to force a frame
+ // reconstruction on the video element.
+ video.style.display = "block";
+ video.getBoundingClientRect();
+ video.style.display = "";
+ video.getBoundingClientRect();
+}
+
+function verifyExpectedEvent(event) {
+ const checkingEvent = expectingEvents.shift();
+
+ if (event.type == checkingEvent) {
+ ok(true, "checking event type: " + checkingEvent);
+ } else {
+ expectingEventPromise.reject(new Error(`Got event: ${event.type}, expected: ${checkingEvent}`));
+ }
+
+ if (!expectingEvents.length) {
+ SimpleTest.executeSoon(expectingEventPromise.resolve);
+ }
+}
+
+async function waitForEvent(...eventTypes) {
+ expectingEvents = eventTypes;
+
+ await new Promise((resolve, reject) => expectingEventPromise = {resolve, reject}).catch(e => {
+ // Throw error here to get the caller in error stack.
+ ok(false, e);
+ });
+}
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ "set": [
+ ["media.cache_size", 40000],
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ]});
+ await new Promise(resolve => {
+ video.addEventListener("canplaythrough", resolve, {once: true});
+ video.src = "seek_with_sound.ogg";
+ });
+
+ video.addEventListener("play", verifyExpectedEvent);
+ video.addEventListener("pause", verifyExpectedEvent);
+ video.addEventListener("volumechange", verifyExpectedEvent);
+ video.addEventListener("seeking", verifyExpectedEvent);
+ video.addEventListener("seeked", verifyExpectedEvent);
+ document.addEventListener("mozfullscreenchange", verifyExpectedEvent);
+
+ ["mousedown", "mouseup", "dblclick", "click"]
+ .forEach((eventType) => {
+ window.addEventListener(eventType, (evt) => {
+ // Prevent default action of leaked events and make the tests fail.
+ evt.preventDefault();
+ ok(false, "Event " + eventType + " in videocontrol should not leak to content;" +
+ "the event was dispatched from the " + evt.target.tagName.toLowerCase() + " element.");
+ });
+ });
+
+ // Check initial state upon load
+ is(video.paused, true, "checking video play state");
+ is(video.muted, false, "checking video mute state");
+});
+
+add_task(async function click_playbutton() {
+ synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
+ await waitForEvent("play");
+ is(video.paused, false, "checking video play state");
+ is(video.muted, false, "checking video mute state");
+});
+
+add_task(async function click_pausebutton() {
+ synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
+ await waitForEvent("pause");
+ is(video.paused, true, "checking video play state");
+ is(video.muted, false, "checking video mute state");
+});
+
+add_task(async function mute_volume() {
+ synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
+ await waitForEvent("volumechange");
+ is(video.paused, true, "checking video play state");
+ is(video.muted, true, "checking video mute state");
+});
+
+add_task(async function unmute_volume() {
+ synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
+ await waitForEvent("volumechange");
+ is(video.paused, true, "checking video play state");
+ is(video.muted, false, "checking video mute state");
+});
+
+/*
+ * Bug 470596: Make sure that having CSS border or padding doesn't
+ * break the controls (though it should move them)
+ */
+add_task(async function styled_video() {
+ video.style.border = "medium solid purple";
+ video.style.borderWidth = "30px 40px 50px 60px";
+ video.style.padding = "10px 20px 30px 40px";
+ // totals: top: 40px, right: 60px, bottom: 80px, left: 100px
+
+ // Click the play button
+ synthesizeMouse(video, 100 + playButtonCenterX, 40 + playButtonCenterY, { });
+ await waitForEvent("play");
+ is(video.paused, false, "checking video play state");
+ is(video.muted, false, "checking video mute state");
+
+ // Pause the video
+ video.pause();
+ await waitForEvent("pause");
+ is(video.paused, true, "checking video play state");
+ is(video.muted, false, "checking video mute state");
+
+ // Click the mute button
+ synthesizeMouse(video, 100 + muteButtonCenterX, 40 + muteButtonCenterY, {});
+ await waitForEvent("volumechange");
+ is(video.paused, true, "checking video play state");
+ is(video.muted, true, "checking video mute state");
+
+ // Clear the style set
+ video.style.border = "";
+ video.style.borderWidth = "";
+ video.style.padding = "";
+
+ // Unmute the video
+ video.muted = false;
+ await waitForEvent("volumechange");
+ is(video.paused, true, "checking video play state");
+ is(video.muted, false, "checking video mute state");
+});
+
+/*
+ * Previous tests have moved playback postion, reset it to 0.
+ */
+add_task(async function reset_currentTime() {
+ ok(true, "video position is at " + video.currentTime);
+ video.currentTime = 0.0;
+ await waitForEvent("seeking", "seeked");
+ // Bug 477434 -- sometimes we get 0.098999 here instead of 0!
+ // is(video.currentTime, 0.0, "checking playback position");
+ ok(true, "video position is at " + video.currentTime);
+});
+
+/*
+ * Drag the slider's thumb to the halfway point with the mouse.
+ */
+add_task(async function drag_slider() {
+ const beginDragX = scrubberOffsetX;
+ const endDragX = scrubberOffsetX + (scrubberWidth / 2);
+ const expectedTime = videoDuration / 2;
+
+ function mousemoved(evt) {
+ ok(false, "Mousemove event should not be handled by content while dragging; " +
+ "the event was dispatched from the " + evt.target.tagName.toLowerCase() + " element.");
+ }
+
+ window.addEventListener("mousemove", mousemoved);
+
+ synthesizeMouse(video, beginDragX, scrubberCenterY, {type: "mousedown", button: 0});
+ synthesizeMouse(video, endDragX, scrubberCenterY, {type: "mousemove", button: 0});
+ synthesizeMouse(video, endDragX, scrubberCenterY, {type: "mouseup", button: 0});
+ await waitForEvent("seeking", "seeked");
+ ok(true, "video position is at " + video.currentTime);
+ // The width of srubber is not equal in every platform as we use system default font
+ // in duration and position box. We can not precisely drag to expected position, so
+ // we just make sure the difference is within 10% of video duration.
+ ok(Math.abs(video.currentTime - expectedTime) < videoDuration / 10, "checking expected playback position");
+
+ window.removeEventListener("mousemove", mousemoved);
+});
+
+/*
+ * Click the slider at the 1/4 point with the mouse (jump backwards)
+ */
+add_task(async function click_slider() {
+ synthesizeMouse(video, scrubberOffsetX + (scrubberWidth / 4), scrubberCenterY, {});
+ await waitForEvent("seeking", "seeked");
+ ok(true, "video position is at " + video.currentTime);
+ // The scrubber currently just jumps towards the nearest pageIncrement point, not
+ // precisely to the point clicked. So, expectedTime isn't (videoDuration / 4).
+ // We should end up at 1.733, but sometimes we end up at 1.498. I guess
+ // it's timing depending if the <scale> things it's click-and-hold, or a
+ // single click. So, just make sure we end up less that the previous
+ // position.
+ const lastPosition = (videoDuration / 2) - 0.1;
+ ok(video.currentTime < lastPosition, "checking expected playback position");
+
+ // Set volume to 0.1 so one down arrow hit will decrease it to 0.
+ video.volume = 0.1;
+ await waitForEvent("volumechange");
+ is(video.volume, 0.1, "Volume should be set.");
+ ok(!video.muted, "Video is not muted.");
+});
+
+// See bug 694696.
+add_task(async function change_volume() {
+ video.focus();
+
+ synthesizeKey("KEY_ArrowDown");
+ await waitForEvent("volumechange");
+ is(video.volume, 0, "Volume should be 0.");
+ ok(!video.muted, "Video is not muted.");
+ ok(await isMuteButtonMuted(), "Mute button says it's muted");
+
+ synthesizeKey("KEY_ArrowUp");
+ await waitForEvent("volumechange");
+ is(video.volume, 0.1, "Volume is increased.");
+ ok(!video.muted, "Video is not muted.");
+ ok(!(await isMuteButtonMuted()), "Mute button says it's not muted");
+
+ synthesizeKey("KEY_ArrowDown");
+ await waitForEvent("volumechange");
+ is(video.volume, 0, "Volume should be 0.");
+ ok(!video.muted, "Video is not muted.");
+ ok(await isMuteButtonMuted(), "Mute button says it's muted");
+
+ synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
+ await waitForEvent("volumechange");
+ is(video.volume, 0.5, "Volume should be 0.5.");
+ ok(!video.muted, "Video is not muted.");
+
+ synthesizeKey("KEY_ArrowUp");
+ await waitForEvent("volumechange");
+ is(video.volume, 0.6, "Volume should be 0.6.");
+ ok(!video.muted, "Video is not muted.");
+
+ synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
+ await waitForEvent("volumechange");
+ is(video.volume, 0.6, "Volume should be 0.6.");
+ ok(video.muted, "Video is muted.");
+ ok(await isMuteButtonMuted(), "Mute button says it's muted");
+
+ synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
+ await waitForEvent("volumechange");
+ is(video.volume, 0.6, "Volume should be 0.6.");
+ ok(!video.muted, "Video is not muted.");
+ ok(!(await isMuteButtonMuted()), "Mute button says it's not muted");
+
+ synthesizeMouse(video, fullscreenButtonCenterX, fullscreenButtonCenterY, {});
+ await waitForEvent("mozfullscreenchange");
+ is(video.volume, 0.6, "Volume should still be 0.6");
+ await isVolumeSliderShowingCorrectVolume(video.volume);
+
+ synthesizeKey("KEY_Escape");
+ await waitForEvent("mozfullscreenchange");
+ is(video.volume, 0.6, "Volume should still be 0.6");
+ await isVolumeSliderShowingCorrectVolume(video.volume);
+ forceReframe();
+
+ video.focus();
+ synthesizeKey("KEY_ArrowDown");
+ await waitForEvent("volumechange");
+ is(video.volume, 0.5, "Volume should be decreased by 0.1");
+ await isVolumeSliderShowingCorrectVolume(video.volume);
+});
+
+add_task(async function whitespace_pause_video() {
+ synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
+ await waitForEvent("play");
+
+ video.focus();
+ sendString(" ");
+ await waitForEvent("pause");
+
+ synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
+ await waitForEvent("play");
+});
+
+/*
+ * Bug 1352724: Click and hold on timeline should pause video immediately.
+ */
+add_task(async function click_and_hold_slider() {
+ synthesizeMouse(video, scrubberOffsetX + 10, scrubberCenterY, {type: "mousedown", button: 0});
+ await waitForEvent("pause", "seeking", "seeked");
+
+ synthesizeMouse(video, scrubberOffsetX + 10, scrubberCenterY, {});
+ await waitForEvent("play");
+});
+
+/*
+ * Bug 1402877: Don't let click event dispatch through media controls to video element.
+ */
+add_task(async function click_event_dispatch() {
+ const clientScriptClickHandler = (e) => {
+ ok(false, "Should not receive the event");
+ };
+ video.addEventListener("click", clientScriptClickHandler);
+
+ video.pause();
+ await waitForEvent("pause");
+ video.currentTime = 0.0;
+ await waitForEvent("seeking", "seeked");
+ is(video.paused, true, "checking video play state");
+ synthesizeMouse(video, scrubberOffsetX + 10, scrubberCenterY, {});
+ await waitForEvent("seeking", "seeked");
+
+ video.removeEventListener("click", clientScriptClickHandler);
+});
+
+// Bug 1367194: Always ensure video is paused before finishing the test.
+add_task(async function ensure_video_pause() {
+ if (!video.paused) {
+ video.pause();
+ await waitForEvent("pause");
+ }
+});
+
+// Bug 1452342: Make sure the cursor hides and shows on full screen mode.
+add_task(async function ensure_fullscreen_cursor() {
+ video.removeAttribute("mozNoDynamicControls");
+ video.play();
+ await waitForEvent("play");
+
+ video.mozRequestFullScreen();
+ await waitForEvent("mozfullscreenchange");
+
+ const controlsSpacer = getElementWithinVideo(video, "controlsSpacer");
+ is(controlsSpacer.hasAttribute("hideCursor"), true, "Cursor is hidden");
+
+ let delta = 1;
+ await SimpleTest.promiseWaitForCondition(() => {
+ // Wiggle the mouse a bit
+ synthesizeMouse(video, playButtonCenterX + delta, playButtonCenterY + delta, { type: "mousemove" });
+ delta = !delta;
+ return !controlsSpacer.hasAttribute("hideCursor");
+ }, "Waiting for hideCursor attribute to disappear");
+ is(controlsSpacer.hasAttribute("hideCursor"), false, "Cursor is shown");
+
+ // Restore
+ video.setAttribute("mozNoDynamicControls", "");
+ document.mozCancelFullScreen();
+ await waitForEvent("mozfullscreenchange");
+ if (!video.paused) {
+ video.pause();
+ await waitForEvent("pause");
+ }
+});
+
+// Bug 1505547: Make sure the fullscreen button works if the video element is in shadow tree.
+add_task(async function ensure_fullscreen_button() {
+ video.removeAttribute("mozNoDynamicControls");
+ let host = document.getElementById("host");
+ let root = host.attachShadow({ mode: "open" });
+ root.appendChild(video);
+
+ video.mozRequestFullScreen();
+ await waitForEvent("mozfullscreenchange");
+
+ // Compute the location to click on to hit the fullscreen button.
+ // Use the video size instead of the screen size here, because mozfullscreenchange does not
+ // guarantee that our document covers the screen, see bug 1575630.
+ const r = video.getBoundingClientRect();
+ const buttonCenterX = r.right - fullscreenButtonWidth / 2 - controlBarMargin;
+ const buttonCenterY = r.bottom - fullscreenButtonHeight / 2;
+
+ // Wiggle the mouse a bit
+ synthesizeMouse(video, buttonCenterX, buttonCenterY, { type: "mousemove" });
+
+ synthesizeMouse(video, buttonCenterX, buttonCenterY, {});
+ await waitForEvent("mozfullscreenchange");
+
+ // Restore
+ video.setAttribute("mozNoDynamicControls", "");
+ document.getElementById("content").appendChild(video);
+});
+
+</script>
+</pre>
+</body>
+</html>