gh-13264: Add tests for media player (gh-13262)

This commit is contained in:
mr. m
2026-04-15 20:25:10 +02:00
committed by GitHub
parent b988f23a14
commit 29e7fe12a8
38 changed files with 1690 additions and 0 deletions

View File

@@ -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;

View File

@@ -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"

View File

@@ -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"]

View File

@@ -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;
}
});

View File

@@ -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;
}
});

View File

@@ -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;
}
});

View File

@@ -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;
}
});

View File

@@ -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 <video src="gizmo.mp4"> elements (with and without
// controls). Looping keeps playback stable across slow CI, and using a real
// <video> element is closer to what Zen users actually play (YouTube, etc.)
// than a bare <audio>.
const MEDIA_PAGE =
"https://example.com/browser/toolkit/components/pictureinpicture/tests/test-page-with-sound.html";
const VIDEO_SELECTOR = "#with-controls";
async function addMediaTab() {
const tab = BrowserTestUtils.addTab(gBrowser, MEDIA_PAGE, {
skipAnimation: true,
});
await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
return tab;
}
async function playVideoIn(tab) {
await SpecialPowers.spawn(
tab.linkedBrowser,
[VIDEO_SELECTOR],
async selector => {
const video = content.document.querySelector(selector);
await video.play();
}
);
// Wait for the browser to actually consider the tab "playing" — this is
// what drives DOMAudioPlaybackStarted into the media controller.
await BrowserTestUtils.waitForCondition(
() => tab.soundPlaying,
"tab reports soundplaying"
);
}
async function pauseVideoIn(tab) {
await SpecialPowers.spawn(
tab.linkedBrowser,
[VIDEO_SELECTOR],
async selector => {
const video = content.document.querySelector(selector);
video.pause();
}
);
}
function mediaBar() {
return document.getElementById("zen-media-controls-toolbar");
}
function isMediaBarVisible() {
return !mediaBar().hasAttribute("hidden");
}
async function waitForMediaBarVisible() {
await BrowserTestUtils.waitForCondition(
isMediaBarVisible,
"media bar becomes visible"
);
}
// Click a toolbarbutton on the media bar. We dispatch a "command" event
// directly because that's what the controller listens for and it sidesteps
// the flakiness of synthesizing a mouse click on a small toolbar button.
function clickMediaButton(id) {
const button = document.getElementById(id);
ok(button, `media bar button ${id} exists`);
button.dispatchEvent(new Event("command", { bubbles: true }));
}
async function setMediaSessionMetadata(tab, metadata) {
await SpecialPowers.spawn(tab.linkedBrowser, [metadata], async meta => {
content.navigator.mediaSession.metadata = new content.MediaMetadata(meta);
});
}
async function setMediaSessionActionHandler(tab, action) {
// Installs a MediaSession action handler and returns a promise that
// resolves when the handler fires. The promise is surfaced via a
// content-side global the caller can await.
await SpecialPowers.spawn(tab.linkedBrowser, [action], async a => {
content.wrappedJSObject.__zenActionFired = new content.Promise(resolve => {
content.navigator.mediaSession.setActionHandler(a, () => resolve(true));
});
});
}
async function waitForMediaSessionAction(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
return content.wrappedJSObject.__zenActionFired;
});
}

View File

