diff options
Diffstat (limited to 'toolkit/components/pictureinpicture/tests/browser_improved_controls.js')
-rw-r--r-- | toolkit/components/pictureinpicture/tests/browser_improved_controls.js | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/toolkit/components/pictureinpicture/tests/browser_improved_controls.js b/toolkit/components/pictureinpicture/tests/browser_improved_controls.js new file mode 100644 index 0000000000..deadc8d53b --- /dev/null +++ b/toolkit/components/pictureinpicture/tests/browser_improved_controls.js @@ -0,0 +1,303 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const TEST_PAGE_LONG = TEST_ROOT + "test-video-selection.html"; + +const IMPROVED_CONTROLS_ENABLED_PREF = + "media.videocontrols.picture-in-picture.improved-video-controls.enabled"; + +async function getVideoCurrentTime(browser, videoID) { + return SpecialPowers.spawn(browser, [videoID], async videoID => { + return content.document.getElementById(videoID).currentTime; + }); +} + +async function getVideoDuration(browser, videoID) { + return SpecialPowers.spawn(browser, [videoID], async videoID => { + return content.document.getElementById(videoID).duration; + }); +} + +async function timestampUpdated(timestampEl, expectedTimestamp) { + await BrowserTestUtils.waitForMutationCondition( + timestampEl, + { childList: true }, + () => { + return expectedTimestamp === timestampEl.textContent; + } + ); +} + +function checkTimeCloseEnough(actual, expected, message) { + let equal = Math.abs(actual - expected); + if (equal <= 0.5) { + is(equal <= 0.5, true, message); + } else { + is(actual, expected, message); + } +} + +/** + * Tests the functionality of improved Picture-in-picture + * playback controls. + */ +add_task(async () => { + let videoID = "with-controls"; + + await BrowserTestUtils.withNewTab( + { + url: TEST_PAGE, + gBrowser, + }, + async browser => { + let waitForVideoEvent = eventType => { + return BrowserTestUtils.waitForContentEvent(browser, eventType, true); + }; + + await ensureVideosReady(browser); + await SpecialPowers.spawn(browser, [videoID], async videoID => { + await content.document.getElementById(videoID).play(); + }); + + await SpecialPowers.pushPrefEnv({ + set: [[IMPROVED_CONTROLS_ENABLED_PREF, true]], + }); + + // Open the video in PiP + let pipWin = await triggerPictureInPicture(browser, videoID); + ok(pipWin, "Got Picture-in-Picture window."); + + let fullscreenButton = pipWin.document.getElementById("fullscreen"); + let seekForwardButton = pipWin.document.getElementById("seekForward"); + let seekBackwardButton = pipWin.document.getElementById("seekBackward"); + + // Try seek forward button + let seekedForwardPromise = waitForVideoEvent("seeked"); + EventUtils.synthesizeMouseAtCenter(seekForwardButton, {}, pipWin); + ok(await seekedForwardPromise, "The Forward button triggers"); + + // Try seek backward button + let seekedBackwardPromise = waitForVideoEvent("seeked"); + EventUtils.synthesizeMouseAtCenter(seekBackwardButton, {}, pipWin); + ok(await seekedBackwardPromise, "The Backward button triggers"); + + // The Fullsreen button appears when the pref is enabled and the fullscreen hidden property is set to false + Assert.ok(!fullscreenButton.hidden, "The Fullscreen button is visible"); + + // The seek Forward button appears when the pref is enabled and the seek forward button hidden property is set to false + Assert.ok(!seekForwardButton.hidden, "The Forward button is visible"); + + // The seek Backward button appears when the pref is enabled and the seek backward button hidden property is set to false + Assert.ok(!seekBackwardButton.hidden, "The Backward button is visible"); + + // CLose the PIP window + let pipClosed = BrowserTestUtils.domWindowClosed(pipWin); + let closeButton = pipWin.document.getElementById("close"); + EventUtils.synthesizeMouseAtCenter(closeButton, {}, pipWin); + await pipClosed; + + await SpecialPowers.pushPrefEnv({ + set: [[IMPROVED_CONTROLS_ENABLED_PREF, false]], + }); + + // Open the video in PiP + pipWin = await triggerPictureInPicture(browser, videoID); + ok(pipWin, "Got Picture-in-Picture window."); + + fullscreenButton = pipWin.document.getElementById("fullscreen"); + seekForwardButton = pipWin.document.getElementById("seekForward"); + seekBackwardButton = pipWin.document.getElementById("seekBackward"); + + // The Fullsreen button disappears when the pref is disabled and the fullscreen hidden property is set to true + Assert.ok( + fullscreenButton.hidden, + "The Fullscreen button is not visible" + ); + + // The seek Forward button disappears when the pref is disabled and the seek forward button hidden property is set to true + Assert.ok(seekForwardButton.hidden, "The Forward button is not visible"); + + // The seek Backward button disappears when the pref is disabled and the seek backward button hidden property is set to true + Assert.ok( + seekBackwardButton.hidden, + "The Backward button is not visible" + ); + } + ); +}); + +/** + * Tests the functionality of Picture-in-picture + * video scrubber + */ +add_task(async function testVideoScrubber() { + let videoID = "long"; + + await BrowserTestUtils.withNewTab( + { + url: TEST_PAGE_LONG, + gBrowser, + }, + async browser => { + await ensureVideosReady(browser); + + await SpecialPowers.pushPrefEnv({ + set: [[IMPROVED_CONTROLS_ENABLED_PREF, true]], + }); + + // Open the video in PiP + let pipWin = await triggerPictureInPicture(browser, videoID); + ok(pipWin, "Got Picture-in-Picture window."); + + let scrubber = pipWin.document.getElementById("scrubber"); + scrubber.focus(); + + let currentTime = await getVideoCurrentTime(browser, videoID); + let expectedVideoTime = 0; + const duration = await getVideoDuration(browser, videoID); + checkTimeCloseEnough( + currentTime, + expectedVideoTime, + "Video current time is 0" + ); + + let timestampEl = pipWin.document.getElementById("timestamp"); + let expectedTimestamp = "0:00 / 0:08"; + + // Wait for the timestamp to update + await timestampUpdated(timestampEl, expectedTimestamp); + let actualTimestamp = timestampEl.textContent; + is(actualTimestamp, expectedTimestamp, "Timestamp reads 0:00 / 0:08"); + + EventUtils.synthesizeKey("KEY_ArrowRight", {}, pipWin); + + currentTime = await getVideoCurrentTime(browser, videoID); + expectedVideoTime = 5; + checkTimeCloseEnough( + currentTime, + expectedVideoTime, + "Video current time is 5" + ); + + expectedTimestamp = "0:05 / 0:08"; + await timestampUpdated(timestampEl, expectedTimestamp); + actualTimestamp = timestampEl.textContent; + is(actualTimestamp, expectedTimestamp, "Timestamp reads 0:05 / 0:08"); + + EventUtils.synthesizeKey("KEY_ArrowLeft", {}, pipWin); + + currentTime = await getVideoCurrentTime(browser, videoID); + expectedVideoTime = 0; + checkTimeCloseEnough( + currentTime, + expectedVideoTime, + "Video current time is 0" + ); + + expectedTimestamp = "0:00 / 0:08"; + await timestampUpdated(timestampEl, expectedTimestamp); + actualTimestamp = timestampEl.textContent; + is(actualTimestamp, expectedTimestamp, "Timestamp reads 0:00 / 0:08"); + + let rect = scrubber.getBoundingClientRect(); + + EventUtils.synthesizeMouse( + scrubber, + rect.width / 2, + rect.height / 2, + {}, + pipWin + ); + + expectedVideoTime = duration / 2; + currentTime = await getVideoCurrentTime(browser, videoID); + checkTimeCloseEnough( + currentTime, + expectedVideoTime, + "Video current time is 3.98..." + ); + + expectedTimestamp = "0:04 / 0:08"; + await timestampUpdated(timestampEl, expectedTimestamp); + actualTimestamp = timestampEl.textContent; + is(actualTimestamp, expectedTimestamp, "Timestamp reads 0:04 / 0:08"); + + EventUtils.synthesizeMouse( + scrubber, + rect.width / 2, + rect.height / 2, + { type: "mousedown" }, + pipWin + ); + + EventUtils.synthesizeMouse( + scrubber, + rect.width, + rect.height / 2, + { type: "mousemove" }, + pipWin + ); + + EventUtils.synthesizeMouse( + scrubber, + rect.width, + rect.height / 2, + { type: "mouseup" }, + pipWin + ); + + expectedVideoTime = duration; + currentTime = await getVideoCurrentTime(browser, videoID); + checkTimeCloseEnough( + currentTime, + expectedVideoTime, + "Video current time is 7.96..." + ); + + await ensureMessageAndClosePiP(browser, videoID, pipWin, false); + } + ); +}); + +/** + * Tests the behavior of the scrubber and position/duration indicator for a + * video with an invalid/non-finite duration. + */ +add_task(async function testInvalidDuration() { + await BrowserTestUtils.withNewTab( + { + url: TEST_PAGE_WITH_NAN_VIDEO_DURATION, + gBrowser, + }, + async browser => { + const videoID = "nan-duration"; + + // This tests skips calling ensureVideosReady, because canplaythrough + // will never fire for the NaN duration video. + + await SpecialPowers.pushPrefEnv({ + set: [[IMPROVED_CONTROLS_ENABLED_PREF, true]], + }); + + // Open the video in PiP + let pipWin = await triggerPictureInPicture(browser, videoID); + ok(pipWin, "Got Picture-in-Picture window."); + + // Both the scrubber and the duration should be hidden. + let timestampEl = pipWin.document.getElementById("timestamp"); + ok(timestampEl.hidden, "Timestamp in the PIP window should be hidden."); + + let scrubberEl = pipWin.document.getElementById("scrubber"); + ok( + scrubberEl.hidden, + "Scrubber control in the PIP window should be hidden" + ); + } + ); +}); |