Bug 1176019 - Cache layers of background tabs r=mconley

We maintain a simple LRU cache of tab layers by setting their
docShellIsActive = false with preserveLayers(true). Once they
are pushed out of the cache by more recently used tabs, their
layers are discarded.

Luckily most of the complexity of this could be contained in
the AsyncTabSwitcher - the one change that had to sit outside of
that was moving the aTab.closing = true earlier in the removeTab
call, so that we could use that information to eagerly evict tabs
from the cache. This was to address a leak in a few tests on try.

MozReview-Commit-ID: 2E3uU8LEYkD

--HG--
extra : rebase_source : b7e7bb3fcf1ed59e79a7c9fb9d3e6ce735ab54e9
This commit is contained in:
Doug Thayer 2018-04-16 15:35:41 -07:00
Родитель c34ad7d35b
Коммит bbadb1c753
4 изменённых файлов: 126 добавлений и 7 удалений

Просмотреть файл

@ -1540,6 +1540,14 @@ pref("browser.tabs.remote.warmup.enabled", true);
pref("browser.tabs.remote.warmup.enabled", false);
#endif
// Caches tab layers to improve perceived performance
// of tab switches.
#if defined(NIGHTLY_BUILD)
pref("browser.tabs.remote.tabCacheSize", 5);
#else
pref("browser.tabs.remote.tabCacheSize", 0);
#endif
pref("browser.tabs.remote.warmup.maxTabs", 3);
pref("browser.tabs.remote.warmup.unloadDelayMs", 2000);

Просмотреть файл

@ -195,6 +195,12 @@ We use a few tricks and optimizations to help improve the perceived performance
4. On platforms that support ``occlusionstatechange`` events (as of this writing, only macOS) and ``sizemodechange`` events (Windows, macOS and Linux), we stop rendering the layers for the currently selected tab when the window is minimized or fully occluded by another window.
5. Based on the browser.tabs.remote.tabCacheSize pref, we keep recently used tabs'
layers around to speed up tab switches by avoiding the round trip to the content
process. This uses a simple array (``_tabLayerCache``) inside tabbrowser.js, which
we examine when determining if we want to unload a tab's layers or not. This is still
experimental as of Nightly 62.
.. _async-tab-switcher.warming:
Warming

Просмотреть файл

@ -97,6 +97,8 @@ window._gBrowser = {
_contentWaitingCount: 0,
_tabLayerCache: [],
tabAnimationsInProgress: 0,
_XUL_NS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
@ -2756,6 +2758,13 @@ window._gBrowser = {
}
}
// this._switcher would normally cover removing a tab from this
// cache, but we may not have one at this time.
let tabCacheIndex = this._tabLayerCache.indexOf(aTab);
if (tabCacheIndex != -1) {
this._tabLayerCache.splice(tabCacheIndex, 1);
}
this._blurTab(aTab);
var closeWindow = false;

Просмотреть файл