@@ -11,6 +11,7 @@ BROWSER_CHROME_MANIFESTS += [
"safebrowsing/browser.toml",
"sandbox/browser.toml",
"shell/browser.toml",
"tabMediaIndicator/browser.toml",
"tooltiptext/browser.toml",
]
XPCSHELL_TESTS_MANIFESTS += [

Binary file not shown.

View File

@@ -0,0 +1,44 @@
[DEFAULT]
subsuite = "media-bc"
tags = "audiochannel"
support-files = [
"almostSilentAudioTrack.webm",
"audio.ogg",
"audioEndedDuringPlaying.webm",
"file_almostSilentAudioTrack.html",
"file_autoplay_media.html",
"file_empty.html",
"file_mediaPlayback.html",
"file_mediaPlayback2.html",
"file_mediaPlaybackFrame.html",
"file_mediaPlaybackFrame2.html",
"file_silentAudioTrack.html",
"file_webAudio.html",
"gizmo.mp4",
"head.js",
"noaudio.webm",
"silentAudioTrack.webm",
]
["browser_destroy_iframe.js"]
https_first_disabled = true
["browser_mediaPlayback.js"]
["browser_mediaPlayback_mute.js"]
["browser_mediaplayback_audibility_change.js"]
["browser_mute.js"]
["browser_mute2.js"]
["browser_mute_webAudio.js"]
["browser_sound_indicator_silent_video.js"]
["browser_webAudio_hideSoundPlayingIcon.js"]
["browser_webAudio_silentData.js"]
["browser_webaudio_audibility_change.js"]

View File

@@ -0,0 +1,50 @@
const EMPTY_PAGE_URL = GetTestWebBasedURL("file_empty.html");
const AUTPLAY_PAGE_URL = GetTestWebBasedURL("file_autoplay_media.html");
const CORS_AUTPLAY_PAGE_URL = GetTestWebBasedURL(
"file_autoplay_media.html",
true
);
/**
* When an iframe that has audible media gets destroyed, if there is no other
* audible playing media existing in the page, then the sound indicator should
* disappear.
*/
add_task(async function testDestroyAudibleIframe() {
const iframesURL = [AUTPLAY_PAGE_URL, CORS_AUTPLAY_PAGE_URL];
for (let iframeURL of iframesURL) {
info(`open a tab, create an iframe and load an autoplay media page inside`);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
EMPTY_PAGE_URL
);
await createIframeAndLoadURL(tab, iframeURL);
info(`sound indicator should appear because of audible playing media`);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear after destroying iframe`);
await removeIframe(tab);
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
}
});
function createIframeAndLoadURL(tab, url) {
// eslint-disable-next-line no-shadow
return SpecialPowers.spawn(tab.linkedBrowser, [url], async url => {
const iframe = content.document.createElement("iframe");
content.document.body.appendChild(iframe);
iframe.src = url;
info(`load ${url} for iframe`);
await new Promise(r => (iframe.onload = r));
});
}
function removeIframe(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
content.document.getElementsByTagName("iframe")[0].remove();
});
}

View File

@@ -0,0 +1,42 @@
const PAGE = GetTestWebBasedURL("file_mediaPlayback.html");
const FRAME = GetTestWebBasedURL("file_mediaPlaybackFrame.html");
function wait_for_event(browser, event) {
return BrowserTestUtils.waitForEvent(browser, event, false, e => {
is(
e.originalTarget,
browser,
"Event must be dispatched to correct browser."
);
ok(!e.cancelable, "The event should not be cancelable");
return true;
});
}
async function test_on_browser(url, browser) {
info(`run test for ${url}`);
const startPromise = wait_for_event(browser, "DOMAudioPlaybackStarted");
BrowserTestUtils.startLoadingURIString(browser, url);
await startPromise;
await wait_for_event(browser, "DOMAudioPlaybackStopped");
}
add_task(async function test_page() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
test_on_browser.bind(undefined, PAGE)
);
});
add_task(async function test_frame() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
test_on_browser.bind(undefined, FRAME)
);
});

View File

@@ -0,0 +1,118 @@
const PAGE = GetTestWebBasedURL("file_mediaPlayback2.html");
const FRAME = GetTestWebBasedURL("file_mediaPlaybackFrame2.html");
function wait_for_event(browser, event) {
return BrowserTestUtils.waitForEvent(browser, event, false, e => {
is(
e.originalTarget,
browser,
"Event must be dispatched to correct browser."
);
return true;
});
}
function test_audio_in_browser() {
function get_audio_element() {
var doc = content.document;
var list = doc.getElementsByTagName("audio");
if (list.length == 1) {
return list[0];
}
// iframe?
list = doc.getElementsByTagName("iframe");
var iframe = list[0];
list = iframe.contentDocument.getElementsByTagName("audio");
return list[0];
}
var audio = get_audio_element();
return {
computedVolume: audio.computedVolume,
computedMuted: audio.computedMuted,
};
}
async function test_on_browser(url, browser) {
BrowserTestUtils.startLoadingURIString(browser, url);
await wait_for_event(browser, "DOMAudioPlaybackStarted");
var result = await SpecialPowers.spawn(browser, [], test_audio_in_browser);
is(result.computedVolume, 1, "Audio volume is 1");
is(result.computedMuted, false, "Audio is not muted");
ok(!browser.audioMuted, "Audio should not be muted by default");
browser.mute();
ok(browser.audioMuted, "Audio should be muted now");
await wait_for_event(browser, "DOMAudioPlaybackStopped");
result = await SpecialPowers.spawn(browser, [], test_audio_in_browser);
is(result.computedVolume, 0, "Audio volume is 0 when muted");
is(result.computedMuted, true, "Audio is muted");
}
async function test_visibility(url, browser) {
BrowserTestUtils.startLoadingURIString(browser, url);
await wait_for_event(browser, "DOMAudioPlaybackStarted");
var result = await SpecialPowers.spawn(browser, [], test_audio_in_browser);
is(result.computedVolume, 1, "Audio volume is 1");
is(result.computedMuted, false, "Audio is not muted");
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
function () {}
);
ok(!browser.audioMuted, "Audio should not be muted by default");
browser.mute();
ok(browser.audioMuted, "Audio should be muted now");
await wait_for_event(browser, "DOMAudioPlaybackStopped");
result = await SpecialPowers.spawn(browser, [], test_audio_in_browser);
is(result.computedVolume, 0, "Audio volume is 0 when muted");
is(result.computedMuted, true, "Audio is muted");
}
add_task(async function () {
await SpecialPowers.pushPrefEnv({
set: [["media.useAudioChannelService.testing", true]],
});
});
add_task(async function test_page() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
test_on_browser.bind(undefined, PAGE)
);
});
add_task(async function test_frame() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
test_on_browser.bind(undefined, FRAME)
);
});
add_task(async function test_frame() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
test_visibility.bind(undefined, PAGE)
);
});

View File

@@ -0,0 +1,258 @@
/**
* When media changes its audible state, the sound indicator should be
* updated as well, which should appear only when web audio is audible.
*/
add_task(async function testUpdateSoundIndicatorWhenMediaPlaybackChanges() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
await initMediaPlaybackDocument(tab, "audio.ogg");
info(`sound indicator should appear when audible audio starts playing`);
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when audio stops playing`);
await pauseMedia(tab);
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testUpdateSoundIndicatorWhenMediaBecomeSilent() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
await initMediaPlaybackDocument(tab, "audioEndedDuringPlaying.webm");
info(`sound indicator should appear when audible audio starts playing`);
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when audio becomes silent`);
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testSoundIndicatorWouldWorkForMediaWithoutPreload() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
await initMediaPlaybackDocument(tab, "audio.ogg", { preload: "none" });
info(`sound indicator should appear when audible audio starts playing`);
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when audio stops playing`);
await pauseMedia(tab);
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testSoundIndicatorShouldDisappearAfterTabNavigation() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
await initMediaPlaybackDocument(tab, "audio.ogg");
info(`sound indicator should appear when audible audio starts playing`);
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear after navigating tab to blank page`);
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, "about:blank");
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testSoundIndicatorForAudioStream() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
await initMediaStreamPlaybackDocument(tab);
info(`sound indicator should appear when audible audio starts playing`);
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when audio stops playing`);
await pauseMedia(tab);
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testPerformPlayOnMediaLoadingNewSource() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
await initMediaPlaybackDocument(tab, "audio.ogg");
info(`sound indicator should appear when audible audio starts playing`);
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when audio stops playing`);
await pauseMedia(tab);
await waitForTabSoundIndicatorDisappears(tab);
info(`reset media src and play it again should make sound indicator appear`);
await assignNewSourceForAudio(tab, "audio.ogg");
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testSoundIndicatorShouldDisappearWhenAbortingMedia() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
await initMediaPlaybackDocument(tab, "audio.ogg");
info(`sound indicator should appear when audible audio starts playing`);
await playMedia(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when aborting audio source`);
await assignNewSourceForAudio(tab, "");
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testNoSoundIndicatorForMediaWithoutAudioTrack() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab({ needObserver: true });
await initMediaPlaybackDocument(tab, "noaudio.webm", { createVideo: true });
info(`no sound indicator should show for playing media without audio track`);
await playMedia(tab, { resolveOnTimeupdate: true });
ok(!tab.observer.hasEverUpdated(), "didn't ever update sound indicator");
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testSoundIndicatorWhenChangingMediaMuted() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab({ needObserver: true });
await initMediaPlaybackDocument(tab, "audio.ogg", { muted: true });
info(`no sound indicator should show for playing muted media`);
await playMedia(tab, { resolveOnTimeupdate: true });
ok(!tab.observer.hasEverUpdated(), "didn't ever update sound indicator");
info(`unmuted media should make sound indicator appear`);
await Promise.all([
waitForTabSoundIndicatorAppears(tab),
updateMedia(tab, { muted: false }),
]);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function testSoundIndicatorWhenChangingMediaVolume() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab({ needObserver: true });
await initMediaPlaybackDocument(tab, "audio.ogg", { volume: 0.0 });
info(`no sound indicator should show for playing volume zero media`);
await playMedia(tab, { resolveOnTimeupdate: true });
ok(!tab.observer.hasEverUpdated(), "didn't ever update sound indicator");
info(`unmuted media by setting volume should make sound indicator appear`);
await Promise.all([
waitForTabSoundIndicatorAppears(tab),
updateMedia(tab, { volume: 1.0 }),
]);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
/**
* Following are helper functions
*/
function initMediaPlaybackDocument(
tab,
fileName,
{ preload, createVideo, muted = false, volume = 1.0 } = {}
) {
return SpecialPowers.spawn(
tab.linkedBrowser,
[fileName, preload, createVideo, muted, volume],
// eslint-disable-next-line no-shadow
async (fileName, preload, createVideo, muted, volume) => {
if (createVideo) {
content.media = content.document.createElement("video");
} else {
content.media = content.document.createElement("audio");
}
if (preload) {
content.media.preload = preload;
}
content.media.muted = muted;
content.media.volume = volume;
content.media.src = fileName;
}
);
}
function initMediaStreamPlaybackDocument(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
content.media = content.document.createElement("audio");
content.media.srcObject =
new content.AudioContext().createMediaStreamDestination().stream;
});
}
function playMedia(tab, { resolveOnTimeupdate } = {}) {
return SpecialPowers.spawn(
tab.linkedBrowser,
[resolveOnTimeupdate],
// eslint-disable-next-line no-shadow
async resolveOnTimeupdate => {
await content.media.play();
if (resolveOnTimeupdate) {
await new Promise(r => (content.media.ontimeupdate = r));
}
}
);
}
function pauseMedia(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
content.media.pause();
});
}
function assignNewSourceForAudio(tab, fileName) {
// eslint-disable-next-line no-shadow
return SpecialPowers.spawn(tab.linkedBrowser, [fileName], async fileName => {
content.media.src = "";
content.media.removeAttribute("src");
content.media.src = fileName;
});
}
function updateMedia(tab, { muted, volume } = {}) {
return SpecialPowers.spawn(
tab.linkedBrowser,
[muted, volume],
// eslint-disable-next-line no-shadow
(muted, volume) => {
if (muted != undefined) {
content.media.muted = muted;
}
if (volume != undefined) {
content.media.volume = volume;
}
}
);
}

