diff options
Diffstat (limited to '')
28 files changed, 1289 insertions, 0 deletions
diff --git a/browser/base/content/test/tabMediaIndicator/almostSilentAudioTrack.webm b/browser/base/content/test/tabMediaIndicator/almostSilentAudioTrack.webm Binary files differnew file mode 100644 index 0000000000..0b8f8f746f --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/almostSilentAudioTrack.webm diff --git a/browser/base/content/test/tabMediaIndicator/audio.ogg b/browser/base/content/test/tabMediaIndicator/audio.ogg Binary files differnew file mode 100644 index 0000000000..bed764fbf1 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/audio.ogg diff --git a/browser/base/content/test/tabMediaIndicator/audioEndedDuringPlaying.webm b/browser/base/content/test/tabMediaIndicator/audioEndedDuringPlaying.webm Binary files differnew file mode 100644 index 0000000000..4f82b5da76 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/audioEndedDuringPlaying.webm diff --git a/browser/base/content/test/tabMediaIndicator/browser.toml b/browser/base/content/test/tabMediaIndicator/browser.toml new file mode 100644 index 0000000000..9eac10b029 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser.toml @@ -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"] diff --git a/browser/base/content/test/tabMediaIndicator/browser_destroy_iframe.js b/browser/base/content/test/tabMediaIndicator/browser_destroy_iframe.js new file mode 100644 index 0000000000..f977d1d664 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_destroy_iframe.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(); + }); +} diff --git a/browser/base/content/test/tabMediaIndicator/browser_mediaPlayback.js b/browser/base/content/test/tabMediaIndicator/browser_mediaPlayback.js new file mode 100644 index 0000000000..89143bf837 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_mediaPlayback.js @@ -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) + ); +}); diff --git a/browser/base/content/test/tabMediaIndicator/browser_mediaPlayback_mute.js b/browser/base/content/test/tabMediaIndicator/browser_mediaPlayback_mute.js new file mode 100644 index 0000000000..fb771c23a7 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_mediaPlayback_mute.js @@ -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) + ); +}); diff --git a/browser/base/content/test/tabMediaIndicator/browser_mediaplayback_audibility_change.js b/browser/base/content/test/tabMediaIndicator/browser_mediaplayback_audibility_change.js new file mode 100644 index 0000000000..6c0c9de2b9 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_mediaplayback_audibility_change.js @@ -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; + } + } + ); +} diff --git a/browser/base/content/test/tabMediaIndicator/browser_mute.js b/browser/base/content/test/tabMediaIndicator/browser_mute.js new file mode 100644 index 0000000000..826e06c3db --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_mute.js @@ -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 + ); +}); diff --git a/browser/base/content/test/tabMediaIndicator/browser_mute2.js b/browser/base/content/test/tabMediaIndicator/browser_mute2.js new file mode 100644 index 0000000000..5e845454d3 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_mute2.js @@ -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 + ); +}); diff --git a/browser/base/content/test/tabMediaIndicator/browser_mute_webAudio.js b/browser/base/content/test/tabMediaIndicator/browser_mute_webAudio.js new file mode 100644 index 0000000000..d360edd60b --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_mute_webAudio.js @@ -0,0 +1,75 @@ +// 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"); + let tabContent = tab.querySelector(".tab-content"); + await hoverIcon(tabContent); + await clickIcon(tab.overlayIcon); + 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 hoverIcon(tabContent); + await clickIcon(tab.overlayIcon); + ok(!tab.linkedBrowser.audioMuted, "Audio should be unmuted now"); + + info("- tab should be audible -"); + await waitForTabSoundIndicatorAppears(tab); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/tabMediaIndicator/browser_sound_indicator_silent_video.js b/browser/base/content/test/tabMediaIndicator/browser_sound_indicator_silent_video.js new file mode 100644 index 0000000000..882a9bc804 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_sound_indicator_silent_video.js @@ -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); + } +); diff --git a/browser/base/content/test/tabMediaIndicator/browser_webAudio_hideSoundPlayingIcon.js b/browser/base/content/test/tabMediaIndicator/browser_webAudio_hideSoundPlayingIcon.js new file mode 100644 index 0000000000..be40f6e146 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_webAudio_hideSoundPlayingIcon.js @@ -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(); +}); diff --git a/browser/base/content/test/tabMediaIndicator/browser_webAudio_silentData.js b/browser/base/content/test/tabMediaIndicator/browser_webAudio_silentData.js new file mode 100644 index 0000000000..5831d3c0ce --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_webAudio_silentData.js @@ -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); +}); diff --git a/browser/base/content/test/tabMediaIndicator/browser_webaudio_audibility_change.js b/browser/base/content/test/tabMediaIndicator/browser_webaudio_audibility_change.js new file mode 100644 index 0000000000..7cdc87020a --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/browser_webaudio_audibility_change.js @@ -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); + }); +} diff --git a/browser/base/content/test/tabMediaIndicator/file_almostSilentAudioTrack.html b/browser/base/content/test/tabMediaIndicator/file_almostSilentAudioTrack.html new file mode 100644 index 0000000000..3ce9a68b98 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_almostSilentAudioTrack.html @@ -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> diff --git a/browser/base/content/test/tabMediaIndicator/file_autoplay_media.html b/browser/base/content/test/tabMediaIndicator/file_autoplay_media.html new file mode 100644 index 0000000000..f0dcfdab52 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_autoplay_media.html @@ -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> diff --git a/browser/base/content/test/tabMediaIndicator/file_empty.html b/browser/base/content/test/tabMediaIndicator/file_empty.html new file mode 100644 index 0000000000..13d5eeee78 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_empty.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +<title>empty page</title> +</head> +<body> +</body> +</html> diff --git a/browser/base/content/test/tabMediaIndicator/file_mediaPlayback.html b/browser/base/content/test/tabMediaIndicator/file_mediaPlayback.html new file mode 100644 index 0000000000..5df0bc1542 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_mediaPlayback.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> diff --git a/browser/base/content/test/tabMediaIndicator/file_mediaPlayback2.html b/browser/base/content/test/tabMediaIndicator/file_mediaPlayback2.html new file mode 100644 index 0000000000..890b494a05 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_mediaPlayback2.html @@ -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> diff --git a/browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame.html b/browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame.html new file mode 100644 index 0000000000..119db62ecc --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<iframe src="file_mediaPlayback.html"></iframe> diff --git a/browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame2.html b/browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame2.html new file mode 100644 index 0000000000..d96a4cd4e9 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame2.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<iframe src="file_mediaPlayback2.html"></iframe> diff --git a/browser/base/content/test/tabMediaIndicator/file_silentAudioTrack.html b/browser/base/content/test/tabMediaIndicator/file_silentAudioTrack.html new file mode 100644 index 0000000000..afdf2c5297 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_silentAudioTrack.html @@ -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> diff --git a/browser/base/content/test/tabMediaIndicator/file_webAudio.html b/browser/base/content/test/tabMediaIndicator/file_webAudio.html new file mode 100644 index 0000000000..f6fb5e7c07 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/file_webAudio.html @@ -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> diff --git a/browser/base/content/test/tabMediaIndicator/gizmo.mp4 b/browser/base/content/test/tabMediaIndicator/gizmo.mp4 Binary files differnew file mode 100644 index 0000000000..87efad5ade --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/gizmo.mp4 diff --git a/browser/base/content/test/tabMediaIndicator/head.js b/browser/base/content/test/tabMediaIndicator/head.js new file mode 100644 index 0000000000..8b82e21a43 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/head.js @@ -0,0 +1,158 @@ +/** + * 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); +} diff --git a/browser/base/content/test/tabMediaIndicator/noaudio.webm b/browser/base/content/test/tabMediaIndicator/noaudio.webm Binary files differnew file mode 100644 index 0000000000..9207017fb6 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/noaudio.webm diff --git a/browser/base/content/test/tabMediaIndicator/silentAudioTrack.webm b/browser/base/content/test/tabMediaIndicator/silentAudioTrack.webm Binary files differnew file mode 100644 index 0000000000..8e08a86c45 --- /dev/null +++ b/browser/base/content/test/tabMediaIndicator/silentAudioTrack.webm |