--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3259,18 +3259,18 @@
// Auxilliary state variables:
visibleTab: this.selectedTab, // Tab that's on screen.
spinnerTab: null, // Tab showing a spinner.
originalTab: this.selectedTab, // Tab that we started on.
tabbrowser: this, // Reference to gBrowser.
- loadTimer: null, // TAB_SWITCH_TIMEOUT timer.
- unloadTimer: null, // UNLOAD_DELAY timer.
+ loadTimer: null, // TAB_SWITCH_TIMEOUT nsITimer instance.
+ unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
// Map from tabs to STATE_* (below).
tabState: new Map(),
// True if we're in the midst of switching tabs.
switchInProgress: false,
// Keep an exact list of content processes (tabParent) in which
@@ -3288,16 +3288,34 @@
STATE_UNLOADED: 0,
STATE_LOADING: 1,
STATE_LOADED: 2,
STATE_UNLOADING: 3,
// re-entrancy guard:
_processing: false,
+ // Wraps nsITimer. Must not use the vanilla setTimeout and
+ // clearTimeout, because they will be blocked by nsIPromptService
+ // dialogs.
+ setTimer: function(callback, timeout) {
+ let event = {
+ notify: callback
+ };
+
+ var timer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(event, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ clearTimer: function(timer) {
+ timer.cancel();
+ },
+
getTabState: function(tab) {
let state = this.tabState.get(tab);
if (state === undefined) {
return this.STATE_UNLOADED;
}
return state;
},
@@ -3327,18 +3345,24 @@
window.addEventListener("MozAfterPaint", this);
window.addEventListener("MozLayerTreeReady", this);
window.addEventListener("MozLayerTreeCleared", this);
window.addEventListener("TabRemotenessChange", this);
this.setTabState(this.requestedTab, this.STATE_LOADED);
},
destroy: function() {
- clearTimeout(this.unloadTimer);
- clearTimeout(this.loadTimer);
+ if (this.unloadTimer) {
+ this.clearTimer(this.unloadTimer);
+ this.unloadTimer = null;
+ }
+ if (this.loadTimer) {
+ this.clearTimer(this.loadTimer);
+ this.loadTimer = null;
+ }
window.removeEventListener("MozAfterPaint", this);
window.removeEventListener("MozLayerTreeReady", this);
window.removeEventListener("MozLayerTreeCleared", this);
window.removeEventListener("TabRemotenessChange", this);
this.tabbrowser._switcher = null;
@@ -3454,17 +3478,17 @@
this.assert(!this.loadTimer);
// 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;
this.log("Loading tab " + this.tinfo(this.loadingTab));
this.setTabState(this.requestedTab, this.STATE_LOADING);
- this.loadTimer = setTimeout(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
+ this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
},
// This function runs before every event. It fixes up the state
// to account for closed tabs.
preActions: function() {
this.assert(this.tabbrowser._switcher);
this.assert(this.tabbrowser._switcher === this);
@@ -3478,17 +3502,17 @@
this.lastVisibleTab = null;
}
if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
this.spinnerHidden();
this.spinnerTab = null;
}
if (this.loadingTab && !this.loadingTab.linkedBrowser) {
this.loadingTab = null;
- clearTimeout(this.loadTimer);
+ this.clearTimer(this.loadTimer);
this.loadTimer = null;
}
},
// 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.
@@ -3533,16 +3557,17 @@
}
this.logState("done");
},
// Fires when we're ready to unload unused tabs.
onUnloadTimeout: function() {
this.logState("onUnloadTimeout");
+ this.unloadTimer = null;
this.preActions();
let numPending = 0;
// Unload any tabs that can be unloaded.
for (let [tab, state] of this.tabState) {
if (state == this.STATE_LOADED &&
!this.maybeVisibleTabs.has(tab) &&
@@ -3555,17 +3580,17 @@
if (state != this.STATE_UNLOADED && tab !== this.requestedTab) {
numPending++;
}
}
if (numPending) {
// Keep the timer going since there may be more tabs to unload.
- this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+ this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
}
this.postActions();
},
// Fires when an ongoing load has taken too long.
onLoadTimeout: function() {
this.logState("onLoadTimeout");
@@ -3580,17 +3605,17 @@
this.logState("onLayersReady");
let tab = this.tabbrowser.getTabForBrowser(browser);
this.setTabState(tab, this.STATE_LOADED);
this.maybeFinishTabSwitch();
if (this.loadingTab === tab) {
- clearTimeout(this.loadTimer);
+ this.clearTimer(this.loadTimer);
this.loadTimer = null;
this.loadingTab = null;
}
},
// 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.
@@ -3648,25 +3673,27 @@
let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
fl.tabParent.suppressDisplayport(true);
this.activeSuppressDisplayport.add(fl.tabParent);
}
this.preActions();
- clearTimeout(this.unloadTimer);
- this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+ if (this.unloadTimer) {
+ this.clearTimer(this.unloadTimer);
+ }
+ this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
this.postActions();
},
handleEvent: function(event, delayed = false) {
if (this._processing) {
- setTimeout(() => this.handleEvent(event, true), 0);
+ this.setTimer(() => this.handleEvent(event, true), 0);
return;
}
if (delayed && this.tabbrowser._switcher != this) {
// if we delayed processing this event, we might be out of date, in which
// case we drop the delayed events
return;
}
this._processing = true;