View File

@@ -0,0 +1,19 @@
const PAGE = "data:text/html,page";
function test_on_browser(browser) {
ok(!browser.audioMuted, "Audio should not be muted by default");
browser.mute();
ok(browser.audioMuted, "Audio should be muted now");
browser.unmute();
ok(!browser.audioMuted, "Audio should be unmuted now");
}
add_task(async function () {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
test_on_browser
);
});

View File

@@ -0,0 +1,32 @@
const PAGE = "data:text/html,page";
async function test_on_browser(browser) {
ok(!browser.audioMuted, "Audio should not be muted by default");
browser.mute();
ok(browser.audioMuted, "Audio should be muted now");
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
test_on_browser2
);
browser.unmute();
ok(!browser.audioMuted, "Audio should be unmuted now");
}
function test_on_browser2(browser) {
ok(!browser.audioMuted, "Audio should not be muted by default");
}
add_task(async function () {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
test_on_browser
);
});

View File

@@ -0,0 +1,72 @@
// The tab closing code leaves an uncaught rejection. This test has been
// whitelisted until the issue is fixed.
if (!gMultiProcessBrowser) {
const { PromiseTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.expectUncaughtRejection(/is no longer, usable/);
}
const PAGE = GetTestWebBasedURL("file_webAudio.html");
function start_webAudio() {
var startButton = content.document.getElementById("start");
if (!startButton) {
ok(false, "Can't get the start button!");
}
startButton.click();
}
function stop_webAudio() {
var stopButton = content.document.getElementById("stop");
if (!stopButton) {
ok(false, "Can't get the stop button!");
}
stopButton.click();
}
add_task(async function setup_test_preference() {
await SpecialPowers.pushPrefEnv({
set: [
["media.useAudioChannelService.testing", true],
["media.block-autoplay-until-in-foreground", true],
],
});
});
add_task(async function mute_web_audio() {
info("- open new tab -");
let tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
"about:blank"
);
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, PAGE);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
info("- tab should be audible -");
await waitForTabSoundIndicatorAppears(tab);
info("- mute browser -");
ok(!tab.linkedBrowser.audioMuted, "Audio should not be muted by default");
await clickIcon(tab.audioButton);
ok(tab.linkedBrowser.audioMuted, "Audio should be muted now");
info("- stop web audip -");
await SpecialPowers.spawn(tab.linkedBrowser, [], stop_webAudio);
info("- start web audio -");
await SpecialPowers.spawn(tab.linkedBrowser, [], start_webAudio);
info("- unmute browser -");
ok(tab.linkedBrowser.audioMuted, "Audio should be muted now");
await clickIcon(tab.audioButton);
ok(!tab.linkedBrowser.audioMuted, "Audio should be unmuted now");
info("- tab should be audible -");
await waitForTabSoundIndicatorAppears(tab);
info("- remove tab -");
BrowserTestUtils.removeTab(tab);
});