@ -20,6 +20,8 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "gTabWarmingMax",
"browser.tabs.remote.warmup.maxTabs");
XPCOMUtils.defineLazyPreferenceGetter(this, "gTabWarmingUnloadDelayMs",
"browser.tabs.remote.warmup.unloadDelayMs");
XPCOMUtils.defineLazyPreferenceGetter(this, "gTabCacheSize",
"browser.tabs.remote.tabCacheSize");
/**
* The tab switcher is responsible for asynchronously switching
@ -291,6 +293,10 @@ class AsyncTabSwitcher {
this.window.isFullyOccluded;
}
get tabLayerCache() {
return this.tabbrowser._tabLayerCache;
}
finish() {
this.log("FINISH");
@ -508,6 +514,15 @@ class AsyncTabSwitcher {
this.assert(this.tabbrowser._switcher);
this.assert(this.tabbrowser._switcher === this);
for (let i = 0; i < this.tabLayerCache.length; i++) {
let tab = this.tabLayerCache[i];
if (!tab.linkedBrowser) {
this.tabState.delete(tab);
this.tabLayerCache.splice(i, 1);
i--;
}
}
for (let [tab, ] of this.tabState) {
if (!tab.linkedBrowser) {
this.tabState.delete(tab);
@ -566,6 +581,13 @@ class AsyncTabSwitcher {
this.loadRequestedTab();
}
let numBackgroundCached = 0;
for (let tab of this.tabLayerCache) {
if (tab !== this.requestedTab) {
numBackgroundCached++;
}
}
// See how many tabs still have work to do.
let numPending = 0;
let numWarming = 0;
@ -575,7 +597,9 @@ class AsyncTabSwitcher {
continue;
}
if (state == this.STATE_LOADED && tab !== this.requestedTab) {
if (state == this.STATE_LOADED &&
tab !== this.requestedTab &&
!this.tabLayerCache.includes(tab)) {
numPending++;
if (tab !== this.visibleTab) {
@ -598,8 +622,10 @@ class AsyncTabSwitcher {
this.maybeFinishTabSwitch();
if (numWarming > gTabWarmingMax) {
this.logState("Hit tabWarmingMax");
if (numWarming > gTabWarmingMax || numBackgroundCached > 0) {
if (numWarming > gTabWarmingMax) {
this.logState("Hit tabWarmingMax");
}
if (this.unloadTimer) {
this.clearTimer(this.unloadTimer);
}
@ -632,21 +658,34 @@ class AsyncTabSwitcher {
this.warmingTabs = new WeakSet();
let numPending = 0;
for (let tab of this.tabLayerCache) {
if (tab !== this.requestedTab) {
let browser = tab.linkedBrowser;
browser.preserveLayers(true);
browser.docShellIsActive = false;
}
}
// Unload any tabs that can be unloaded.
for (let [tab, state] of this.tabState) {
if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
continue;
}
let isInLayerCache = this.tabLayerCache.includes(tab);
if (state == this.STATE_LOADED &&
!this.maybeVisibleTabs.has(tab) &&
tab !== this.lastVisibleTab &&
tab !== this.loadingTab &&
tab !== this.requestedTab) {
tab !== this.requestedTab &&
!isInLayerCache) {
this.setTabState(tab, this.STATE_UNLOADING);
}
if (state != this.STATE_UNLOADED && tab !== this.requestedTab) {
if (state != this.STATE_UNLOADED &&
tab !== this.requestedTab &&
!isInLayerCache) {
numPending++;
}
}
@ -870,17 +909,49 @@ class AsyncTabSwitcher {
this.queueUnload(gTabWarmingUnloadDelayMs);
}
cleanUpTabAfterEviction(tab) {
this.assert(tab !== this.requestedTab);
let browser = tab.linkedBrowser;
if (browser) {
browser.preserveLayers(false);
}
this.setTabState(tab, this.STATE_UNLOADING);
}
evictOldestTabFromCache() {
let tab = this.tabLayerCache.shift();
this.cleanUpTabAfterEviction(tab);
}
maybePromoteTabInLayerCache(tab) {
if (gTabCacheSize > 1 &&
tab.linkedBrowser.isRemoteBrowser &&
tab.linkedBrowser.currentURI.spec != "about:blank") {
let tabIndex = this.tabLayerCache.indexOf(tab);
if (tabIndex != -1) {
this.tabLayerCache.splice(tabIndex, 1);
}
this.tabLayerCache.push(tab);
if (this.tabLayerCache.length > gTabCacheSize) {
this.evictOldestTabFromCache();
}
}
}
// Called when the user asks to switch to a given tab.
requestTab(tab) {
if (tab === this.requestedTab) {
return;
}
let tabState = this.getTabState(tab);
if (gTabWarmingEnabled) {
let warmingState = "disqualified";
if (this.canWarmTab(tab)) {
let tabState = this.getTabState(tab);
if (tabState == this.STATE_LOADING) {
warmingState = "stillLoading";
} else if (tabState == this.STATE_LOADED) {
@ -906,6 +977,9 @@ class AsyncTabSwitcher {
this.startTabSwitch();
this.requestedTab = tab;
if (tabState == this.STATE_LOADED) {
this.maybeVisibleTabs.clear();
}
tab.linkedBrowser.setAttribute("primary", "true");
if (this.lastPrimaryTab && this.lastPrimaryTab != tab) {
@ -992,6 +1066,10 @@ class AsyncTabSwitcher {
if (this.switchInProgress && this.requestedTab &&
(this.getTabState(this.requestedTab) == this.STATE_LOADED ||
this.requestedTab === this.blankTab)) {
if (this.requestedTab !== this.blankTab) {
this.maybePromoteTabInLayerCache(this.requestedTab);
}
// After this point the tab has switched from the content thread's point of view.
// The changes will be visible after the next refresh driver tick + composite.
let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", this.window);
@ -1078,19 +1156,37 @@ class AsyncTabSwitcher {
let tab = this.tabbrowser.tabs[i];
let state = this.getTabState(tab);
let isWarming = this.warmingTabs.has(tab);
let isCached = this.tabLayerCache.includes(tab);
let isClosing = tab.closing;
let linkedBrowser = tab.linkedBrowser;
let isActive = linkedBrowser && linkedBrowser.docShellIsActive;
let isRendered = linkedBrowser && linkedBrowser.renderLayers;
accum += i + ":";
if (tab === this.lastVisibleTab) accum += "V";
if (tab === this.loadingTab) accum += "L";
if (tab === this.requestedTab) accum += "R";
if (tab === this.blankTab) accum += "B";
if (isWarming) accum += "(W)";
let extraStates = "";
if (isWarming) extraStates += "W";
if (isCached) extraStates += "C";
if (isClosing) extraStates += "X";
if (isActive) extraStates += "A";
if (isRendered) extraStates += "R";
if (extraStates != "") {
accum += `(${extraStates})`;
}
if (state == this.STATE_LOADED) accum += "(+)";
if (state == this.STATE_LOADING) accum += "(+?)";
if (state == this.STATE_UNLOADED) accum += "(-)";
if (state == this.STATE_UNLOADING) accum += "(-?)";
accum += " ";
}
accum += "cached: " + this.tabLayerCache.length;
if (this._useDumpForLogging) {
dump(accum + "\n");
} else {