Commit 5bc9e5bb authored by Gabriele Svelto's avatar Gabriele Svelto
Browse files

Bug 675539 - Unload tabs in low-memory scenarios r=mconley

This adds a mechanism that discards tabs when the browser detects a low-memory
scenario. Tabs are discarded in LRU order prioritizing regular tabs over
pinned ones, pinned ones over tabs playing audio and all of the previous over
pinned tabs playing audio.

Differential Revision: https://phabricator.services.mozilla.com/D20476

--HG--
extra : moz-landing-system : lando
parent 0443979a
......@@ -480,6 +480,13 @@ pref("browser.tabs.remote.separatePrivilegedContentProcess", true);
// Turn on HTTP response process selection.
pref("browser.tabs.remote.useHTTPResponseProcessSelection", true);
// Unload tabs on low-memory on nightly.
#ifdef RELEASE_OR_BETA
pref("browser.tabs.unloadOnLowMemory", false);
#else
pref("browser.tabs.unloadOnLowMemory", true);
#endif
pref("browser.ctrlTab.recentlyUsedOrder", true);
// By default, do not export HTML at shutdown.
......
......@@ -433,6 +433,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
ShellService: "resource:///modules/ShellService.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TabUnloader: "resource:///modules/TabUnloader.jsm",
UIState: "resource://services-sync/UIState.jsm",
UITour: "resource:///modules/UITour.jsm",
WebChannel: "resource://gre/modules/WebChannel.jsm",
......@@ -1714,6 +1715,10 @@ BrowserGlue.prototype = {
LiveBookmarkMigrator.migrate().catch(Cu.reportError);
});
}
Services.tm.idleDispatchToMainThread(() => {
TabUnloader.init();
});
},
/**
......
/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["TabUnloader"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
/**
* This module is responsible for detecting low-memory scenarios and unloading
* tabs in response to them.
*/
var TabUnloader = {
/**
* Initialize low-memory detection and tab auto-unloading.
*/
init() {
if (Services.prefs.getBoolPref("browser.tabs.unloadOnLowMemory", true)) {
Services.obs.addObserver(this, "memory-pressure", /* ownsWeak */ true);
}
},
observe(subject, topic, data) {
if (topic == "memory-pressure" && data != "heap-minimize") {
unloadLeastRecentlyUsedTab();
}
},
QueryInterface: ChromeUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]),
};
function unloadLeastRecentlyUsedTab() {
let bgTabBrowsers = getSortedBackgroundTabBrowsers();
for (let tb of bgTabBrowsers) {
if (tb.browser.discardBrowser(tb.tab)) {
return;
}
}
}
/* Sort tabs in the order we use for unloading, first non-pinned, non-audible
* tabs in LRU order, then non-audible tabs in LRU order, then non-pinned
* audible tabs in LRU order and finally pinned, audible tabs in LRU order. */
function sortTabs(a, b) {
if (a.tab.soundPlaying != b.tab.soundPlaying) {
return a.tab.soundPlaying - b.tab.soundPlaying;
}
if (a.tab.pinned != b.tab.pinned) {
return a.tab.pinned - b.tab.pinned;
}
return a.tab.lastAccessed - b.tab.lastAccessed;
}
function getSortedBackgroundTabBrowsers() {
let bgTabBrowsers = [];
for (let win of Services.wm.getEnumerator("navigator:browser")) {
for (let tab of win.gBrowser.tabs) {
if (!tab.selected && tab.linkedBrowser.isConnected) {
bgTabBrowsers.push({ tab, browser: win.gBrowser });
}
}
}
return bgTabBrowsers.sort(sortTabs);
}
......@@ -151,6 +151,7 @@ EXTRA_JS_MODULES += [
'SiteDataManager.jsm',
'SitePermissions.jsm',
'TabsList.jsm',
'TabUnloader.jsm',
'ThemeVariableMap.jsm',
'TransientPrefs.jsm',
'webrtcUI.jsm',
......
......@@ -24,6 +24,11 @@ skip-if = !e10s
[browser_SitePermissions_combinations.js]
[browser_SitePermissions_expiry.js]
[browser_SitePermissions_tab_urls.js]
[browser_TabUnloader.js]
support-files =
../../../base/content/test/tabs/dummy_page.html
../../../base/content/test/tabs/file_mediaPlayback.html
../../../base/content/test/general/audio.ogg
[browser_taskbar_preview.js]
skip-if = os != win || (os == win && bits == 64) # bug 1456807
[browser_UnsubmittedCrashHandler.js]
......
/* 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 = "http://example.com/browser/browser/modules/test/browser/";
async function play(tab) {
let browser = tab.linkedBrowser;
await ContentTask.spawn(browser, {}, async function() {
let audio = content.document.querySelector("audio");
await audio.play();
});
}
async function addTab() {
return BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: BASE_URL + "dummy_page.html",
waitForLoad: true,
});
}
async function addAudioTab() {
let tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: BASE_URL + "file_mediaPlayback.html",
waitForLoad: true,
waitForStateStop: true,
});
await play(tab);
return tab;
}
add_task(async function test() {
// 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);
// Pretend we've visited the tabs
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");
// Check that the tabs are present
ok(tab1.linkedPanel && tab2.linkedPanel && pinnedTab.linkedPanel &&
soundTab.linkedPanel && pinnedSoundTab.linkedPanel, "tabs are present");
// Check that heap-minimize memory-pressure events do not unload tabs
TabUnloader.observe(null, "memory-pressure", "heap-minimize");
ok(tab1.linkedPanel && tab2.linkedPanel && pinnedTab.linkedPanel &&
soundTab.linkedPanel && pinnedSoundTab.linkedPanel,
"heap-minimize memory-pressure notification did not unload a tab");
// Check that low-memory memory-pressure events unload tabs
TabUnloader.observe(null, "memory-pressure", "low-memory");
ok(!tab1.linkedPanel,
"low-memory memory-pressure notification unloaded the LRU tab");
// If no normal tab is available unload pinned tabs
TabUnloader.observe(null, "memory-pressure", "low-memory");
ok(!tab2.linkedPanel, "unloaded a second tab in LRU order");
TabUnloader.observe(null, "memory-pressure", "low-memory");
ok(!pinnedTab.linkedPanel, "unloaded a pinned tab");
// If no pinned tab is available unload tabs playing sound
TabUnloader.observe(null, "memory-pressure", "low-memory");
ok(!soundTab.linkedPanel, "unloaded a tab playing sound");
// If no pinned tab or tab playing sound is available unload tabs that are
// both pinned and playing sound
TabUnloader.observe(null, "memory-pressure", "low-memory");
ok(!pinnedSoundTab.linkedPanel, "unloaded a pinned tab playing sound");
// Check low-memory-ongoing events
await BrowserTestUtils.switchTab(gBrowser, tab1);
await BrowserTestUtils.switchTab(gBrowser, tab0);
TabUnloader.observe(null, "memory-pressure", "low-memory-ongoing");
ok(!tab1.linkedPanel,
"low-memory memory-pressure notification unloaded the LRU tab");
// Cleanup
BrowserTestUtils.removeTab(tab1);
BrowserTestUtils.removeTab(tab2);
BrowserTestUtils.removeTab(pinnedTab);
BrowserTestUtils.removeTab(soundTab);
BrowserTestUtils.removeTab(pinnedSoundTab);
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment