diff --git a/browser/components/sessionstore/test/browser.ini b/browser/components/sessionstore/test/browser.ini index 5bb361206686..55939409c956 100644 --- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -60,6 +60,7 @@ support-files = [browser_aboutPrivateBrowsing.js] [browser_aboutSessionRestore.js] [browser_attributes.js] +[browser_backup_recovery.js] [browser_broadcast.js] [browser_capabilities.js] [browser_cleaner.js] @@ -180,7 +181,6 @@ skip-if = true # Needs to be rewritten as Marionette test, bug 995916 [browser_739805.js] [browser_819510_perwindowpb.js] skip-if = os == "linux" # Intermittent failures, bug 894063 -[browser_833286_atomic_backup.js] # Disabled for frequent intermittent failures [browser_464620_a.js] diff --git a/browser/components/sessionstore/test/browser_394759_perwindowpb.js b/browser/components/sessionstore/test/browser_394759_perwindowpb.js index da8fa4091217..a8374170882d 100644 --- a/browser/components/sessionstore/test/browser_394759_perwindowpb.js +++ b/browser/components/sessionstore/test/browser_394759_perwindowpb.js @@ -3,63 +3,56 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Private Browsing Test for Bug 394759 **/ -function test() { - waitForExplicitFinish(); - let windowsToClose = []; - let closedWindowCount = 0; - // Prevent VM timers issues, cache now and increment it manually. - let now = Date.now(); - const TESTS = [ - { url: "about:config", - key: "bug 394759 Non-PB", - value: "uniq" + (++now) }, - { url: "about:mozilla", - key: "bug 394759 PB", - value: "uniq" + (++now) }, - ]; +let closedWindowCount = 0; +// Prevent VM timers issues, cache now and increment it manually. +let now = Date.now(); - registerCleanupFunction(function() { - Services.prefs.clearUserPref("browser.sessionstore.interval"); - windowsToClose.forEach(function(win) { - win.close(); - }); +const TESTS = [ + { url: "about:config", + key: "bug 394759 Non-PB", + value: "uniq" + (++now) }, + { url: "about:mozilla", + key: "bug 394759 PB", + value: "uniq" + (++now) }, +]; + +function promiseTestOpenCloseWindow(aIsPrivate, aTest) { + return Task.spawn(function*() { + let win = yield promiseNewWindowLoaded({ "private": aIsPrivate }); + win.gBrowser.selectedBrowser.loadURI(aTest.url); + yield promiseBrowserLoaded(win.gBrowser.selectedBrowser); + yield Promise.resolve(); + // Mark the window with some unique data to be restored later on. + ss.setWindowValue(win, aTest.key, aTest.value); + // Close. + yield promiseWindowClosed(win); }); +} - function testOpenCloseWindow(aIsPrivate, aTest, aCallback) { - whenNewWindowLoaded({ private: aIsPrivate }, function(win) { - whenBrowserLoaded(win.gBrowser.selectedBrowser, function() { - executeSoon(function() { - // Mark the window with some unique data to be restored later on. - ss.setWindowValue(win, aTest.key, aTest.value); - // Close. - win.close(); - aCallback(); - }); - }); - win.gBrowser.selectedBrowser.loadURI(aTest.url); - }); - } +function promiseTestOnWindow(aIsPrivate, aValue) { + return Task.spawn(function*() { + let win = yield promiseNewWindowLoaded({ "private": aIsPrivate }); + yield promiseCheckClosedWindows(aIsPrivate, aValue); + registerCleanupFunction(() => promiseWindowClosed(win)); + }); +} - function testOnWindow(aIsPrivate, aValue, aCallback) { - whenNewWindowLoaded({ private: aIsPrivate }, function(win) { - windowsToClose.push(win); - executeSoon(function() checkClosedWindows(aIsPrivate, aValue, aCallback)); - }); - } - - function checkClosedWindows(aIsPrivate, aValue, aCallback) { +function promiseCheckClosedWindows(aIsPrivate, aValue) { + return Task.spawn(function*() { let data = JSON.parse(ss.getClosedWindowData())[0]; - is(ss.getClosedWindowCount(), 1, "Check the closed window count"); + is(ss.getClosedWindowCount(), 1, "Check that the closed window count hasn't changed"); ok(JSON.stringify(data).indexOf(aValue) > -1, "Check the closed window data was stored correctly"); - aCallback(); - } + }); +} - function setupBlankState(aCallback) { +function promiseBlankState() { + return Task.spawn(function*() { // Set interval to a large time so state won't be written while we setup // environment. Services.prefs.setIntPref("browser.sessionstore.interval", 100000); + registerCleanupFunction(() => Services.prefs.clearUserPref("browser.sessionstore.interval")); // Set up the browser in a blank state. Popup windows in previous tests // result in different states on different platforms. @@ -70,40 +63,39 @@ function test() { }], _closedWindows: [] }); + ss.setBrowserState(blankState); // Wait for the sessionstore.js file to be written before going on. // Note: we don't wait for the complete event, since if asyncCopy fails we // would timeout. - waitForSaveState(function(writing) { - ok(writing, "sessionstore.js is being written"); - closedWindowCount = ss.getClosedWindowCount(); - is(closedWindowCount, 0, "Correctly set window count"); - executeSoon(aCallback); - }); + yield forceSaveState(); + closedWindowCount = ss.getClosedWindowCount(); + is(closedWindowCount, 0, "Correctly set window count"); // Remove the sessionstore.js file before setting the interval to 0 - let profilePath = Services.dirsvc.get("ProfD", Ci.nsIFile); - let sessionStoreJS = profilePath.clone(); - sessionStoreJS.append("sessionstore.js"); - if (sessionStoreJS.exists()) - sessionStoreJS.remove(false); - info("sessionstore.js was correctly removed: " + (!sessionStoreJS.exists())); + yield SessionFile.wipe(); // Make sure that sessionstore.js can be forced to be created by setting // the interval pref to 0. - Services.prefs.setIntPref("browser.sessionstore.interval", 0); - } - - setupBlankState(function() { - testOpenCloseWindow(false, TESTS[0], function() { - testOpenCloseWindow(true, TESTS[1], function() { - testOnWindow(false, TESTS[0].value, function() { - testOnWindow(true, TESTS[0].value, finish); - }); - }); - }); + yield forceSaveState(); }); } +add_task(function* init() { + while (ss.getClosedWindowCount() > 0) { + ss.forgetClosedWindow(0); + } + while (ss.getClosedTabCount(window) > 0) { + ss.forgetClosedTab(window, 0); + } +}); + +add_task(function* main() { + yield promiseTestOpenCloseWindow(false, TESTS[0]); + yield promiseTestOpenCloseWindow(true, TESTS[1]); + yield promiseTestOnWindow(false, TESTS[0].value); + yield promiseTestOnWindow(true, TESTS[0].value); +}); + diff --git a/browser/components/sessionstore/test/browser_454908.js b/browser/components/sessionstore/test/browser_454908.js index 1deffa2a7ed7..6ae6ed3f1585 100644 --- a/browser/components/sessionstore/test/browser_454908.js +++ b/browser/components/sessionstore/test/browser_454908.js @@ -13,7 +13,7 @@ const PASS = "pwd-" + Math.random(); /** * Bug 454908 - Don't save/restore values of password fields. */ -add_task(function test_dont_save_passwords() { +add_task(function* test_dont_save_passwords() { // Make sure we do save form data. Services.prefs.clearUserPref("browser.sessionstore.privacy_level"); @@ -40,13 +40,12 @@ add_task(function test_dont_save_passwords() { is(passwd, "", "password wasn't saved/restored"); // Write to disk and read our file. - yield SessionSaver.run(); - let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); - let data = yield OS.File.read(path); - let state = new TextDecoder().decode(data); + yield forceSaveState(); + yield promiseForEachSessionRestoreFile((state, key) => + // Ensure that we have not saved our password. + ok(!state.contains(PASS), "password has not been written to file " + key) + ); - // Ensure that sessionstore.js doesn't contain our password. - is(state.indexOf(PASS), -1, "password has not been written to disk"); // Cleanup. gBrowser.removeTab(tab); diff --git a/browser/components/sessionstore/test/browser_625016.js b/browser/components/sessionstore/test/browser_625016.js index 29085c72f55a..57c740bc7895 100644 --- a/browser/components/sessionstore/test/browser_625016.js +++ b/browser/components/sessionstore/test/browser_625016.js @@ -36,7 +36,7 @@ add_task(function* new_window() { yield promiseWindowClosed(newWin); newWin = null; - let state = JSON.parse((yield promiseSaveFileContents())); + let state = JSON.parse((yield promiseRecoveryFileContents())); is(state.windows.length, 2, "observe1: 2 windows in data written to disk"); is(state._closedWindows.length, 0, @@ -60,7 +60,7 @@ add_task(function* new_tab() { try { newTab = gBrowser.addTab("about:mozilla"); - let state = JSON.parse((yield promiseSaveFileContents())); + let state = JSON.parse((yield promiseRecoveryFileContents())); is(state.windows.length, 1, "observe2: 1 window in data being written to disk"); is(state._closedWindows.length, 1, diff --git a/browser/components/sessionstore/test/browser_819510_perwindowpb.js b/browser/components/sessionstore/test/browser_819510_perwindowpb.js index 562c5aed9f37..01160fa4a489 100644 --- a/browser/components/sessionstore/test/browser_819510_perwindowpb.js +++ b/browser/components/sessionstore/test/browser_819510_perwindowpb.js @@ -164,7 +164,7 @@ function waitForWindowClose(aWin, aCallback) { } function forceWriteState(aCallback) { - return promiseSaveFileContents().then(function(data) { + return promiseRecoveryFileContents().then(function(data) { aCallback(JSON.parse(data)); }); } diff --git a/browser/components/sessionstore/test/browser_833286_atomic_backup.js b/browser/components/sessionstore/test/browser_833286_atomic_backup.js deleted file mode 100644 index ab7f68f5c497..000000000000 --- a/browser/components/sessionstore/test/browser_833286_atomic_backup.js +++ /dev/null @@ -1,99 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -// This tests are for a sessionstore.js atomic backup. -// Each test will wait for a write to the Session Store -// before executing. - -let tmp = {}; -Cu.import("resource://gre/modules/osfile.jsm", tmp); -Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp); - -const {OS, SessionFile} = tmp; - -const PREF_SS_INTERVAL = "browser.sessionstore.interval"; -// Full paths for sessionstore.js and sessionstore.bak. -const path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); -const backupPath = OS.Path.join(OS.Constants.Path.profileDir, - "sessionstore.bak"); - -// A text decoder. -let gDecoder = new TextDecoder(); -// Global variables that contain sessionstore.js and sessionstore.bak data for -// comparison between tests. -let gSSData; -let gSSBakData; - - - -add_task(function* testAfterFirstWrite() { - // Ensure sessionstore.bak is not created. We start with a clean - // profile so there was nothing to move to sessionstore.bak before - // initially writing sessionstore.js - let ssExists = yield OS.File.exists(path); - let ssBackupExists = yield OS.File.exists(backupPath); - ok(ssExists, "sessionstore.js should exist."); - ok(!ssBackupExists, "sessionstore.bak should not have been created, yet"); - - // Save sessionstore.js data to compare to the sessionstore.bak data in the - // next test. - let array = yield OS.File.read(path); - gSSData = gDecoder.decode(array); - - // Manually move to the backup since the first write has already happened - // and a backup would not be triggered again. - yield OS.File.move(path, backupPath); - - yield forceSaveState(); -}); - -add_task(function* testReadBackup() { - // Ensure sessionstore.bak is finally created. - let ssExists = yield OS.File.exists(path); - let ssBackupExists = yield OS.File.exists(backupPath); - ok(ssExists, "sessionstore.js exists."); - ok(ssBackupExists, "sessionstore.bak should now be created."); - - // Read sessionstore.bak data. - let array = yield OS.File.read(backupPath); - gSSBakData = gDecoder.decode(array); - - // Make sure that the sessionstore.bak is identical to the last - // sessionstore.js. - is(gSSBakData, gSSData, "sessionstore.js is backed up correctly."); - - // Read latest sessionstore.js. - array = yield OS.File.read(path); - gSSData = gDecoder.decode(array); - - // Read sessionstore.js with SessionFile.read. - let ssDataRead = yield SessionFile.read(); - is(ssDataRead, gSSData, "SessionFile.read read sessionstore.js correctly."); - - // Remove sessionstore.js to test fallback onto sessionstore.bak. - yield OS.File.remove(path); - ssExists = yield OS.File.exists(path); - ok(!ssExists, "sessionstore.js should be removed now."); - - // Read sessionstore.bak with SessionFile.read. - ssDataRead = yield SessionFile.read(); - is(ssDataRead, gSSBakData, - "SessionFile.read read sessionstore.bak correctly."); - - yield forceSaveState(); -}); - -add_task(function* testBackupUnchanged() { - // Ensure sessionstore.bak is backed up only once. - - // Read sessionstore.bak data. - let array = yield OS.File.read(backupPath); - let ssBakData = gDecoder.decode(array); - // Ensure the sessionstore.bak did not change. - is(ssBakData, gSSBakData, "sessionstore.bak is unchanged."); -}); - -add_task(function* cleanup() { - // Cleaning up after the test: removing the sessionstore.bak file. - yield OS.File.remove(backupPath); -}); diff --git a/browser/components/sessionstore/test/browser_backup_recovery.js b/browser/components/sessionstore/test/browser_backup_recovery.js new file mode 100644 index 000000000000..c939b81f4ac0 --- /dev/null +++ b/browser/components/sessionstore/test/browser_backup_recovery.js @@ -0,0 +1,132 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This tests are for a sessionstore.js atomic backup. +// Each test will wait for a write to the Session Store +// before executing. + +let OS = Cu.import("resource://gre/modules/osfile.jsm", {}).OS; +let {File, Constants, Path} = OS; + +const PREF_SS_INTERVAL = "browser.sessionstore.interval"; +const Paths = SessionFile.Paths; + +// A text decoder. +let gDecoder = new TextDecoder(); +// Global variables that contain sessionstore.js and sessionstore.bak data for +// comparison between tests. +let gSSData; +let gSSBakData; + +function promiseRead(path) { + return File.read(path, {encoding: "utf-8"}); +} + +add_task(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(function* test_creation() { + + let OLD_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak"); + let OLD_UPGRADE_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak-0000000"); + + yield File.writeAtomic(OLD_BACKUP, "sessionstore.bak"); + yield File.writeAtomic(OLD_UPGRADE_BACKUP, "sessionstore upgrade backup"); + + yield SessionFile.wipe(); + yield SessionFile.read(); // Reinitializes SessionFile + for (let k of Paths.loadOrder) { + ok(!(yield File.exists(Paths[k])), "After wipe " + k + " sessionstore file doesn't exist"); + } + ok(!(yield File.exists(OLD_BACKUP)), "After wipe, old backup doesn't exist"); + ok(!(yield File.exists(OLD_UPGRADE_BACKUP)), "After wipe, old upgrade backup doesn't exist"); + + let URL_BASE = "http://example.com/?atomic_backup_test_creation=" + Math.random(); + let URL = URL_BASE + "?first_write"; + let tab = gBrowser.addTab(URL); + + info("Testing situation after a single write"); + yield promiseBrowserLoaded(tab.linkedBrowser); + SyncHandlers.get(tab.linkedBrowser).flush(); + yield SessionSaver.run(); + + ok((yield File.exists(Paths.recovery)), "After write, recovery sessionstore file exists again"); + ok(!(yield File.exists(Paths.recoveryBackup)), "After write, recoveryBackup sessionstore doesn't exist"); + ok((yield promiseRead(Paths.recovery)).indexOf(URL) != -1, "Recovery sessionstore file contains the required tab"); + ok(!(yield File.exists(Paths.clean)), "After first write, clean shutdown sessionstore doesn't exist, since we haven't shutdown yet"); + + info("Testing situation after a second write"); + let URL2 = URL_BASE + "?second_write"; + tab.linkedBrowser.loadURI(URL2); + yield promiseBrowserLoaded(tab.linkedBrowser); + SyncHandlers.get(tab.linkedBrowser).flush(); + yield SessionSaver.run(); + + ok((yield File.exists(Paths.recovery)), "After second write, recovery sessionstore file still exists"); + ok((yield promiseRead(Paths.recovery)).indexOf(URL2) != -1, "Recovery sessionstore file contains the latest url"); + ok((yield File.exists(Paths.recoveryBackup)), "After write, recoveryBackup sessionstore now exists"); + let backup = yield promiseRead(Paths.recoveryBackup); + ok(backup.indexOf(URL2) == -1, "Recovery backup doesn't contain the latest url"); + ok(backup.indexOf(URL) != -1, "Recovery backup contains the original url"); + ok(!(yield File.exists(Paths.clean)), "After first write, clean shutdown sessinstore doesn't exist, since we haven't shutdown yet"); + + info("Reinitialize, ensure that we haven't leaked sensitive files"); + yield SessionFile.read(); // Reinitializes SessionFile + yield SessionSaver.run(); + ok(!(yield File.exists(Paths.clean)), "After second write, clean shutdown sessonstore doesn't exist, since we haven't shutdown yet"); + ok(!(yield File.exists(Paths.upgradeBackup)), "After second write, clean shutdwn sessionstore doesn't exist, since we haven't shutdown yet"); + ok(!(yield File.exists(Paths.nextUpgradeBackup)), "After second write, clean sutdown sessionstore doesn't exist, since we haven't shutdown yet"); + + gBrowser.removeTab(tab); + yield SessionFile.wipe(); +}); + +let promiseSource = Task.async(function*(name) { + let URL = "http://example.com/?atomic_backup_test_recovery=" + Math.random() + "&name=" + name; + let tab = gBrowser.addTab(URL); + + yield promiseBrowserLoaded(tab.linkedBrowser); + SyncHandlers.get(tab.linkedBrowser).flush(); + yield SessionSaver.run(); + gBrowser.removeTab(tab); + + let SOURCE = yield promiseRead(Paths.recovery); + yield SessionFile.wipe(); + return SOURCE; +}); + +add_task(function* test_recovery() { + yield SessionFile.wipe(); + info("Attempting to recover from the recovery file"); + let SOURCE = yield promiseSource("Paths.recovery"); + // Ensure that we can recover from Paths.recovery + yield File.makeDir(Paths.backups); + yield File.writeAtomic(Paths.recovery, SOURCE); + is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file"); + yield SessionFile.wipe(); + + info("Corrupting recovery file, attempting to recover from recovery backup"); + SOURCE = yield promiseSource("Paths.recoveryBackup"); + yield File.makeDir(Paths.backups); + yield File.writeAtomic(Paths.recoveryBackup, SOURCE); + yield File.writeAtomic(Paths.recovery, ""); + is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file"); +}); + +add_task(function* test_clean() { + yield SessionFile.wipe(); + let SOURCE = yield promiseSource("Paths.clean"); + yield File.writeAtomic(Paths.clean, SOURCE); + yield SessionFile.read(); + yield SessionSaver.run(); + is((yield promiseRead(Paths.cleanBackup)), SOURCE, "After first read/write, clean shutdown file has been moved to cleanBackup"); +}); + +add_task(function* cleanup() { + yield SessionFile.wipe(); +}); + diff --git a/browser/components/sessionstore/test/browser_privatetabs.js b/browser/components/sessionstore/test/browser_privatetabs.js index fc80f8c9b668..35c55869eba3 100644 --- a/browser/components/sessionstore/test/browser_privatetabs.js +++ b/browser/components/sessionstore/test/browser_privatetabs.js @@ -34,10 +34,8 @@ add_task(function() { SyncHandlers.get(tab2.linkedBrowser).flush(); info("Checking out state"); - yield SessionSaver.run(); - let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); - let data = yield OS.File.read(path); - let state = new TextDecoder().decode(data); + let state = yield promiseRecoveryFileContents(); + info("State: " + state); // Ensure that sessionstore.js only knows about the public tab ok(state.indexOf(URL_PUBLIC) != -1, "State contains public tab"); diff --git a/browser/components/sessionstore/test/browser_upgrade_backup.js b/browser/components/sessionstore/test/browser_upgrade_backup.js index 39b97719c59e..5c27789679c8 100644 --- a/browser/components/sessionstore/test/browser_upgrade_backup.js +++ b/browser/components/sessionstore/test/browser_upgrade_backup.js @@ -5,45 +5,36 @@ Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://gre/modules/osfile.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); -function test() { - waitForExplicitFinish(); +const Paths = SessionFile.Paths; - Task.spawn(function task() { - try { - // Wait until initialization is complete - yield SessionStore.promiseInitialized; +add_task(function* init() { + // Wait until initialization is complete + yield SessionStore.promiseInitialized; + yield SessionFile.wipe(); +}); - const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID"; - let buildID = Services.appinfo.platformBuildID; +add_task(function* test_upgrade_backup() { + const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID"; + let buildID = Services.appinfo.platformBuildID; + info("Let's check if we create an upgrade backup"); + Services.prefs.setCharPref(PREF_UPGRADE, ""); + let contents = JSON.stringify({"browser_upgrade_backup.js": Math.random()}); + yield OS.File.writeAtomic(Paths.clean, contents); + yield SessionFile.read(); // First call to read() initializes the SessionWorker + yield SessionFile.write(""); // First call to write() triggers the backup - // Write state once before starting the test to - // ensure sessionstore.js writes won't happen in between. - yield forceSaveState(); + is(Services.prefs.getCharPref(PREF_UPGRADE), buildID, "upgrade backup should be set"); - // Force backup to take place with a file decided by us - Services.prefs.setCharPref(PREF_UPGRADE, ""); - let contents = "browser_upgrade_backup.js"; - let pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); - yield OS.File.writeAtomic(pathStore, contents, { tmpPath: pathStore + ".tmp" }); - yield SessionStore._internal._performUpgradeBackup(); - is(Services.prefs.getCharPref(PREF_UPGRADE), buildID, "upgrade backup should be set (again)"); + is((yield OS.File.exists(Paths.upgradeBackup)), true, "upgrade backup file has been created"); - let pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak-" + Services.appinfo.platformBuildID); - is((yield OS.File.exists(pathBackup)), true, "upgrade backup file has been created"); + let data = yield OS.File.read(Paths.upgradeBackup); + is(contents, (new TextDecoder()).decode(data), "upgrade backup contains the expected contents"); - let data = yield OS.File.read(pathBackup); - is(new TextDecoder().decode(data), contents, "upgrade backup contains the expected contents"); - - // Ensure that we don't re-backup by accident - yield OS.File.writeAtomic(pathStore, "something else entirely", { tmpPath: pathStore + ".tmp" }); - yield SessionStore._internal._performUpgradeBackup(); - data = yield OS.File.read(pathBackup); - is(new TextDecoder().decode(data), contents, "upgrade backup hasn't changed"); - - } catch (ex) { - ok(false, "Uncaught error: " + ex + " at " + ex.stack); - } finally { - finish(); - } - }); -} + info("Let's check that we don't overwrite this upgrade backup"); + let new_contents = JSON.stringify({"something else entirely": Math.random()}); + yield OS.File.writeAtomic(Paths.clean, new_contents); + yield SessionFile.read(); // Reinitialize the SessionWorker + yield SessionFile.write(""); // Next call to write() shouldn't trigger the backup + data = yield OS.File.read(Paths.upgradeBackup); + is(contents, (new TextDecoder()).decode(data), "upgrade backup hasn't changed"); +}); diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js index 18e92c44e084..fdf1158d0644 100644 --- a/browser/components/sessionstore/test/head.js +++ b/browser/components/sessionstore/test/head.js @@ -39,9 +39,11 @@ registerCleanupFunction(() => { let tmp = {}; Cu.import("resource://gre/modules/Promise.jsm", tmp); +Cu.import("resource://gre/modules/Task.jsm", tmp); Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp); Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", tmp); -let {Promise, SessionStore, SessionSaver} = tmp; +Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp); +let {Promise, Task, SessionStore, SessionSaver, SessionFile} = tmp; let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); @@ -282,13 +284,26 @@ function forceSaveState() { return SessionSaver.run(); } -function promiseSaveFileContents() { +function promiseRecoveryFileContents() { let promise = forceSaveState(); return promise.then(function() { - return OS.File.read(OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"), { encoding: "utf-8" }); + return OS.File.read(SessionFile.Paths.recovery, { encoding: "utf-8" }); }); } +let promiseForEachSessionRestoreFile = Task.async(function*(cb) { + for (let key of SessionFile.Paths.loadOrder) { + let data = ""; + try { + data = yield OS.File.read(SessionFile.Paths[key], { encoding: "utf-8" }); + } catch (ex if ex instanceof OS.File.Error + && ex.becauseNoSuchFile) { + // Ignore missing files + } + cb(data, key); + } +}); + function whenBrowserLoaded(aBrowser, aCallback = next, ignoreSubFrames = true) { aBrowser.addEventListener("load", function onLoad(event) { if (!ignoreSubFrames || event.target == aBrowser.contentDocument) { diff --git a/browser/components/sessionstore/test/unit/head.js b/browser/components/sessionstore/test/unit/head.js index f3afa0782c8e..54aacf827c9c 100644 --- a/browser/components/sessionstore/test/unit/head.js +++ b/browser/components/sessionstore/test/unit/head.js @@ -5,29 +5,28 @@ let Ci = Components.interfaces; Components.utils.import("resource://gre/modules/Services.jsm"); // Call a function once initialization of SessionStartup is complete -let afterSessionStartupInitialization = - function afterSessionStartupInitialization(cb) { - do_print("Waiting for session startup initialization"); - let observer = function() { - try { - do_print("Session startup initialization observed"); - Services.obs.removeObserver(observer, "sessionstore-state-finalized"); - cb(); - } catch (ex) { - do_throw(ex); - } - }; +function afterSessionStartupInitialization(cb) { + do_print("Waiting for session startup initialization"); + let observer = function() { + try { + do_print("Session startup initialization observed"); + Services.obs.removeObserver(observer, "sessionstore-state-finalized"); + cb(); + } catch (ex) { + do_throw(ex); + } + }; - // We need the Crash Monitor initialized for sessionstartup to run - // successfully. - Components.utils.import("resource://gre/modules/CrashMonitor.jsm"); - CrashMonitor.init(); + // We need the Crash Monitor initialized for sessionstartup to run + // successfully. + Components.utils.import("resource://gre/modules/CrashMonitor.jsm"); + CrashMonitor.init(); - // Start sessionstartup initialization. - let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. - getService(Ci.nsIObserver); - Services.obs.addObserver(startup, "final-ui-startup", false); - Services.obs.addObserver(startup, "quit-application", false); - Services.obs.notifyObservers(null, "final-ui-startup", ""); - Services.obs.addObserver(observer, "sessionstore-state-finalized", false); + // Start sessionstartup initialization. + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsIObserver); + Services.obs.addObserver(startup, "final-ui-startup", false); + Services.obs.addObserver(startup, "quit-application", false); + Services.obs.notifyObservers(null, "final-ui-startup", ""); + Services.obs.addObserver(observer, "sessionstore-state-finalized", false); }; diff --git a/browser/components/sessionstore/test/unit/test_backup.js b/browser/components/sessionstore/test/unit/test_backup.js deleted file mode 100644 index 0d8bac0990a4..000000000000 --- a/browser/components/sessionstore/test/unit/test_backup.js +++ /dev/null @@ -1,42 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - - -let toplevel = this; -Cu.import("resource://gre/modules/osfile.jsm"); - -function run_test() { - do_get_profile(); - Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel); - pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); - run_next_test(); -} - -let pathStore; -function pathBackup(ext) { - return OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak" + ext); -} - -// Ensure that things proceed smoothly if there is no file to back up -add_task(function test_nothing_to_backup() { - yield SessionFile.createBackupCopy(""); -}); - -// Create a file, back it up, remove it -add_task(function test_do_backup() { - let content = "test_1"; - let ext = ".upgrade_test_1"; - yield OS.File.writeAtomic(pathStore, content, {tmpPath: pathStore + ".tmp"}); - - do_print("Ensuring that the backup is created"); - yield SessionFile.createBackupCopy(ext); - do_check_true((yield OS.File.exists(pathBackup(ext)))); - - let data = yield OS.File.read(pathBackup(ext)); - do_check_eq((new TextDecoder()).decode(data), content); - - do_print("Ensuring that we can remove the backup"); - yield SessionFile.removeBackupCopy(ext); - do_check_false((yield OS.File.exists(pathBackup(ext)))); -}); - diff --git a/browser/components/sessionstore/test/unit/test_backup_once.js b/browser/components/sessionstore/test/unit/test_backup_once.js index 8dab30670e9c..75d327dafa7d 100644 --- a/browser/components/sessionstore/test/unit/test_backup_once.js +++ b/browser/components/sessionstore/test/unit/test_backup_once.js @@ -1,48 +1,151 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -let toplevel = this; -Cu.import("resource://gre/modules/osfile.jsm"); +"use strict"; + +let {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); +let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); +let {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +let {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {}); + +let File = OS.File; +let Paths; +let SessionFile; + +// We need a XULAppInfo to initialize SessionFile +let (XULAppInfo = { + vendor: "Mozilla", + name: "SessionRestoreTest", + ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}", + version: "1", + appBuildID: "2007010101", + platformVersion: "", + platformBuildID: "2007010101", + inSafeMode: false, + logConsoleErrors: true, + OS: "XPCShell", + XPCOMABI: "noarch-spidermonkey", + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIXULAppInfo, + Ci.nsIXULRuntime, + ]) +}) { + let XULAppInfoFactory = { + createInstance: function (outer, iid) { + if (outer != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + return XULAppInfo.QueryInterface(iid); + } + }; + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"), + "XULAppInfo", "@mozilla.org/xre/app-info;1", + XULAppInfoFactory); +}; function run_test() { - let profd = do_get_profile(); - Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel); - decoder = new TextDecoder(); - pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); - pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"); - let source = do_get_file("data/sessionstore_valid.js"); - source.copyTo(profd, "sessionstore.js"); run_next_test(); } +add_task(function* init() { + // Make sure that we have a profile before initializing SessionFile + let profd = do_get_profile(); + SessionFile = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}).SessionFile; + Paths = SessionFile.Paths; + + + let source = do_get_file("data/sessionstore_valid.js"); + source.copyTo(profd, "sessionstore.js"); + + // Finish initialization of SessionFile + yield SessionFile.read(); +}); + let pathStore; let pathBackup; let decoder; -// Write to the store, and check that a backup is created first -add_task(function test_first_write_backup() { - let content = "test_1"; - let initial_content = decoder.decode(yield OS.File.read(pathStore)); +function promise_check_exist(path, shouldExist) { + return Task.spawn(function*() { + do_print("Ensuring that " + path + (shouldExist?" exists":" does not exist")); + if ((yield OS.File.exists(path)) != shouldExist) { + throw new Error("File " + path + " should " + (shouldExist?"exist":"not exist")); + } + }); +} - do_check_true(!(yield OS.File.exists(pathBackup))); - yield SessionFile.write(content); - do_check_true(yield OS.File.exists(pathBackup)); +function promise_check_contents(path, expect) { + return Task.spawn(function*() { + do_print("Checking whether " + path + " has the right contents"); + let actual = yield OS.File.read(path, { encoding: "utf-8"}); + if (actual != expect) { + throw new Error("File " + path + " should contain\n\t" + expect + "\nbut contains " + actual); + } + }); +} - let backup_content = decoder.decode(yield OS.File.read(pathBackup)); - do_check_eq(initial_content, backup_content); +// Write to the store, and check that it creates: +// - $Path.recovery with the new data +// - $Path.nextUpgradeBackup with the old data +add_task(function* test_first_write_backup() { + let initial_content = "initial content " + Math.random(); + let new_content = "test_1 " + Math.random(); + + do_print("Before the first write, none of the files should exist"); + yield promise_check_exist(Paths.backups, false); + + yield File.makeDir(Paths.backups); + yield File.writeAtomic(Paths.clean, initial_content, { encoding: "utf-8" }); + yield SessionFile.write(new_content); + + do_print("After first write, a few files should have been created"); + yield promise_check_exist(Paths.backups, true); + yield promise_check_exist(Paths.clean, false); + yield promise_check_exist(Paths.cleanBackup, true); + yield promise_check_exist(Paths.recovery, true); + yield promise_check_exist(Paths.recoveryBackup, false); + yield promise_check_exist(Paths.nextUpgradeBackup, true); + + yield promise_check_contents(Paths.recovery, new_content); + yield promise_check_contents(Paths.nextUpgradeBackup, initial_content); }); -// Write to the store again, and check that the backup is not updated -add_task(function test_second_write_no_backup() { - let content = "test_2"; - let initial_content = decoder.decode(yield OS.File.read(pathStore)); - let initial_backup_content = decoder.decode(yield OS.File.read(pathBackup)); +// Write to the store again, and check that +// - $Path.clean is not written +// - $Path.recovery contains the new data +// - $Path.recoveryBackup contains the previous data +add_task(function* test_second_write_no_backup() { + let new_content = "test_2 " + Math.random(); + let previous_backup_content = yield File.read(Paths.recovery, { encoding: "utf-8" }); - yield SessionFile.write(content); + yield OS.File.remove(Paths.cleanBackup); - let written_content = decoder.decode(yield OS.File.read(pathStore)); - do_check_eq(content, written_content); + yield SessionFile.write(new_content); + + yield promise_check_exist(Paths.backups, true); + yield promise_check_exist(Paths.clean, false); + yield promise_check_exist(Paths.cleanBackup, false); + yield promise_check_exist(Paths.recovery, true); + yield promise_check_exist(Paths.nextUpgradeBackup, true); + + yield promise_check_contents(Paths.recovery, new_content); + yield promise_check_contents(Paths.recoveryBackup, previous_backup_content); +}); + +// Make sure that we create $Paths.clean and remove $Paths.recovery* +// upon shutdown +add_task(function* test_shutdown() { + let output = "test_3 " + Math.random(); + + yield File.writeAtomic(Paths.recovery, "I should disappear"); + yield File.writeAtomic(Paths.recoveryBackup, "I should also disappear"); + + yield SessionWorker.post("write", [output, { isFinalWrite: true, performShutdownCleanup: true}]); + + do_check_false((yield File.exists(Paths.recovery))); + do_check_false((yield File.exists(Paths.recoveryBackup))); + let input = yield File.read(Paths.clean, { encoding: "utf-8"}); + do_check_eq(input, output); - let backup_content = decoder.decode(yield OS.File.read(pathBackup)); - do_check_eq(initial_backup_content, backup_content); }); diff --git a/browser/components/sessionstore/test/unit/xpcshell.ini b/browser/components/sessionstore/test/unit/xpcshell.ini index b2dcb0b8c2df..8abbff5b8700 100644 --- a/browser/components/sessionstore/test/unit/xpcshell.ini +++ b/browser/components/sessionstore/test/unit/xpcshell.ini @@ -7,7 +7,6 @@ support-files = data/sessionstore_invalid.js data/sessionstore_valid.js -[test_backup.js] [test_backup_once.js] [test_startup_nosession_async.js] [test_startup_session_async.js]