Bug 449371 Firefox/Thunderbird crashes at exit [@ gdk_display_x11_finalize], p=Brian...
[wine-gecko.git] / browser / components / sessionstore / src / nsSessionStore.js
blob5bc6e1cd542257614035be2cd9248a393354234b
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
14 * The Original Code is the nsSessionStore component.
16 * The Initial Developer of the Original Code is
17 * Simon Bünzli <zeniko@gmail.com>
18 * Portions created by the Initial Developer are Copyright (C) 2006
19 * the Initial Developer. All Rights Reserved.
21 * Contributor(s):
22 * Dietrich Ayala <dietrich@mozilla.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 /**
39 * Session Storage and Restoration
41 * Overview
42 * This service keeps track of a user's session, storing the various bits
43 * required to return the browser to it's current state. The relevant data is
44 * stored in memory, and is periodically saved to disk in a file in the
45 * profile directory. The service is started at first window load, in
46 * delayedStartup, and will restore the session from the data received from
47 * the nsSessionStartup service.
50 /* :::::::: Constants and Helpers ::::::::::::::: */
52 const Cc = Components.classes;
53 const Ci = Components.interfaces;
54 const Cr = Components.results;
55 const Cu = Components.utils;
57 const STATE_STOPPED = 0;
58 const STATE_RUNNING = 1;
59 const STATE_QUITTING = -1;
60 const STATE_DISABLED = -2;
62 const STATE_STOPPED_STR = "stopped";
63 const STATE_RUNNING_STR = "running";
65 const PRIVACY_NONE = 0;
66 const PRIVACY_ENCRYPTED = 1;
67 const PRIVACY_FULL = 2;
69 const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
71 // global notifications observed
72 const OBSERVING = [
73 "domwindowopened", "domwindowclosed",
74 "quit-application-requested", "quit-application-granted",
75 "quit-application", "browser:purge-session-history"
79 XUL Window properties to (re)store
80 Restored in restoreDimensions()
82 const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
84 /*
85 Hideable window features to (re)store
86 Restored in restoreWindowFeatures()
88 const WINDOW_HIDEABLE_FEATURES = [
89 "menubar", "toolbar", "locationbar",
90 "personalbar", "statusbar", "scrollbars"
94 docShell capabilities to (re)store
95 Restored in restoreHistory()
96 eg: browser.docShell["allow" + aCapability] = false;
98 const CAPABILITIES = [
99 "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images"
102 // module for JSON conversion (needed for the nsISessionStore API)
103 Cu.import("resource://gre/modules/JSON.jsm");
104 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
106 function debug(aMsg) {
107 aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
108 Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
109 .logStringMessage(aMsg);
112 /* :::::::: The Service ::::::::::::::: */
114 function SessionStoreService() {
117 SessionStoreService.prototype = {
118 classDescription: "Browser Session Store Service",
119 contractID: "@mozilla.org/browser/sessionstore;1",
120 classID: Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}"),
121 QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
122 Ci.nsIDOMEventListener,
123 Ci.nsIObserver,
124 Ci.nsISupportsWeakReference]),
126 // xul:tab attributes to (re)store (extensions might want to hook in here)
127 xulAttributes: [],
129 // set default load state
130 _loadState: STATE_STOPPED,
132 // minimal interval between two save operations (in milliseconds)
133 _interval: 10000,
135 // when crash recovery is disabled, session data is not written to disk
136 _resume_from_crash: true,
138 // During the initial restore tracks the number of windows yet to be restored
139 _restoreCount: 0,
141 // time in milliseconds (Date.now()) when the session was last written to file
142 _lastSaveTime: 0,
144 // states for all currently opened windows
145 _windows: {},
147 // in case the last closed window ain't a navigator:browser one
148 _lastWindowClosed: null,
150 // not-"dirty" windows usually don't need to have their data updated
151 _dirtyWindows: {},
153 // flag all windows as dirty
154 _dirty: false,
156 /* ........ Global Event Handlers .............. */
159 * Initialize the component
161 init: function sss_init(aWindow) {
162 if (this._loadState == STATE_DISABLED)
163 return;
165 if (!aWindow || this._loadState == STATE_RUNNING) {
166 // make sure that all browser windows which try to initialize
167 // SessionStore are really tracked by it
168 if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
169 this.onLoad(aWindow);
170 return;
173 this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
174 getService(Ci.nsIPrefService).getBranch("browser.");
175 this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
177 var observerService = Cc["@mozilla.org/observer-service;1"].
178 getService(Ci.nsIObserverService);
180 // if the service is disabled, do not init
181 if (!this._prefBranch.getBoolPref("sessionstore.enabled")) {
182 // Notify observers that the sessionstore has done everything it is going to.
183 observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
184 // Mark as disabled so we don't even try to initialise again.
185 this._loadState = STATE_DISABLED;
186 return;
189 OBSERVING.forEach(function(aTopic) {
190 observerService.addObserver(this, aTopic, true);
191 }, this);
193 // get interval from prefs - used often, so caching/observing instead of fetching on-demand
194 this._interval = this._prefBranch.getIntPref("sessionstore.interval");
195 this._prefBranch.addObserver("sessionstore.interval", this, true);
197 // get crash recovery state from prefs and allow for proper reaction to state changes
198 this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
199 this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true);
201 // observe prefs changes so we can modify stored data to match
202 this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
204 // get file references
205 var dirService = Cc["@mozilla.org/file/directory_service;1"].
206 getService(Ci.nsIProperties);
207 this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile);
208 this._sessionFileBackup = this._sessionFile.clone();
209 this._sessionFile.append("sessionstore.js");
210 this._sessionFileBackup.append("sessionstore.bak");
212 // get string containing session state
213 var iniString;
214 try {
215 var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
216 getService(Ci.nsISessionStartup);
217 if (ss.doRestore())
218 iniString = ss.state;
220 catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
222 if (iniString) {
223 try {
224 // parse the session state into JS objects
225 this._initialState = this._safeEval(iniString);
226 // set bool detecting crash
227 this._lastSessionCrashed =
228 this._initialState.session && this._initialState.session.state &&
229 this._initialState.session.state == STATE_RUNNING_STR;
231 // make sure that at least the first window doesn't have anything hidden
232 delete this._initialState.windows[0].hidden;
234 catch (ex) { debug("The session file is invalid: " + ex); }
237 // if last session crashed, backup the session
238 if (this._lastSessionCrashed) {
239 try {
240 this._writeFile(this._sessionFileBackup, iniString);
242 catch (ex) { } // nothing else we can do here
245 // remove the session data files if crash recovery is disabled
246 if (!this._resume_from_crash)
247 this._clearDisk();
249 // As this is called at delayedStartup, restoration must be initiated here
250 this.onLoad(aWindow);
254 * Called on application shutdown, after notifications:
255 * quit-application-granted, quit-application
257 _uninit: function sss_uninit() {
258 if (this._doResumeSession()) { // save all data for session resuming
259 this.saveState(true);
261 else { // discard all session related data
262 this._clearDisk();
264 // Make sure to break our cycle with the save timer
265 if (this._saveTimer) {
266 this._saveTimer.cancel();
267 this._saveTimer = null;
272 * Handle notifications
274 observe: function sss_observe(aSubject, aTopic, aData) {
275 // for event listeners
276 var _this = this;
278 switch (aTopic) {
279 case "domwindowopened": // catch new windows
280 aSubject.addEventListener("load", function(aEvent) {
281 aEvent.currentTarget.removeEventListener("load", arguments.callee, false);
282 _this.onLoad(aEvent.currentTarget);
283 }, false);
284 break;
285 case "domwindowclosed": // catch closed windows
286 this.onClose(aSubject);
287 break;
288 case "quit-application-requested":
289 // get a current snapshot of all windows
290 this._forEachBrowserWindow(function(aWindow) {
291 this._collectWindowData(aWindow);
293 this._dirtyWindows = [];
294 this._dirty = false;
295 break;
296 case "quit-application-granted":
297 // freeze the data at what we've got (ignoring closing windows)
298 this._loadState = STATE_QUITTING;
299 break;
300 case "quit-application":
301 if (aData == "restart")
302 this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
303 this._loadState = STATE_QUITTING; // just to be sure
304 this._uninit();
305 break;
306 case "browser:purge-session-history": // catch sanitization
307 this._forEachBrowserWindow(function(aWindow) {
308 Array.forEach(aWindow.getBrowser().browsers, function(aBrowser) {
309 delete aBrowser.parentNode.__SS_data;
312 this._lastWindowClosed = null;
313 this._clearDisk();
314 // also clear all data about closed tabs
315 for (ix in this._windows) {
316 this._windows[ix]._closedTabs = [];
318 // give the tabbrowsers a chance to clear their histories first
319 var win = this._getMostRecentBrowserWindow();
320 if (win)
321 win.setTimeout(function() { _this.saveState(true); }, 0);
322 else
323 this.saveState(true);
324 break;
325 case "nsPref:changed": // catch pref changes
326 switch (aData) {
327 // if the user decreases the max number of closed tabs they want
328 // preserved update our internal states to match that max
329 case "sessionstore.max_tabs_undo":
330 var ix;
331 for (ix in this._windows) {
332 this._windows[ix]._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"));
334 break;
335 case "sessionstore.interval":
336 this._interval = this._prefBranch.getIntPref("sessionstore.interval");
337 // reset timer and save
338 if (this._saveTimer) {
339 this._saveTimer.cancel();
340 this._saveTimer = null;
342 this.saveStateDelayed(null, -1);
343 break;
344 case "sessionstore.resume_from_crash":
345 this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
346 // either create the file with crash recovery information or remove it
347 // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
348 if (this._resume_from_crash)
349 this.saveState(true);
350 else if (this._loadState == STATE_RUNNING)
351 this._clearDisk();
352 break;
354 break;
355 case "timer-callback": // timer call back for delayed saving
356 this._saveTimer = null;
357 this.saveState();
358 break;
362 /* ........ Window Event Handlers .............. */
365 * Implement nsIDOMEventListener for handling various window and tab events
367 handleEvent: function sss_handleEvent(aEvent) {
368 switch (aEvent.type) {
369 case "load":
370 case "pageshow":
371 this.onTabLoad(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
372 break;
373 case "input":
374 case "DOMAutoComplete":
375 this.onTabInput(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
376 break;
377 case "TabOpen":
378 case "TabClose":
379 var panelID = aEvent.originalTarget.linkedPanel;
380 var tabpanel = aEvent.originalTarget.ownerDocument.getElementById(panelID);
381 if (aEvent.type == "TabOpen") {
382 this.onTabAdd(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
384 else {
385 this.onTabClose(aEvent.currentTarget.ownerDocument.defaultView, aEvent.originalTarget);
386 this.onTabRemove(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
388 break;
389 case "TabSelect":
390 var tabpanels = aEvent.currentTarget.mPanelContainer;
391 this.onTabSelect(aEvent.currentTarget.ownerDocument.defaultView, tabpanels);
392 break;
397 * If it's the first window load since app start...
398 * - determine if we're reloading after a crash or a forced-restart
399 * - restore window state
400 * - restart downloads
401 * Set up event listeners for this window's tabs
402 * @param aWindow
403 * Window reference
405 onLoad: function sss_onLoad(aWindow) {
406 // return if window has already been initialized
407 if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
408 return;
410 // ignore non-browser windows and windows opened while shutting down
411 if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
412 this._loadState == STATE_QUITTING)
413 return;
415 // assign it a unique identifier (timestamp)
416 aWindow.__SSi = "window" + Date.now();
418 // and create its data object
419 this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [] };
421 // perform additional initialization when the first window is loading
422 if (this._loadState == STATE_STOPPED) {
423 this._loadState = STATE_RUNNING;
424 this._lastSaveTime = Date.now();
426 // don't save during the first ten seconds
427 // (until most of the pages have been restored)
428 this.saveStateDelayed(aWindow, 10000);
430 // restore a crashed session resp. resume the last session if requested
431 if (this._initialState) {
432 // make sure that the restored tabs are first in the window
433 this._initialState._firstTabs = true;
434 this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
435 this.restoreWindow(aWindow, this._initialState, this._isCmdLineEmpty(aWindow));
436 delete this._initialState;
438 else {
439 // Nothing to restore, notify observers things are complete.
440 var observerService = Cc["@mozilla.org/observer-service;1"].
441 getService(Ci.nsIObserverService);
442 observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
446 var tabbrowser = aWindow.getBrowser();
447 var tabpanels = tabbrowser.mPanelContainer;
449 // add tab change listeners to all already existing tabs
450 for (var i = 0; i < tabpanels.childNodes.length; i++) {
451 this.onTabAdd(aWindow, tabpanels.childNodes[i], true);
453 // notification of tab add/remove/selection
454 tabbrowser.addEventListener("TabOpen", this, true);
455 tabbrowser.addEventListener("TabClose", this, true);
456 tabbrowser.addEventListener("TabSelect", this, true);
460 * On window close...
461 * - remove event listeners from tabs
462 * - save all window data
463 * @param aWindow
464 * Window reference
466 onClose: function sss_onClose(aWindow) {
467 // ignore windows not tracked by SessionStore
468 if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
469 return;
472 if (this.windowToFocus && this.windowToFocus == aWindow) {
473 delete this.windowToFocus;
476 var tabbrowser = aWindow.getBrowser();
477 var tabpanels = tabbrowser.mPanelContainer;
479 tabbrowser.removeEventListener("TabOpen", this, true);
480 tabbrowser.removeEventListener("TabClose", this, true);
481 tabbrowser.removeEventListener("TabSelect", this, true);
483 if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down
484 // update all window data for a last time
485 this._collectWindowData(aWindow);
487 // preserve this window's data (in case it was the last navigator:browser)
488 this._lastWindowClosed = this._windows[aWindow.__SSi];
489 this._lastWindowClosed.title = aWindow.content.document.title;
490 this._updateCookies([this._lastWindowClosed]);
492 // clear this window from the list
493 delete this._windows[aWindow.__SSi];
495 // save the state without this window to disk
496 this.saveStateDelayed();
499 for (var i = 0; i < tabpanels.childNodes.length; i++) {
500 this.onTabRemove(aWindow, tabpanels.childNodes[i], true);
503 // cache the window state until the window is completely gone
504 aWindow.__SS_dyingCache = this._windows[aWindow.__SSi] || this._lastWindowClosed;
506 // reset the _tab property to avoid keeping the tab's XUL element alive
507 // longer than we need it
508 var tabCount = aWindow.__SS_dyingCache.tabs.length;
509 for (var t = 0; t < tabCount; t++) {
510 delete aWindow.__SS_dyingCache.tabs[t]._tab;
513 delete aWindow.__SSi;
517 * set up listeners for a new tab
518 * @param aWindow
519 * Window reference
520 * @param aPanel
521 * TabPanel reference
522 * @param aNoNotification
523 * bool Do not save state if we're updating an existing tab
525 onTabAdd: function sss_onTabAdd(aWindow, aPanel, aNoNotification) {
526 aPanel.addEventListener("load", this, true);
527 aPanel.addEventListener("pageshow", this, true);
528 aPanel.addEventListener("input", this, true);
529 aPanel.addEventListener("DOMAutoComplete", this, true);
531 if (!aNoNotification) {
532 this.saveStateDelayed(aWindow);
537 * remove listeners for a tab
538 * @param aWindow
539 * Window reference
540 * @param aPanel
541 * TabPanel reference
542 * @param aNoNotification
543 * bool Do not save state if we're updating an existing tab
545 onTabRemove: function sss_onTabRemove(aWindow, aPanel, aNoNotification) {
546 aPanel.removeEventListener("load", this, true);
547 aPanel.removeEventListener("pageshow", this, true);
548 aPanel.removeEventListener("input", this, true);
549 aPanel.removeEventListener("DOMAutoComplete", this, true);
551 delete aPanel.__SS_data;
552 delete aPanel.__SS_text;
554 if (!aNoNotification) {
555 this.saveStateDelayed(aWindow);
560 * When a tab closes, collect it's properties
561 * @param aWindow
562 * Window reference
563 * @param aTab
564 * TabPanel reference
566 onTabClose: function sss_onTabClose(aWindow, aTab) {
567 // notify the tabbrowser that the tab state will be retrieved for the last time
568 // (so that extension authors can easily set data on soon-to-be-closed tabs)
569 var event = aWindow.document.createEvent("Events");
570 event.initEvent("SSTabClosing", true, false);
571 aTab.dispatchEvent(event);
573 var maxTabsUndo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
574 // don't update our internal state if we don't have to
575 if (maxTabsUndo == 0) {
576 return;
579 // make sure that the tab related data is up-to-date
580 var tabState = this._collectTabData(aTab);
581 this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
583 // reset the _tab property to avoid keeping the tab's XUL element alive
584 // longer than we need it
585 delete tabState._tab;
587 // store closed-tab data for undo
588 if (tabState.entries.length > 1 || tabState.entries[0].url != "about:blank") {
589 this._windows[aWindow.__SSi]._closedTabs.unshift({
590 state: tabState,
591 title: aTab.getAttribute("label"),
592 image: aTab.getAttribute("image"),
593 pos: aTab._tPos
595 var length = this._windows[aWindow.__SSi]._closedTabs.length;
596 if (length > maxTabsUndo)
597 this._windows[aWindow.__SSi]._closedTabs.splice(maxTabsUndo, length - maxTabsUndo);
602 * When a tab loads, save state.
603 * @param aWindow
604 * Window reference
605 * @param aPanel
606 * TabPanel reference
607 * @param aEvent
608 * Event obj
610 onTabLoad: function sss_onTabLoad(aWindow, aPanel, aEvent) {
611 // react on "load" and solitary "pageshow" events (the first "pageshow"
612 // following "load" is too late for deleting the data caches)
613 if (aEvent.type != "load" && !aEvent.persisted) {
614 return;
617 delete aPanel.__SS_data;
618 delete aPanel.__SS_text;
619 this.saveStateDelayed(aWindow);
621 // attempt to update the current URL we send in a crash report
622 this._updateCrashReportURL(aWindow);
626 * Called when a tabpanel sends the "input" notification
627 * stores textarea data
628 * @param aWindow
629 * Window reference
630 * @param aPanel
631 * TabPanel reference
632 * @param aEvent
633 * Event obj
635 onTabInput: function sss_onTabInput(aWindow, aPanel, aEvent) {
636 if (this._saveTextData(aPanel, aEvent.originalTarget)) {
637 this.saveStateDelayed(aWindow, 3000);
642 * When a tab is selected, save session data
643 * @param aWindow
644 * Window reference
645 * @param aPanels
646 * TabPanel reference
648 onTabSelect: function sss_onTabSelect(aWindow, aPanels) {
649 if (this._loadState == STATE_RUNNING) {
650 this._windows[aWindow.__SSi].selected = aPanels.selectedIndex;
651 this.saveStateDelayed(aWindow);
653 // attempt to update the current URL we send in a crash report
654 this._updateCrashReportURL(aWindow);
658 /* ........ nsISessionStore API .............. */
660 getBrowserState: function sss_getBrowserState() {
661 return this._toJSONString(this._getCurrentState());
664 setBrowserState: function sss_setBrowserState(aState) {
665 var window = this._getMostRecentBrowserWindow();
666 if (!window) {
667 this._openWindowWithState("(" + aState + ")");
668 return;
671 // close all other browser windows
672 this._forEachBrowserWindow(function(aWindow) {
673 if (aWindow != window) {
674 aWindow.close();
678 // restore to the given state
679 this.restoreWindow(window, "(" + aState + ")", true);
682 getWindowState: function sss_getWindowState(aWindow) {
683 if (!aWindow.__SSi && aWindow.__SS_dyingCache)
684 return this._toJSONString({ windows: [aWindow.__SS_dyingCache] });
686 return this._toJSONString(this._getWindowState(aWindow));
689 setWindowState: function sss_setWindowState(aWindow, aState, aOverwrite) {
690 this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite);
693 getTabState: function sss_getTabState(aTab) {
694 var tabState = this._collectTabData(aTab);
696 var window = aTab.ownerDocument.defaultView;
697 this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
699 return this._toJSONString(tabState);
702 setTabState: function sss_setTabState(aTab, aState) {
703 var tabState = this._safeEval("(" + aState + ")");
704 if (!tabState.entries || !tabState.entries.length) {
705 Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
706 return;
708 tabState._tab = aTab;
710 var window = aTab.ownerDocument.defaultView;
711 this.restoreHistoryPrecursor(window, [tabState], 0, 0, 0);
714 duplicateTab: function sss_duplicateTab(aWindow, aTab) {
715 var tabState = this._collectTabData(aTab, true);
716 var sourceWindow = aTab.ownerDocument.defaultView;
717 this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
719 var newTab = aWindow.getBrowser().addTab();
720 tabState._tab = newTab;
721 this.restoreHistoryPrecursor(aWindow, [tabState], 0, 0, 0);
723 return newTab;
726 getClosedTabCount: function sss_getClosedTabCount(aWindow) {
727 if (!aWindow.__SSi && aWindow.__SS_dyingCache)
728 return aWindow.__SS_dyingCache._closedTabs.length;
729 if (!aWindow.__SSi)
730 return 0; // not a browser window, or not otherwise tracked by SS.
732 return this._windows[aWindow.__SSi]._closedTabs.length;
735 closedTabNameAt: function sss_closedTabNameAt(aWindow, aIx) {
736 var tabs;
738 if (aWindow.__SSi && aWindow.__SSi in this._windows)
739 tabs = this._windows[aWindow.__SSi]._closedTabs;
740 else if (aWindow.__SS_dyingCache)
741 tabs = aWindow.__SS_dyingCache._closedTabs;
742 else
743 Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
745 return tabs && aIx in tabs ? tabs[aIx].title : null;
748 getClosedTabData: function sss_getClosedTabDataAt(aWindow) {
749 if (!aWindow.__SSi && aWindow.__SS_dyingCache)
750 return this._toJSONString(aWindow.__SS_dyingCache._closedTabs);
752 return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
755 undoCloseTab: function sss_undoCloseTab(aWindow, aIndex) {
756 var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
758 // default to the most-recently closed tab
759 aIndex = aIndex || 0;
761 if (aIndex in closedTabs) {
762 var browser = aWindow.getBrowser();
764 // fetch the data of closed tab, while removing it from the array
765 var closedTab = closedTabs.splice(aIndex, 1).shift();
766 var closedTabState = closedTab.state;
768 // create a new tab
769 closedTabState._tab = browser.addTab();
771 // restore the tab's position
772 browser.moveTabTo(closedTabState._tab, closedTab.pos);
774 // restore tab content
775 this.restoreHistoryPrecursor(aWindow, [closedTabState], 1, 0, 0);
777 // focus the tab's content area
778 var content = browser.getBrowserForTab(closedTabState._tab).contentWindow;
779 aWindow.setTimeout(function() { content.focus(); }, 0);
781 else {
782 Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
786 getWindowValue: function sss_getWindowValue(aWindow, aKey) {
787 if (aWindow.__SSi) {
788 var data = this._windows[aWindow.__SSi].extData || {};
789 return data[aKey] || "";
791 else if (aWindow.__SS_dyingCache) {
792 data = aWindow.__SS_dyingCache.extData || {};
793 return data[aKey] || "";
795 else {
796 Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
800 setWindowValue: function sss_setWindowValue(aWindow, aKey, aStringValue) {
801 if (aWindow.__SSi) {
802 if (!this._windows[aWindow.__SSi].extData) {
803 this._windows[aWindow.__SSi].extData = {};
805 this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
806 this.saveStateDelayed(aWindow);
808 else {
809 Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
813 deleteWindowValue: function sss_deleteWindowValue(aWindow, aKey) {
814 if (this._windows[aWindow.__SSi].extData[aKey])
815 delete this._windows[aWindow.__SSi].extData[aKey];
816 else
817 Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
820 getTabValue: function sss_getTabValue(aTab, aKey) {
821 var data = aTab.__SS_extdata || {};
822 return data[aKey] || "";
825 setTabValue: function sss_setTabValue(aTab, aKey, aStringValue) {
826 if (!aTab.__SS_extdata) {
827 aTab.__SS_extdata = {};
829 aTab.__SS_extdata[aKey] = aStringValue;
830 this.saveStateDelayed(aTab.ownerDocument.defaultView);
833 deleteTabValue: function sss_deleteTabValue(aTab, aKey) {
834 if (aTab.__SS_extdata[aKey])
835 delete aTab.__SS_extdata[aKey];
836 else
837 Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
841 persistTabAttribute: function sss_persistTabAttribute(aName) {
842 this.xulAttributes.push(aName);
843 this.saveStateDelayed();
846 /* ........ Saving Functionality .............. */
849 * Store all session data for a window
850 * @param aWindow
851 * Window reference
853 _saveWindowHistory: function sss_saveWindowHistory(aWindow) {
854 var tabbrowser = aWindow.getBrowser();
855 var tabs = tabbrowser.mTabs;
856 var tabsData = this._windows[aWindow.__SSi].tabs = [];
858 for (var i = 0; i < tabs.length; i++)
859 tabsData.push(this._collectTabData(tabs[i]));
861 this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1;
865 * Collect data related to a single tab
866 * @param aTab
867 * tabbrowser tab
868 * @param aFullData
869 * always return privacy sensitive data (use with care)
870 * @returns object
872 _collectTabData: function sss_collectTabData(aTab, aFullData) {
873 var tabData = { entries: [], index: 0 };
874 var browser = aTab.linkedBrowser;
876 if (!browser || !browser.currentURI)
877 // can happen when calling this function right after .addTab()
878 return tabData;
879 else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tab)
880 // use the data to be restored when the tab hasn't been completely loaded
881 return browser.parentNode.__SS_data;
883 var history = null;
884 try {
885 history = browser.sessionHistory;
887 catch (ex) { } // this could happen if we catch a tab during (de)initialization
889 if (history && browser.parentNode.__SS_data &&
890 browser.parentNode.__SS_data.entries[history.index] && !aFullData) {
891 tabData = browser.parentNode.__SS_data;
892 tabData.index = history.index + 1;
894 else if (history && history.count > 0) {
895 for (var j = 0; j < history.count; j++)
896 tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
897 aFullData));
898 tabData.index = history.index + 1;
900 // make sure not to cache privacy sensitive data which shouldn't get out
901 if (!aFullData)
902 browser.parentNode.__SS_data = tabData;
904 else {
905 tabData.entries[0] = { url: browser.currentURI.spec };
906 tabData.index = 1;
909 var disallow = [];
910 for (var i = 0; i < CAPABILITIES.length; i++)
911 if (!browser.docShell["allow" + CAPABILITIES[i]])
912 disallow.push(CAPABILITIES[i]);
913 if (disallow.length > 0)
914 tabData.disallow = disallow.join(",");
915 else if (tabData.disallow)
916 delete tabData.disallow;
918 if (this.xulAttributes.length > 0) {
919 var xulattr = Array.filter(aTab.attributes, function(aAttr) {
920 return this.xulAttributes.indexOf(aAttr.name) > -1;
921 }, this).map(function(aAttr) {
922 return aAttr.name + "=" + encodeURI(aAttr.value);
924 tabData.xultab = xulattr.join(" ");
927 if (aTab.__SS_extdata)
928 tabData.extData = aTab.__SS_extdata;
929 else if (tabData.extData)
930 delete tabData.extData;
932 return tabData;
936 * Get an object that is a serialized representation of a History entry
937 * Used for data storage
938 * @param aEntry
939 * nsISHEntry instance
940 * @param aFullData
941 * always return privacy sensitive data (use with care)
942 * @returns object
944 _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) {
945 var entry = { url: aEntry.URI.spec };
947 if (aEntry.title && aEntry.title != entry.url) {
948 entry.title = aEntry.title;
950 if (aEntry.isSubFrame) {
951 entry.subframe = true;
953 if (!(aEntry instanceof Ci.nsISHEntry)) {
954 return entry;
957 var cacheKey = aEntry.cacheKey;
958 if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
959 cacheKey.data != 0) {
960 // XXXbz would be better to have cache keys implement
961 // nsISerializable or something.
962 entry.cacheKey = cacheKey.data;
964 entry.ID = aEntry.ID;
966 if (aEntry.contentType)
967 entry.contentType = aEntry.contentType;
969 var x = {}, y = {};
970 aEntry.getScrollPosition(x, y);
971 if (x.value != 0 || y.value != 0)
972 entry.scroll = x.value + "," + y.value;
974 try {
975 var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
976 if (aEntry.postData && (aFullData ||
977 prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) {
978 aEntry.postData.QueryInterface(Ci.nsISeekableStream).
979 seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
980 var stream = Cc["@mozilla.org/binaryinputstream;1"].
981 createInstance(Ci.nsIBinaryInputStream);
982 stream.setInputStream(aEntry.postData);
983 var postBytes = stream.readByteArray(stream.available());
984 var postdata = String.fromCharCode.apply(null, postBytes);
985 if (aFullData || prefPostdata == -1 ||
986 postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
987 prefPostdata) {
988 // We can stop doing base64 encoding once our serialization into JSON
989 // is guaranteed to handle all chars in strings, including embedded
990 // nulls.
991 entry.postdata_b64 = btoa(postdata);
995 catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
997 if (aEntry.owner) {
998 // Not catching anything specific here, just possible errors
999 // from writeCompoundObject and the like.
1000 try {
1001 var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
1002 createInstance(Ci.nsIObjectOutputStream);
1003 var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
1004 pipe.init(false, false, 0, 0xffffffff, null);
1005 binaryStream.setOutputStream(pipe.outputStream);
1006 binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
1007 binaryStream.close();
1009 // Now we want to read the data from the pipe's input end and encode it.
1010 var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
1011 createInstance(Ci.nsIBinaryInputStream);
1012 scriptableStream.setInputStream(pipe.inputStream);
1013 var ownerBytes =
1014 scriptableStream.readByteArray(scriptableStream.available());
1015 // We can stop doing base64 encoding once our serialization into JSON
1016 // is guaranteed to handle all chars in strings, including embedded
1017 // nulls.
1018 entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
1020 catch (ex) { debug(ex); }
1023 if (!(aEntry instanceof Ci.nsISHContainer)) {
1024 return entry;
1027 if (aEntry.childCount > 0) {
1028 entry.children = [];
1029 for (var i = 0; i < aEntry.childCount; i++) {
1030 var child = aEntry.GetChildAt(i);
1031 if (child) {
1032 entry.children.push(this._serializeHistoryEntry(child, aFullData));
1034 else { // to maintain the correct frame order, insert a dummy entry
1035 entry.children.push({ url: "about:blank" });
1040 return entry;
1044 * Updates the current document's cache of user entered text data
1045 * @param aPanel
1046 * TabPanel reference
1047 * @param aTextarea
1048 * HTML content element
1049 * @returns bool
1051 _saveTextData: function sss_saveTextData(aPanel, aTextarea) {
1052 var id = aTextarea.id ? "#" + aTextarea.id :
1053 aTextarea.name;
1054 if (!id
1055 || !(aTextarea instanceof Ci.nsIDOMHTMLTextAreaElement
1056 || aTextarea instanceof Ci.nsIDOMHTMLInputElement)) {
1057 return false; // nothing to save
1060 if (!aPanel.__SS_text) {
1061 aPanel.__SS_text = [];
1062 aPanel.__SS_text._refs = [];
1065 // get the index of the reference to the text element
1066 var ix = aPanel.__SS_text._refs.indexOf(aTextarea);
1067 if (ix == -1) {
1068 // we haven't registered this text element yet - do so now
1069 aPanel.__SS_text._refs.push(aTextarea);
1070 ix = aPanel.__SS_text.length;
1072 else if (!aPanel.__SS_text[ix].cache) {
1073 // we've already marked this text element for saving (the cache is
1074 // added during save operations and would have to be updated here)
1075 return false;
1078 // determine the frame we're in and encode it into the textarea's ID
1079 var content = aTextarea.ownerDocument.defaultView;
1080 while (content != content.top) {
1081 var frames = content.parent.frames;
1082 for (var i = 0; i < frames.length && frames[i] != content; i++);
1083 id = i + "|" + id;
1084 content = content.parent;
1087 // mark this element for saving
1088 aPanel.__SS_text[ix] = { id: id, element: aTextarea };
1090 return true;
1094 * go through all tabs and store the current scroll positions
1095 * and innerHTML content of WYSIWYG editors
1096 * @param aWindow
1097 * Window reference
1099 _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) {
1100 var browsers = aWindow.getBrowser().browsers;
1101 for (var i = 0; i < browsers.length; i++) {
1102 try {
1103 var tabData = this._windows[aWindow.__SSi].tabs[i];
1104 if (tabData.entries.length == 0 ||
1105 browsers[i].parentNode.__SS_data && browsers[i].parentNode.__SS_data._tab)
1106 continue; // ignore incompletely initialized tabs
1107 this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
1109 catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
1114 * go through all frames and store the current scroll positions
1115 * and innerHTML content of WYSIWYG editors
1116 * @param aWindow
1117 * Window reference
1118 * @param aBrowser
1119 * single browser reference
1120 * @param aTabData
1121 * tabData object to add the information to
1122 * @param aFullData
1123 * always return privacy sensitive data (use with care)
1125 _updateTextAndScrollDataForTab:
1126 function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
1127 var text = [];
1128 if (aBrowser.parentNode.__SS_text &&
1129 (aFullData || this._checkPrivacyLevel(aBrowser.currentURI.schemeIs("https")))) {
1130 for (var ix = aBrowser.parentNode.__SS_text.length - 1; ix >= 0; ix--) {
1131 var data = aBrowser.parentNode.__SS_text[ix];
1132 if (!data.cache)
1133 // update the text element's value before adding it to the data structure
1134 data.cache = encodeURI(data.element.value);
1135 text.push(data.id + "=" + data.cache);
1138 if (aBrowser.currentURI.spec == "about:config")
1139 text = ["#textbox=" + encodeURI(aBrowser.contentDocument.getElementById("textbox").
1140 wrappedJSObject.value)];
1141 if (text.length > 0)
1142 aTabData.text = text.join(" ");
1143 else if (aTabData.text)
1144 delete aTabData.text;
1146 var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
1147 // entry data needn't exist for tabs just initialized with an incomplete session state
1148 if (aTabData.entries[tabIndex])
1149 this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
1150 aTabData.entries[tabIndex], aFullData);
1154 * go through all subframes and store the current scroll positions
1155 * and innerHTML content of WYSIWYG editors
1156 * @param aWindow
1157 * Window reference
1158 * @param aContent
1159 * frame reference
1160 * @param aData
1161 * part of a tabData object to add the information to
1162 * @param aFullData
1163 * always return privacy sensitive data (use with care)
1165 _updateTextAndScrollDataForFrame:
1166 function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData, aFullData) {
1167 for (var i = 0; i < aContent.frames.length; i++) {
1168 if (aData.children && aData.children[i])
1169 this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i], aData.children[i], aFullData);
1171 // designMode is undefined e.g. for XUL documents (as about:config)
1172 var isHTTPS = this._getURIFromString((aContent.parent || aContent).
1173 document.location.href).schemeIs("https");
1174 if ((aContent.document.designMode || "") == "on" &&
1175 (aFullData || this._checkPrivacyLevel(isHTTPS))) {
1176 if (aData.innerHTML === undefined && !aFullData) {
1177 // we get no "input" events from iframes - listen for keypress here
1178 var _this = this;
1179 aContent.addEventListener("keypress", function(aEvent) {
1180 _this.saveStateDelayed(aWindow, 3000); }, true);
1182 aData.innerHTML = aContent.document.body.innerHTML;
1184 aData.scroll = aContent.scrollX + "," + aContent.scrollY;
1188 * store all hosts for a URL
1189 * @param aWindow
1190 * Window reference
1192 _updateCookieHosts: function sss_updateCookieHosts(aWindow) {
1193 var hosts = this._windows[aWindow.__SSi]._hosts = {};
1195 // get all possible subdomain levels for a given URL
1196 var _this = this;
1197 function extractHosts(aEntry) {
1198 if (/^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.test(aEntry.url) &&
1199 !hosts[RegExp.$1] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) {
1200 var host = RegExp.$1;
1201 var ix;
1202 for (ix = host.indexOf(".") + 1; ix; ix = host.indexOf(".", ix) + 1) {
1203 hosts[host.substr(ix)] = true;
1205 hosts[host] = true;
1207 else if (/^file:\/\/([^\/]*)/.test(aEntry.url)) {
1208 hosts[RegExp.$1] = true;
1210 if (aEntry.children) {
1211 aEntry.children.forEach(extractHosts);
1215 this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); });
1219 * Serialize cookie data
1220 * @param aWindows
1221 * array of Window references
1223 _updateCookies: function sss_updateCookies(aWindows) {
1224 var cookiesEnum = Cc["@mozilla.org/cookiemanager;1"].
1225 getService(Ci.nsICookieManager).enumerator;
1226 // collect the cookies per window
1227 for (var i = 0; i < aWindows.length; i++)
1228 aWindows[i].cookies = [];
1230 // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
1231 var MAX_EXPIRY = Math.pow(2, 62);
1232 while (cookiesEnum.hasMoreElements()) {
1233 var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
1234 if (cookie.isSession && this._checkPrivacyLevel(cookie.isSecure)) {
1235 var jscookie = null;
1236 aWindows.forEach(function(aWindow) {
1237 if (aWindow._hosts && aWindow._hosts[cookie.rawHost]) {
1238 // serialize the cookie when it's first needed
1239 if (!jscookie) {
1240 jscookie = { host: cookie.host, value: cookie.value };
1241 // only add attributes with non-default values (saving a few bits)
1242 if (cookie.path) jscookie.path = cookie.path;
1243 if (cookie.name) jscookie.name = cookie.name;
1244 if (cookie.isSecure) jscookie.secure = true;
1245 if (cookie.isHttpOnly) jscookie.httponly = true;
1246 if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry;
1248 aWindow.cookies.push(jscookie);
1254 // don't include empty cookie sections
1255 for (i = 0; i < aWindows.length; i++)
1256 if (aWindows[i].cookies.length == 0)
1257 delete aWindows[i].cookies;
1261 * Store window dimensions, visibility, sidebar
1262 * @param aWindow
1263 * Window reference
1265 _updateWindowFeatures: function sss_updateWindowFeatures(aWindow) {
1266 var winData = this._windows[aWindow.__SSi];
1268 WINDOW_ATTRIBUTES.forEach(function(aAttr) {
1269 winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
1270 }, this);
1272 var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
1273 return aWindow[aItem] && !aWindow[aItem].visible;
1275 if (hidden.length != 0)
1276 winData.hidden = hidden.join(",");
1277 else if (winData.hidden)
1278 delete winData.hidden;
1280 var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
1281 if (sidebar)
1282 winData.sidebar = sidebar;
1283 else if (winData.sidebar)
1284 delete winData.sidebar;
1288 * serialize session data as Ini-formatted string
1289 * @returns string
1291 _getCurrentState: function sss_getCurrentState() {
1292 var activeWindow = this._getMostRecentBrowserWindow();
1294 if (this._loadState == STATE_RUNNING) {
1295 // update the data for all windows with activities since the last save operation
1296 this._forEachBrowserWindow(function(aWindow) {
1297 if (this._dirty || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) {
1298 this._collectWindowData(aWindow);
1300 else { // always update the window features (whose change alone never triggers a save operation)
1301 this._updateWindowFeatures(aWindow);
1303 }, this);
1304 this._dirtyWindows = [];
1305 this._dirty = false;
1308 // collect the data for all windows
1309 var total = [], windows = [];
1310 var ix;
1311 for (ix in this._windows) {
1312 total.push(this._windows[ix]);
1313 windows.push(ix);
1315 this._updateCookies(total);
1317 // if no browser window remains open, return the state of the last closed window
1318 if (total.length == 0 && this._lastWindowClosed) {
1319 total.push(this._lastWindowClosed);
1321 if (activeWindow) {
1322 this.activeWindowSSiCache = activeWindow.__SSi || "";
1324 ix = this.activeWindowSSiCache ? windows.indexOf(this.activeWindowSSiCache) : -1;
1326 return { windows: total, selectedWindow: ix + 1 };
1330 * serialize session data for a window
1331 * @param aWindow
1332 * Window reference
1333 * @returns string
1335 _getWindowState: function sss_getWindowState(aWindow) {
1336 if (this._loadState == STATE_RUNNING) {
1337 this._collectWindowData(aWindow);
1340 var total = [this._windows[aWindow.__SSi]];
1341 this._updateCookies(total);
1343 return { windows: total };
1346 _collectWindowData: function sss_collectWindowData(aWindow) {
1347 // update the internal state data for this window
1348 this._saveWindowHistory(aWindow);
1349 this._updateTextAndScrollData(aWindow);
1350 this._updateCookieHosts(aWindow);
1351 this._updateWindowFeatures(aWindow);
1353 this._dirtyWindows[aWindow.__SSi] = false;
1356 /* ........ Restoring Functionality .............. */
1359 * restore features to a single window
1360 * @param aWindow
1361 * Window reference
1362 * @param aState
1363 * JS object or its eval'able source
1364 * @param aOverwriteTabs
1365 * bool overwrite existing tabs w/ new ones
1366 * @param aFollowUp
1367 * bool this isn't the restoration of the first window
1369 restoreWindow: function sss_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) {
1370 if (this._restoreCount) {
1371 this._restoreCount--;
1372 if (this._restoreCount == 0) {
1373 // This was the last window restored at startup, notify observers.
1374 var observerService = Cc["@mozilla.org/observer-service;1"].
1375 getService(Ci.nsIObserverService);
1376 observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
1380 if (!aFollowUp) {
1381 this.windowToFocus = aWindow;
1383 // initialize window if necessary
1384 if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
1385 this.onLoad(aWindow);
1387 try {
1388 var root = typeof aState == "string" ? this._safeEval(aState) : aState;
1389 if (!root.windows[0]) {
1390 return; // nothing to restore
1393 catch (ex) { // invalid state object - don't restore anything
1394 debug(ex);
1395 return;
1398 var winData;
1399 if (!aState.selectedWindow) {
1400 aState.selectedWindow = 0;
1402 // open new windows for all further window entries of a multi-window session
1403 // (unless they don't contain any tab data)
1404 for (var w = 1; w < root.windows.length; w++) {
1405 winData = root.windows[w];
1406 if (winData && winData.tabs && winData.tabs[0]) {
1407 var window = this._openWindowWithState({ windows: [winData] });
1408 if (w == aState.selectedWindow - 1) {
1409 this.windowToFocus = window;
1413 winData = root.windows[0];
1414 if (!winData.tabs) {
1415 winData.tabs = [];
1418 var tabbrowser = aWindow.getBrowser();
1419 var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
1420 var newTabCount = winData.tabs.length;
1422 for (var t = 0; t < newTabCount; t++) {
1423 winData.tabs[t]._tab = t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab();
1424 // when resuming at startup: add additionally requested pages to the end
1425 if (!aOverwriteTabs && root._firstTabs) {
1426 tabbrowser.moveTabTo(winData.tabs[t]._tab, t);
1430 // when overwriting tabs, remove all superflous ones
1431 for (t = openTabCount - 1; t >= newTabCount; t--) {
1432 tabbrowser.removeTab(tabbrowser.mTabs[t]);
1435 if (aOverwriteTabs) {
1436 this.restoreWindowFeatures(aWindow, winData);
1438 if (winData.cookies) {
1439 this.restoreCookies(winData.cookies);
1441 if (winData.extData) {
1442 if (!this._windows[aWindow.__SSi].extData) {
1443 this._windows[aWindow.__SSi].extData = {}
1445 for (var key in winData.extData) {
1446 this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
1449 if (winData._closedTabs && (root._firstTabs || aOverwriteTabs)) {
1450 this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs;
1453 this.restoreHistoryPrecursor(aWindow, winData.tabs, (aOverwriteTabs ?
1454 (parseInt(winData.selected) || 1) : 0), 0, 0);
1458 * Manage history restoration for a window
1459 * @param aTabs
1460 * Array of tab data
1461 * @param aCurrentTabs
1462 * Array of tab references
1463 * @param aSelectTab
1464 * Index of selected tab
1465 * @param aIx
1466 * Index of the next tab to check readyness for
1467 * @param aCount
1468 * Counter for number of times delaying b/c browser or history aren't ready
1470 restoreHistoryPrecursor: function sss_restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount) {
1471 var tabbrowser = aWindow.getBrowser();
1473 // make sure that all browsers and their histories are available
1474 // - if one's not, resume this check in 100ms (repeat at most 10 times)
1475 for (var t = aIx; t < aTabs.length; t++) {
1476 try {
1477 if (!tabbrowser.getBrowserForTab(aTabs[t]._tab).webNavigation.sessionHistory) {
1478 throw new Error();
1481 catch (ex) { // in case browser or history aren't ready yet
1482 if (aCount < 10) {
1483 var restoreHistoryFunc = function(self) {
1484 self.restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount + 1);
1486 aWindow.setTimeout(restoreHistoryFunc, 100, this);
1487 return;
1492 // mark the tabs as loading
1493 for (t = 0; t < aTabs.length; t++) {
1494 if (!aTabs[t].entries || !aTabs[t].entries[0])
1495 continue; // there won't be anything to load
1497 var tab = aTabs[t]._tab;
1498 var browser = tabbrowser.getBrowserForTab(tab);
1499 browser.stop(); // in case about:blank isn't done yet
1501 tab.setAttribute("busy", "true");
1502 tabbrowser.updateIcon(tab);
1503 tabbrowser.setTabTitleLoading(tab);
1505 // keep the data around to prevent dataloss in case
1506 // a tab gets closed before it's been properly restored
1507 browser.parentNode.__SS_data = aTabs[t];
1510 // make sure to restore the selected tab first (if any)
1511 if (aSelectTab-- && aTabs[aSelectTab]) {
1512 aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]);
1513 tabbrowser.selectedTab = aTabs[0]._tab;
1516 // helper hash for ensuring unique frame IDs
1517 var idMap = { used: {} };
1518 this.restoreHistory(aWindow, aTabs, idMap);
1522 * Restory history for a window
1523 * @param aWindow
1524 * Window reference
1525 * @param aTabs
1526 * Array of tab data
1527 * @param aIdMap
1528 * Hash for ensuring unique frame IDs
1530 restoreHistory: function sss_restoreHistory(aWindow, aTabs, aIdMap) {
1531 var _this = this;
1532 while (aTabs.length > 0 && (!aTabs[0]._tab || !aTabs[0]._tab.parentNode)) {
1533 aTabs.shift(); // this tab got removed before being completely restored
1535 if (aTabs.length == 0) {
1536 return; // no more tabs to restore
1539 var tabData = aTabs.shift();
1541 var tab = tabData._tab;
1542 var browser = aWindow.getBrowser().getBrowserForTab(tab);
1543 var history = browser.webNavigation.sessionHistory;
1545 if (history.count > 0) {
1546 history.PurgeHistory(history.count);
1548 history.QueryInterface(Ci.nsISHistoryInternal);
1550 if (!tabData.entries) {
1551 tabData.entries = [];
1553 if (tabData.extData) {
1554 tab.__SS_extdata = tabData.extData;
1557 for (var i = 0; i < tabData.entries.length; i++) {
1558 history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true);
1561 // make sure to reset the capabilities and attributes, in case this tab gets reused
1562 var disallow = (tabData.disallow)?tabData.disallow.split(","):[];
1563 CAPABILITIES.forEach(function(aCapability) {
1564 browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1;
1566 Array.filter(tab.attributes, function(aAttr) {
1567 return (_this.xulAttributes.indexOf(aAttr.name) > -1);
1568 }).forEach(tab.removeAttribute, tab);
1569 if (tabData.xultab) {
1570 tabData.xultab.split(" ").forEach(function(aAttr) {
1571 if (/^([^\s=]+)=(.*)/.test(aAttr)) {
1572 tab.setAttribute(RegExp.$1, decodeURI(RegExp.$2));
1577 // notify the tabbrowser that the tab chrome has been restored
1578 var event = aWindow.document.createEvent("Events");
1579 event.initEvent("SSTabRestoring", true, false);
1580 tab.dispatchEvent(event);
1582 var activeIndex = (tabData.index || tabData.entries.length) - 1;
1583 try {
1584 browser.webNavigation.gotoIndex(activeIndex);
1586 catch (ex) { } // ignore an invalid tabData.index
1588 // restore those aspects of the currently active documents
1589 // which are not preserved in the plain history entries
1590 // (mainly scroll state and text data)
1591 browser.__SS_restore_data = tabData.entries[activeIndex] || {};
1592 browser.__SS_restore_text = tabData.text || "";
1593 browser.__SS_restore_tab = tab;
1594 browser.__SS_restore = this.restoreDocument_proxy;
1595 browser.addEventListener("load", browser.__SS_restore, true);
1597 aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aIdMap); }, 0);
1601 * expands serialized history data into a session-history-entry instance
1602 * @param aEntry
1603 * Object containing serialized history data for a URL
1604 * @param aIdMap
1605 * Hash for ensuring unique frame IDs
1606 * @returns nsISHEntry
1608 _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) {
1609 var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
1610 createInstance(Ci.nsISHEntry);
1612 var ioService = Cc["@mozilla.org/network/io-service;1"].
1613 getService(Ci.nsIIOService);
1614 shEntry.setURI(ioService.newURI(aEntry.url, null, null));
1615 shEntry.setTitle(aEntry.title || aEntry.url);
1616 if (aEntry.subframe)
1617 shEntry.setIsSubFrame(aEntry.subframe || false);
1618 shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
1619 if (aEntry.contentType)
1620 shEntry.contentType = aEntry.contentType;
1622 if (aEntry.cacheKey) {
1623 var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
1624 createInstance(Ci.nsISupportsPRUint32);
1625 cacheKey.data = aEntry.cacheKey;
1626 shEntry.cacheKey = cacheKey;
1629 if (aEntry.ID) {
1630 // get a new unique ID for this frame (since the one from the last
1631 // start might already be in use)
1632 var id = aIdMap[aEntry.ID] || 0;
1633 if (!id) {
1634 for (id = Date.now(); aIdMap.used[id]; id++);
1635 aIdMap[aEntry.ID] = id;
1636 aIdMap.used[id] = true;
1638 shEntry.ID = id;
1641 if (aEntry.scroll) {
1642 var scrollPos = (aEntry.scroll || "0,0").split(",");
1643 scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
1644 shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
1647 var postdata;
1648 if (aEntry.postdata_b64) { // Firefox 3
1649 postdata = atob(aEntry.postdata_b64);
1650 } else if (aEntry.postdata) { // Firefox 2
1651 postdata = aEntry.postdata;
1654 if (postdata) {
1655 var stream = Cc["@mozilla.org/io/string-input-stream;1"].
1656 createInstance(Ci.nsIStringInputStream);
1657 stream.setData(postdata, postdata.length);
1658 shEntry.postData = stream;
1661 if (aEntry.owner_b64) { // Firefox 3
1662 var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
1663 createInstance(Ci.nsIStringInputStream);
1664 var binaryData = atob(aEntry.owner_b64);
1665 ownerInput.setData(binaryData, binaryData.length);
1666 var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
1667 createInstance(Ci.nsIObjectInputStream);
1668 binaryStream.setInputStream(ownerInput);
1669 try { // Catch possible deserialization exceptions
1670 shEntry.owner = binaryStream.readObject(true);
1671 } catch (ex) { debug(ex); }
1672 } else if (aEntry.ownerURI) { // Firefox 2
1673 var uriObj = ioService.newURI(aEntry.ownerURI, null, null);
1674 shEntry.owner = Cc["@mozilla.org/scriptsecuritymanager;1"].
1675 getService(Ci.nsIScriptSecurityManager).
1676 getCodebasePrincipal(uriObj);
1679 if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
1680 for (var i = 0; i < aEntry.children.length; i++) {
1681 shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i);
1685 return shEntry;
1689 * Restore properties to a loaded document
1691 restoreDocument_proxy: function sss_restoreDocument_proxy(aEvent) {
1692 // wait for the top frame to be loaded completely
1693 if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView || aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
1694 return;
1697 var textArray = this.__SS_restore_text ? this.__SS_restore_text.split(" ") : [];
1698 function restoreTextData(aContent, aPrefix) {
1699 textArray.forEach(function(aEntry) {
1700 if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry) && (!RegExp.$1 || RegExp.$1 == aPrefix)) {
1701 var document = aContent.document;
1702 var node = RegExp.$2 ? document.getElementById(RegExp.$3) : document.getElementsByName(RegExp.$3)[0] || null;
1703 if (node && "value" in node) {
1704 node.value = decodeURI(RegExp.$4);
1706 var event = document.createEvent("UIEvents");
1707 event.initUIEvent("input", true, true, aContent, 0);
1708 node.dispatchEvent(event);
1714 function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
1715 restoreTextData(aContent, aPrefix);
1716 if (aData.innerHTML) {
1717 aContent.setTimeout(function(aHTML) { if (this.document.designMode == "on") { this.document.body.innerHTML = aHTML; } }, 0, aData.innerHTML);
1719 if (aData.scroll && /(\d+),(\d+)/.test(aData.scroll)) {
1720 aContent.scrollTo(RegExp.$1, RegExp.$2);
1722 for (var i = 0; i < aContent.frames.length; i++) {
1723 if (aData.children && aData.children[i]) {
1724 restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], i + "|" + aPrefix);
1729 var content = aEvent.originalTarget.defaultView;
1730 if (this.currentURI.spec == "about:config") {
1731 // unwrap the document for about:config because otherwise the properties
1732 // of the XBL bindings - as the textbox - aren't accessible (see bug 350718)
1733 content = content.wrappedJSObject;
1735 restoreTextDataAndScrolling(content, this.__SS_restore_data, "");
1737 // notify the tabbrowser that this document has been completely restored
1738 var event = this.ownerDocument.createEvent("Events");
1739 event.initEvent("SSTabRestored", true, false);
1740 this.__SS_restore_tab.dispatchEvent(event);
1742 this.removeEventListener("load", this.__SS_restore, true);
1743 delete this.__SS_restore_data;
1744 delete this.__SS_restore_text;
1745 delete this.__SS_restore_tab;
1746 delete this.__SS_restore;
1750 * Restore visibility and dimension features to a window
1751 * @param aWindow
1752 * Window reference
1753 * @param aWinData
1754 * Object containing session data for the window
1756 restoreWindowFeatures: function sss_restoreWindowFeatures(aWindow, aWinData) {
1757 var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
1758 WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
1759 aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
1762 var _this = this;
1763 aWindow.setTimeout(function() {
1764 _this.restoreDimensions.apply(_this, [aWindow, aWinData.width || 0,
1765 aWinData.height || 0, "screenX" in aWinData ? aWinData.screenX : NaN,
1766 "screenY" in aWinData ? aWinData.screenY : NaN,
1767 aWinData.sizemode || "", aWinData.sidebar || ""]);
1768 }, 0);
1772 * Restore a window's dimensions
1773 * @param aWidth
1774 * Window width
1775 * @param aHeight
1776 * Window height
1777 * @param aLeft
1778 * Window left
1779 * @param aTop
1780 * Window top
1781 * @param aSizeMode
1782 * Window size mode (eg: maximized)
1783 * @param aSidebar
1784 * Sidebar command
1786 restoreDimensions: function sss_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
1787 var win = aWindow;
1788 var _this = this;
1789 function win_(aName) { return _this._getWindowDimension(win, aName); }
1791 // only modify those aspects which aren't correct yet
1792 if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
1793 aWindow.resizeTo(aWidth, aHeight);
1795 if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
1796 aWindow.moveTo(aLeft, aTop);
1798 if (aSizeMode == "maximized" && win_("sizemode") != "maximized") {
1799 aWindow.maximize();
1801 else if (aSizeMode && aSizeMode != "maximized" && win_("sizemode") != "normal") {
1802 aWindow.restore();
1804 var sidebar = aWindow.document.getElementById("sidebar-box");
1805 if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
1806 aWindow.toggleSidebar(aSidebar);
1808 // since resizing/moving a window brings it to the foreground,
1809 // we might want to re-focus the last focused window
1810 if (this.windowToFocus) {
1811 this.windowToFocus.focus();
1816 * Restores cookies (accepting both Firefox 2.0 and current format)
1817 * @param aCookies
1818 * Array of cookie objects
1820 restoreCookies: function sss_restoreCookies(aCookies) {
1821 if (aCookies.count && aCookies.domain1) {
1822 // convert to the new cookie serialization format
1823 var converted = [];
1824 for (var i = 1; i <= aCookies.count; i++) {
1825 // for simplicity we only accept the format we produced ourselves
1826 var parsed = aCookies["value" + i].match(/^([^=;]+)=([^;]*);(?:domain=[^;]+;)?(?:path=([^;]*);)?(secure;)?(httponly;)?/);
1827 if (parsed && /^https?:\/\/([^\/]+)/.test(aCookies["domain" + i]))
1828 converted.push({
1829 host: RegExp.$1, path: parsed[3], name: parsed[1], value: parsed[2],
1830 secure: parsed[4], httponly: parsed[5]
1833 aCookies = converted;
1836 var cookieManager = Cc["@mozilla.org/cookiemanager;1"].
1837 getService(Ci.nsICookieManager2);
1838 // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
1839 var MAX_EXPIRY = Math.pow(2, 62);
1840 for (i = 0; i < aCookies.length; i++) {
1841 var cookie = aCookies[i];
1842 try {
1843 cookieManager.add(cookie.host, cookie.path || "", cookie.name || "", cookie.value, !!cookie.secure, !!cookie.httponly, true, "expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
1845 catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
1849 /* ........ Disk Access .............. */
1852 * save state delayed by N ms
1853 * marks window as dirty (i.e. data update can't be skipped)
1854 * @param aWindow
1855 * Window reference
1856 * @param aDelay
1857 * Milliseconds to delay
1859 saveStateDelayed: function sss_saveStateDelayed(aWindow, aDelay) {
1860 if (aWindow) {
1861 this._dirtyWindows[aWindow.__SSi] = true;
1864 if (!this._saveTimer && this._resume_from_crash) {
1865 // interval until the next disk operation is allowed
1866 var minimalDelay = this._lastSaveTime + this._interval - Date.now();
1868 // if we have to wait, set a timer, otherwise saveState directly
1869 aDelay = Math.max(minimalDelay, aDelay || 2000);
1870 if (aDelay > 0) {
1871 this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1872 this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
1874 else {
1875 this.saveState();
1881 * save state to disk
1882 * @param aUpdateAll
1883 * Bool update all windows
1885 saveState: function sss_saveState(aUpdateAll) {
1886 // if crash recovery is disabled, only save session resuming information
1887 if (!this._resume_from_crash && this._loadState == STATE_RUNNING)
1888 return;
1890 this._dirty = aUpdateAll;
1891 var oState = this._getCurrentState();
1892 oState.session = { state: ((this._loadState == STATE_RUNNING) ? STATE_RUNNING_STR : STATE_STOPPED_STR) };
1893 this._writeFile(this._sessionFile, oState.toSource());
1894 this._lastSaveTime = Date.now();
1898 * delete session datafile and backup
1900 _clearDisk: function sss_clearDisk() {
1901 if (this._sessionFile.exists()) {
1902 try {
1903 this._sessionFile.remove(false);
1905 catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
1907 if (this._sessionFileBackup.exists()) {
1908 try {
1909 this._sessionFileBackup.remove(false);
1911 catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
1915 /* ........ Auxiliary Functions .............. */
1918 * call a callback for all currently opened browser windows
1919 * (might miss the most recent one)
1920 * @param aFunc
1921 * Callback each window is passed to
1923 _forEachBrowserWindow: function sss_forEachBrowserWindow(aFunc) {
1924 var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
1925 getService(Ci.nsIWindowMediator);
1926 var windowsEnum = windowMediator.getEnumerator("navigator:browser");
1928 while (windowsEnum.hasMoreElements()) {
1929 var window = windowsEnum.getNext();
1930 if (window.__SSi) {
1931 aFunc.call(this, window);
1937 * Returns most recent window
1938 * @returns Window reference
1940 _getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() {
1941 var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
1942 getService(Ci.nsIWindowMediator);
1943 return windowMediator.getMostRecentWindow("navigator:browser");
1947 * open a new browser window for a given session state
1948 * called when restoring a multi-window session
1949 * @param aState
1950 * Object containing session data
1952 _openWindowWithState: function sss_openWindowWithState(aState) {
1953 var argString = Cc["@mozilla.org/supports-string;1"].
1954 createInstance(Ci.nsISupportsString);
1955 argString.data = "";
1957 //XXXzeniko shouldn't it be possible to set the window's dimensions here (as feature)?
1958 var window = Cc["@mozilla.org/embedcomp/window-watcher;1"].
1959 getService(Ci.nsIWindowWatcher).
1960 openWindow(null, this._prefBranch.getCharPref("chromeURL"), "_blank",
1961 "chrome,dialog=no,all", argString);
1963 window.__SS_state = aState;
1964 var _this = this;
1965 window.addEventListener("load", function(aEvent) {
1966 aEvent.currentTarget.removeEventListener("load", arguments.callee, true);
1967 _this.restoreWindow(aEvent.currentTarget, aEvent.currentTarget.__SS_state, true, true);
1968 delete aEvent.currentTarget.__SS_state;
1969 }, true);
1971 return window;
1975 * Whether or not to resume session, if not recovering from a crash.
1976 * @returns bool
1978 _doResumeSession: function sss_doResumeSession() {
1979 return this._prefBranch.getIntPref("startup.page") == 3 ||
1980 this._prefBranch.getBoolPref("sessionstore.resume_session_once");
1984 * whether the user wants to load any other page at startup
1985 * (except the homepage) - needed for determining whether to overwrite the current tabs
1986 * C.f.: nsBrowserContentHandler's defaultArgs implementation.
1987 * @returns bool
1989 _isCmdLineEmpty: function sss_isCmdLineEmpty(aWindow) {
1990 var defaultArgs = Cc["@mozilla.org/browser/clh;1"].
1991 getService(Ci.nsIBrowserHandler).defaultArgs;
1992 if (aWindow.arguments && aWindow.arguments[0] &&
1993 aWindow.arguments[0] == defaultArgs)
1994 aWindow.arguments[0] = null;
1996 return !aWindow.arguments || !aWindow.arguments[0];
2000 * don't save sensitive data if the user doesn't want to
2001 * (distinguishes between encrypted and non-encrypted sites)
2002 * @param aIsHTTPS
2003 * Bool is encrypted
2004 * @returns bool
2006 _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) {
2007 return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
2011 * on popup windows, the XULWindow's attributes seem not to be set correctly
2012 * we use thus JSDOMWindow attributes for sizemode and normal window attributes
2013 * (and hope for reasonable values when maximized/minimized - since then
2014 * outerWidth/outerHeight aren't the dimensions of the restored window)
2015 * @param aWindow
2016 * Window reference
2017 * @param aAttribute
2018 * String sizemode | width | height | other window attribute
2019 * @returns string
2021 _getWindowDimension: function sss_getWindowDimension(aWindow, aAttribute) {
2022 if (aAttribute == "sizemode") {
2023 switch (aWindow.windowState) {
2024 case aWindow.STATE_MAXIMIZED:
2025 return "maximized";
2026 case aWindow.STATE_MINIMIZED:
2027 return "minimized";
2028 default:
2029 return "normal";
2033 var dimension;
2034 switch (aAttribute) {
2035 case "width":
2036 dimension = aWindow.outerWidth;
2037 break;
2038 case "height":
2039 dimension = aWindow.outerHeight;
2040 break;
2041 default:
2042 dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
2043 break;
2046 if (aWindow.windowState == aWindow.STATE_NORMAL) {
2047 return dimension;
2049 return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
2053 * Get nsIURI from string
2054 * @param string
2055 * @returns nsIURI
2057 _getURIFromString: function sss_getURIFromString(aString) {
2058 var ioService = Cc["@mozilla.org/network/io-service;1"].
2059 getService(Ci.nsIIOService);
2060 return ioService.newURI(aString, null, null);
2064 * Annotate a breakpad crash report with the currently selected tab's URL.
2066 _updateCrashReportURL: function sss_updateCrashReportURL(aWindow) {
2067 if (!Ci.nsICrashReporter) {
2068 // if breakpad isn't built, don't bother next time at all
2069 this._updateCrashReportURL = function(aWindow) {};
2070 return;
2072 try {
2073 var currentUrl = aWindow.getBrowser().currentURI.spec;
2074 var cr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsICrashReporter);
2075 cr.annotateCrashReport("URL", currentUrl);
2077 catch (ex) {
2078 // don't make noise when crashreporter is built but not enabled
2079 if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED)
2080 debug(ex);
2085 * safe eval'ing
2087 _safeEval: function sss_safeEval(aStr) {
2088 return Cu.evalInSandbox(aStr, new Cu.Sandbox("about:blank"));
2092 * Converts a JavaScript object into a JSON string
2093 * (see http://www.json.org/ for more information).
2095 * The inverse operation consists of eval("(" + JSON_string + ")");
2096 * and should be provably safe.
2098 * @param aJSObject is the object to be converted
2099 * @return the object's JSON representation
2101 _toJSONString: function sss_toJSONString(aJSObject) {
2102 var str = JSON.toString(aJSObject, ["_tab", "_hosts"] /* keys to drop */);
2104 // sanity check - so that API consumers can just eval this string
2105 if (!JSON.isMostlyHarmless(str))
2106 throw new Error("JSON conversion failed unexpectedly!");
2108 return str;
2111 /* ........ Storage API .............. */
2114 * write file to disk
2115 * @param aFile
2116 * nsIFile
2117 * @param aData
2118 * String data
2120 _writeFile: function sss_writeFile(aFile, aData) {
2121 // init stream
2122 var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
2123 createInstance(Ci.nsIFileOutputStream);
2124 stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
2126 // convert to UTF-8
2127 var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
2128 createInstance(Ci.nsIScriptableUnicodeConverter);
2129 converter.charset = "UTF-8";
2130 var convertedData = converter.ConvertFromUnicode(aData);
2131 convertedData += converter.Finish();
2133 // write and close stream
2134 stream.write(convertedData, convertedData.length);
2135 if (stream instanceof Ci.nsISafeOutputStream) {
2136 stream.finish();
2137 } else {
2138 stream.close();
2143 function NSGetModule(aComMgr, aFileSpec)
2144 XPCOMUtils.generateModule([SessionStoreService]);