summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/tabMediaIndicator
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/tabMediaIndicator')
-rw-r--r--browser/base/content/test/tabMediaIndicator/almostSilentAudioTrack.webmbin0 -> 1699661 bytes
-rw-r--r--browser/base/content/test/tabMediaIndicator/audio.oggbin0 -> 14293 bytes
-rw-r--r--browser/base/content/test/tabMediaIndicator/audioEndedDuringPlaying.webmbin0 -> 109366 bytes
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser.ini33
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_destroy_iframe.js50
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_mediaPlayback.js42
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_mediaPlayback_mute.js118
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_mediaplayback_audibility_change.js257
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_mute.js19
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_mute2.js32
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_mute_webAudio.js75
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_sound_indicator_silent_video.js88
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_webAudio_hideSoundPlayingIcon.js60
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_webAudio_silentData.js57
-rw-r--r--browser/base/content/test/tabMediaIndicator/browser_webaudio_audibility_change.js172
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_almostSilentAudioTrack.html18
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_autoplay_media.html9
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_empty.html8
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_mediaPlayback.html9
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_mediaPlayback2.html14
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame.html2
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_mediaPlaybackFrame2.html2
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_silentAudioTrack.html18
-rw-r--r--browser/base/content/test/tabMediaIndicator/file_webAudio.html29
-rw-r--r--browser/base/content/test/tabMediaIndicator/gizmo.mp4bin0 -> 455255 bytes
-rw-r--r--browser/base/content/test/tabMediaIndicator/head.js156
-rw-r--r--browser/base/content/test/tabMediaIndicator/noaudio.webmbin0 -> 105755 bytes
-rw-r--r--browser/base/content/test/tabMediaIndicator/silentAudioTrack.webmbin0 -> 224800 bytes
28 files changed, 1268 insertions, 0 deletions
diff --git a/browser/base/content/test/tabMediaIndicator/almostSilentAudioTrack.webm b/browser/base/content/test/tabMediaIndicator/almostSilentAudioTrack.webm
new file mode 100644
index 0000000000..0b8f8f746f
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/almostSilentAudioTrack.webm
Binary files differ
diff --git a/browser/base/content/test/tabMediaIndicator/audio.ogg b/browser/base/content/test/tabMediaIndicator/audio.ogg
new file mode 100644
index 0000000000..bed764fbf1
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/audio.ogg
Binary files differ
diff --git a/browser/base/content/test/tabMediaIndicator/audioEndedDuringPlaying.webm b/browser/base/content/test/tabMediaIndicator/audioEndedDuringPlaying.webm
new file mode 100644
index 0000000000..4f82b5da76
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/audioEndedDuringPlaying.webm
Binary files differ
diff --git a/browser/base/content/test/tabMediaIndicator/browser.ini b/browser/base/content/test/tabMediaIndicator/browser.ini
new file mode 100644
index 0000000000..52ae2a3097
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/browser.ini
@@ -0,0 +1,33 @@
+[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_audibility_change.js]
+[browser_mediaPlayback.js]
+[browser_mediaPlayback_mute.js]
+[browser_mute.js]
+[browser_mute2.js]
+[browser_mute_webAudio.js]
+[browser_sound_indicator_silent_video.js]
+[browser_webaudio_audibility_change.js]
+[browser_webAudio_hideSoundPlayingIcon.js]
+[browser_webAudio_silentData.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..923ddc6296
--- /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.loadURI(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..3e066c0e03
--- /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.loadURI(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.loadURI(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..ae26c775cb
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/browser_mediaplayback_audibility_change.js
@@ -0,0 +1,257 @@
+/**
+ * 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.loadURI(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..b51adc270b
--- /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..2d65276fc2
--- /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..a97f427347
--- /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.loadURI(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..28e1985d57
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/browser_sound_indicator_silent_video.js
@@ -0,0 +1,88 @@
+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;
+ ok(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.loadURI(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.loadURI(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..1dcf1c2535
--- /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.loadURI(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
new file mode 100644
index 0000000000..87efad5ade
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/gizmo.mp4
Binary files differ
diff --git a/browser/base/content/test/tabMediaIndicator/head.js b/browser/base/content/test/tabMediaIndicator/head.js
new file mode 100644
index 0000000000..6920203fc4
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/head.js
@@ -0,0 +1,156 @@
+/**
+ * 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) {
+ 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
new file mode 100644
index 0000000000..9207017fb6
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/noaudio.webm
Binary files differ
diff --git a/browser/base/content/test/tabMediaIndicator/silentAudioTrack.webm b/browser/base/content/test/tabMediaIndicator/silentAudioTrack.webm
new file mode 100644
index 0000000000..8e08a86c45
--- /dev/null
+++ b/browser/base/content/test/tabMediaIndicator/silentAudioTrack.webm
Binary files differ