View File

@@ -0,0 +1,95 @@
const SILENT_PAGE = GetTestWebBasedURL("file_silentAudioTrack.html");
const ALMOST_SILENT_PAGE = GetTestWebBasedURL(
"file_almostSilentAudioTrack.html"
);
function check_audio_playing_state(isPlaying) {
let autoPlay = content.document.getElementById("autoplay");
if (!autoPlay) {
ok(false, "Can't get the audio element!");
}
is(
autoPlay.paused,
!isPlaying,
"The playing state of autoplay audio is correct."
);
// wait for a while to make sure the video is playing and related event has
// been dispatched (if any).
let PLAYING_TIME_SEC = 0.5;
Assert.less(
PLAYING_TIME_SEC,
autoPlay.duration,
"The playing time is valid."
);
return new Promise(resolve => {
autoPlay.ontimeupdate = function () {
if (autoPlay.currentTime > PLAYING_TIME_SEC) {
resolve();
}
};
});
}
add_task(async function should_not_show_sound_indicator_for_silent_video() {
info("- open new foreground tab -");
let tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
"about:blank"
);
info("- tab should not have sound indicator before playing silent video -");
await waitForTabSoundIndicatorDisappears(tab);
info("- loading autoplay silent video -");
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, SILENT_PAGE);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
await SpecialPowers.spawn(
tab.linkedBrowser,
[true],
check_audio_playing_state
);
info("- tab should not have sound indicator after playing silent video -");
await waitForTabSoundIndicatorDisappears(tab);
info("- remove tab -");
BrowserTestUtils.removeTab(tab);
});
add_task(
async function should_not_show_sound_indicator_for_almost_silent_video() {
info("- open new foreground tab -");
let tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
"about:blank"
);
info(
"- tab should not have sound indicator before playing almost silent video -"
);
await waitForTabSoundIndicatorDisappears(tab);
info("- loading autoplay almost silent video -");
BrowserTestUtils.startLoadingURIString(
tab.linkedBrowser,
ALMOST_SILENT_PAGE
);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
await SpecialPowers.spawn(
tab.linkedBrowser,
[true],
check_audio_playing_state
);
info(
"- tab should not have sound indicator after playing almost silent video -"
);
await waitForTabSoundIndicatorDisappears(tab);
info("- remove tab -");
BrowserTestUtils.removeTab(tab);
}
);

