From 29e7fe12a88021122bd85103b2115d68c28230a8 Mon Sep 17 00:00:00 2001 From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:25:10 +0200 Subject: [PATCH] gh-13264: Add tests for media player (gh-13262) --- src/zen/common/styles/zen-buttons.css | 4 + src/zen/tests/manifest.toml | 4 + src/zen/tests/media/browser.toml | 16 ++ src/zen/tests/media/browser_media_metadata.js | 63 +++++ src/zen/tests/media/browser_media_mute.js | 64 +++++ .../tests/media/browser_media_next_track.js | 74 +++++ .../browser_media_shows_on_tab_switch.js | 74 +++++ src/zen/tests/media/head.js | 96 +++++++ src/zen/tests/mochitests/moz.build | 1 + .../almostSilentAudioTrack.webm | Bin 0 -> 1699661 bytes .../mochitests/tabMediaIndicator/audio.ogg | Bin 0 -> 14293 bytes .../audioEndedDuringPlaying.webm | Bin 0 -> 109366 bytes .../mochitests/tabMediaIndicator/browser.toml | 44 +++ .../browser_destroy_iframe.js | 50 ++++ .../browser_mediaPlayback.js | 42 +++ .../browser_mediaPlayback_mute.js | 118 ++++++++ ...browser_mediaplayback_audibility_change.js | 258 ++++++++++++++++++ .../tabMediaIndicator/browser_mute.js | 19 ++ .../tabMediaIndicator/browser_mute2.js | 32 +++ .../browser_mute_webAudio.js | 72 +++++ .../browser_sound_indicator_silent_video.js | 95 +++++++ .../browser_webAudio_hideSoundPlayingIcon.js | 60 ++++ .../browser_webAudio_silentData.js | 57 ++++ .../browser_webaudio_audibility_change.js | 172 ++++++++++++ .../file_almostSilentAudioTrack.html | 18 ++ .../file_autoplay_media.html | 9 + .../tabMediaIndicator/file_empty.html | 8 + .../tabMediaIndicator/file_mediaPlayback.html | 9 + .../file_mediaPlayback2.html | 14 + .../file_mediaPlaybackFrame.html | 2 + .../file_mediaPlaybackFrame2.html | 2 + .../file_silentAudioTrack.html | 18 ++ .../tabMediaIndicator/file_webAudio.html | 29 ++ .../mochitests/tabMediaIndicator/gizmo.mp4 | Bin 0 -> 455255 bytes .../mochitests/tabMediaIndicator/head.js | 165 +++++++++++ .../mochitests/tabMediaIndicator/noaudio.webm | Bin 0 -> 105755 bytes .../tabMediaIndicator/silentAudioTrack.webm | Bin 0 -> 224800 bytes src/zen/tests/moz.build | 1 + 38 files changed, 1690 insertions(+) create mode 100644 src/zen/tests/media/browser.toml create mode 100644 src/zen/tests/media/browser_media_metadata.js create mode 100644 src/zen/tests/media/browser_media_mute.js create mode 100644 src/zen/tests/media/browser_media_next_track.js create mode 100644 src/zen/tests/media/browser_media_shows_on_tab_switch.js create mode 100644 src/zen/tests/media/head.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/almostSilentAudioTrack.webm create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/audio.ogg create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/audioEndedDuringPlaying.webm create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser.toml create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_destroy_iframe.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_mediaPlayback.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_mediaPlayback_mute.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_mediaplayback_audibility_change.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_mute.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_mute2.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_mute_webAudio.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_sound_indicator_silent_video.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_webAudio_hideSoundPlayingIcon.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_webAudio_silentData.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/browser_webaudio_audibility_change.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_almostSilentAudioTrack.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_autoplay_media.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_empty.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_mediaPlayback.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_mediaPlayback2.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_mediaPlaybackFrame.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_mediaPlaybackFrame2.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_silentAudioTrack.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/file_webAudio.html create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/gizmo.mp4 create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/head.js create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/noaudio.webm create mode 100644 src/zen/tests/mochitests/tabMediaIndicator/silentAudioTrack.webm diff --git a/src/zen/common/styles/zen-buttons.css b/src/zen/common/styles/zen-buttons.css index d94c2c367..a3dfd404a 100644 --- a/src/zen/common/styles/zen-buttons.css +++ b/src/zen/common/styles/zen-buttons.css @@ -38,6 +38,10 @@ dialog[defaultButton="accept"]::part(dialog-button) { padding-inline-end: 4em; } + @media (-moz-platform: linux) { + padding-inline-end: 3.1em; + } + &::after { border-radius: 4px; font-weight: 600; diff --git a/src/zen/tests/manifest.toml b/src/zen/tests/manifest.toml index 60c0975dc..18334b2f8 100644 --- a/src/zen/tests/manifest.toml +++ b/src/zen/tests/manifest.toml @@ -25,5 +25,9 @@ disable = [ "browser_setDesktopBackgroundPreview.js", ] +[tabMediaIndicator] +source = "browser/components/tabbrowser/test/browser/tabMediaIndicator" +is_direct_path = true + [tooltiptext] source = "toolkit/components/tooltiptext" diff --git a/src/zen/tests/media/browser.toml b/src/zen/tests/media/browser.toml new file mode 100644 index 000000000..52752f239 --- /dev/null +++ b/src/zen/tests/media/browser.toml @@ -0,0 +1,16 @@ +# 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/. + +[DEFAULT] +support-files = [ + "head.js", +] + +["browser_media_metadata.js"] + +["browser_media_mute.js"] + +["browser_media_next_track.js"] + +["browser_media_shows_on_tab_switch.js"] diff --git a/src/zen/tests/media/browser_media_metadata.js b/src/zen/tests/media/browser_media_metadata.js new file mode 100644 index 000000000..a048b8cac --- /dev/null +++ b/src/zen/tests/media/browser_media_metadata.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// User flow: +// 1. A page (think Spotify, YouTube Music) plays media and publishes +// title/artist via navigator.mediaSession.metadata. +// 2. User switches off that tab, media bar appears. +// 3. The title and artist labels in the bar show what the page published. +// 4. The page then updates the metadata mid-playback (next song starts). +// 5. The bar updates live, without the user having to switch tabs again. +// +// This is what makes the bar feel connected to the playing page instead of +// a generic "something is playing" indicator. + +add_task(async function test_media_bar_shows_metadata_from_page() { + const originalTab = gBrowser.selectedTab; + const mediaTab = await addMediaTab(); + await BrowserTestUtils.switchTab(gBrowser, mediaTab); + + try { + await setMediaSessionMetadata(mediaTab, { + title: "Sandstorm", + artist: "Darude", + }); + await playVideoIn(mediaTab); + await BrowserTestUtils.switchTab(gBrowser, originalTab); + await waitForMediaBarVisible(); + + const titleEl = document.getElementById("zen-media-title"); + const artistEl = document.getElementById("zen-media-artist"); + + await BrowserTestUtils.waitForCondition( + () => titleEl.textContent === "Sandstorm", + "title label reflects the page's mediaSession metadata" + ); + Assert.equal( + artistEl.textContent, + "Darude", + "artist label reflects the page's mediaSession metadata" + ); + + // Page updates metadata mid-playback. + await setMediaSessionMetadata(mediaTab, { + title: "Levels", + artist: "Avicii", + }); + await BrowserTestUtils.waitForCondition( + () => titleEl.textContent === "Levels", + "title updates live when the page changes its mediaSession metadata" + ); + Assert.equal( + artistEl.textContent, + "Avicii", + "artist updates live alongside the title" + ); + } finally { + await pauseVideoIn(mediaTab); + BrowserTestUtils.removeTab(mediaTab); + gBrowser.selectedTab = originalTab; + } +}); diff --git a/src/zen/tests/media/browser_media_mute.js b/src/zen/tests/media/browser_media_mute.js new file mode 100644 index 000000000..766059fd0 --- /dev/null +++ b/src/zen/tests/media/browser_media_mute.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// User flow: +// 1. User plays a video, switches tabs, media bar appears. +// 2. User clicks the mute button on the Zen media bar. +// 3. The underlying tab actually goes silent (browser.audioMuted flips). +// 4. The media bar reflects that with the `muted` attribute so the icon +// changes. +// 5. Clicking again unmutes. +// +// If this breaks, the user sees a mute button that looks toggled but the +// audio keeps playing — or worse, the tab is muted but the button still +// says "unmuted". + +add_task(async function test_mute_from_media_bar() { + const originalTab = gBrowser.selectedTab; + const mediaTab = await addMediaTab(); + await BrowserTestUtils.switchTab(gBrowser, mediaTab); + + try { + await playVideoIn(mediaTab); + await BrowserTestUtils.switchTab(gBrowser, originalTab); + await waitForMediaBarVisible(); + + ok( + !mediaTab.linkedBrowser.audioMuted, + "precondition: playing tab starts unmuted" + ); + ok( + !mediaBar().hasAttribute("muted"), + "precondition: media bar has no muted attribute" + ); + + clickMediaButton("zen-media-mute-button"); + await BrowserTestUtils.waitForCondition( + () => mediaTab.linkedBrowser.audioMuted, + "tab becomes muted after clicking the media bar mute button" + ); + ok( + mediaBar().hasAttribute("muted"), + "media bar reflects the muted state in its attribute" + ); + + clickMediaButton("zen-media-mute-button"); + await BrowserTestUtils.waitForCondition( + () => !mediaTab.linkedBrowser.audioMuted, + "clicking again unmutes the tab" + ); + ok( + !mediaBar().hasAttribute("muted"), + "media bar drops the muted attribute" + ); + } finally { + if (mediaTab.linkedBrowser.audioMuted) { + mediaTab.toggleMuteAudio(); + } + await pauseVideoIn(mediaTab); + BrowserTestUtils.removeTab(mediaTab); + gBrowser.selectedTab = originalTab; + } +}); diff --git a/src/zen/tests/media/browser_media_next_track.js b/src/zen/tests/media/browser_media_next_track.js new file mode 100644 index 000000000..f4797e7e0 --- /dev/null +++ b/src/zen/tests/media/browser_media_next_track.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// User flow: +// 1. A music page registers a "nexttrack" action handler (like most +// streaming sites do). +// 2. User is on another tab, media bar is showing with the next-track +// button enabled. +// 3. User clicks next-track. +// 4. The action fires inside the page — the page is responsible for +// loading the next song. Zen's job here is to relay the click. +// +// Also guards the button-enablement logic: if the page does NOT register a +// handler, the next-track button must be disabled. Otherwise clicks go +// nowhere and users think the bar is broken. + +add_task(async function test_next_track_relays_to_page() { + const originalTab = gBrowser.selectedTab; + const mediaTab = await addMediaTab(); + await BrowserTestUtils.switchTab(gBrowser, mediaTab); + + try { + await playVideoIn(mediaTab); + await setMediaSessionActionHandler(mediaTab, "nexttrack"); + + await BrowserTestUtils.switchTab(gBrowser, originalTab); + await waitForMediaBarVisible(); + + const nextButton = document.getElementById("zen-media-nexttrack-button"); + + // supportedkeyschange propagates asynchronously; wait for the bar's + // next-track button to become enabled before clicking. + await BrowserTestUtils.waitForCondition( + () => !nextButton.disabled, + "next-track button becomes enabled once the page registers a handler" + ); + + const actionFired = waitForMediaSessionAction(mediaTab); + clickMediaButton("zen-media-nexttrack-button"); + + const result = await actionFired; + ok(result, "page's nexttrack MediaSession handler was invoked"); + } finally { + await pauseVideoIn(mediaTab); + BrowserTestUtils.removeTab(mediaTab); + gBrowser.selectedTab = originalTab; + } +}); + +add_task(async function test_next_track_button_disabled_without_handler() { + const originalTab = gBrowser.selectedTab; + const mediaTab = await addMediaTab(); + await BrowserTestUtils.switchTab(gBrowser, mediaTab); + + try { + // Deliberately do NOT install a nexttrack handler. + await playVideoIn(mediaTab); + await BrowserTestUtils.switchTab(gBrowser, originalTab); + await waitForMediaBarVisible(); + + const nextButton = document.getElementById("zen-media-nexttrack-button"); + Assert.equal( + nextButton.disabled, + true, + "next-track button stays disabled when the page registers no handler" + ); + } finally { + await pauseVideoIn(mediaTab); + BrowserTestUtils.removeTab(mediaTab); + gBrowser.selectedTab = originalTab; + } +}); diff --git a/src/zen/tests/media/browser_media_shows_on_tab_switch.js b/src/zen/tests/media/browser_media_shows_on_tab_switch.js new file mode 100644 index 000000000..783a88af1 --- /dev/null +++ b/src/zen/tests/media/browser_media_shows_on_tab_switch.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// User flow: +// 1. User opens a page with audio and hits play. +// 2. User switches to a different tab. +// 3. The Zen media control bar should appear (so the user can still +// pause/skip without going back to the noisy tab). +// 4. User switches back to the audio tab. +// 5. The media bar should hide again — it's redundant next to the real +// page controls. +// +// This covers the real contract users see: the DOMAudioPlaybackStarted → +// TabSelect → showMediaControls chain in nsZenMediaController, plus the +// inverse path on selecting the playing tab. A regression anywhere in that +// chain (event wiring, the 500ms tab-switch debounce, the hidden attribute +// flip) surfaces as a bar that either never shows or never hides. + +// note: We keep setting timeouts because media player takes a bit to +// get removed (after the animation, more specifically) + +add_task(async function test_media_bar_shows_when_switching_off_playing_tab() { + gZenMediaController.onControllerClose(); + await BrowserTestUtils.waitForCondition( + () => !isMediaBarVisible(), + "media bar hides again once the playing tab regains focus" + ); + + const originalTab = gBrowser.selectedTab; + const mediaTab = await addMediaTab(); + await BrowserTestUtils.switchTab(gBrowser, mediaTab); + + ok( + !isMediaBarVisible(), + "media bar is hidden while the playing tab is the active tab" + ); + + try { + await playVideoIn(mediaTab); + + ok( + !isMediaBarVisible(), + "media bar remains hidden while focused on the playing tab" + ); + + // Switch away. The controller schedules showMediaControls() on a 500ms + // timer; wait for the visibility flip rather than racing it. + await BrowserTestUtils.switchTab(gBrowser, originalTab); + await new Promise(r => setTimeout(r, 1000)); + await BrowserTestUtils.waitForCondition( + isMediaBarVisible, + "media bar becomes visible after switching off the playing tab" + ); + + Assert.equal( + gZenMediaController._currentBrowser?.browserId, + mediaTab.linkedBrowser.browserId, + "media controller is bound to the media tab's browser, not the selected tab" + ); + + await BrowserTestUtils.switchTab(gBrowser, mediaTab); + await new Promise(r => setTimeout(r, 1000)); + await BrowserTestUtils.waitForCondition( + () => !isMediaBarVisible(), + "media bar hides again once the playing tab regains focus" + ); + } finally { + await pauseVideoIn(mediaTab); + BrowserTestUtils.removeTab(mediaTab); + gBrowser.selectedTab = originalTab; + } +}); diff --git a/src/zen/tests/media/head.js b/src/zen/tests/media/head.js new file mode 100644 index 000000000..82de52c6c --- /dev/null +++ b/src/zen/tests/media/head.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Shared mozilla-central fixture from the Picture-in-Picture tests: an HTML +// page with two looping