mirror of
https://github.com/zen-browser/desktop
synced 2026-04-25 17:15:00 +02:00
gh-13264: Add tests for media player (gh-13262)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
16
src/zen/tests/media/browser.toml
Normal file
16
src/zen/tests/media/browser.toml
Normal 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"]
|
||||
63
src/zen/tests/media/browser_media_metadata.js
Normal file
63
src/zen/tests/media/browser_media_metadata.js
Normal 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;
|
||||
}
|
||||
});
|
||||
64
src/zen/tests/media/browser_media_mute.js
Normal file
64
src/zen/tests/media/browser_media_mute.js
Normal 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;
|
||||
}
|
||||
});
|
||||
74
src/zen/tests/media/browser_media_next_track.js
Normal file
74
src/zen/tests/media/browser_media_next_track.js
Normal 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;
|
||||
}
|
||||
});
|
||||
74
src/zen/tests/media/browser_media_shows_on_tab_switch.js
Normal file
74
src/zen/tests/media/browser_media_shows_on_tab_switch.js
Normal 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;
|
||||
}
|
||||
});
|
||||
96
src/zen/tests/media/head.js
Normal file
96
src/zen/tests/media/head.js
Normal 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;
|
||||
});
|
||||
}
|
||||
@@ -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.
BIN
src/zen/tests/mochitests/tabMediaIndicator/audio.ogg
Normal file
BIN
src/zen/tests/mochitests/tabMediaIndicator/audio.ogg
Normal file
Binary file not shown.
Binary file not shown.
44
src/zen/tests/mochitests/tabMediaIndicator/browser.toml
Normal file
44
src/zen/tests/mochitests/tabMediaIndicator/browser.toml
Normal 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"]
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
19
src/zen/tests/mochitests/tabMediaIndicator/browser_mute.js
Normal file
19
src/zen/tests/mochitests/tabMediaIndicator/browser_mute.js
Normal 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
|
||||
);
|
||||
});
|
||||
32
src/zen/tests/mochitests/tabMediaIndicator/browser_mute2.js
Normal file
32
src/zen/tests/mochitests/tabMediaIndicator/browser_mute2.js
Normal 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
|
||||
);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>empty page</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<iframe src="file_mediaPlayback.html"></iframe>
|
||||
@@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<iframe src="file_mediaPlayback2.html"></iframe>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
BIN
src/zen/tests/mochitests/tabMediaIndicator/gizmo.mp4
Normal file
BIN
src/zen/tests/mochitests/tabMediaIndicator/gizmo.mp4
Normal file
Binary file not shown.
165
src/zen/tests/mochitests/tabMediaIndicator/head.js
Normal file
165
src/zen/tests/mochitests/tabMediaIndicator/head.js
Normal 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);
|
||||
}
|
||||
BIN
src/zen/tests/mochitests/tabMediaIndicator/noaudio.webm
Normal file
BIN
src/zen/tests/mochitests/tabMediaIndicator/noaudio.webm
Normal file
Binary file not shown.
BIN
src/zen/tests/mochitests/tabMediaIndicator/silentAudioTrack.webm
Normal file
BIN
src/zen/tests/mochitests/tabMediaIndicator/silentAudioTrack.webm
Normal file
Binary file not shown.
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user