Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* eslint-disable mozilla/no-arbitrary-setTimeout */
const PAGE =
const TABATTR_REMOVAL_PREFNAME = "browser.tabs.delayHidingAudioPlayingIconMS";
const INITIAL_TABATTR_REMOVAL_DELAY_MS = Services.prefs.getIntPref(
TABATTR_REMOVAL_PREFNAME
);
async function pause(tab, options) {
let extendedDelay = options && options.extendedDelay;
if (extendedDelay) {
// Use 10s to remove possibility of race condition with attr removal.
Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, 10000);
}
try {
let browser = tab.linkedBrowser;
let awaitDOMAudioPlaybackStopped;
if (!browser.audioMuted) {
awaitDOMAudioPlaybackStopped = BrowserTestUtils.waitForEvent(
browser,
"DOMAudioPlaybackStopped",
"DOMAudioPlaybackStopped event should get fired after pause"
);
}
await SpecialPowers.spawn(browser, [], async function () {
let audio = content.document.querySelector("audio");
audio.pause();
});
// If the tab has already be muted, it means the tab won't have soundplaying,
// so we don't need to check this attribute.
if (browser.audioMuted) {
return;
}
if (extendedDelay) {
ok(
tab.hasAttribute("soundplaying"),
"The tab should still have the soundplaying attribute immediately after pausing"
);
await awaitDOMAudioPlaybackStopped;
ok(
tab.hasAttribute("soundplaying"),
"The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped"
);
}
await wait_for_tab_playing_event(tab, false);
ok(
!tab.hasAttribute("soundplaying"),
"The tab should not have the soundplaying attribute after the timeout has resolved"
);
} finally {
// Make sure other tests don't timeout if an exception gets thrown above.
// Need to use setIntPref instead of clearUserPref because
// testing/profiles/common/user.js overrides the default value to help this and
// other tests run faster.
Services.prefs.setIntPref(
TABATTR_REMOVAL_PREFNAME,
INITIAL_TABATTR_REMOVAL_DELAY_MS
);
}
}
async function hide_tab(tab) {
let tabHidden = BrowserTestUtils.waitForEvent(tab, "TabHide");
gBrowser.hideTab(tab);
return tabHidden;
}
async function show_tab(tab) {
let tabShown = BrowserTestUtils.waitForEvent(tab, "TabShow");
gBrowser.showTab(tab);
return tabShown;
}
async function test_tooltip(icon, expectedTooltip, isActiveTab, tab) {
let tooltip = document.getElementById("tabbrowser-tab-tooltip");
let tabContent = tab.querySelector(".tab-content");
await hover_icon(tabContent, tooltip);
await hover_icon(icon, tooltip);
if (isActiveTab) {
// The active tab should have the keybinding shortcut in the tooltip.
// We check this by ensuring that the strings are not equal but the expected
// message appears in the beginning.
isnot(
tooltip.getAttribute("label"),
expectedTooltip,
"Tooltips should not be equal"
);
is(
tooltip.getAttribute("label").indexOf(expectedTooltip),
0,
"Correct tooltip expected"
);
} else {
is(
tooltip.getAttribute("label"),
expectedTooltip,
"Tooltips should not be equal"
);
}
leave_icon(icon);
}
function get_tab_state(tab) {
return JSON.parse(SessionStore.getTabState(tab));
}
async function test_muting_using_menu(tab, expectMuted) {
// Show the popup menu
let contextMenu = document.getElementById("tabContextMenu");
let popupShownPromise = BrowserTestUtils.waitForEvent(
contextMenu,
"popupshown"
);
EventUtils.synthesizeMouseAtCenter(tab, { type: "contextmenu", button: 2 });
await popupShownPromise;
// Check the menu
let expectedLabel = expectMuted ? "Unmute Tab" : "Mute Tab";
let expectedAccessKey = expectMuted ? "m" : "M";
let toggleMute = document.getElementById("context_toggleMuteTab");
is(toggleMute.label, expectedLabel, "Correct label expected");
is(toggleMute.accessKey, expectedAccessKey, "Correct accessKey expected");
is(
toggleMute.hasAttribute("muted"),
expectMuted,
"Should have the correct state for the muted attribute"
);
ok(
!toggleMute.hasAttribute("soundplaying"),
"Should not have the soundplaying attribute"
);
await play(tab);
is(
toggleMute.hasAttribute("muted"),
expectMuted,
"Should have the correct state for the muted attribute"
);
is(
!toggleMute.hasAttribute("soundplaying"),
expectMuted,
"The value of soundplaying attribute is incorrect"
);
await pause(tab);
is(
toggleMute.hasAttribute("muted"),
expectMuted,
"Should have the correct state for the muted attribute"
);
ok(
!toggleMute.hasAttribute("soundplaying"),
"Should not have the soundplaying attribute"
);
// Click on the menu and wait for the tab to be muted.
let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted);
let popupHiddenPromise = BrowserTestUtils.waitForEvent(
contextMenu,
"popuphidden"
);
contextMenu.activateItem(toggleMute);
await popupHiddenPromise;
await mutedPromise;
}
async function test_playing_icon_on_tab(tab, browser, isPinned) {
let icon = isPinned ? tab.overlayIcon : tab.overlayIcon;
let isActiveTab = tab === gBrowser.selectedTab;
await play(tab);
await test_tooltip(icon, "Mute tab", isActiveTab, tab);
ok(
!("muted" in get_tab_state(tab)),
"No muted attribute should be persisted"
);
ok(
!("muteReason" in get_tab_state(tab)),
"No muteReason property should be persisted"
);
await test_mute_tab(tab, icon, true);
ok("muted" in get_tab_state(tab), "Muted attribute should be persisted");
ok(
"muteReason" in get_tab_state(tab),
"muteReason property should be persisted"
);
await test_tooltip(icon, "Unmute tab", isActiveTab, tab);
await test_mute_tab(tab, icon, false);
ok(
!("muted" in get_tab_state(tab)),
"No muted attribute should be persisted"
);
ok(
!("muteReason" in get_tab_state(tab)),
"No muteReason property should be persisted"
);
await test_tooltip(icon, "Mute tab", isActiveTab, tab);
await test_mute_tab(tab, icon, true);
await pause(tab);
ok(tab.hasAttribute("muted"), "Tab should still be muted (attribute check)");
ok(
!tab.hasAttribute("soundplaying"),
"Tab should not be playing (attribute check)"
);
ok(tab.muted, "Tab should still be muted (property check)");
ok(!tab.soundPlaying, "Tab should not be playing (property check)");
await test_tooltip(icon, "Unmute tab", isActiveTab, tab);
await test_mute_tab(tab, icon, false);
ok(
!tab.hasAttribute("muted"),
"Tab should not be be muted (attribute check)"
);
ok(
!tab.hasAttribute("soundplaying"),
"Tab should not be be playing (attribute check)"
);
ok(!tab.muted, "Tab should not be be muted (property check)");
ok(!tab.soundPlaying, "Tab should not be be playing (property check)");
// Make sure it's possible to mute using the context menu.
await test_muting_using_menu(tab, false);
// Make sure it's possible to unmute using the context menu.
await test_muting_using_menu(tab, true);
}
async function test_playing_icon_on_hidden_tab(tab) {
let oldSelectedTab = gBrowser.selectedTab;
let otherTabs = [
await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE, true, true),
await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE, true, true),
];
let tabContainer = tab.container;
let alltabsButton = document.getElementById("alltabs-button");
let alltabsBadge = alltabsButton.badgeLabel;
function assertIconShowing() {
is(
getComputedStyle(alltabsBadge).backgroundImage,
"The audio playing icon is shown"
);
ok(
tabContainer.hasAttribute("hiddensoundplaying"),
"There are hidden audio tabs"
);
}
function assertIconHidden() {
is(
getComputedStyle(alltabsBadge).backgroundImage,
"none",
"The audio playing icon is hidden"
);
ok(
!tabContainer.hasAttribute("hiddensoundplaying"),
"There are no hidden audio tabs"
);
}
// Keep the passed in tab selected.
gBrowser.selectedTab = tab;
// Play sound in the other two (visible) tabs.
await play(otherTabs[0]);
await play(otherTabs[1]);
assertIconHidden();
// Hide one of the noisy tabs, we see the icon.
await hide_tab(otherTabs[0]);
assertIconShowing();
// Hiding the other tab keeps the icon.
await hide_tab(otherTabs[1]);
assertIconShowing();
// Pausing both tabs will hide the icon.
await pause(otherTabs[0]);
assertIconShowing();
await pause(otherTabs[1]);
assertIconHidden();
// The icon returns when audio starts again.
await play(otherTabs[0]);
await play(otherTabs[1]);
assertIconShowing();
// There is still an icon after hiding one tab.
await show_tab(otherTabs[0]);
assertIconShowing();
// The icon is hidden when both of the tabs are shown.
await show_tab(otherTabs[1]);
assertIconHidden();
await BrowserTestUtils.removeTab(otherTabs[0]);
await BrowserTestUtils.removeTab(otherTabs[1]);
// Make sure we didn't change the selected tab.
gBrowser.selectedTab = oldSelectedTab;
}
async function test_swapped_browser_while_playing(oldTab, newBrowser) {
// The tab was muted so it won't have soundplaying attribute even it's playing.
ok(
oldTab.hasAttribute("muted"),
"Expected the correct muted attribute on the old tab"
);
is(
oldTab.muteReason,
null,
"Expected the correct muteReason attribute on the old tab"
);
ok(
!oldTab.hasAttribute("soundplaying"),
"Expected the correct soundplaying attribute on the old tab"
);
let newTab = gBrowser.getTabForBrowser(newBrowser);
let AttrChangePromise = BrowserTestUtils.waitForEvent(
newTab,
"TabAttrModified",
false,
event => {
return event.detail.changed.includes("muted");
}
);
gBrowser.swapBrowsersAndCloseOther(newTab, oldTab);
await AttrChangePromise;
ok(
newTab.hasAttribute("muted"),
"Expected the correct muted attribute on the new tab"
);
is(
newTab.muteReason,
null,
"Expected the correct muteReason property on the new tab"
);
ok(
!newTab.hasAttribute("soundplaying"),
"Expected the correct soundplaying attribute on the new tab"
);
await test_tooltip(newTab.overlayIcon, "Unmute tab", true, newTab);
}
async function test_swapped_browser_while_not_playing(oldTab, newBrowser) {
ok(
oldTab.hasAttribute("muted"),
"Expected the correct muted attribute on the old tab"
);
is(
oldTab.muteReason,
null,
"Expected the correct muteReason property on the old tab"
);
ok(
!oldTab.hasAttribute("soundplaying"),
"Expected the correct soundplaying attribute on the old tab"
);
let newTab = gBrowser.getTabForBrowser(newBrowser);
let AttrChangePromise = BrowserTestUtils.waitForEvent(
newTab,
"TabAttrModified",
false,
event => {
return event.detail.changed.includes("muted");
}
);
let AudioPlaybackPromise = new Promise(resolve => {
let observer = () => {
ok(false, "Should not see an audio-playback notification");
};
Services.obs.addObserver(observer, "audio-playback");
setTimeout(() => {
Services.obs.removeObserver(observer, "audio-playback");
resolve();
}, 100);
});
gBrowser.swapBrowsersAndCloseOther(newTab, oldTab);
await AttrChangePromise;
ok(
newTab.hasAttribute("muted"),
"Expected the correct muted attribute on the new tab"
);
is(
newTab.muteReason,
null,
"Expected the correct muteReason property on the new tab"
);
ok(
!newTab.hasAttribute("soundplaying"),
"Expected the correct soundplaying attribute on the new tab"
);
// Wait to see if an audio-playback event is dispatched.
await AudioPlaybackPromise;
ok(
newTab.hasAttribute("muted"),
"Expected the correct muted attribute on the new tab"
);
is(
newTab.muteReason,
null,
"Expected the correct muteReason property on the new tab"
);
ok(
!newTab.hasAttribute("soundplaying"),
"Expected the correct soundplaying attribute on the new tab"
);
await test_tooltip(newTab.overlayIcon, "Unmute tab", true, newTab);
}
async function test_browser_swapping(tab) {
// First, test swapping with a playing but muted tab.
await play(tab);
await test_mute_tab(tab, tab.overlayIcon, true);
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
async function (newBrowser) {
await test_swapped_browser_while_playing(tab, newBrowser);
// Now, test swapping with a muted but not playing tab.
// Note that the tab remains muted, so we only need to pause playback.
tab = gBrowser.getTabForBrowser(newBrowser);
await pause(tab);
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:blank",
},
secondAboutBlankBrowser =>
test_swapped_browser_while_not_playing(tab, secondAboutBlankBrowser)
);
}
);
}
async function test_click_on_pinned_tab_after_mute() {
async function taskFn(browser) {
let tab = gBrowser.getTabForBrowser(browser);
gBrowser.selectedTab = originallySelectedTab;
isnot(
tab,
gBrowser.selectedTab,
"Sanity check, the tab should not be selected!"
);
// Steps to reproduce the bug:
// Pin the tab.
gBrowser.pinTab(tab);
// Start playback and wait for it to finish.
await play(tab);
// Mute the tab.
let icon = tab.overlayIcon;
await test_mute_tab(tab, icon, true);
// Pause playback and wait for it to finish.
await pause(tab);
// Unmute tab.
await test_mute_tab(tab, icon, false);
// Now click on the tab.
EventUtils.synthesizeMouseAtCenter(tab.iconImage, { button: 0 });
is(tab, gBrowser.selectedTab, "Tab switch should be successful");
// Cleanup.
gBrowser.unpinTab(tab);
gBrowser.selectedTab = originallySelectedTab;
}
let originallySelectedTab = gBrowser.selectedTab;
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
taskFn
);
}
// This test only does something useful in e10s!
async function test_cross_process_load() {
async function taskFn(browser) {
let tab = gBrowser.getTabForBrowser(browser);
// Start playback and wait for it to finish.
await play(tab);
let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(
tab,
"TabAttrModified",
false,
event => event.detail.changed.includes("soundplaying")
);
// Go to a different process.
BrowserTestUtils.startLoadingURIString(browser, "about:mozilla");
await BrowserTestUtils.browserLoaded(browser);
await soundPlayingStoppedPromise;
ok(
!tab.hasAttribute("soundplaying"),
"Tab should not be playing sound any more"
);
ok(!tab.soundPlaying, "Tab should not be playing sound any more");
}
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
taskFn
);
}
async function test_mute_keybinding() {
async function test_muting_using_keyboard(tab) {
let mutedPromise = get_wait_for_mute_promise(tab, true);
EventUtils.synthesizeKey("m", { ctrlKey: true });
await mutedPromise;
is(
tab.hasAttribute("indicator-replaces-favicon"),
!tab.pinned,
"Mute indicator should replace the favicon on hover if the tab isn't pinned"
);
mutedPromise = get_wait_for_mute_promise(tab, false);
EventUtils.synthesizeKey("m", { ctrlKey: true });
await mutedPromise;
}
async function taskFn(browser) {
let tab = gBrowser.getTabForBrowser(browser);
// Make sure it's possible to mute before the tab is playing.
await test_muting_using_keyboard(tab);
// Start playback and wait for it to finish.
await play(tab);
// Make sure it's possible to mute after the tab is playing.
await test_muting_using_keyboard(tab);
// Pause playback and wait for it to finish.
await pause(tab);
// Make sure things work if the tab is pinned.
gBrowser.pinTab(tab);
// Make sure it's possible to mute before the tab is playing.
await test_muting_using_keyboard(tab);
// Start playback and wait for it to finish.
await play(tab);
// Make sure it's possible to mute after the tab is playing.
await test_muting_using_keyboard(tab);
gBrowser.unpinTab(tab);
}
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
taskFn
);
}
async function test_on_browser(browser) {
let tab = gBrowser.getTabForBrowser(browser);
// Test the icon in a normal tab.
await test_playing_icon_on_tab(tab, browser, false);
gBrowser.pinTab(tab);
// Test the icon in a pinned tab.
await test_playing_icon_on_tab(tab, browser, true);
gBrowser.unpinTab(tab);
// Test the sound playing icon for hidden tabs.
await test_playing_icon_on_hidden_tab(tab);
// Retest with another browser in the foreground tab
if (gBrowser.selectedBrowser.currentURI.spec == PAGE) {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "data:text/html,test",
},
() => test_on_browser(browser)
);
} else {
await test_browser_swapping(tab, browser);
}
}
async function test_delayed_tabattr_removal() {
async function taskFn(browser) {
let tab = gBrowser.getTabForBrowser(browser);
await play(tab);
// Extend the delay to guarantee the soundplaying attribute
// is not removed from the tab when audio is stopped. Without
// the extended delay the attribute could be removed in the
// same tick and the test wouldn't catch that this broke.
await pause(tab, { extendedDelay: true });
}
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
taskFn
);
}
requestLongerTimeout(2);
add_task(async function test_page() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE,
},
test_on_browser
);
});
add_task(test_click_on_pinned_tab_after_mute);
add_task(test_cross_process_load);
add_task(test_mute_keybinding);
add_task(test_delayed_tabattr_removal);