Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
const { TabUnloader } = ChromeUtils.importESModule(
);
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);
}
});
});
}