Bug 1388664: Execute _saveStateAsync in idle callback. r?mikedeboer
MozReview-Commit-ID: EmqpUkoUeUi
--- a/browser/components/sessionstore/SessionSaver.jsm
+++ b/browser/components/sessionstore/SessionSaver.jsm
@@ -107,16 +107,22 @@ this.SessionSaver = Object.freeze({
var SessionSaverInternal = {
/**
* The timeout ID referencing an active timer for a delayed save. When no
* save is pending, this is null.
*/
_timeoutID: null,
/**
+ * The idle callback ID referencing an active idle callback. When no idle
+ * callback is pending, this is null.
+ * */
+ _idleCallbackID: null,
+
+ /**
* A timestamp that keeps track of when we saved the session last. We will
* this to determine the correct interval between delayed saves to not deceed
* the configured session write interval.
*/
_lastSaveTime: 0,
/**
* `true` if the user has been idle for at least
@@ -171,33 +177,52 @@ var SessionSaverInternal = {
}
// Interval until the next disk operation is allowed.
let interval = this._isIdle ? this._intervalWhileIdle : this._intervalWhileActive;
delay = Math.max(this._lastSaveTime + interval - Date.now(), delay, 0);
// Schedule a state save.
this._wasIdle = this._isIdle;
- this._timeoutID = setTimeout(() => this._saveStateAsync(), delay);
+ this._timeoutID = setTimeout(() => {
+ let hiddenDOMWindow = Services.appShell.hiddenDOMWindow;
+
+ // Execute _saveStateAsync when we have enough idle time. Otherwise,
+ // another idle request is made to schedule _saveStateAsync again.
+ let saveStateAsyncWhenIdle = (deadline) => {
+ // When looking at the telemetry data, the time it takes to execute
+ // _saveStateAsync is around 5.9ms (median). Therefore,
+ // we'll not execute the function when the idle time is less than 5ms.
+ if (deadline.timeRemaining() < 5) {
+ this._idleCallbackID = hiddenDOMWindow.requestIdleCallback(saveStateAsyncWhenIdle);
+ return;
+ }
+ this._saveStateAsync();
+ };
+
+ this._idleCallbackID = hiddenDOMWindow.requestIdleCallback(saveStateAsyncWhenIdle);
+ }, delay);
},
/**
* Sets the last save time to the current time. This will cause us to wait for
* at least the configured interval when runDelayed() is called next.
*/
updateLastSaveTime() {
this._lastSaveTime = Date.now();
},
/**
* Cancels all pending session saves.
*/
cancel() {
clearTimeout(this._timeoutID);
this._timeoutID = null;
+ Services.appShell.hiddenDOMWindow.cancelIdleCallback(this._idleCallbackID);
+ this._idleCallbackID = null;
},
/**
* Observe idle/ active notifications.
*/
observe(subject, topic, data) {
switch (topic) {
case "idle":
--- a/browser/components/sessionstore/test/browser_backup_recovery.js
+++ b/browser/components/sessionstore/test/browser_backup_recovery.js
@@ -25,16 +25,19 @@ function promiseRead(path) {
add_task(async function init() {
// Make sure that we are not racing with SessionSaver's time based
// saves.
Services.prefs.setIntPref(PREF_SS_INTERVAL, 10000000);
registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_SS_INTERVAL));
});
add_task(async function test_creation() {
+ // Cancel all pending session saves so they won't get in our way.
+ SessionSaver.cancel();
+
// Create dummy sessionstore backups
let OLD_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.baklz4");
let OLD_UPGRADE_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.baklz4-0000000");
await File.writeAtomic(OLD_BACKUP, "sessionstore.bak");
await File.writeAtomic(OLD_UPGRADE_BACKUP, "sessionstore upgrade backup");
await SessionFile.wipe();