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
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.
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 ***** */
39 * Session Storage and Restoration
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
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"];
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
,
124 Ci
.nsISupportsWeakReference
]),
126 // xul:tab attributes to (re)store (extensions might want to hook in here)
129 // set default load state
130 _loadState
: STATE_STOPPED
,
132 // minimal interval between two save operations (in milliseconds)
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
141 // time in milliseconds (Date.now()) when the session was last written to file
144 // states for all currently opened 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
153 // flag all windows as dirty
156 /* ........ Global Event Handlers .............. */
159 * Initialize the component
161 init
: function sss_init(aWindow
) {
162 if (this._loadState
== STATE_DISABLED
)
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
);
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
;
189 OBSERVING
.forEach(function(aTopic
) {
190 observerService
.addObserver(this, aTopic
, true);
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
215 var ss
= Cc
["@mozilla.org/browser/sessionstartup;1"].
216 getService(Ci
.nsISessionStartup
);
218 iniString
= ss
.state
;
220 catch(ex
) { dump(ex
+ "\n"); } // no state to restore, which is ok
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
) {
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
)
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
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
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
);
285 case "domwindowclosed": // catch closed windows
286 this.onClose(aSubject
);
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
= [];
296 case "quit-application-granted":
297 // freeze the data at what we've got (ignoring closing windows)
298 this._loadState
= STATE_QUITTING
;
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
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;
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();
321 win
.setTimeout(function() { _this
.saveState(true); }, 0);
323 this.saveState(true);
325 case "nsPref:changed": // catch pref changes
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":
331 for (ix
in this._windows
) {
332 this._windows
[ix
]._closedTabs
.splice(this._prefBranch
.getIntPref("sessionstore.max_tabs_undo"));
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);
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
)
355 case "timer-callback": // timer call back for delayed saving
356 this._saveTimer
= null;
362 /* ........ Window Event Handlers .............. */
365 * Implement nsIDOMEventListener for handling various window and tab events
367 handleEvent
: function sss_handleEvent(aEvent
) {
368 switch (aEvent
.type
) {
371 this.onTabLoad(aEvent
.currentTarget
.ownerDocument
.defaultView
, aEvent
.currentTarget
, aEvent
);
374 case "DOMAutoComplete":
375 this.onTabInput(aEvent
.currentTarget
.ownerDocument
.defaultView
, aEvent
.currentTarget
, aEvent
);
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
);
385 this.onTabClose(aEvent
.currentTarget
.ownerDocument
.defaultView
, aEvent
.originalTarget
);
386 this.onTabRemove(aEvent
.currentTarget
.ownerDocument
.defaultView
, tabpanel
);
390 var tabpanels
= aEvent
.currentTarget
.mPanelContainer
;
391 this.onTabSelect(aEvent
.currentTarget
.ownerDocument
.defaultView
, tabpanels
);
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
405 onLoad
: function sss_onLoad(aWindow
) {
406 // return if window has already been initialized
407 if (aWindow
&& aWindow
.__SSi
&& this._windows
[aWindow
.__SSi
])
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
)
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
;
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);
461 * - remove event listeners from tabs
462 * - save all window data
466 onClose
: function sss_onClose(aWindow
) {
467 // ignore windows not tracked by SessionStore
468 if (!aWindow
.__SSi
|| !this._windows
[aWindow
.__SSi
]) {
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
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
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
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) {
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({
591 title
: aTab
.getAttribute("label"),
592 image
: aTab
.getAttribute("image"),
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.
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
) {
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
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
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();
667 this._openWindowWithState("(" + aState
+ ")");
671 // close all other browser windows
672 this._forEachBrowserWindow(function(aWindow
) {
673 if (aWindow
!= window
) {
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
;
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);
726 getClosedTabCount
: function sss_getClosedTabCount(aWindow
) {
727 if (!aWindow
.__SSi
&& aWindow
.__SS_dyingCache
)
728 return aWindow
.__SS_dyingCache
._closedTabs
.length
;
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
) {
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
;
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
;
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);
782 Components
.returnCode
= Cr
.NS_ERROR_INVALID_ARG
;
786 getWindowValue
: function sss_getWindowValue(aWindow
, aKey
) {
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
] || "";
796 Components
.returnCode
= Cr
.NS_ERROR_INVALID_ARG
;
800 setWindowValue
: function sss_setWindowValue(aWindow
, aKey
, aStringValue
) {
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
);
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
];
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
];
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
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
869 * always return privacy sensitive data (use with care)
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()
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
;
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),
898 tabData
.index
= history
.index
+ 1;
900 // make sure not to cache privacy sensitive data which shouldn't get out
902 browser
.parentNode
.__SS_data
= tabData
;
905 tabData
.entries
[0] = { url
: browser
.currentURI
.spec
};
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
;
936 * Get an object that is a serialized representation of a History entry
937 * Used for data storage
939 * nsISHEntry instance
941 * always return privacy sensitive data (use with care)
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
)) {
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
;
970 aEntry
.getScrollPosition(x
, y
);
971 if (x
.value
!= 0 || y
.value
!= 0)
972 entry
.scroll
= x
.value
+ "," + y
.value
;
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
<=
988 // We can stop doing base64 encoding once our serialization into JSON
989 // is guaranteed to handle all chars in strings, including embedded
991 entry
.postdata_b64
= btoa(postdata
);
995 catch (ex
) { debug(ex
); } // POSTDATA is tricky - especially since some extensions don't get it right
998 // Not catching anything specific here, just possible errors
999 // from writeCompoundObject and the like.
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
);
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
1018 entry
.owner_b64
= btoa(String
.fromCharCode
.apply(null, ownerBytes
));
1020 catch (ex
) { debug(ex
); }
1023 if (!(aEntry
instanceof Ci
.nsISHContainer
)) {
1027 if (aEntry
.childCount
> 0) {
1028 entry
.children
= [];
1029 for (var i
= 0; i
< aEntry
.childCount
; i
++) {
1030 var child
= aEntry
.GetChildAt(i
);
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" });
1044 * Updates the current document's cache of user entered text data
1046 * TabPanel reference
1048 * HTML content element
1051 _saveTextData
: function sss_saveTextData(aPanel
, aTextarea
) {
1052 var id
= aTextarea
.id
? "#" + aTextarea
.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
);
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)
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
++);
1084 content
= content
.parent
;
1087 // mark this element for saving
1088 aPanel
.__SS_text
[ix
] = { id
: id
, element
: aTextarea
};
1094 * go through all tabs and store the current scroll positions
1095 * and innerHTML content of WYSIWYG editors
1099 _updateTextAndScrollData
: function sss_updateTextAndScrollData(aWindow
) {
1100 var browsers
= aWindow
.getBrowser().browsers
;
1101 for (var i
= 0; i
< browsers
.length
; i
++) {
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
1119 * single browser reference
1121 * tabData object to add the information to
1123 * always return privacy sensitive data (use with care)
1125 _updateTextAndScrollDataForTab
:
1126 function sss_updateTextAndScrollDataForTab(aWindow
, aBrowser
, aTabData
, aFullData
) {
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
];
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
1161 * part of a tabData object to add the information to
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
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
1192 _updateCookieHosts
: function sss_updateCookieHosts(aWindow
) {
1193 var hosts
= this._windows
[aWindow
.__SSi
]._hosts
= {};
1195 // get all possible subdomain levels for a given URL
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;
1202 for (ix
= host
.indexOf(".") + 1; ix
; ix
= host
.indexOf(".", ix
) + 1) {
1203 hosts
[host
.substr(ix
)] = 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
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
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
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
);
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");
1282 winData
.sidebar
= sidebar
;
1283 else if (winData
.sidebar
)
1284 delete winData
.sidebar
;
1288 * serialize session data as Ini-formatted 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
);
1304 this._dirtyWindows
= [];
1305 this._dirty
= false;
1308 // collect the data for all windows
1309 var total
= [], windows
= [];
1311 for (ix
in this._windows
) {
1312 total
.push(this._windows
[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
);
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
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
1363 * JS object or its eval'able source
1364 * @param aOverwriteTabs
1365 * bool overwrite existing tabs w/ new ones
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
, "");
1381 this.windowToFocus
= aWindow
;
1383 // initialize window if necessary
1384 if (aWindow
&& (!aWindow
.__SSi
|| !this._windows
[aWindow
.__SSi
]))
1385 this.onLoad(aWindow
);
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
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
) {
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
1461 * @param aCurrentTabs
1462 * Array of tab references
1464 * Index of selected tab
1466 * Index of the next tab to check readyness for
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
++) {
1477 if (!tabbrowser
.getBrowserForTab(aTabs
[t
]._tab
).webNavigation
.sessionHistory
) {
1481 catch (ex
) { // in case browser or history aren't ready yet
1483 var restoreHistoryFunc = function(self
) {
1484 self
.restoreHistoryPrecursor(aWindow
, aTabs
, aSelectTab
, aIx
, aCount
+ 1);
1486 aWindow
.setTimeout(restoreHistoryFunc
, 100, this);
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
1528 * Hash for ensuring unique frame IDs
1530 restoreHistory
: function sss_restoreHistory(aWindow
, aTabs
, aIdMap
) {
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;
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
1603 * Object containing serialized history data for a URL
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
;
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;
1634 for (id
= Date
.now(); aIdMap
.used
[id
]; id
++);
1635 aIdMap
[aEntry
.ID
] = id
;
1636 aIdMap
.used
[id
] = true;
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]);
1648 if (aEntry
.postdata_b64
) { // Firefox 3
1649 postdata
= atob(aEntry
.postdata_b64
);
1650 } else if (aEntry
.postdata
) { // Firefox 2
1651 postdata
= aEntry
.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
);
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
) {
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
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;
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
|| ""]);
1772 * Restore a window's dimensions
1782 * Window size mode (eg: maximized)
1786 restoreDimensions
: function sss_restoreDimensions(aWindow
, aWidth
, aHeight
, aLeft
, aTop
, aSizeMode
, aSidebar
) {
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") {
1801 else if (aSizeMode
&& aSizeMode
!= "maximized" && win_("sizemode") != "normal") {
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)
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
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
]))
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
];
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)
1857 * Milliseconds to delay
1859 saveStateDelayed
: function sss_saveStateDelayed(aWindow
, aDelay
) {
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);
1871 this._saveTimer
= Cc
["@mozilla.org/timer;1"].createInstance(Ci
.nsITimer
);
1872 this._saveTimer
.init(this, aDelay
, Ci
.nsITimer
.TYPE_ONE_SHOT
);
1881 * save state to disk
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
)
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()) {
1903 this._sessionFile
.remove(false);
1905 catch (ex
) { dump(ex
+ '\n'); } // couldn't remove the file - what now?
1907 if (this._sessionFileBackup
.exists()) {
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)
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();
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
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
;
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
;
1975 * Whether or not to resume session, if not recovering from a crash.
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.
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)
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)
2018 * String sizemode | width | height | other window attribute
2021 _getWindowDimension
: function sss_getWindowDimension(aWindow
, aAttribute
) {
2022 if (aAttribute
== "sizemode") {
2023 switch (aWindow
.windowState
) {
2024 case aWindow
.STATE_MAXIMIZED
:
2026 case aWindow
.STATE_MINIMIZED
:
2034 switch (aAttribute
) {
2036 dimension
= aWindow
.outerWidth
;
2039 dimension
= aWindow
.outerHeight
;
2042 dimension
= aAttribute
in aWindow
? aWindow
[aAttribute
] : "";
2046 if (aWindow
.windowState
== aWindow
.STATE_NORMAL
) {
2049 return aWindow
.document
.documentElement
.getAttribute(aAttribute
) || dimension
;
2053 * Get nsIURI from string
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
) {};
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
);
2078 // don't make noise when crashreporter is built but not enabled
2079 if (ex
.result
!= Components
.results
.NS_ERROR_NOT_INITIALIZED
)
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!");
2111 /* ........ Storage API .............. */
2114 * write file to disk
2120 _writeFile
: function sss_writeFile(aFile
, aData
) {
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);
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
) {
2143 function NSGetModule(aComMgr
, aFileSpec
)
2144 XPCOMUtils
.generateModule([SessionStoreService
]);