--- a/browser/modules/AsyncTabSwitcher.jsm
+++ b/browser/modules/AsyncTabSwitcher.jsm
@@ -116,16 +116,21 @@ class AsyncTabSwitcher {
this.STATE_UNLOADED = 0;
this.STATE_LOADING = 1;
this.STATE_LOADED = 2;
this.STATE_UNLOADING = 3;
// re-entrancy guard:
this._processing = false;
+ // For telemetry, keeps track of what most recently cleared
+ // the loadTimer, which can tell us something about the cause
+ // of tab switch spinners.
+ this._loadTimerClearedBy = "none",
+
this._useDumpForLogging = false;
this._logInit = false;
this.window.addEventListener("MozAfterPaint", this);
this.window.addEventListener("MozLayerTreeReady", this);
this.window.addEventListener("MozLayerTreeCleared", this);
this.window.addEventListener("TabRemotenessChange", this);
this.window.addEventListener("sizemodechange", this);
@@ -447,16 +452,28 @@ class AsyncTabSwitcher {
// Don't break a user's browser if an assertion fails.
if (AppConstants.DEBUG) {
throw new Error("Assertion failure");
}
}
}
+ maybeClearLoadTimer(caller) {
+ if (this.loadingTab) {
+ this._loadTimerClearedBy = caller;
+ this.loadingTab = null;
+ if (this.loadTimer) {
+ this.clearTimer(this.loadTimer);
+ this.loadTimer = null;
+ }
+ }
+ }
+
+
// We've decided to try to load requestedTab.
loadRequestedTab() {
this.assert(!this.loadTimer);
this.assert(!this.minimizedOrFullyOccluded);
// loadingTab can be non-null here if we timed out loading the current tab.
// In that case we just overwrite it with a different tab; it's had its chance.
this.loadingTab = this.requestedTab;
@@ -510,19 +527,17 @@ class AsyncTabSwitcher {
if (this.blankTab && !this.blankTab.linkedBrowser) {
this.blankTab = null;
}
if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
this.spinnerHidden();
this.spinnerTab = null;
}
if (this.loadingTab && !this.loadingTab.linkedBrowser) {
- this.loadingTab = null;
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
+ this.maybeClearLoadTimer("preActions");
}
}
// This code runs after we've responded to an event or requested a new
// tab. It's expected that we've already updated all the principal
// state variables. This function takes care of updating any auxilliary
// state.
postActions() {
@@ -536,21 +551,17 @@ class AsyncTabSwitcher {
this.assert(!this.loadTimer || this.loadingTab);
this.assert(!this.loadingTab || this.loadTimer);
// If we're switching to a non-remote tab, there's no need to wait
// for it to send layers to the compositor, as this will happen
// synchronously. Clearing this here means that in the next step,
// we can load the non-remote browser immediately.
if (!this.requestedTab.linkedBrowser.isRemoteBrowser) {
- this.loadingTab = null;
- if (this.loadTimer) {
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- }
+ this.maybeClearLoadTimer("postActions");
}
// If we're not loading anything, try loading the requested tab.
let stateOfRequestedTab = this.getTabState(this.requestedTab);
if (!this.loadTimer && !this.minimizedOrFullyOccluded &&
(stateOfRequestedTab == this.STATE_UNLOADED ||
stateOfRequestedTab == this.STATE_UNLOADING ||
this.warmingTabs.has(this.requestedTab))) {
@@ -648,18 +659,17 @@ class AsyncTabSwitcher {
this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
}
}
// Fires when an ongoing load has taken too long.
onLoadTimeout() {
this.logState("onLoadTimeout");
this.preActions();
- this.loadTimer = null;
- this.loadingTab = null;
+ this.maybeClearLoadTimer("onLoadTimeout");
this.postActions();
}
// Fires when the layers become available for a tab.
onLayersReady(browser) {
let tab = this.tabbrowser.getTabForBrowser(browser);
if (!tab) {
// We probably got a layer update from a tab that got before
@@ -671,19 +681,17 @@ class AsyncTabSwitcher {
this.logState(`onLayersReady(${tab._tPos}, ${browser.isRemoteBrowser})`);
this.assert(this.getTabState(tab) == this.STATE_LOADING ||
this.getTabState(tab) == this.STATE_LOADED);
this.setTabState(tab, this.STATE_LOADED);
this.unwarmTab(tab);
if (this.loadingTab === tab) {
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- this.loadingTab = null;
+ this.maybeClearLoadTimer("onLayersReady");
}
}
// Fires when we paint the screen. Any tab switches we initiated
// previously are done, so there's no need to keep the old layers
// around.
onPaint() {
this.maybeVisibleTabs.clear();
@@ -739,21 +747,17 @@ class AsyncTabSwitcher {
if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
continue;
}
if (state == this.STATE_LOADING || state == this.STATE_LOADED) {
this.setTabState(tab, this.STATE_UNLOADING);
}
}
- if (this.loadTimer) {
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- }
- this.loadingTab = null;
+ this.maybeClearLoadTimer("onSizeModeOrOcc");
} else {
// We're no longer minimized or occluded. This means we might want
// to activate the current tab's docShell.
this.maybeActivateDocShell(this.tabbrowser.selectedTab);
}
}
onSwapDocShells(ourBrowser, otherBrowser) {
@@ -778,26 +782,22 @@ class AsyncTabSwitcher {
});
}
onEndSwapDocShells(ourBrowser, otherBrowser) {
// The swap has happened. We reset the loadingTab in
// case it has been swapped. We also set ourBrowser's state
// to whatever otherBrowser's state was before the swap.
- if (this.loadTimer) {
- // Clearing the load timer means that we will
- // immediately display a spinner if ourBrowser isn't
- // ready yet. Typically it will already be ready
- // though. If it's not, we're probably in a new window,
- // in which case we have no other tabs to display anyway.
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- }
- this.loadingTab = null;
+ // Clearing the load timer means that we will
+ // immediately display a spinner if ourBrowser isn't
+ // ready yet. Typically it will already be ready
+ // though. If it's not, we're probably in a new window,
+ // in which case we have no other tabs to display anyway.
+ this.maybeClearLoadTimer("onEndSwapDocShells");
let { state: otherState } = this.swapMap.get(otherBrowser);
this.swapMap.delete(otherBrowser);
let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
if (ourTab) {
this.setTabStateNoAction(ourTab, otherState);
@@ -1002,26 +1002,30 @@ class AsyncTabSwitcher {
this.assert(!this.spinnerTab);
let browser = this.requestedTab.linkedBrowser;
this.assert(browser.isRemoteBrowser);
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", this.window);
// We have a second, similar probe for capturing recordings of
// when the spinner is displayed for very long periods.
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", this.window);
this.addMarker("AsyncTabSwitch:SpinnerShown");
+ Services.telemetry
+ .getHistogramById("FX_TAB_SWITCH_SPINNER_VISIBLE_TRIGGER")
+ .add(this._loadTimerClearedBy);
}
spinnerHidden() {
this.assert(this.spinnerTab);
this.log("DEBUG: spinner time = " +
TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", this.window));
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", this.window);
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", this.window);
this.addMarker("AsyncTabSwitch:SpinnerHidden");
// we do not get a onPaint after displaying the spinner
+ this._loadTimerClearedBy = "none";
}
addMarker(marker) {
if (Services.profiler) {
Services.profiler.AddMarker(marker);
}
}