diff options
Diffstat (limited to 'browser/base/content/test/general/browser_audioTabIcon.js')
-rw-r--r-- | browser/base/content/test/general/browser_audioTabIcon.js | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/browser/base/content/test/general/browser_audioTabIcon.js b/browser/base/content/test/general/browser_audioTabIcon.js new file mode 100644 index 000000000..4d7a7bbd8 --- /dev/null +++ b/browser/base/content/test/general/browser_audioTabIcon.js @@ -0,0 +1,504 @@ +const PAGE = "https://example.com/browser/browser/base/content/test/general/file_mediaPlayback.html"; +const TABATTR_REMOVAL_PREFNAME = "browser.tabs.delayHidingAudioPlayingIconMS"; +const INITIAL_TABATTR_REMOVAL_DELAY_MS = Services.prefs.getIntPref(TABATTR_REMOVAL_PREFNAME); + +function* wait_for_tab_playing_event(tab, expectPlaying) { + if (tab.soundPlaying == expectPlaying) { + ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + return true; + } + return yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => { + if (event.detail.changed.includes("soundplaying")) { + is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + return true; + } + return false; + }); +} + +function* play(tab) { + let browser = tab.linkedBrowser; + yield ContentTask.spawn(browser, {}, function* () { + let audio = content.document.querySelector("audio"); + audio.play(); + }); + + yield wait_for_tab_playing_event(tab, true); +} + +function* pause(tab, options) { + ok(tab.hasAttribute("soundplaying"), "The tab should have the soundplaying attribute when pause() is called"); + + 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 = + BrowserTestUtils.waitForEvent(browser, "DOMAudioPlaybackStopped", "DOMAudioPlaybackStopped event should get fired after pause"); + let awaitTabPausedAttrModified = + wait_for_tab_playing_event(tab, false); + yield ContentTask.spawn(browser, {}, function* () { + let audio = content.document.querySelector("audio"); + audio.pause(); + }); + + if (extendedDelay) { + ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after pausing"); + + yield awaitDOMAudioPlaybackStopped; + ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped"); + } + + yield awaitTabPausedAttrModified; + 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 prefs_general.js + // overrides the default value to help this and other tests run faster. + Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, INITIAL_TABATTR_REMOVAL_DELAY_MS); + } +} + +function disable_non_test_mouse(disable) { + let utils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + utils.disableNonTestMouseEvents(disable); +} + +function* hover_icon(icon, tooltip) { + disable_non_test_mouse(true); + + 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; +} + +function leave_icon(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"}); + + disable_non_test_mouse(false); +} + +function* test_tooltip(icon, expectedTooltip, isActiveTab) { + let tooltip = document.getElementById("tabbrowser-tab-tooltip"); + + yield 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); +} + +// The set of tabs which have ever had their mute state changed. +// Used to determine whether the tab should have a muteReason value. +let everMutedTabs = new WeakSet(); + +function get_wait_for_mute_promise(tab, expectMuted) { + return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => { + if (event.detail.changed.includes("muted")) { + is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted"); + is(tab.muted, expectMuted, "The tab muted property " + (expectMuted ? "" : "not ") + "be true"); + + if (expectMuted || everMutedTabs.has(tab)) { + everMutedTabs.add(tab); + is(tab.muteReason, null, "The tab should have a null muteReason value"); + } else { + is(tab.muteReason, undefined, "The tab should have an undefined muteReason value"); + } + return true; + } + return false; + }); +} + +function* test_mute_tab(tab, icon, expectMuted) { + let mutedPromise = test_mute_keybinding(tab, expectMuted); + + let activeTab = gBrowser.selectedTab; + + let tooltip = document.getElementById("tabbrowser-tab-tooltip"); + + yield hover_icon(icon, tooltip); + EventUtils.synthesizeMouseAtCenter(icon, {button: 0}); + leave_icon(icon); + + is(gBrowser.selectedTab, activeTab, "Clicking on mute should not change the currently selected tab"); + + return mutedPromise; +} + +function get_tab_state(tab) { + const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); + return JSON.parse(ss.getTabState(tab)); +} + +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}); + yield popupShownPromise; + + // Check the menu + let expectedLabel = expectMuted ? "Unmute Tab" : "Mute Tab"; + let toggleMute = document.getElementById("context_toggleMuteTab"); + is(toggleMute.label, expectedLabel, "Correct label expected"); + is(toggleMute.accessKey, "M", "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"); + + yield play(tab); + + is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute"); + ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute"); + + yield 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"); + EventUtils.synthesizeMouseAtCenter(toggleMute, {}); + yield popupHiddenPromise; + yield mutedPromise; +} + +function* test_playing_icon_on_tab(tab, browser, isPinned) { + let icon = document.getAnonymousElementByAttribute(tab, "anonid", + isPinned ? "overlay-icon" : "soundplaying-icon"); + let isActiveTab = tab === gBrowser.selectedTab; + + yield play(tab); + + yield test_tooltip(icon, "Mute tab", isActiveTab); + + 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"); + + yield 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"); + + yield test_tooltip(icon, "Unmute tab", isActiveTab); + + yield 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"); + + yield test_tooltip(icon, "Mute tab", isActiveTab); + + yield test_mute_tab(tab, icon, true); + + yield pause(tab); + + ok(tab.hasAttribute("muted") && + !tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing"); + ok(tab.muted && !tab.soundPlaying, "Tab should still be muted but not playing"); + + yield test_tooltip(icon, "Unmute tab", isActiveTab); + + yield test_mute_tab(tab, icon, false); + + ok(!tab.hasAttribute("muted") && + !tab.hasAttribute("soundplaying"), "Tab should not be be muted or playing"); + ok(!tab.muted && !tab.soundPlaying, "Tab should not be be muted or playing"); + + // Make sure it's possible to mute using the context menu. + yield test_muting_using_menu(tab, false); + + // Make sure it's possible to unmute using the context menu. + yield test_muting_using_menu(tab, true); +} + +function* test_swapped_browser_while_playing(oldTab, newBrowser) { + 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("soundplaying") && + event.detail.changed.includes("muted"); + }); + + gBrowser.swapBrowsersAndCloseOther(newTab, oldTab); + yield 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"); + + let icon = document.getAnonymousElementByAttribute(newTab, "anonid", + "soundplaying-icon"); + yield test_tooltip(icon, "Unmute tab", true); +} + +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 = (subject, topic, data) => { + ok(false, "Should not see an audio-playback notification"); + }; + Services.obs.addObserver(observer, "audiochannel-activity-normal", false); + setTimeout(() => { + Services.obs.removeObserver(observer, "audiochannel-activity-normal"); + resolve(); + }, 100); + }); + + gBrowser.swapBrowsersAndCloseOther(newTab, oldTab); + yield 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. + yield 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"); + + let icon = document.getAnonymousElementByAttribute(newTab, "anonid", + "soundplaying-icon"); + yield test_tooltip(icon, "Unmute tab", true); +} + +function* test_browser_swapping(tab, browser) { + // First, test swapping with a playing but muted tab. + yield play(tab); + + let icon = document.getAnonymousElementByAttribute(tab, "anonid", + "soundplaying-icon"); + yield test_mute_tab(tab, icon, true); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, function*(newBrowser) { + yield 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); + yield pause(tab); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, secondAboutBlankBrowser => test_swapped_browser_while_not_playing(tab, secondAboutBlankBrowser)); + }); +} + +function* test_click_on_pinned_tab_after_mute() { + 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. + yield play(tab); + + // Mute the tab. + let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon"); + yield test_mute_tab(tab, icon, true); + + // Pause playback and wait for it to finish. + yield pause(tab); + + // Unmute tab. + yield test_mute_tab(tab, icon, false); + + // Now click on the tab. + let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image"); + EventUtils.synthesizeMouseAtCenter(image, {button: 0}); + + is(tab, gBrowser.selectedTab, "Tab switch should be successful"); + + // Cleanup. + gBrowser.unpinTab(tab); + gBrowser.selectedTab = originallySelectedTab; + } + + let originallySelectedTab = gBrowser.selectedTab; + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +// This test only does something useful in e10s! +function* test_cross_process_load() { + function* taskFn(browser) { + let tab = gBrowser.getTabForBrowser(browser); + + // Start playback and wait for it to finish. + yield play(tab); + + let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, + event => event.detail.changed.includes("soundplaying") + ); + + // Go to a different process. + browser.loadURI("about:"); + yield BrowserTestUtils.browserLoaded(browser); + + yield soundPlayingStoppedPromise; + + ok(!tab.hasAttribute("soundplaying"), "Tab should not be playing sound any more"); + ok(!tab.soundPlaying, "Tab should not be playing sound any more"); + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +function* test_mute_keybinding() { + function* test_muting_using_keyboard(tab) { + let mutedPromise = get_wait_for_mute_promise(tab, true); + EventUtils.synthesizeKey("m", {ctrlKey: true}); + yield mutedPromise; + mutedPromise = get_wait_for_mute_promise(tab, false); + EventUtils.synthesizeKey("m", {ctrlKey: true}); + yield mutedPromise; + } + function* taskFn(browser) { + let tab = gBrowser.getTabForBrowser(browser); + + // Make sure it's possible to mute before the tab is playing. + yield test_muting_using_keyboard(tab); + + // Start playback and wait for it to finish. + yield play(tab); + + // Make sure it's possible to mute after the tab is playing. + yield test_muting_using_keyboard(tab); + + // Pause playback and wait for it to finish. + yield 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. + yield test_muting_using_keyboard(tab); + + // Start playback and wait for it to finish. + yield play(tab); + + // Make sure it's possible to mute after the tab is playing. + yield test_muting_using_keyboard(tab); + + gBrowser.unpinTab(tab); + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +function* test_on_browser(browser) { + let tab = gBrowser.getTabForBrowser(browser); + + // Test the icon in a normal tab. + yield test_playing_icon_on_tab(tab, browser, false); + + gBrowser.pinTab(tab); + + // Test the icon in a pinned tab. + yield test_playing_icon_on_tab(tab, browser, true); + + gBrowser.unpinTab(tab); + + // Retest with another browser in the foreground tab + if (gBrowser.selectedBrowser.currentURI.spec == PAGE) { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "data:text/html,test" + }, () => test_on_browser(browser)); + } else { + yield test_browser_swapping(tab, browser); + } +} + +function* test_delayed_tabattr_removal() { + function* taskFn(browser) { + let tab = gBrowser.getTabForBrowser(browser); + yield 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. + yield pause(tab, {extendedDelay: true}); + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, taskFn); +} + +add_task(function*() { + yield new Promise((resolve) => { + SpecialPowers.pushPrefEnv({"set": [ + ["browser.tabs.showAudioPlayingIcon", true], + ]}, resolve); + }); +}); + +requestLongerTimeout(2); +add_task(function* test_page() { + yield 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); |