View File

@@ -0,0 +1,60 @@
/**
* This test is used to ensure the 'sound-playing' icon would not disappear after
* sites call AudioContext.resume().
*/
"use strict";
function setup_test_preference() {
return SpecialPowers.pushPrefEnv({
set: [
["media.useAudioChannelService.testing", true],
["browser.tabs.delayHidingAudioPlayingIconMS", 0],
],
});
}
async function resumeAudioContext() {
const ac = content.ac;
await ac.resume();
ok(true, "AudioContext is resumed.");
}
async function testResumeRunningAudioContext() {
info(`- create new tab -`);
const tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
"about:blank"
);
const browser = tab.linkedBrowser;
info(`- create audio context -`);
// We want the same audio context to be used across different content tasks.
await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
content.ac = new content.AudioContext();
const ac = content.ac;
const dest = ac.destination;
const osc = ac.createOscillator();
osc.connect(dest);
osc.start();
});
info(`- wait for 'sound-playing' icon showing -`);
await waitForTabSoundIndicatorAppears(tab);
info(`- resume AudioContext -`);
await SpecialPowers.spawn(browser, [], resumeAudioContext);
info(`- 'sound-playing' icon should still exist -`);
await waitForTabSoundIndicatorAppears(tab);
info(`- remove tab -`);
await BrowserTestUtils.removeTab(tab);
}
add_task(async function start_test() {
info("- setup test preference -");
await setup_test_preference();
info("- start testing -");
await testResumeRunningAudioContext();
});

