From d65b69f9862a4bd2c5b9663f03dab4b649b808cd Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Wed, 5 Feb 2014 23:47:51 -0800 Subject: [PATCH] Bug 965137 - Synchronously write session on application-background. r=mfinkle --- mobile/android/components/SessionStore.js | 66 ++++++++++++++++++----- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/mobile/android/components/SessionStore.js b/mobile/android/components/SessionStore.js index 46ad244424a..8c57b649745 100644 --- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -43,6 +43,7 @@ SessionStore.prototype = { _lastSaveTime: 0, _interval: 10000, _maxTabsUndo: 1, + _pendingWrite: 0, init: function ss_init() { // Get file references @@ -77,6 +78,7 @@ SessionStore.prototype = { observerService.addObserver(this, "domwindowclosed", true); observerService.addObserver(this, "browser:purge-session-history", true); observerService.addObserver(this, "Session:Restore", true); + observerService.addObserver(this, "application-background", true); break; case "final-ui-startup": observerService.removeObserver(this, "final-ui-startup"); @@ -102,7 +104,7 @@ SessionStore.prototype = { if (this._loadState == STATE_RUNNING) { // Save the purged state immediately - this.saveStateNow(); + this.saveState(); } Services.obs.notifyObservers(null, "sessionstore-state-purge-complete", ""); @@ -110,7 +112,9 @@ SessionStore.prototype = { case "timer-callback": // Timer call back for delayed saving this._saveTimer = null; - this.saveState(); + if (this._pendingWrite) { + this.saveState(); + } break; case "Session:Restore": { if (aData) { @@ -143,6 +147,13 @@ SessionStore.prototype = { } break; } + case "application-background": + // We receive this notification when Android's onPause callback is + // executed. After onPause, the application may be terminated at any + // point without notice; therefore, we must synchronously write out any + // pending save state to ensure that this data does not get lost. + this.flushPendingState(); + break; } }, @@ -306,7 +317,7 @@ SessionStore.prototype = { delete aBrowser.__SS_data; this._collectTabData(aWindow, aBrowser, data); - this.saveStateNow(); + this.saveState(); } this._updateCrashReportURL(aWindow); @@ -342,6 +353,7 @@ SessionStore.prototype = { // If we have to wait, set a timer, otherwise saveState directly let delay = Math.max(minimalDelay, 2000); if (delay > 0) { + this._pendingWrite++; this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); this._saveTimer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT); } else { @@ -350,16 +362,25 @@ SessionStore.prototype = { } }, - saveStateNow: function ss_saveStateNow() { + saveState: function ss_saveState() { + this._pendingWrite++; + this._saveState(true); + }, + + // Immediately and synchronously writes any pending state to disk. + flushPendingState: function ss_flushPendingState() { + if (this._pendingWrite) { + this._saveState(false); + } + }, + + _saveState: function ss_saveState(aAsync) { // Kill any queued timer and save immediately if (this._saveTimer) { this._saveTimer.cancel(); this._saveTimer = null; } - this.saveState(); - }, - saveState: function ss_saveState() { let data = this._getCurrentState(); let normalData = { windows: [] }; let privateData = { windows: [] }; @@ -388,7 +409,7 @@ SessionStore.prototype = { } // Write only non-private data to disk - this._writeFile(this._sessionFile, JSON.stringify(normalData)); + this._writeFile(this._sessionFile, JSON.stringify(normalData), aAsync); // If we have private data, send it to Java; otherwise, send null to // indicate that there is no private data @@ -464,7 +485,7 @@ SessionStore.prototype = { } }, - _writeFile: function ss_writeFile(aFile, aData) { + _writeFile: function ss_writeFile(aFile, aData, aAsync) { let stateString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); stateString.data = aData; Services.obs.notifyObservers(stateString, "sessionstore-state-write", ""); @@ -473,11 +494,28 @@ SessionStore.prototype = { if (!stateString.data) return; - // Asynchronously copy the data to the file. - let array = new TextEncoder().encode(aData); - OS.File.writeAtomic(aFile.path, array, { tmpPath: aFile.path + ".tmp" }).then(function onSuccess() { - Services.obs.notifyObservers(null, "sessionstore-state-write-complete", ""); - }); + if (aAsync) { + let array = new TextEncoder().encode(aData); + let pendingWrite = this._pendingWrite; + OS.File.writeAtomic(aFile.path, array, { tmpPath: aFile.path + ".tmp" }).then(function onSuccess() { + // Make sure this._pendingWrite is the same value it was before we + // fired off the async write. If the count is different, another write + // is pending, so we shouldn't reset this._pendingWrite yet. + if (pendingWrite === this._pendingWrite) + this._pendingWrite = 0; + Services.obs.notifyObservers(null, "sessionstore-state-write-complete", ""); + }.bind(this)); + } else { + this._pendingWrite = 0; + let foStream = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + foStream.init(aFile, 0x02 | 0x08 | 0x20, 0666, 0); + let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. + createInstance(Ci.nsIConverterOutputStream); + converter.init(foStream, "UTF-8", 0, 0); + converter.writeString(aData); + converter.close(); + } }, _updateCrashReportURL: function ss_updateCrashReportURL(aWindow) {