Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const Cc = Components.classes;
6 const Ci = Components.interfaces;
7 const Cu = Components.utils;
8 const Cr = Components.results;
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
11 Cu.import("resource://gre/modules/Services.jsm");
12 Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
14 #ifdef MOZ_CRASHREPORTER
15 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
16 "@mozilla.org/xre/app-info;1", "nsICrashReporter");
17 #endif
19 XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
20 "resource://gre/modules/CrashMonitor.jsm");
22 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
23 "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
25 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
26 Cu.import("resource://gre/modules/NetUtil.jsm");
27 return NetUtil;
28 });
30 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
31 "resource://gre/modules/UITelemetry.jsm");
33 // -----------------------------------------------------------------------
34 // Session Store
35 // -----------------------------------------------------------------------
37 const STATE_STOPPED = 0;
38 const STATE_RUNNING = 1;
39 const STATE_QUITTING = -1;
41 function SessionStore() { }
43 SessionStore.prototype = {
44 classID: Components.ID("{8c1f07d6-cba3-4226-a315-8bd43d67d032}"),
46 QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
47 Ci.nsIDOMEventListener,
48 Ci.nsIObserver,
49 Ci.nsISupportsWeakReference]),
51 _windows: {},
52 _tabsFromOtherGroups: [],
53 _selectedWindow: 1,
54 _orderedWindows: [],
55 _lastSaveTime: 0,
56 _lastSessionTime: 0,
57 _interval: 10000,
58 _maxTabsUndo: 1,
59 _shouldRestore: false,
61 // Tab telemetry variables
62 _maxTabsOpen: 1,
64 init: function ss_init() {
65 // Get file references
66 this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
67 this._sessionFileBackup = this._sessionFile.clone();
68 this._sessionCache = this._sessionFile.clone();
69 this._sessionFile.append("sessionstore.js");
70 this._sessionFileBackup.append("sessionstore.bak");
71 this._sessionCache.append("sessionstoreCache");
73 this._loadState = STATE_STOPPED;
75 try {
76 UITelemetry.addSimpleMeasureFunction("metro-tabs",
77 this._getTabStats.bind(this));
78 } catch (ex) {
79 // swallow exception that occurs if metro-tabs measure is already set up
80 }
82 CrashMonitor.previousCheckpoints.then(checkpoints => {
83 let previousSessionCrashed = false;
85 if (checkpoints) {
86 // If the previous session finished writing the final state, we'll
87 // assume there was no crash.
88 previousSessionCrashed = !checkpoints["sessionstore-final-state-write-complete"];
89 } else {
90 // If no checkpoints are saved, this is the first run with CrashMonitor or the
91 // metroSessionCheckpoints file was corrupted/deleted, so fallback to defining
92 // a crash as init-ing with an unexpected previousExecutionState
93 // 1 == RUNNING, 2 == SUSPENDED
94 previousSessionCrashed = Services.metro.previousExecutionState == 1 ||
95 Services.metro.previousExecutionState == 2;
96 }
98 Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!previousSessionCrashed);
99 });
101 try {
102 let shutdownWasUnclean = false;
104 if (this._sessionFileBackup.exists()) {
105 this._sessionFileBackup.remove(false);
106 shutdownWasUnclean = true;
107 }
109 if (this._sessionFile.exists()) {
110 this._sessionFile.copyTo(null, this._sessionFileBackup.leafName);
112 switch(Services.metro.previousExecutionState) {
113 // 0 == NotRunning
114 case 0:
115 // Disable crash recovery if we have exceeded the timeout
116 this._lastSessionTime = this._sessionFile.lastModifiedTime;
117 let delta = Date.now() - this._lastSessionTime;
118 let timeout =
119 Services.prefs.getIntPref(
120 "browser.sessionstore.resume_from_crash_timeout");
121 this._shouldRestore = shutdownWasUnclean
122 && (delta < (timeout * 60000));
123 break;
124 // 1 == Running
125 case 1:
126 // We should never encounter this situation
127 Components.utils.reportError("SessionRestore.init called with "
128 + "previous execution state 'Running'");
129 this._shouldRestore = true;
130 break;
131 // 2 == Suspended
132 case 2:
133 // We should never encounter this situation
134 Components.utils.reportError("SessionRestore.init called with "
135 + "previous execution state 'Suspended'");
136 this._shouldRestore = true;
137 break;
138 // 3 == Terminated
139 case 3:
140 // Terminated means that Windows terminated our already-suspended
141 // process to get back some resources. When we re-launch, we want
142 // to provide the illusion that our process was suspended the
143 // whole time, and never terminated.
144 this._shouldRestore = true;
145 break;
146 // 4 == ClosedByUser
147 case 4:
148 // ClosedByUser indicates that the user performed a "close" gesture
149 // on our tile. We should act as if the browser closed normally,
150 // even if we were closed from a suspended state (in which case
151 // we'll have determined that it was an unclean shtudown)
152 this._shouldRestore = false;
153 break;
154 }
155 }
157 if (!this._sessionCache.exists() || !this._sessionCache.isDirectory()) {
158 this._sessionCache.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
159 }
160 } catch (ex) {
161 Cu.reportError(ex); // file was write-locked?
162 }
164 this._interval = Services.prefs.getIntPref("browser.sessionstore.interval");
165 this._maxTabsUndo = Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
167 // Disable crash recovery if it has been turned off
168 if (!Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash"))
169 this._shouldRestore = false;
171 // Do we need to restore session just this once, in case of a restart?
172 if (Services.prefs.getBoolPref("browser.sessionstore.resume_session_once")) {
173 Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", false);
174 this._shouldRestore = true;
175 }
176 },
178 _clearDisk: function ss_clearDisk() {
179 if (this._sessionFile.exists()) {
180 try {
181 this._sessionFile.remove(false);
182 } catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
183 }
184 if (this._sessionFileBackup.exists()) {
185 try {
186 this._sessionFileBackup.remove(false);
187 } catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
188 }
190 this._clearCache();
191 },
193 _clearCache: function ss_clearCache() {
194 // First, let's get a list of files we think should be active
195 let activeFiles = [];
196 this._forEachBrowserWindow(function(aWindow) {
197 let tabs = aWindow.Browser.tabs;
198 for (let i = 0; i < tabs.length; i++) {
199 let browser = tabs[i].browser;
200 if (browser.__SS_extdata && "thumbnail" in browser.__SS_extdata)
201 activeFiles.push(browser.__SS_extdata.thumbnail);
202 }
203 });
205 // Now, let's find the stale files in the cache folder
206 let staleFiles = [];
207 let cacheFiles = this._sessionCache.directoryEntries;
208 while (cacheFiles.hasMoreElements()) {
209 let file = cacheFiles.getNext().QueryInterface(Ci.nsILocalFile);
210 let fileURI = Services.io.newFileURI(file);
211 if (activeFiles.indexOf(fileURI) == -1)
212 staleFiles.push(file);
213 }
215 // Remove the stale files in a separate step to keep the enumerator from
216 // messing up if we remove the files as we collect them.
217 staleFiles.forEach(function(aFile) {
218 aFile.remove(false);
219 })
220 },
222 _getTabStats: function() {
223 return {
224 currTabCount: this._currTabCount,
225 maxTabCount: this._maxTabsOpen
226 };
227 },
229 observe: function ss_observe(aSubject, aTopic, aData) {
230 let self = this;
231 let observerService = Services.obs;
232 switch (aTopic) {
233 case "app-startup":
234 observerService.addObserver(this, "final-ui-startup", true);
235 observerService.addObserver(this, "domwindowopened", true);
236 observerService.addObserver(this, "domwindowclosed", true);
237 observerService.addObserver(this, "browser-lastwindow-close-granted", true);
238 observerService.addObserver(this, "browser:purge-session-history", true);
239 observerService.addObserver(this, "quit-application-requested", true);
240 observerService.addObserver(this, "quit-application-granted", true);
241 observerService.addObserver(this, "quit-application", true);
242 observerService.addObserver(this, "reset-telemetry-vars", true);
243 break;
244 case "final-ui-startup":
245 observerService.removeObserver(this, "final-ui-startup");
246 if (WindowsPrefSync) {
247 // Pulls in Desktop controlled prefs and pushes out Metro controlled prefs
248 WindowsPrefSync.init();
249 }
250 this.init();
251 break;
252 case "domwindowopened":
253 let window = aSubject;
254 window.addEventListener("load", function() {
255 self.onWindowOpen(window);
256 window.removeEventListener("load", arguments.callee, false);
257 }, false);
258 break;
259 case "domwindowclosed": // catch closed windows
260 this.onWindowClose(aSubject);
261 break;
262 case "browser-lastwindow-close-granted":
263 // If a save has been queued, kill the timer and save state now
264 if (this._saveTimer) {
265 this._saveTimer.cancel();
266 this._saveTimer = null;
267 this.saveState();
268 }
270 // Freeze the data at what we've got (ignoring closing windows)
271 this._loadState = STATE_QUITTING;
272 break;
273 case "quit-application-requested":
274 // Get a current snapshot of all windows
275 this._forEachBrowserWindow(function(aWindow) {
276 self._collectWindowData(aWindow);
277 });
278 break;
279 case "quit-application-granted":
280 // Get a current snapshot of all windows
281 this._forEachBrowserWindow(function(aWindow) {
282 self._collectWindowData(aWindow);
283 });
285 // Freeze the data at what we've got (ignoring closing windows)
286 this._loadState = STATE_QUITTING;
287 break;
288 case "quit-application":
289 // If we are restarting, lets restore the tabs
290 if (aData == "restart") {
291 Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
293 // Ignore purges when restarting. The notification is fired after "quit-application".
294 Services.obs.removeObserver(this, "browser:purge-session-history");
295 }
297 // Freeze the data at what we've got (ignoring closing windows)
298 this._loadState = STATE_QUITTING;
300 // No need for this back up, we are shutting down just fine
301 if (this._sessionFileBackup.exists())
302 this._sessionFileBackup.remove(false);
304 observerService.removeObserver(this, "domwindowopened");
305 observerService.removeObserver(this, "domwindowclosed");
306 observerService.removeObserver(this, "browser-lastwindow-close-granted");
307 observerService.removeObserver(this, "quit-application-requested");
308 observerService.removeObserver(this, "quit-application-granted");
309 observerService.removeObserver(this, "quit-application");
310 observerService.removeObserver(this, "reset-telemetry-vars");
312 // If a save has been queued, kill the timer and save state now
313 if (this._saveTimer) {
314 this._saveTimer.cancel();
315 this._saveTimer = null;
316 }
317 this.saveState();
318 break;
319 case "browser:purge-session-history": // catch sanitization
320 this._clearDisk();
322 // If the browser is shutting down, simply return after clearing the
323 // session data on disk as this notification fires after the
324 // quit-application notification so the browser is about to exit.
325 if (this._loadState == STATE_QUITTING)
326 return;
328 // Clear all data about closed tabs
329 for (let [ssid, win] in Iterator(this._windows))
330 win._closedTabs = [];
332 if (this._loadState == STATE_RUNNING) {
333 // Save the purged state immediately
334 this.saveStateNow();
335 }
336 break;
337 case "timer-callback":
338 // Timer call back for delayed saving
339 this._saveTimer = null;
340 this.saveState();
341 break;
342 case "reset-telemetry-vars":
343 // Used in mochitests only.
344 this._maxTabsOpen = 1;
345 }
346 },
348 updateTabTelemetryVars: function(window) {
349 this._currTabCount = window.Browser.tabs.length;
350 if (this._currTabCount > this._maxTabsOpen) {
351 this._maxTabsOpen = this._currTabCount;
352 }
353 },
355 handleEvent: function ss_handleEvent(aEvent) {
356 let window = aEvent.currentTarget.ownerDocument.defaultView;
357 switch (aEvent.type) {
358 case "load":
359 browser = aEvent.currentTarget;
360 if (aEvent.target == browser.contentDocument && browser.__SS_tabFormData) {
361 browser.messageManager.sendAsyncMessage("SessionStore:restoreSessionTabData", {
362 formdata: browser.__SS_tabFormData.formdata,
363 scroll: browser.__SS_tabFormData.scroll
364 });
365 }
366 break;
367 case "TabOpen":
368 this.updateTabTelemetryVars(window);
369 let browser = aEvent.originalTarget.linkedBrowser;
370 browser.addEventListener("load", this, true);
371 case "TabClose": {
372 let browser = aEvent.originalTarget.linkedBrowser;
373 if (aEvent.type == "TabOpen") {
374 this.onTabAdd(window, browser);
375 }
376 else {
377 this.onTabClose(window, browser);
378 this.onTabRemove(window, browser);
379 }
380 break;
381 }
382 case "TabRemove":
383 this.updateTabTelemetryVars(window);
384 break;
385 case "TabSelect": {
386 let browser = aEvent.originalTarget.linkedBrowser;
387 this.onTabSelect(window, browser);
388 break;
389 }
390 }
391 },
393 receiveMessage: function ss_receiveMessage(aMessage) {
394 let browser = aMessage.target;
395 switch (aMessage.name) {
396 case "SessionStore:collectFormdata":
397 browser.__SS_data.formdata = aMessage.json.data;
398 break;
399 case "SessionStore:collectScrollPosition":
400 browser.__SS_data.scroll = aMessage.json.data;
401 break;
402 default:
403 let window = aMessage.target.ownerDocument.defaultView;
404 this.onTabLoad(window, aMessage.target, aMessage);
405 break;
406 }
407 },
409 onWindowOpen: function ss_onWindowOpen(aWindow) {
410 // Return if window has already been initialized
411 if (aWindow && aWindow.__SSID && this._windows[aWindow.__SSID])
412 return;
414 // Ignore non-browser windows and windows opened while shutting down
415 if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState == STATE_QUITTING)
416 return;
418 // Assign it a unique identifier and create its data object
419 aWindow.__SSID = "window" + gUUIDGenerator.generateUUID().toString();
420 this._windows[aWindow.__SSID] = { tabs: [], selected: 0, _closedTabs: [] };
421 this._orderedWindows.push(aWindow.__SSID);
423 // Perform additional initialization when the first window is loading
424 if (this._loadState == STATE_STOPPED) {
425 this._loadState = STATE_RUNNING;
426 this._lastSaveTime = Date.now();
428 // Nothing to restore, notify observers things are complete
429 if (!this.shouldRestore()) {
430 this._clearCache();
431 Services.obs.notifyObservers(null, "sessionstore-windows-restored", "");
432 }
433 }
435 // Add tab change listeners to all already existing tabs
436 let tabs = aWindow.Browser.tabs;
437 for (let i = 0; i < tabs.length; i++)
438 this.onTabAdd(aWindow, tabs[i].browser, true);
440 // Notification of tab add/remove/selection
441 let tabContainer = aWindow.document.getElementById("tabs");
442 tabContainer.addEventListener("TabOpen", this, true);
443 tabContainer.addEventListener("TabClose", this, true);
444 tabContainer.addEventListener("TabRemove", this, true);
445 tabContainer.addEventListener("TabSelect", this, true);
446 },
448 onWindowClose: function ss_onWindowClose(aWindow) {
449 // Ignore windows not tracked by SessionStore
450 if (!aWindow.__SSID || !this._windows[aWindow.__SSID])
451 return;
453 let tabContainer = aWindow.document.getElementById("tabs");
454 tabContainer.removeEventListener("TabOpen", this, true);
455 tabContainer.removeEventListener("TabClose", this, true);
456 tabContainer.removeEventListener("TabRemove", this, true);
457 tabContainer.removeEventListener("TabSelect", this, true);
459 if (this._loadState == STATE_RUNNING) {
460 // Update all window data for a last time
461 this._collectWindowData(aWindow);
463 // Clear this window from the list
464 delete this._windows[aWindow.__SSID];
466 // Save the state without this window to disk
467 this.saveStateDelayed();
468 }
470 let tabs = aWindow.Browser.tabs;
471 for (let i = 0; i < tabs.length; i++)
472 this.onTabRemove(aWindow, tabs[i].browser, true);
474 delete aWindow.__SSID;
475 },
477 onTabAdd: function ss_onTabAdd(aWindow, aBrowser, aNoNotification) {
478 aBrowser.messageManager.addMessageListener("pageshow", this);
479 aBrowser.messageManager.addMessageListener("Content:SessionHistory", this);
480 aBrowser.messageManager.addMessageListener("SessionStore:collectFormdata", this);
481 aBrowser.messageManager.addMessageListener("SessionStore:collectScrollPosition", this);
483 if (!aNoNotification)
484 this.saveStateDelayed();
485 this._updateCrashReportURL(aWindow);
486 },
488 onTabRemove: function ss_onTabRemove(aWindow, aBrowser, aNoNotification) {
489 aBrowser.messageManager.removeMessageListener("pageshow", this);
490 aBrowser.messageManager.removeMessageListener("Content:SessionHistory", this);
491 aBrowser.messageManager.removeMessageListener("SessionStore:collectFormdata", this);
492 aBrowser.messageManager.removeMessageListener("SessionStore:collectScrollPosition", this);
494 // If this browser is being restored, skip any session save activity
495 if (aBrowser.__SS_restore)
496 return;
498 delete aBrowser.__SS_data;
500 if (!aNoNotification)
501 this.saveStateDelayed();
502 },
504 onTabClose: function ss_onTabClose(aWindow, aBrowser) {
505 if (this._maxTabsUndo == 0)
506 return;
508 if (aWindow.Browser.tabs.length > 0) {
509 // Bundle this browser's data and extra data and save in the closedTabs
510 // window property
511 //
512 // NB: The access to aBrowser.__SS_extdata throws during automation (in
513 // browser_msgmgr_01). See bug 888736.
514 let data = aBrowser.__SS_data;
515 if (!data) {
516 return; // Cannot restore an empty tab.
517 }
518 try { data.extData = aBrowser.__SS_extdata; } catch (e) { }
520 this._windows[aWindow.__SSID]._closedTabs.unshift({ state: data });
521 let length = this._windows[aWindow.__SSID]._closedTabs.length;
522 if (length > this._maxTabsUndo)
523 this._windows[aWindow.__SSID]._closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
524 }
525 },
527 onTabLoad: function ss_onTabLoad(aWindow, aBrowser, aMessage) {
528 // If this browser is being restored, skip any session save activity
529 if (aBrowser.__SS_restore)
530 return;
532 // Ignore a transient "about:blank"
533 if (!aBrowser.canGoBack && aBrowser.currentURI.spec == "about:blank")
534 return;
536 if (aMessage.name == "Content:SessionHistory") {
537 delete aBrowser.__SS_data;
538 this._collectTabData(aBrowser, aMessage.json);
539 }
541 // Save out the state as quickly as possible
542 if (aMessage.name == "pageshow")
543 this.saveStateNow();
545 this._updateCrashReportURL(aWindow);
546 },
548 onTabSelect: function ss_onTabSelect(aWindow, aBrowser) {
549 if (this._loadState != STATE_RUNNING)
550 return;
552 let index = aWindow.Elements.browsers.selectedIndex;
553 this._windows[aWindow.__SSID].selected = parseInt(index) + 1; // 1-based
555 // Restore the resurrected browser
556 if (aBrowser.__SS_restore) {
557 let data = aBrowser.__SS_data;
558 if (data.entries.length > 0) {
559 let json = {
560 uri: data.entries[data.index - 1].url,
561 flags: null,
562 entries: data.entries,
563 index: data.index
564 };
565 aBrowser.messageManager.sendAsyncMessage("WebNavigation:LoadURI", json);
566 }
568 delete aBrowser.__SS_restore;
569 }
571 this._updateCrashReportURL(aWindow);
572 },
574 saveStateDelayed: function ss_saveStateDelayed() {
575 if (!this._saveTimer) {
576 // Interval until the next disk operation is allowed
577 let minimalDelay = this._lastSaveTime + this._interval - Date.now();
579 // If we have to wait, set a timer, otherwise saveState directly
580 let delay = Math.max(minimalDelay, 2000);
581 if (delay > 0) {
582 this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
583 this._saveTimer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
584 } else {
585 this.saveState();
586 }
587 }
588 },
590 saveStateNow: function ss_saveStateNow() {
591 // Kill any queued timer and save immediately
592 if (this._saveTimer) {
593 this._saveTimer.cancel();
594 this._saveTimer = null;
595 }
596 this.saveState();
597 },
599 saveState: function ss_saveState() {
600 let data = this._getCurrentState();
601 // sanity check before we overwrite the session file
602 if (data.windows && data.windows.length && data.selectedWindow) {
603 this._writeFile(this._sessionFile, JSON.stringify(data));
605 this._lastSaveTime = Date.now();
606 } else {
607 dump("SessionStore: Not saving state with invalid data: " + JSON.stringify(data) + "\n");
608 }
609 },
611 _getCurrentState: function ss_getCurrentState() {
612 let self = this;
613 this._forEachBrowserWindow(function(aWindow) {
614 self._collectWindowData(aWindow);
615 });
617 let data = { windows: [] };
618 for (let i = 0; i < this._orderedWindows.length; i++)
619 data.windows.push(this._windows[this._orderedWindows[i]]);
620 data.selectedWindow = this._selectedWindow;
621 return data;
622 },
624 _collectTabData: function ss__collectTabData(aBrowser, aHistory) {
625 // If this browser is being restored, skip any session save activity
626 if (aBrowser.__SS_restore)
627 return;
629 let aHistory = aHistory || { entries: [{ url: aBrowser.currentURI.spec, title: aBrowser.contentTitle }], index: 1 };
631 let tabData = {};
632 tabData.entries = aHistory.entries;
633 tabData.index = aHistory.index;
634 tabData.attributes = { image: aBrowser.mIconURL };
636 aBrowser.__SS_data = tabData;
637 },
639 _getTabData: function(aWindow) {
640 return aWindow.Browser.tabs
641 .filter(tab => !tab.isPrivate && tab.browser.__SS_data)
642 .map(tab => {
643 let browser = tab.browser;
644 let tabData = browser.__SS_data;
645 if (browser.__SS_extdata)
646 tabData.extData = browser.__SS_extdata;
647 return tabData;
648 });
649 },
651 _collectWindowData: function ss__collectWindowData(aWindow) {
652 // Ignore windows not tracked by SessionStore
653 if (!aWindow.__SSID || !this._windows[aWindow.__SSID])
654 return;
656 let winData = this._windows[aWindow.__SSID];
658 let index = aWindow.Elements.browsers.selectedIndex;
659 winData.selected = parseInt(index) + 1; // 1-based
661 let tabData = this._getTabData(aWindow);
662 winData.tabs = tabData.concat(this._tabsFromOtherGroups);
663 },
665 _forEachBrowserWindow: function ss_forEachBrowserWindow(aFunc) {
666 let windowsEnum = Services.wm.getEnumerator("navigator:browser");
667 while (windowsEnum.hasMoreElements()) {
668 let window = windowsEnum.getNext();
669 if (window.__SSID && !window.closed)
670 aFunc.call(this, window);
671 }
672 },
674 _writeFile: function ss_writeFile(aFile, aData) {
675 let stateString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
676 stateString.data = aData;
677 Services.obs.notifyObservers(stateString, "sessionstore-state-write", "");
679 // Don't touch the file if an observer has deleted all state data
680 if (!stateString.data)
681 return;
683 // Initialize the file output stream.
684 let ostream = Cc["@mozilla.org/network/safe-file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
685 ostream.init(aFile, 0x02 | 0x08 | 0x20, 0600, ostream.DEFER_OPEN);
687 // Obtain a converter to convert our data to a UTF-8 encoded input stream.
688 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
689 converter.charset = "UTF-8";
691 // Asynchronously copy the data to the file.
692 let istream = converter.convertToInputStream(aData);
693 NetUtil.asyncCopy(istream, ostream, function(rc) {
694 if (Components.isSuccessCode(rc)) {
695 if (Services.startup.shuttingDown) {
696 Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", "");
697 }
698 Services.obs.notifyObservers(null, "sessionstore-state-write-complete", "");
699 }
700 });
701 },
703 _updateCrashReportURL: function ss_updateCrashReportURL(aWindow) {
704 #ifdef MOZ_CRASHREPORTER
705 try {
706 let currentURI = aWindow.Browser.selectedBrowser.currentURI.clone();
707 // if the current URI contains a username/password, remove it
708 try {
709 currentURI.userPass = "";
710 }
711 catch (ex) { } // ignore failures on about: URIs
713 CrashReporter.annotateCrashReport("URL", currentURI.spec);
714 }
715 catch (ex) {
716 // don't make noise when crashreporter is built but not enabled
717 if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED)
718 Components.utils.reportError("SessionStore:" + ex);
719 }
720 #endif
721 },
723 getBrowserState: function ss_getBrowserState() {
724 let data = this._getCurrentState();
725 return JSON.stringify(data);
726 },
728 getClosedTabCount: function ss_getClosedTabCount(aWindow) {
729 if (!aWindow || !aWindow.__SSID)
730 return 0; // not a browser window, or not otherwise tracked by SS.
732 return this._windows[aWindow.__SSID]._closedTabs.length;
733 },
735 getClosedTabData: function ss_getClosedTabData(aWindow) {
736 if (!aWindow.__SSID)
737 throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
739 return JSON.stringify(this._windows[aWindow.__SSID]._closedTabs);
740 },
742 undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
743 if (!aWindow.__SSID)
744 throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
746 let closedTabs = this._windows[aWindow.__SSID]._closedTabs;
747 if (!closedTabs)
748 return null;
750 // default to the most-recently closed tab
751 aIndex = aIndex || 0;
752 if (!(aIndex in closedTabs))
753 throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
755 // fetch the data of closed tab, while removing it from the array
756 let closedTab = closedTabs.splice(aIndex, 1).shift();
758 // create a new tab and bring to front
759 let tab = aWindow.Browser.addTab(closedTab.state.entries[closedTab.state.index - 1].url, true);
761 tab.browser.messageManager.sendAsyncMessage("WebNavigation:LoadURI", {
762 uri: closedTab.state.entries[closedTab.state.index - 1].url,
763 flags: null,
764 entries: closedTab.state.entries,
765 index: closedTab.state.index
766 });
768 // Put back the extra data
769 tab.browser.__SS_extdata = closedTab.extData;
771 return tab.chromeTab;
772 },
774 forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
775 if (!aWindow.__SSID)
776 throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
778 let closedTabs = this._windows[aWindow.__SSID]._closedTabs;
780 // default to the most-recently closed tab
781 aIndex = aIndex || 0;
782 if (!(aIndex in closedTabs))
783 throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
785 // remove closed tab from the array
786 closedTabs.splice(aIndex, 1);
787 },
789 getTabValue: function ss_getTabValue(aTab, aKey) {
790 let browser = aTab.linkedBrowser;
791 let data = browser.__SS_extdata || {};
792 return data[aKey] || "";
793 },
795 setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
796 let browser = aTab.linkedBrowser;
798 // Thumbnails are actually stored in the cache, so do the save and update the URI
799 if (aKey == "thumbnail") {
800 let file = this._sessionCache.clone();
801 file.append("thumbnail-" + browser.contentWindowId);
802 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
804 let source = Services.io.newURI(aStringValue, "UTF8", null);
805 let target = Services.io.newFileURI(file)
807 let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist);
808 persist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
809 persist.saveURI(source, null, null, null, null, file);
811 aStringValue = target.spec;
812 }
814 if (!browser.__SS_extdata)
815 browser.__SS_extdata = {};
816 browser.__SS_extdata[aKey] = aStringValue;
817 this.saveStateDelayed();
818 },
820 deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
821 let browser = aTab.linkedBrowser;
822 if (browser.__SS_extdata && browser.__SS_extdata[aKey])
823 delete browser.__SS_extdata[aKey];
824 else
825 throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
826 },
828 shouldRestore: function ss_shouldRestore() {
829 return this._shouldRestore || (3 == Services.prefs.getIntPref("browser.startup.page"));
830 },
832 restoreLastSession: function ss_restoreLastSession(aBringToFront) {
833 let self = this;
834 function notifyObservers(aMessage) {
835 self._clearCache();
836 Services.obs.notifyObservers(null, "sessionstore-windows-restored", aMessage || "");
837 }
839 // The previous session data has already been renamed to the backup file
840 if (!this._sessionFileBackup.exists()) {
841 notifyObservers("fail")
842 return;
843 }
845 try {
846 let channel = NetUtil.newChannel(this._sessionFileBackup);
847 channel.contentType = "application/json";
848 NetUtil.asyncFetch(channel, function(aStream, aResult) {
849 if (!Components.isSuccessCode(aResult)) {
850 Cu.reportError("SessionStore: Could not read from sessionstore.bak file");
851 notifyObservers("fail");
852 return;
853 }
855 // Read session state file into a string and let observers modify the state before it's being used
856 let state = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
857 state.data = NetUtil.readInputStreamToString(aStream, aStream.available(), { charset : "UTF-8" }) || "";
858 aStream.close();
860 Services.obs.notifyObservers(state, "sessionstore-state-read", "");
862 let data = null;
863 try {
864 data = JSON.parse(state.data);
865 } catch (ex) {
866 Cu.reportError("SessionStore: Could not parse JSON: " + ex);
867 }
869 if (!data || data.windows.length == 0) {
870 notifyObservers("fail");
871 return;
872 }
874 let window = Services.wm.getMostRecentWindow("navigator:browser");
876 if (typeof data.selectedWindow == "number") {
877 this._selectedWindow = data.selectedWindow;
878 }
879 let windowIndex = this._selectedWindow - 1;
880 let tabs = data.windows[windowIndex].tabs;
881 let selected = data.windows[windowIndex].selected;
883 let currentGroupId;
884 try {
885 currentGroupId = JSON.parse(data.windows[windowIndex].extData["tabview-groups"]).activeGroupId;
886 } catch (ex) { /* currentGroupId is undefined if user has no tab groups */ }
888 // Move all window data from sessionstore.js to this._windows.
889 this._orderedWindows = [];
890 for (let i = 0; i < data.windows.length; i++) {
891 let SSID;
892 if (i != windowIndex) {
893 SSID = "window" + gUUIDGenerator.generateUUID().toString();
894 this._windows[SSID] = data.windows[i];
895 } else {
896 SSID = window.__SSID;
897 this._windows[SSID].extData = data.windows[i].extData;
898 this._windows[SSID]._closedTabs =
899 this._windows[SSID]._closedTabs.concat(data.windows[i]._closedTabs);
900 }
901 this._orderedWindows.push(SSID);
902 }
904 if (selected > tabs.length) // Clamp the selected index if it's bogus
905 selected = 1;
907 for (let i=0; i<tabs.length; i++) {
908 let tabData = tabs[i];
909 let tabGroupId = (typeof currentGroupId == "number") ?
910 JSON.parse(tabData.extData["tabview-tab"]).groupID : null;
912 if (tabGroupId && tabGroupId != currentGroupId) {
913 this._tabsFromOtherGroups.push(tabData);
914 continue;
915 }
917 // We must have selected tabs as soon as possible, so we let all tabs be selected
918 // until we get the real selected tab. Then we stop selecting tabs. The end result
919 // is that the right tab is selected, but we also don't get a bunch of errors
920 let bringToFront = (i + 1 <= selected) && aBringToFront;
921 let tab = window.Browser.addTab(tabData.entries[tabData.index - 1].url, bringToFront);
923 // Start a real load for the selected tab
924 if (i + 1 == selected) {
925 let json = {
926 uri: tabData.entries[tabData.index - 1].url,
927 flags: null,
928 entries: tabData.entries,
929 index: tabData.index
930 };
931 tab.browser.messageManager.sendAsyncMessage("WebNavigation:LoadURI", json);
932 } else {
933 // Make sure the browser has its session data for the delay reload
934 tab.browser.__SS_data = tabData;
935 tab.browser.__SS_restore = true;
937 // Restore current title
938 tab.chromeTab.updateTitle(tabData.entries[tabData.index - 1].title);
939 }
941 tab.browser.__SS_tabFormData = tabData
942 tab.browser.__SS_extdata = tabData.extData;
943 }
945 notifyObservers();
946 }.bind(this));
947 } catch (ex) {
948 Cu.reportError("SessionStore: Could not read from sessionstore.bak file: " + ex);
949 notifyObservers("fail");
950 }
951 }
952 };
954 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStore]);