View File

@@ -0,0 +1,57 @@
/**
* This test is used to make sure we won't show the sound indicator for silent
* web audio.
*/
/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
async function waitUntilAudioContextStarts() {
const ac = content.ac;
if (ac.state == "running") {
return;
}
await new Promise(resolve => {
ac.onstatechange = () => {
if (ac.state == "running") {
ac.onstatechange = null;
resolve();
}
};
});
}
add_task(async function testSilentAudioContext() {
info(`- create new tab -`);
const tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
"about:blank"
);
const browser = tab.linkedBrowser;
info(`- create audio context -`);
// We want the same audio context to be used across different content tasks
await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
content.ac = new content.AudioContext();
const ac = content.ac;
const dest = ac.destination;
const source = new content.OscillatorNode(content.ac);
const gain = new content.GainNode(content.ac);
gain.gain.value = 0.0;
source.connect(gain).connect(dest);
source.start();
});
info(`- check AudioContext's state -`);
await SpecialPowers.spawn(browser, [], waitUntilAudioContextStarts);
ok(true, `AudioContext is running.`);
info(`- should not show sound indicator -`);
// If we do the next step too early, then we can't make sure whether that the
// reason of no showing sound indicator is because of silent web audio, or
// because the indicator is just not showing yet.
await new Promise(r => setTimeout(r, 1000));
await waitForTabSoundIndicatorDisappears(tab);
info(`- remove tab -`);
await BrowserTestUtils.removeTab(tab);
});

View File

