From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../modules/test/browser/browser_TabUnloader.js | 381 +++++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100644 browser/modules/test/browser/browser_TabUnloader.js (limited to 'browser/modules/test/browser/browser_TabUnloader.js') diff --git a/browser/modules/test/browser/browser_TabUnloader.js b/browser/modules/test/browser/browser_TabUnloader.js new file mode 100644 index 0000000000..a4af0dbdc8 --- /dev/null +++ b/browser/modules/test/browser/browser_TabUnloader.js @@ -0,0 +1,381 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { TabUnloader } = ChromeUtils.import( + "resource:///modules/TabUnloader.jsm" +); + +const BASE_URL = "https://example.com/browser/browser/modules/test/browser/"; + +async function play(tab) { + let browser = tab.linkedBrowser; + + let waitForAudioPromise = BrowserTestUtils.waitForEvent( + tab, + "TabAttrModified", + false, + event => { + return ( + event.detail.changed.includes("soundplaying") && + tab.hasAttribute("soundplaying") + ); + } + ); + + await SpecialPowers.spawn(browser, [], async function () { + let audio = content.document.querySelector("audio"); + await audio.play(); + }); + + await waitForAudioPromise; +} + +async function addTab(win = window) { + return BrowserTestUtils.openNewForegroundTab({ + gBrowser: win.gBrowser, + url: BASE_URL + "dummy_page.html", + waitForLoad: true, + }); +} + +async function addPrivTab(win = window) { + const tab = BrowserTestUtils.addTab( + win.gBrowser, + BASE_URL + "dummy_page.html" + ); + const browser = win.gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + return tab; +} + +async function addAudioTab(win = window) { + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser: win.gBrowser, + url: BASE_URL + "file_mediaPlayback.html", + waitForLoad: true, + waitForStateStop: true, + }); + + await play(tab); + return tab; +} + +async function addWebRTCTab(win = window) { + let popupPromise = new Promise(resolve => { + win.PopupNotifications.panel.addEventListener( + "popupshown", + function () { + executeSoon(resolve); + }, + { once: true } + ); + }); + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser: win.gBrowser, + url: BASE_URL + "file_webrtc.html", + waitForLoad: true, + waitForStateStop: true, + }); + + await popupPromise; + + let recordingPromise = BrowserTestUtils.contentTopicObserved( + tab.linkedBrowser.browsingContext, + "recording-device-events" + ); + win.PopupNotifications.panel.firstElementChild.button.click(); + await recordingPromise; + + return tab; +} + +async function pressure() { + let tabDiscarded = BrowserTestUtils.waitForEvent( + document, + "TabBrowserDiscarded", + true + ); + TabUnloader.unloadTabAsync(null); + return tabDiscarded; +} + +function pressureAndObserve(aExpectedTopic) { + const promise = new Promise(resolve => { + const observer = { + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + observe(aSubject, aTopicInner, aData) { + if (aTopicInner == aExpectedTopic) { + Services.obs.removeObserver(observer, aTopicInner); + resolve(aData); + } + }, + }; + Services.obs.addObserver(observer, aExpectedTopic); + }); + TabUnloader.unloadTabAsync(null); + return promise; +} + +async function compareTabOrder(expectedOrder) { + let tabInfo = await TabUnloader.getSortedTabs(null); + + is( + tabInfo.length, + expectedOrder.length, + "right number of tabs in discard sort list" + ); + for (let idx = 0; idx < expectedOrder.length; idx++) { + is(tabInfo[idx].tab, expectedOrder[idx], "index " + idx + " is correct"); + } +} + +const PREF_PERMISSION_FAKE = "media.navigator.permission.fake"; +const PREF_AUDIO_LOOPBACK = "media.audio_loopback_dev"; +const PREF_VIDEO_LOOPBACK = "media.video_loopback_dev"; +const PREF_FAKE_STREAMS = "media.navigator.streams.fake"; +const PREF_ENABLE_UNLOADER = "browser.tabs.unloadOnLowMemory"; +const PREF_MAC_LOW_MEM_RESPONSE = "browser.lowMemoryResponseMask"; + +add_task(async function test() { + registerCleanupFunction(() => { + Services.prefs.clearUserPref(PREF_ENABLE_UNLOADER); + if (AppConstants.platform == "macosx") { + Services.prefs.clearUserPref(PREF_MAC_LOW_MEM_RESPONSE); + } + }); + Services.prefs.setBoolPref(PREF_ENABLE_UNLOADER, true); + + // On Mac, tab unloading and memory pressure notifications are limited + // to Nightly so force them on for this test for non-Nightly builds. i.e., + // tests on Release and Beta builds. Mac tab unloading and memory pressure + // notifications require this pref to be set. + if (AppConstants.platform == "macosx") { + Services.prefs.setIntPref(PREF_MAC_LOW_MEM_RESPONSE, 3); + } + + TabUnloader.init(); + + // Set some WebRTC simulation preferences. + let prefs = [ + [PREF_PERMISSION_FAKE, true], + [PREF_AUDIO_LOOPBACK, ""], + [PREF_VIDEO_LOOPBACK, ""], + [PREF_FAKE_STREAMS, true], + ]; + await SpecialPowers.pushPrefEnv({ set: prefs }); + + // Set up 6 tabs, three normal ones, one pinned, one playing sound and one + // pinned playing sound + let tab0 = gBrowser.tabs[0]; + let tab1 = await addTab(); + let tab2 = await addTab(); + let pinnedTab = await addTab(); + gBrowser.pinTab(pinnedTab); + let soundTab = await addAudioTab(); + let pinnedSoundTab = await addAudioTab(); + gBrowser.pinTab(pinnedSoundTab); + + // Open a new private window and add a tab + const windowPriv = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + const tabPriv0 = windowPriv.gBrowser.tabs[0]; + const tabPriv1 = await addPrivTab(windowPriv); + + // Move the original window to the foreground to pass the tests + gBrowser.selectedTab = tab0; + tab0.ownerGlobal.focus(); + + // Pretend we've visited the tabs + await BrowserTestUtils.switchTab(windowPriv.gBrowser, tabPriv1); + await BrowserTestUtils.switchTab(windowPriv.gBrowser, tabPriv0); + await BrowserTestUtils.switchTab(gBrowser, tab1); + await BrowserTestUtils.switchTab(gBrowser, tab2); + await BrowserTestUtils.switchTab(gBrowser, pinnedTab); + await BrowserTestUtils.switchTab(gBrowser, soundTab); + await BrowserTestUtils.switchTab(gBrowser, pinnedSoundTab); + await BrowserTestUtils.switchTab(gBrowser, tab0); + + // Checks the tabs are in the state we expect them to be + ok(pinnedTab.pinned, "tab is pinned"); + ok(pinnedSoundTab.soundPlaying, "tab is playing sound"); + ok( + pinnedSoundTab.pinned && pinnedSoundTab.soundPlaying, + "tab is pinned and playing sound" + ); + + await compareTabOrder([ + tab1, + tab2, + pinnedTab, + tabPriv1, + soundTab, + tab0, + pinnedSoundTab, + tabPriv0, + ]); + + // Check that the tabs are present + ok( + tab1.linkedPanel && + tab2.linkedPanel && + pinnedTab.linkedPanel && + soundTab.linkedPanel && + pinnedSoundTab.linkedPanel && + tabPriv0.linkedPanel && + tabPriv1.linkedPanel, + "tabs are present" + ); + + // Check that low-memory memory-pressure events unload tabs + await pressure(); + ok( + !tab1.linkedPanel, + "low-memory memory-pressure notification unloaded the LRU tab" + ); + + await compareTabOrder([ + tab2, + pinnedTab, + tabPriv1, + soundTab, + tab0, + pinnedSoundTab, + tabPriv0, + ]); + + // If no normal tab is available unload pinned tabs + await pressure(); + ok(!tab2.linkedPanel, "unloaded a second tab in LRU order"); + await compareTabOrder([ + pinnedTab, + tabPriv1, + soundTab, + tab0, + pinnedSoundTab, + tabPriv0, + ]); + + ok(soundTab.soundPlaying, "tab is still playing sound"); + + await pressure(); + ok(!pinnedTab.linkedPanel, "unloaded a pinned tab"); + await compareTabOrder([tabPriv1, soundTab, tab0, pinnedSoundTab, tabPriv0]); + + ok(pinnedSoundTab.soundPlaying, "tab is still playing sound"); + + // There are no unloadable tabs. + TabUnloader.unloadTabAsync(null); + ok(tabPriv1.linkedPanel, "a tab in a private window is never unloaded"); + + const histogram = TelemetryTestUtils.getAndClearHistogram( + "TAB_UNLOAD_TO_RELOAD" + ); + + // It's possible that we're already in the memory-pressure state + // and we may receive the "ongoing" message. + const message = await pressureAndObserve("memory-pressure"); + Assert.ok( + message == "low-memory" || message == "low-memory-ongoing", + "observed the memory-pressure notification because of no discardable tab" + ); + + // Add a WebRTC tab and another sound tab. + let webrtcTab = await addWebRTCTab(); + let anotherSoundTab = await addAudioTab(); + + await BrowserTestUtils.switchTab(gBrowser, tab1); + await BrowserTestUtils.switchTab(gBrowser, pinnedTab); + + const hist = histogram.snapshot(); + const numEvents = Object.values(hist.values).reduce((a, b) => a + b); + Assert.equal(numEvents, 2, "two tabs have been reloaded."); + + // tab0 has never been unloaded. No data is added to the histogram. + await BrowserTestUtils.switchTab(gBrowser, tab0); + + await compareTabOrder([ + tab1, + pinnedTab, + tabPriv1, + soundTab, + webrtcTab, + anotherSoundTab, + tab0, + pinnedSoundTab, + tabPriv0, + ]); + + await BrowserTestUtils.closeWindow(windowPriv); + + let window2 = await BrowserTestUtils.openNewBrowserWindow(); + let win2tab1 = window2.gBrowser.selectedTab; + let win2tab2 = await addTab(window2); + let win2winrtcTab = await addWebRTCTab(window2); + let win2tab3 = await addTab(window2); + + await compareTabOrder([ + tab1, + win2tab1, + win2tab2, + pinnedTab, + soundTab, + webrtcTab, + anotherSoundTab, + win2winrtcTab, + tab0, + win2tab3, + pinnedSoundTab, + ]); + + await BrowserTestUtils.closeWindow(window2); + + await compareTabOrder([ + tab1, + pinnedTab, + soundTab, + webrtcTab, + anotherSoundTab, + tab0, + pinnedSoundTab, + ]); + + // Cleanup + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); + BrowserTestUtils.removeTab(pinnedTab); + BrowserTestUtils.removeTab(soundTab); + BrowserTestUtils.removeTab(pinnedSoundTab); + BrowserTestUtils.removeTab(webrtcTab); + BrowserTestUtils.removeTab(anotherSoundTab); + + await awaitWebRTCClose(); +}); + +// Wait for the WebRTC indicator window to close. +function awaitWebRTCClose() { + if ( + Services.prefs.getBoolPref("privacy.webrtc.legacyGlobalIndicator", false) || + AppConstants.platform == "macosx" + ) { + return null; + } + + let win = Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator"); + if (!win) { + return null; + } + + return new Promise(resolve => { + win.addEventListener("unload", function listener(e) { + if (e.target == win.document) { + win.removeEventListener("unload", listener); + executeSoon(resolve); + } + }); + }); +} -- cgit v1.2.3