summaryrefslogtreecommitdiffstats
path: root/browser/components/sessionstore/test/browser_backup_recovery.js
blob: 81f6788560b3debf3ad3021be034be375e1fe424 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* 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.

var OS = Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
var {File, Constants, Path} = OS;

const PREF_SS_INTERVAL = "browser.sessionstore.interval";
const Paths = SessionFile.Paths;

// A text decoder.
var gDecoder = new TextDecoder();
// Global variables that contain sessionstore.js and sessionstore.bak data for
// comparison between tests.
var gSSData;
var 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() {
  // Create dummy sessionstore backups
  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

  // Ensure none of the sessionstore files and backups exists
  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");

  // Open a new tab, save session, ensure that the correct files 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);
  yield TabStateFlusher.flush(tab.linkedBrowser);
  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");

  // Open a second tab, save session, ensure that the correct files exist.
  info("Testing situation after a second write");
  let URL2 = URL_BASE + "?second_write";
  tab.linkedBrowser.loadURI(URL2);
  yield promiseBrowserLoaded(tab.linkedBrowser);
  yield TabStateFlusher.flush(tab.linkedBrowser);
  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();
});

var 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);
  yield TabStateFlusher.flush(tab.linkedBrowser);
  yield SessionSaver.run();
  gBrowser.removeTab(tab);

  let SOURCE = yield promiseRead(Paths.recovery);
  yield SessionFile.wipe();
  return SOURCE;
});

add_task(function* test_recovery() {
  // Remove all files.
  yield SessionFile.wipe();
  info("Attempting to recover from the recovery file");

  // Create Paths.recovery, ensure that we can recover from it.
  let SOURCE = yield promiseSource("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, "<Invalid JSON>");
  is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
  yield SessionFile.wipe();
});

add_task(function* test_recovery_inaccessible() {
  // Can't do chmod() on non-UNIX platforms, we need that for this test.
  if (AppConstants.platform != "macosx" && AppConstants.platform != "linux") {
    return;
  }

  info("Making recovery file inaccessible, attempting to recover from recovery backup");
  let SOURCE_RECOVERY = yield promiseSource("Paths.recovery");
  let SOURCE = yield promiseSource("Paths.recoveryBackup");
  yield File.makeDir(Paths.backups);
  yield File.writeAtomic(Paths.recoveryBackup, SOURCE);

  // Write a valid recovery file but make it inaccessible.
  yield File.writeAtomic(Paths.recovery, SOURCE_RECOVERY);
  yield File.setPermissions(Paths.recovery, { unixMode: 0 });

  is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
  yield File.setPermissions(Paths.recovery, { unixMode: 0o644 });
});

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");
});


/**
 * Tests loading of sessionstore when format version is known.
 */
add_task(function* test_version() {
  info("Preparing sessionstore");
  let SOURCE = yield promiseSource("Paths.clean");

  // Check there's a format version number
  is(JSON.parse(SOURCE).version[0], "sessionrestore", "Found sessionstore format version");

  // Create Paths.clean file
  yield File.makeDir(Paths.backups);
  yield File.writeAtomic(Paths.clean, SOURCE);

  info("Attempting to recover from the clean file");
  // Ensure that we can recover from Paths.recovery
  is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the clean file");
});

/**
 * Tests fallback to previous backups if format version is unknown.
 */
add_task(function* test_version_fallback() {
  info("Preparing data, making sure that it has a version number");
  let SOURCE = yield promiseSource("Paths.clean");
  let BACKUP_SOURCE = yield promiseSource("Paths.cleanBackup");

  is(JSON.parse(SOURCE).version[0], "sessionrestore", "Found sessionstore format version");
  is(JSON.parse(BACKUP_SOURCE).version[0], "sessionrestore", "Found backup sessionstore format version");

  yield File.makeDir(Paths.backups);

  info("Modifying format version number to something incorrect, to make sure that we disregard the file.");
  let parsedSource = JSON.parse(SOURCE);
  parsedSource.version[0] = "bookmarks";
  yield File.writeAtomic(Paths.clean, JSON.stringify(parsedSource));
  yield File.writeAtomic(Paths.cleanBackup, BACKUP_SOURCE);
  is((yield SessionFile.read()).source, BACKUP_SOURCE, "Recovered the correct source from the backup recovery file");

  info("Modifying format version number to a future version, to make sure that we disregard the file.");
  parsedSource = JSON.parse(SOURCE);
  parsedSource.version[1] = Number.MAX_SAFE_INTEGER;
  yield File.writeAtomic(Paths.clean, JSON.stringify(parsedSource));
  yield File.writeAtomic(Paths.cleanBackup, BACKUP_SOURCE);
  is((yield SessionFile.read()).source, BACKUP_SOURCE, "Recovered the correct source from the backup recovery file");
});

add_task(function* cleanup() {
  yield SessionFile.wipe();
});