@@ -0,0 +1,172 @@
const EMPTY_PAGE_URL = GetTestWebBasedURL("file_empty.html");
/**
* When web audio changes its audible state, the sound indicator should be
* updated as well, which should appear only when web audio is audible.
*/
add_task(
async function testWebAudioAudibilityWouldAffectTheAppearenceOfTabSoundIndicator() {
info(`sound indicator should appear when web audio plays audible sound`);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
EMPTY_PAGE_URL
);
await initWebAudioDocument(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when suspending web audio`);
await suspendWebAudio(tab);
await waitForTabSoundIndicatorDisappears(tab);
info(`sound indicator should appear when resuming web audio`);
await resumeWebAudio(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when muting web audio by docShell`);
await muteWebAudioByDocShell(tab);
await waitForTabSoundIndicatorDisappears(tab);
info(`sound indicator should appear when unmuting web audio by docShell`);
await unmuteWebAudioByDocShell(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when muting web audio by gain node`);
await muteWebAudioByGainNode(tab);
await waitForTabSoundIndicatorDisappears(tab);
info(`sound indicator should appear when unmuting web audio by gain node`);
await unmuteWebAudioByGainNode(tab);
await waitForTabSoundIndicatorAppears(tab);
info(`sound indicator should disappear when closing web audio`);
await closeWebAudio(tab);
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
}
);
add_task(async function testSoundIndicatorShouldDisappearAfterTabNavigation() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
info(`sound indicator should appear when audible web audio starts playing`);
await Promise.all([
initWebAudioDocument(tab),
waitForTabSoundIndicatorAppears(tab),
]);
info(`sound indicator should disappear after navigating tab to blank page`);
await Promise.all([
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, "about:blank"),
waitForTabSoundIndicatorDisappears(tab),
]);
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
add_task(
async function testSoundIndicatorShouldDisappearAfterWebAudioBecomesSilent() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab();
info(`sound indicator should appear when audible web audio starts playing`);
await Promise.all([
initWebAudioDocument(tab, { duration: 0.1 }),
waitForTabSoundIndicatorAppears(tab),
]);
info(`sound indicator should disappear after web audio become silent`);
await waitForTabSoundIndicatorDisappears(tab);
info("remove tab");
BrowserTestUtils.removeTab(tab);
}
);
add_task(async function testNoSoundIndicatorWhenSimplyCreateAudioContext() {
info("create a tab loading media document");
const tab = await createBlankForegroundTab({ needObserver: true });
info(`sound indicator should not appear when simply create an AudioContext`);
await SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
content.ac = new content.AudioContext();
while (content.ac.state != "running") {
info(`wait until web audio starts running`);
await new Promise(r => (content.ac.onstatechange = r));
}
});
ok(!tab.observer.hasEverUpdated(), "didn't ever update sound indicator");
info("remove tab");
BrowserTestUtils.removeTab(tab);
});
/**
* Following are helper functions
*/
function initWebAudioDocument(tab, { duration } = {}) {
// eslint-disable-next-line no-shadow
return SpecialPowers.spawn(tab.linkedBrowser, [duration], async duration => {
content.ac = new content.AudioContext();
const ac = content.ac;
const dest = ac.destination;
const source = new content.OscillatorNode(ac);
source.start(ac.currentTime);
if (duration != undefined) {
source.stop(ac.currentTime + duration);
}
// create a gain node for future muting/unmuting
content.gainNode = ac.createGain();
source.connect(content.gainNode);
content.gainNode.connect(dest);
while (ac.state != "running") {
info(`wait until web audio starts running`);
await new Promise(r => (ac.onstatechange = r));
}
});
}
function suspendWebAudio(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
await content.ac.suspend();
});
}
function resumeWebAudio(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
await content.ac.resume();
});
}
function closeWebAudio(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
await content.ac.close();
});
}
function muteWebAudioByDocShell(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
content.docShell.allowMedia = false;
});
}
function unmuteWebAudioByDocShell(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
content.docShell.allowMedia = true;
});
}
function muteWebAudioByGainNode(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
content.gainNode.gain.setValueAtTime(0, content.ac.currentTime);
});
}
function unmuteWebAudioByGainNode(tab) {
return SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
content.gainNode.gain.setValueAtTime(1.0, content.ac.currentTime);
});
}

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
</head>
<body>
<video id="autoplay" src="almostSilentAudioTrack.webm"></video>
<script type="text/javascript">
// In linux debug on try server, sometimes the download process would fail, so
// we can't activate the "auto-play" or playing after receving "oncanplay".
// Therefore, we just call play here.
var video = document.getElementById("autoplay");
video.loop = true;
video.play();
</script>
</body>

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>autoplay media page</title>
</head>
<body>
<video id="video" src="gizmo.mp4" loop autoplay></video>
</body>
</html>

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<title>empty page</title>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<script type="text/javascript">
var audio = new Audio();
audio.oncanplay = function() {
audio.oncanplay = null;
audio.play();
};
audio.src = "audio.ogg";
</script>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<body>
<script type="text/javascript">
var audio = new Audio();
audio.oncanplay = function() {
audio.oncanplay = null;
audio.play();
};
audio.src = "audio.ogg";
audio.loop = true;
audio.id = "v";
document.body.appendChild(audio);
</script>
</body>

View File

@@ -0,0 +1,2 @@
<!DOCTYPE html>
<iframe src="file_mediaPlayback.html"></iframe>

View File

@@ -0,0 +1,2 @@
<!DOCTYPE html>
<iframe src="file_mediaPlayback2.html"></iframe>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
</head>
<body>
<video id="autoplay" src="silentAudioTrack.webm"></video>
<script type="text/javascript">
// In linux debug on try server, sometimes the download process would fail, so
// we can't activate the "auto-play" or playing after receving "oncanplay".
// Therefore, we just call play here.
var video = document.getElementById("autoplay");
video.loop = true;
video.play();
</script>
</body>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
</head>
<body>
<pre id=state></pre>
<button id="start" onclick="start_webaudio()">Start</button>
<button id="stop" onclick="stop_webaudio()">Stop</button>
<script type="text/javascript">
var ac = new AudioContext();
var dest = ac.destination;
var osc = ac.createOscillator();
osc.connect(dest);
osc.start();
document.querySelector("pre").innerText = ac.state;
ac.onstatechange = function() {
document.querySelector("pre").innerText = ac.state;
}
function start_webaudio() {
ac.resume();
}
function stop_webaudio() {
ac.suspend();
}
</script>
</body>

Binary file not shown.

View File

@@ -0,0 +1,165 @@
/**
* Global variables for testing.
*/
const gEMPTY_PAGE_URL = GetTestWebBasedURL("file_empty.html");
/**
* Return a web-based URL for a given file based on the testing directory.
*
* @param {string} fileName
* file that caller wants its web-based url
* @param {boolean} cors [optional]
* if set, then return a url with different origin
*/
function GetTestWebBasedURL(fileName, cors = false) {
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
const origin = cors ? "http://example.org" : "http://example.com";
return (
getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) +
fileName
);
}
/**
* Wait until tab sound indicator appears on the given tab.
*
* @param {tabbrowser} tab
* given tab where tab sound indicator should appear
*/
async function waitForTabSoundIndicatorAppears(tab) {
if (!tab.soundPlaying) {
info("Tab sound indicator doesn't appear yet");
await BrowserTestUtils.waitForEvent(
tab,
"TabAttrModified",
false,
event => {
return event.detail.changed.includes("soundplaying");
}
);
}
ok(tab.soundPlaying, "Tab sound indicator appears");
}
/**
* Wait until tab sound indicator disappears on the given tab.
*
* @param {tabbrowser} tab
* given tab where tab sound indicator should disappear
*/
async function waitForTabSoundIndicatorDisappears(tab) {
if (tab.soundPlaying) {
info("Tab sound indicator doesn't disappear yet");
await BrowserTestUtils.waitForEvent(
tab,
"TabAttrModified",
false,
event => {
return event.detail.changed.includes("soundplaying");
}
);
}
ok(!tab.soundPlaying, "Tab sound indicator disappears");
}
/**
* Return a new foreground tab loading with an empty file.
*
* @param {boolean} needObserver
* If true, sets an observer property on the returned tab. This property
* exposes `hasEverUpdated()` which will return a bool indicating if the
* sound indicator has ever updated.
*/
async function createBlankForegroundTab({ needObserver } = {}) {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
gEMPTY_PAGE_URL
);
if (needObserver) {
tab.observer = createSoundIndicatorObserver(tab);
}
return tab;
}
function createSoundIndicatorObserver(tab) {
let hasEverUpdated = false;
let listener = event => {
if (event.detail.changed.includes("soundplaying")) {
hasEverUpdated = true;
}
};
tab.addEventListener("TabAttrModified", listener);
return {
hasEverUpdated: () => {
tab.removeEventListener("TabAttrModified", listener);
return hasEverUpdated;
},
};
}
/**
* Sythesize mouse hover on the given icon, which would sythesize `mouseover`
* and `mousemove` event on that. Return a promise that will be resolved when
* the tooptip element shows.
*
* @param {tab icon} icon
* the icon on which we want to mouse hover
* @param {tooltip element} tooltip
* the tab tooltip elementss
*/
function hoverIcon(icon, tooltip) {
disableNonTestMouse(true);
if (!tooltip) {
tooltip = document.getElementById("tabbrowser-tab-tooltip");
}
let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
EventUtils.synthesizeMouse(icon, 1, 1, { type: "mouseover" });
EventUtils.synthesizeMouse(icon, 2, 2, { type: "mousemove" });
EventUtils.synthesizeMouse(icon, 3, 3, { type: "mousemove" });
EventUtils.synthesizeMouse(icon, 4, 4, { type: "mousemove" });
return popupShownPromise;
}
/**
* Leave mouse from the given icon, which would sythesize `mouseout`
* and `mousemove` event on that.
*
* @param {tab icon} icon
* the icon on which we want to mouse hover
* @param {tooltip element} tooltip
* the tab tooltip elementss
*/
function leaveIcon(icon) {
EventUtils.synthesizeMouse(icon, 0, 0, { type: "mouseout" });
EventUtils.synthesizeMouseAtCenter(document.documentElement, {
type: "mousemove",
});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {
type: "mousemove",
});
EventUtils.synthesizeMouseAtCenter(document.documentElement, {
type: "mousemove",
});
disableNonTestMouse(false);
}
/**
* Sythesize mouse click on the given icon.
*
* @param {tab icon} icon
* the icon on which we want to mouse hover
*/
async function clickIcon(icon) {
await hoverIcon(icon);
EventUtils.synthesizeMouseAtCenter(icon, { button: 0 });
leaveIcon(icon);
}
function disableNonTestMouse(disable) {
let utils = window.windowUtils;
utils.disableNonTestMouseEvents(disable);
}

View File

@@ -8,6 +8,7 @@ BROWSER_CHROME_MANIFESTS += [
"folders/browser.toml",
"glance/browser.toml",
"live-folders/browser.toml",
"media/browser.toml",
"pinned/browser.toml",
"popover/browser.toml",
"spaces/browser.toml",