nsSessionStore.js
Go to the documentation of this file.
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
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/
8  *
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.
13  *
14  * The Original Code is the nsSessionStore component.
15  *
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.
20  *
21  * Contributor(s):
22  * Dietrich Ayala <dietrich@mozilla.com>
23  * Ehsan Akhgari <ehsan.akhgari@gmail.com>
24  * Michael Kraft <morac99-firefox@yahoo.com>
25  * Paul O’Shannessy <paul@oshannessy.com>
26  * Nils Maier <maierman@web.de>
27  *
28  * Alternatively, the contents of this file may be used under the terms of
29  * either the GNU General Public License Version 2 or later (the "GPL"), or
30  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31  * in which case the provisions of the GPL or the LGPL are applicable instead
32  * of those above. If you wish to allow use of your version of this file only
33  * under the terms of either the GPL or the LGPL, and not to allow others to
34  * use your version of this file under the terms of the MPL, indicate your
35  * decision by deleting the provisions above and replace them with the notice
36  * and other provisions required by the GPL or the LGPL. If you do not delete
37  * the provisions above, a recipient may use your version of this file under
38  * the terms of any one of the MPL, the GPL or the LGPL.
39  *
40  * ***** END LICENSE BLOCK ***** */
41 
54 /* :::::::: Constants and Helpers ::::::::::::::: */
55 
56 const Cc = Components.classes;
57 const Ci = Components.interfaces;
58 const Cr = Components.results;
59 const Cu = Components.utils;
60 
61 const STATE_STOPPED = 0;
62 const STATE_RUNNING = 1;
63 const STATE_QUITTING = -1;
64 
65 const STATE_STOPPED_STR = "stopped";
66 const STATE_RUNNING_STR = "running";
67 
68 const PRIVACY_NONE = 0;
70 const PRIVACY_FULL = 2;
71 
72 const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
73 const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
74 
75 // global notifications observed
76 const OBSERVING = [
77  "domwindowopened", "domwindowclosed",
78  "quit-application-requested", "quit-application-granted",
79  "browser-lastwindow-close-granted",
80  "quit-application", "browser:purge-session-history",
81  "private-browsing", "browser:purge-domain-data",
82  "private-browsing-change-granted"
83 ];
84 
85 /*
86 XUL Window properties to (re)store
87 Restored in restoreDimensions()
88 */
89 const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
90 
91 /*
92 Hideable window features to (re)store
93 Restored in restoreWindowFeatures()
94 */
96  "menubar", "toolbar", "locationbar",
97  "personalbar", "statusbar", "scrollbars"
98 ];
99 
100 /*
101 docShell capabilities to (re)store
102 Restored in restoreHistory()
103 eg: browser.docShell["allow" + aCapability] = false;
104 
105 XXX keep these in sync with all the attributes starting
106  with "allow" in /docshell/base/nsIDocShell.idl
107 */
108 const CAPABILITIES = [
109  "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images",
110  "DNSPrefetch", "Auth"
111 ];
112 
113 #ifndef XP_WIN
114 #define BROKEN_WM_Z_ORDER
115 #endif
116 
117 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
118 
119 function debug(aMsg) {
120  aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
121  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
122  .logStringMessage(aMsg);
123 }
124 
125 __defineGetter__("NetUtil", function() {
126  delete this.NetUtil;
127  Cu.import("resource://gre/modules/NetUtil.jsm");
128  return NetUtil;
129 });
130 
131 /* :::::::: The Service ::::::::::::::: */
132 
134 }
135 
136 SessionStoreService.prototype = {
137  classDescription: "Browser Session Store Service",
138  contractID: "@mozilla.org/browser/sessionstore;1",
139  classID: Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}"),
140  QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
141  Ci.nsIDOMEventListener,
142  Ci.nsIObserver,
143  Ci.nsISupportsWeakReference]),
144 
145  // xul:tab attributes to (re)store (extensions might want to hook in here);
146  // the favicon is always saved for the about:sessionrestore page
147  xulAttributes: ["image"],
148 
149  // set default load state
150  _loadState: STATE_STOPPED,
151 
152  // minimal interval between two save operations (in milliseconds)
153  _interval: 10000,
154 
155  // when crash recovery is disabled, session data is not written to disk
156  _resume_from_crash: true,
157 
158  // During the initial restore and setBrowserState calls tracks the number of
159  // windows yet to be restored
160  _restoreCount: 0,
161 
162  // whether a setBrowserState call is in progress
163  _browserSetState: false,
164 
165  // time in milliseconds (Date.now()) when the session was last written to file
166  _lastSaveTime: 0,
167 
168  // states for all currently opened windows
169  _windows: {},
170 
171  // states for all recently closed windows
172  _closedWindows: [],
173 
174  // not-"dirty" windows usually don't need to have their data updated
175  _dirtyWindows: {},
176 
177  // collection of session states yet to be restored
178  _statesToRestore: {},
179 
180  // counts the number of crashes since the last clean start
181  _recentCrashes: 0,
182 
183  // whether we are in private browsing mode
184  _inPrivateBrowsing: false,
185 
186  // whether we clearing history on shutdown
187  _clearingOnShutdown: false,
188 
189  // List of windows that are being closed during setBrowserState.
190  _closingWindows: [],
191 
192 #ifndef XP_MACOSX
193  // whether the last window was closed and should be restored
194  _restoreLastWindow: false,
195 #endif
196 
197 /* ........ Global Event Handlers .............. */
198 
202  init: function sss_init(aWindow) {
203  if (!aWindow || this._loadState == STATE_RUNNING) {
204  // make sure that all browser windows which try to initialize
205  // SessionStore are really tracked by it
206  if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
207  this.onLoad(aWindow);
208  return;
209  }
210 
211  this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
212  getService(Ci.nsIPrefService).getBranch("browser.");
213  this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
214 
215  this._observerService = Cc["@mozilla.org/observer-service;1"].
216  getService(Ci.nsIObserverService);
217 
218  OBSERVING.forEach(function(aTopic) {
219  this._observerService.addObserver(this, aTopic, true);
220  }, this);
221 
222  var pbs = Cc["@mozilla.org/privatebrowsing;1"].
223  getService(Ci.nsIPrivateBrowsingService);
224  this._inPrivateBrowsing = pbs.privateBrowsingEnabled;
225 
226  // get interval from prefs - used often, so caching/observing instead of fetching on-demand
227  this._interval = this._prefBranch.getIntPref("sessionstore.interval");
228  this._prefBranch.addObserver("sessionstore.interval", this, true);
229 
230  // get crash recovery state from prefs and allow for proper reaction to state changes
231  this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
232  this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true);
233 
234  // observe prefs changes so we can modify stored data to match
235  this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
236  this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
237 
238  // this pref is only read at startup, so no need to observe it
239  this._sessionhistory_max_entries =
240  this._prefBranch.getIntPref("sessionhistory.max_entries");
241 
242  // get file references
243  var dirService = Cc["@mozilla.org/file/directory_service;1"].
244  getService(Ci.nsIProperties);
245  this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile);
246  this._sessionFileBackup = this._sessionFile.clone();
247  this._sessionFile.append("sessionstore.js");
248  this._sessionFileBackup.append("sessionstore.bak");
249 
250  // get string containing session state
251  var iniString;
252  try {
253  var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
254  getService(Ci.nsISessionStartup);
255  if (ss.doRestore())
256  iniString = ss.state;
257  }
258  catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
259 
260  if (iniString) {
261  try {
262  // parse the session state into JS objects
263  this._initialState = this._safeEval("(" + iniString + ")");
264 
265  let lastSessionCrashed =
266  this._initialState.session && this._initialState.session.state &&
267  this._initialState.session.state == STATE_RUNNING_STR;
268  if (lastSessionCrashed) {
269  this._recentCrashes = (this._initialState.session &&
270  this._initialState.session.recentCrashes || 0) + 1;
271 
272  if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
273  // replace the crashed session with a restore-page-only session
274  let pageData = {
275  url: "about:sessionrestore",
276  formdata: { "#sessionData": iniString }
277  };
278  this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
279  }
280  }
281 
282  // make sure that at least the first window doesn't have anything hidden
283  delete this._initialState.windows[0].hidden;
284  // Since nothing is hidden in the first window, it cannot be a popup
285  delete this._initialState.windows[0].isPopup;
286  }
287  catch (ex) { debug("The session file is invalid: " + ex); }
288  }
289 
290  // remove the session data files if crash recovery is disabled
291  if (!this._resume_from_crash)
292  this._clearDisk();
293  else { // create a backup if the session data file exists
294  try {
295  if (this._sessionFileBackup.exists())
296  this._sessionFileBackup.remove(false);
297  if (this._sessionFile.exists())
298  this._sessionFile.copyTo(null, this._sessionFileBackup.leafName);
299  }
300  catch (ex) { Cu.reportError(ex); } // file was write-locked?
301  }
302 
303  // at this point, we've as good as resumed the session, so we can
304  // clear the resume_session_once flag, if it's set
305  if (this._loadState != STATE_QUITTING &&
306  this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
307  this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
308 
309  // As this is called at delayedStartup, restoration must be initiated here
310  this.onLoad(aWindow);
311  },
312 
317  _uninit: function sss_uninit() {
318  if (this._doResumeSession()) { // save all data for session resuming
319  this.saveState(true);
320  }
321  else { // discard all session related data
322  this._clearDisk();
323  }
324  // Make sure to break our cycle with the save timer
325  if (this._saveTimer) {
326  this._saveTimer.cancel();
327  this._saveTimer = null;
328  }
329  },
330 
334  observe: function sss_observe(aSubject, aTopic, aData) {
335  // for event listeners
336  var _this = this;
337 
338  switch (aTopic) {
339  case "domwindowopened": // catch new windows
340  aSubject.addEventListener("load", function(aEvent) {
341  aEvent.currentTarget.removeEventListener("load", arguments.callee, false);
342  _this.onLoad(aEvent.currentTarget);
343  }, false);
344  break;
345  case "domwindowclosed": // catch closed windows
346  if (this._closingWindows.length > 0) {
347  let index = this._closingWindows.indexOf(aSubject);
348  if (index != -1) {
349  this._closingWindows.splice(index, 1);
350  if (this._closingWindows.length == 0)
352  }
353  }
354  this.onClose(aSubject);
355  break;
356  case "quit-application-requested":
357  // get a current snapshot of all windows
358  this._forEachBrowserWindow(function(aWindow) {
359  this._collectWindowData(aWindow);
360  });
361  this._dirtyWindows = [];
362  break;
363  case "quit-application-granted":
364  // freeze the data at what we've got (ignoring closing windows)
365  this._loadState = STATE_QUITTING;
366  break;
367 #ifndef XP_MACOSX
368  case "browser-lastwindow-close-granted":
369  // last browser window is quitting.
370  // remember to restore the last window when another browser window is openend
371  // do not account for pref(resume_session_once) at this point, as it might be
372  // set by another observer getting this notice after us
373  this._restoreLastWindow = true;
374  break;
375 #endif
376  case "quit-application":
377  if (aData == "restart") {
378  this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
379  this._clearingOnShutdown = false;
380  }
381  this._loadState = STATE_QUITTING; // just to be sure
382  this._uninit();
383  break;
384  case "browser:purge-session-history": // catch sanitization
385  let openWindows = {};
386  this._forEachBrowserWindow(function(aWindow) {
387  Array.forEach(aWindow.gBrowser.browsers, function(aBrowser) {
388  delete aBrowser.__SS_data;
389  });
390  openWindows[aWindow.__SSi] = true;
391  });
392  // also clear all data about closed tabs and windows
393  for (let ix in this._windows) {
394  if (ix in openWindows)
395  this._windows[ix]._closedTabs = [];
396  else
397  delete this._windows[ix];
398  }
399  // also clear all data about closed windows
400  this._closedWindows = [];
401  this._clearDisk();
402  // give the tabbrowsers a chance to clear their histories first
403  var win = this._getMostRecentBrowserWindow();
404  if (win)
405  win.setTimeout(function() { _this.saveState(true); }, 0);
406  else if (this._loadState == STATE_RUNNING)
407  this.saveState(true);
408  // Delete the private browsing backed up state, if any
409  if ("_stateBackup" in this)
410  delete this._stateBackup;
411  if (this._loadState == STATE_QUITTING)
412  this._clearingOnShutdown = true;
413  break;
414  case "browser:purge-domain-data":
415  // does a session history entry contain a url for the given domain?
416  function containsDomain(aEntry) {
417  try {
418  if (this._getURIFromString(aEntry.url).host.hasRootDomain(aData))
419  return true;
420  }
421  catch (ex) { /* url had no host at all */ }
422  return aEntry.children && aEntry.children.some(containsDomain, this);
423  }
424  // remove all closed tabs containing a reference to the given domain
425  for (let ix in this._windows) {
426  let closedTabs = this._windows[ix]._closedTabs;
427  for (let i = closedTabs.length - 1; i >= 0; i--) {
428  if (closedTabs[i].state.entries.some(containsDomain, this))
429  closedTabs.splice(i, 1);
430  }
431  }
432  // remove all open & closed tabs containing a reference to the given
433  // domain in closed windows
434  for (let ix = this._closedWindows.length - 1; ix >= 0; ix--) {
435  let closedTabs = this._closedWindows[ix]._closedTabs;
436  let openTabs = this._closedWindows[ix].tabs;
437  let openTabCount = openTabs.length;
438  for (let i = closedTabs.length - 1; i >= 0; i--)
439  if (closedTabs[i].state.entries.some(containsDomain, this))
440  closedTabs.splice(i, 1);
441  for (let j = openTabs.length - 1; j >= 0; j--) {
442  if (openTabs[j].entries.some(containsDomain, this)) {
443  openTabs.splice(j, 1);
444  if (this._closedWindows[ix].selected > j)
445  this._closedWindows[ix].selected--;
446  }
447  }
448  if (openTabs.length == 0) {
449  this._closedWindows.splice(ix, 1);
450  }
451  else if (openTabs.length != openTabCount) {
452  // Adjust the window's title if we removed an open tab
453  let selectedTab = openTabs[this._closedWindows[ix].selected - 1];
454  // some duplication from restoreHistory - make sure we get the correct title
455  let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
456  if (activeIndex >= selectedTab.entries.length)
457  activeIndex = selectedTab.entries.length - 1;
458  this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
459  }
460  }
461  if (this._loadState == STATE_RUNNING)
462  this.saveState(true);
463  break;
464  case "nsPref:changed": // catch pref changes
465  switch (aData) {
466  // if the user decreases the max number of closed tabs they want
467  // preserved update our internal states to match that max
468  case "sessionstore.max_tabs_undo":
469  for (let ix in this._windows) {
470  this._windows[ix]._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"));
471  }
472  break;
473  case "sessionstore.max_windows_undo":
474  this._capClosedWindows();
475  break;
476  case "sessionstore.interval":
477  this._interval = this._prefBranch.getIntPref("sessionstore.interval");
478  // reset timer and save
479  if (this._saveTimer) {
480  this._saveTimer.cancel();
481  this._saveTimer = null;
482  }
483  this.saveStateDelayed(null, -1);
484  break;
485  case "sessionstore.resume_from_crash":
486  this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
487  // either create the file with crash recovery information or remove it
488  // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
489  if (this._resume_from_crash)
490  this.saveState(true);
491  else if (this._loadState == STATE_RUNNING)
492  this._clearDisk();
493  break;
494  }
495  break;
496  case "timer-callback": // timer call back for delayed saving
497  this._saveTimer = null;
498  this.saveState();
499  break;
500  case "private-browsing":
501  switch (aData) {
502  case "enter":
503  this._inPrivateBrowsing = true;
504  break;
505  case "exit":
506  aSubject.QueryInterface(Ci.nsISupportsPRBool);
507  let quitting = aSubject.data;
508  if (quitting) {
509  // save the backed up state with session set to stopped,
510  // otherwise resuming next time would look like a crash
511  if ("_stateBackup" in this) {
512  var oState = this._stateBackup;
513  oState.session = { state: STATE_STOPPED_STR };
514 
515  this._saveStateObject(oState);
516  }
517  // make sure to restore the non-private session upon resuming
518  this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
519  }
520  else
521  this._inPrivateBrowsing = false;
522  delete this._stateBackup;
523  break;
524  }
525  break;
526  case "private-browsing-change-granted":
527  if (aData == "enter") {
528  this.saveState(true);
529  this._stateBackup = this._safeEval(this._getCurrentState(true).toSource());
530  }
531  break;
532  }
533  },
534 
535 /* ........ Window Event Handlers .............. */
536 
540  handleEvent: function sss_handleEvent(aEvent) {
541  var win = aEvent.currentTarget.ownerDocument.defaultView;
542  switch (aEvent.type) {
543  case "load":
544  case "pageshow":
545  this.onTabLoad(win, aEvent.currentTarget, aEvent);
546  break;
547  case "change":
548  case "input":
549  case "DOMAutoComplete":
550  this.onTabInput(win, aEvent.currentTarget);
551  break;
552  case "scroll":
553  this.onTabScroll(win);
554  break;
555  case "TabOpen":
556  case "TabClose":
557  let browser = aEvent.originalTarget.linkedBrowser;
558  if (aEvent.type == "TabOpen") {
559  this.onTabAdd(win, browser);
560  }
561  else {
562  // aEvent.detail determines if the tab was closed by moving to a different window
563  if (!aEvent.detail)
564  this.onTabClose(win, aEvent.originalTarget);
565  this.onTabRemove(win, browser);
566  }
567  break;
568  case "TabSelect":
569  this.onTabSelect(win);
570  break;
571  }
572  },
573 
583  onLoad: function sss_onLoad(aWindow) {
584  // return if window has already been initialized
585  if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
586  return;
587 
588  // ignore non-browser windows and windows opened while shutting down
589  if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
590  this._loadState == STATE_QUITTING)
591  return;
592 
593  // assign it a unique identifier (timestamp)
594  aWindow.__SSi = "window" + Date.now();
595 
596  // and create its data object
597  this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [] };
598  if (!aWindow.toolbar.visible)
599  this._windows[aWindow.__SSi].isPopup = true;
600 
601  // perform additional initialization when the first window is loading
602  if (this._loadState == STATE_STOPPED) {
603  this._loadState = STATE_RUNNING;
604  this._lastSaveTime = Date.now();
605 
606  // restore a crashed session resp. resume the last session if requested
607  if (this._initialState) {
608  // make sure that the restored tabs are first in the window
609  this._initialState._firstTabs = true;
610  this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
611  this.restoreWindow(aWindow, this._initialState, this._isCmdLineEmpty(aWindow));
612  delete this._initialState;
613 
614  // _loadState changed from "stopped" to "running"
615  // force a save operation so that crashes happening during startup are correctly counted
616  this.saveState(true);
617  }
618  else {
619  // Nothing to restore, notify observers things are complete.
620  this._observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
621 
622  // the next delayed save request should execute immediately
623  this._lastSaveTime -= this._interval;
624  }
625  }
626  // this window was opened by _openWindowWithState
627  else if (!this._isWindowLoaded(aWindow)) {
628  let followUp = this._statesToRestore[aWindow.__SS_restoreID].windows.length == 1;
629  this.restoreWindow(aWindow, this._statesToRestore[aWindow.__SS_restoreID], true, followUp);
630  }
631 #ifndef XP_MACOSX
632  else if (this._restoreLastWindow && aWindow.toolbar.visible &&
633  this._closedWindows.length && this._doResumeSession() &&
634  !this._inPrivateBrowsing) {
635 
636  // default to the most-recently closed window
637  // don't use popup windows
638  let state = null;
639  this._closedWindows = this._closedWindows.filter(function(aWinState) {
640  if (!state && !aWinState.isPopup) {
641  state = aWinState;
642  return false;
643  }
644  return true;
645  });
646  if (state) {
647  delete state.hidden;
648  state = { windows: [state] };
649  this._restoreCount = 1;
650  this.restoreWindow(aWindow, state, this._isCmdLineEmpty(aWindow));
651  }
652  // we actually restored the session just now.
653  this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
654  }
655  if (this._restoreLastWindow && aWindow.toolbar.visible) {
656  // always reset (if not a popup window)
657  // we don't want to restore a window directly after, for example,
658  // undoCloseWindow was executed.
659  this._restoreLastWindow = false;
660  }
661 #endif
662 
663  var tabbrowser = aWindow.gBrowser;
664 
665  // add tab change listeners to all already existing tabs
666  for (let i = 0; i < tabbrowser.browsers.length; i++) {
667  this.onTabAdd(aWindow, tabbrowser.browsers[i], true);
668  }
669  // notification of tab add/remove/selection
670  tabbrowser.addEventListener("TabOpen", this, true);
671  tabbrowser.addEventListener("TabClose", this, true);
672  tabbrowser.addEventListener("TabSelect", this, true);
673  },
674 
682  onClose: function sss_onClose(aWindow) {
683  // this window was about to be restored - conserve its original data, if any
684  let isFullyLoaded = this._isWindowLoaded(aWindow);
685  if (!isFullyLoaded) {
686  if (!aWindow.__SSi)
687  aWindow.__SSi = "window" + Date.now();
688  this._window[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID];
689  delete this._statesToRestore[aWindow.__SS_restoreID];
690  delete aWindow.__SS_restoreID;
691  }
692 
693  // ignore windows not tracked by SessionStore
694  if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
695  return;
696  }
697 
698  if (this.windowToFocus && this.windowToFocus == aWindow) {
699  delete this.windowToFocus;
700  }
701 
702  var tabbrowser = aWindow.gBrowser;
703 
704  tabbrowser.removeEventListener("TabOpen", this, true);
705  tabbrowser.removeEventListener("TabClose", this, true);
706  tabbrowser.removeEventListener("TabSelect", this, true);
707 
708  let winData = this._windows[aWindow.__SSi];
709  if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down
710  // update all window data for a last time
711  this._collectWindowData(aWindow);
712 
713  if (isFullyLoaded) {
714  winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label;
715  winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
716  tabbrowser.selectedTab);
717  this._updateCookies([winData]);
718  }
719 
720  // save the window if it has multiple tabs or a single tab with entries
721  if (winData.tabs.length > 1 ||
722  (winData.tabs.length == 1 && winData.tabs[0].entries.length > 0)) {
723  this._closedWindows.unshift(winData);
724  this._capClosedWindows();
725  }
726 
727  // clear this window from the list
728  delete this._windows[aWindow.__SSi];
729 
730  // save the state without this window to disk
731  this.saveStateDelayed();
732  }
733 
734  for (let i = 0; i < tabbrowser.browsers.length; i++) {
735  this.onTabRemove(aWindow, tabbrowser.browsers[i], true);
736  }
737 
738  // cache the window state until the window is completely gone
739  aWindow.__SS_dyingCache = winData;
740 
741  delete aWindow.__SSi;
742  },
743 
753  onTabAdd: function sss_onTabAdd(aWindow, aBrowser, aNoNotification) {
754  aBrowser.addEventListener("load", this, true);
755  aBrowser.addEventListener("pageshow", this, true);
756  aBrowser.addEventListener("change", this, true);
757  aBrowser.addEventListener("input", this, true);
758  aBrowser.addEventListener("DOMAutoComplete", this, true);
759  aBrowser.addEventListener("scroll", this, true);
760 
761  if (!aNoNotification) {
762  this.saveStateDelayed(aWindow);
763  }
764 
765  this._updateCrashReportURL(aWindow);
766  },
767 
777  onTabRemove: function sss_onTabRemove(aWindow, aBrowser, aNoNotification) {
778  aBrowser.removeEventListener("load", this, true);
779  aBrowser.removeEventListener("pageshow", this, true);
780  aBrowser.removeEventListener("change", this, true);
781  aBrowser.removeEventListener("input", this, true);
782  aBrowser.removeEventListener("DOMAutoComplete", this, true);
783  aBrowser.removeEventListener("scroll", this, true);
784 
785  delete aBrowser.__SS_data;
786 
787  if (!aNoNotification) {
788  this.saveStateDelayed(aWindow);
789  }
790  },
791 
799  onTabClose: function sss_onTabClose(aWindow, aTab) {
800  // notify the tabbrowser that the tab state will be retrieved for the last time
801  // (so that extension authors can easily set data on soon-to-be-closed tabs)
802  var event = aWindow.document.createEvent("Events");
803  event.initEvent("SSTabClosing", true, false);
804  aTab.dispatchEvent(event);
805 
806  var maxTabsUndo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
807  // don't update our internal state if we don't have to
808  if (maxTabsUndo == 0) {
809  return;
810  }
811 
812  // make sure that the tab related data is up-to-date
813  var tabState = this._collectTabData(aTab);
814  this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
815 
816  // store closed-tab data for undo
817  if (tabState.entries.length > 0) {
818  let tabTitle = aTab.label;
819  let tabbrowser = aWindow.gBrowser;
820  tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab);
821 
822  this._windows[aWindow.__SSi]._closedTabs.unshift({
823  state: tabState,
824  title: tabTitle,
825  image: aTab.getAttribute("image"),
826  pos: aTab._tPos
827  });
828  var length = this._windows[aWindow.__SSi]._closedTabs.length;
829  if (length > maxTabsUndo)
830  this._windows[aWindow.__SSi]._closedTabs.splice(maxTabsUndo, length - maxTabsUndo);
831  }
832  },
833 
843  onTabLoad: function sss_onTabLoad(aWindow, aBrowser, aEvent) {
844  // react on "load" and solitary "pageshow" events (the first "pageshow"
845  // following "load" is too late for deleting the data caches)
846  if (aEvent.type != "load" && !aEvent.persisted) {
847  return;
848  }
849 
850  delete aBrowser.__SS_data;
851  this.saveStateDelayed(aWindow);
852 
853  // attempt to update the current URL we send in a crash report
854  this._updateCrashReportURL(aWindow);
855  },
856 
864  onTabInput: function sss_onTabInput(aWindow, aBrowser) {
865  if (aBrowser.__SS_data)
866  delete aBrowser.__SS_data._formDataSaved;
867 
868  this.saveStateDelayed(aWindow, 3000);
869  },
870 
876  onTabScroll: function sss_onTabScroll(aWindow) {
877  this.saveStateDelayed(aWindow, 3000);
878  },
879 
885  onTabSelect: function sss_onTabSelect(aWindow) {
886  if (this._loadState == STATE_RUNNING) {
887  this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
888  this.saveStateDelayed(aWindow);
889 
890  // attempt to update the current URL we send in a crash report
891  this._updateCrashReportURL(aWindow);
892  }
893  },
894 
895 /* ........ nsISessionStore API .............. */
896 
897  getBrowserState: function sss_getBrowserState() {
898  return this._toJSONString(this._getCurrentState());
899  },
900 
901  setBrowserState: function sss_setBrowserState(aState) {
902  try {
903  var state = this._safeEval("(" + aState + ")");
904  }
905  catch (ex) { /* invalid state object - don't restore anything */ }
906  if (!state || !state.windows)
907  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
908 
909  this._browserSetState = true;
910 
911  var window = this._getMostRecentBrowserWindow();
912  if (!window) {
913  this._restoreCount = 1;
914  this._openWindowWithState(state);
915  return;
916  }
917 
918  // make sure closed window data isn't kept
919  this._closedWindows = [];
920 
921  // determine how many windows are meant to be restored
922  this._restoreCount = state.windows ? state.windows.length : 0;
923 
924  var self = this;
925  // close all other browser windows
926  this._forEachBrowserWindow(function(aWindow) {
927  if (aWindow != window) {
928  self._closingWindows.push(aWindow);
929  aWindow.close();
930  }
931  });
932 
933  // restore to the given state
934  this.restoreWindow(window, state, true);
935  },
936 
937  getWindowState: function sss_getWindowState(aWindow) {
938  if (!aWindow.__SSi && !aWindow.__SS_dyingCache)
939  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
940 
941  if (!aWindow.__SSi)
942  return this._toJSONString({ windows: [aWindow.__SS_dyingCache] });
943  return this._toJSONString(this._getWindowState(aWindow));
944  },
945 
946  setWindowState: function sss_setWindowState(aWindow, aState, aOverwrite) {
947  if (!aWindow.__SSi)
948  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
949 
950  this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite);
951  },
952 
953  getTabState: function sss_getTabState(aTab) {
954  if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
955  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
956 
957  var tabState = this._collectTabData(aTab);
958 
959  var window = aTab.ownerDocument.defaultView;
960  this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
961 
962  return this._toJSONString(tabState);
963  },
964 
965  setTabState: function sss_setTabState(aTab, aState) {
966  var tabState = this._safeEval("(" + aState + ")");
967  if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
968  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
969 
970  var window = aTab.ownerDocument.defaultView;
971  this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
972  },
973 
974  duplicateTab: function sss_duplicateTab(aWindow, aTab) {
975  if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
976  !aWindow.getBrowser)
977  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
978 
979  var tabState = this._collectTabData(aTab, true);
980  var sourceWindow = aTab.ownerDocument.defaultView;
981  this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
982 
983  var newTab = aWindow.getBrowser().addTab();
984  this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0);
985 
986  return newTab;
987  },
988 
989  getClosedTabCount: function sss_getClosedTabCount(aWindow) {
990  if (!aWindow.__SSi && aWindow.__SS_dyingCache)
991  return aWindow.__SS_dyingCache._closedTabs.length;
992  if (!aWindow.__SSi)
993  // XXXzeniko shouldn't we throw here?
994  return 0; // not a browser window, or not otherwise tracked by SS.
995 
996  return this._windows[aWindow.__SSi]._closedTabs.length;
997  },
998 
999  getClosedTabData: function sss_getClosedTabDataAt(aWindow) {
1000  if (!aWindow.__SSi && !aWindow.__SS_dyingCache)
1001  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1002 
1003  if (!aWindow.__SSi)
1004  return this._toJSONString(aWindow.__SS_dyingCache._closedTabs);
1005  return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
1006  },
1007 
1008  undoCloseTab: function sss_undoCloseTab(aWindow, aIndex) {
1009  if (!aWindow.__SSi)
1010  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1011 
1012  var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
1013 
1014  // default to the most-recently closed tab
1015  aIndex = aIndex || 0;
1016  if (!(aIndex in closedTabs))
1017  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1018 
1019  // fetch the data of closed tab, while removing it from the array
1020  let closedTab = closedTabs.splice(aIndex, 1).shift();
1021  let closedTabState = closedTab.state;
1022 
1023  // create a new tab
1024  let browser = aWindow.gBrowser;
1025  let tab = browser.addTab();
1026 
1027  // restore the tab's position
1028  browser.moveTabTo(tab, closedTab.pos);
1029 
1030  // restore tab content
1031  this.restoreHistoryPrecursor(aWindow, [tab], [closedTabState], 1, 0, 0);
1032 
1033  // focus the tab's content area
1034  let content = browser.getBrowserForTab(tab).contentWindow;
1035  aWindow.setTimeout(function() { content.focus(); }, 0);
1036 
1037  return tab;
1038  },
1039 
1040  forgetClosedTab: function sss_forgetClosedTab(aWindow, aIndex) {
1041  if (!aWindow.__SSi)
1042  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1043 
1044  var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
1045 
1046  // default to the most-recently closed tab
1047  aIndex = aIndex || 0;
1048  if (!(aIndex in closedTabs))
1049  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1050 
1051  // remove closed tab from the array
1052  closedTabs.splice(aIndex, 1);
1053  },
1054 
1055  getClosedWindowCount: function sss_getClosedWindowCount() {
1056  return this._closedWindows.length;
1057  },
1058 
1059  getClosedWindowData: function sss_getClosedWindowData() {
1060  return this._toJSONString(this._closedWindows);
1061  },
1062 
1063  undoCloseWindow: function sss_undoCloseWindow(aIndex) {
1064  if (!(aIndex in this._closedWindows))
1065  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1066 
1067  // reopen the window
1068  let state = { windows: this._closedWindows.splice(aIndex, 1) };
1069  let window = this._openWindowWithState(state);
1070  this.windowToFocus = window;
1071  return window;
1072  },
1073 
1074  forgetClosedWindow: function sss_forgetClosedWindow(aIndex) {
1075  // default to the most-recently closed window
1076  aIndex = aIndex || 0;
1077  if (!(aIndex in this._closedWindows))
1078  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1079 
1080  // remove closed window from the array
1081  this._closedWindows.splice(aIndex, 1);
1082  },
1083 
1084  getWindowValue: function sss_getWindowValue(aWindow, aKey) {
1085  if (aWindow.__SSi) {
1086  var data = this._windows[aWindow.__SSi].extData || {};
1087  return data[aKey] || "";
1088  }
1089  if (aWindow.__SS_dyingCache) {
1090  data = aWindow.__SS_dyingCache.extData || {};
1091  return data[aKey] || "";
1092  }
1093  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1094  },
1095 
1096  setWindowValue: function sss_setWindowValue(aWindow, aKey, aStringValue) {
1097  if (aWindow.__SSi) {
1098  if (!this._windows[aWindow.__SSi].extData) {
1099  this._windows[aWindow.__SSi].extData = {};
1100  }
1101  this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
1102  this.saveStateDelayed(aWindow);
1103  }
1104  else {
1105  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1106  }
1107  },
1108 
1109  deleteWindowValue: function sss_deleteWindowValue(aWindow, aKey) {
1110  if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
1111  this._windows[aWindow.__SSi].extData[aKey])
1112  delete this._windows[aWindow.__SSi].extData[aKey];
1113  else
1114  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1115  },
1116 
1117  getTabValue: function sss_getTabValue(aTab, aKey) {
1118  var data = aTab.__SS_extdata || {};
1119  return data[aKey] || "";
1120  },
1121 
1122  setTabValue: function sss_setTabValue(aTab, aKey, aStringValue) {
1123  if (!aTab.__SS_extdata) {
1124  aTab.__SS_extdata = {};
1125  }
1126  aTab.__SS_extdata[aKey] = aStringValue;
1127  this.saveStateDelayed(aTab.ownerDocument.defaultView);
1128  },
1129 
1130  deleteTabValue: function sss_deleteTabValue(aTab, aKey) {
1131  if (aTab.__SS_extdata && aTab.__SS_extdata[aKey])
1132  delete aTab.__SS_extdata[aKey];
1133  else
1134  throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
1135  },
1136 
1137  persistTabAttribute: function sss_persistTabAttribute(aName) {
1138  if (this.xulAttributes.indexOf(aName) != -1)
1139  return; // this attribute is already being tracked
1140 
1141  this.xulAttributes.push(aName);
1142  this.saveStateDelayed();
1143  },
1144 
1145 /* ........ Saving Functionality .............. */
1146 
1152  _saveWindowHistory: function sss_saveWindowHistory(aWindow) {
1153  var tabbrowser = aWindow.getBrowser();
1154  var tabs = tabbrowser.mTabs;
1155  var tabsData = this._windows[aWindow.__SSi].tabs = [];
1156 
1157  for (var i = 0; i < tabs.length; i++)
1158  tabsData.push(this._collectTabData(tabs[i]));
1159 
1160  this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1;
1161  },
1162 
1171  _collectTabData: function sss_collectTabData(aTab, aFullData) {
1172  var tabData = { entries: [] };
1173  var browser = aTab.linkedBrowser;
1174 
1175  if (!browser || !browser.currentURI)
1176  // can happen when calling this function right after .addTab()
1177  return tabData;
1178  else if (browser.__SS_data && browser.__SS_data._tabStillLoading)
1179  // use the data to be restored when the tab hasn't been completely loaded
1180  return browser.__SS_data;
1181 
1182  var history = null;
1183  try {
1184  history = browser.sessionHistory;
1185  }
1186  catch (ex) { } // this could happen if we catch a tab during (de)initialization
1187 
1188  // XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
1189  // data even when we shouldn't (e.g. Back, different anchor)
1190  if (history && browser.__SS_data &&
1191  browser.__SS_data.entries[history.index] &&
1192  history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
1193  tabData = browser.__SS_data;
1194  tabData.index = history.index + 1;
1195  }
1196  else if (history && history.count > 0) {
1197  for (var j = 0; j < history.count; j++)
1198  tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
1199  aFullData));
1200  tabData.index = history.index + 1;
1201 
1202  // make sure not to cache privacy sensitive data which shouldn't get out
1203  if (!aFullData)
1204  browser.__SS_data = tabData;
1205  }
1206  else if (browser.currentURI.spec != "about:blank" ||
1207  browser.contentDocument.body.hasChildNodes()) {
1208  tabData.entries[0] = { url: browser.currentURI.spec };
1209  tabData.index = 1;
1210  }
1211 
1212  // If there is a userTypedValue set, then either the user has typed something
1213  // in the URL bar, or a new tab was opened with a URI to load. userTypedClear
1214  // is used to indicate whether the tab was in some sort of loading state with
1215  // userTypedValue.
1216  if (browser.userTypedValue) {
1217  tabData.userTypedValue = browser.userTypedValue;
1218  tabData.userTypedClear = browser.userTypedClear;
1219  }
1220 
1221  var disallow = [];
1222  for (var i = 0; i < CAPABILITIES.length; i++)
1223  if (!browser.docShell["allow" + CAPABILITIES[i]])
1224  disallow.push(CAPABILITIES[i]);
1225  if (disallow.length > 0)
1226  tabData.disallow = disallow.join(",");
1227  else if (tabData.disallow)
1228  delete tabData.disallow;
1229 
1230  if (this.xulAttributes.length > 0) {
1231  tabData.attributes = {};
1232  Array.forEach(aTab.attributes, function(aAttr) {
1233  if (this.xulAttributes.indexOf(aAttr.name) > -1)
1234  tabData.attributes[aAttr.name] = aAttr.value;
1235  }, this);
1236  }
1237 
1238  if (aTab.__SS_extdata)
1239  tabData.extData = aTab.__SS_extdata;
1240  else if (tabData.extData)
1241  delete tabData.extData;
1242 
1243  if (history && browser.docShell instanceof Ci.nsIDocShell)
1244  this._serializeSessionStorage(tabData, history, browser.docShell, aFullData);
1245 
1246  return tabData;
1247  },
1248 
1258  _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) {
1259  var entry = { url: aEntry.URI.spec };
1260 
1261  if (aEntry.title && aEntry.title != entry.url) {
1262  entry.title = aEntry.title;
1263  }
1264  if (aEntry.isSubFrame) {
1265  entry.subframe = true;
1266  }
1267  if (!(aEntry instanceof Ci.nsISHEntry)) {
1268  return entry;
1269  }
1270 
1271  var cacheKey = aEntry.cacheKey;
1272  if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
1273  cacheKey.data != 0) {
1274  // XXXbz would be better to have cache keys implement
1275  // nsISerializable or something.
1276  entry.cacheKey = cacheKey.data;
1277  }
1278  entry.ID = aEntry.ID;
1279 
1280  if (aEntry.referrerURI)
1281  entry.referrer = aEntry.referrerURI.spec;
1282 
1283  if (aEntry.contentType)
1284  entry.contentType = aEntry.contentType;
1285 
1286  var x = {}, y = {};
1287  aEntry.getScrollPosition(x, y);
1288  if (x.value != 0 || y.value != 0)
1289  entry.scroll = x.value + "," + y.value;
1290 
1291  try {
1292  var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
1293  if (aEntry.postData && (aFullData ||
1294  prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) {
1295  aEntry.postData.QueryInterface(Ci.nsISeekableStream).
1296  seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
1297  var stream = Cc["@mozilla.org/binaryinputstream;1"].
1298  createInstance(Ci.nsIBinaryInputStream);
1299  stream.setInputStream(aEntry.postData);
1300  var postBytes = stream.readByteArray(stream.available());
1301  var postdata = String.fromCharCode.apply(null, postBytes);
1302  if (aFullData || prefPostdata == -1 ||
1303  postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
1304  prefPostdata) {
1305  // We can stop doing base64 encoding once our serialization into JSON
1306  // is guaranteed to handle all chars in strings, including embedded
1307  // nulls.
1308  entry.postdata_b64 = btoa(postdata);
1309  }
1310  }
1311  }
1312  catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
1313 
1314  if (aEntry.owner) {
1315  // Not catching anything specific here, just possible errors
1316  // from writeCompoundObject and the like.
1317  try {
1318  var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
1319  createInstance(Ci.nsIObjectOutputStream);
1320  var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
1321  pipe.init(false, false, 0, 0xffffffff, null);
1322  binaryStream.setOutputStream(pipe.outputStream);
1323  binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
1324  binaryStream.close();
1325 
1326  // Now we want to read the data from the pipe's input end and encode it.
1327  var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
1328  createInstance(Ci.nsIBinaryInputStream);
1329  scriptableStream.setInputStream(pipe.inputStream);
1330  var ownerBytes =
1331  scriptableStream.readByteArray(scriptableStream.available());
1332  // We can stop doing base64 encoding once our serialization into JSON
1333  // is guaranteed to handle all chars in strings, including embedded
1334  // nulls.
1335  entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
1336  }
1337  catch (ex) { debug(ex); }
1338  }
1339 
1340  if (!(aEntry instanceof Ci.nsISHContainer)) {
1341  return entry;
1342  }
1343 
1344  if (aEntry.childCount > 0) {
1345  entry.children = [];
1346  for (var i = 0; i < aEntry.childCount; i++) {
1347  var child = aEntry.GetChildAt(i);
1348  if (child) {
1349  entry.children.push(this._serializeHistoryEntry(child, aFullData));
1350  }
1351  else { // to maintain the correct frame order, insert a dummy entry
1352  entry.children.push({ url: "about:blank" });
1353  }
1354  // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
1355  if (/^wyciwyg:\/\//.test(entry.children[i].url)) {
1356  delete entry.children;
1357  break;
1358  }
1359  }
1360  }
1361 
1362  return entry;
1363  },
1364 
1377  function sss_serializeSessionStorage(aTabData, aHistory, aDocShell, aFullData) {
1378  let storageData = {};
1379  let hasContent = false;
1380 
1381  for (let i = 0; i < aHistory.count; i++) {
1382  let uri = aHistory.getEntryAtIndex(i, false).URI;
1383  // sessionStorage is saved per origin (cf. nsDocShell::GetSessionStorageForURI)
1384  let domain = uri.spec;
1385  try {
1386  if (uri.host)
1387  domain = uri.prePath;
1388  }
1389  catch (ex) { /* this throws for host-less URIs (such as about: or jar:) */ }
1390  if (storageData[domain] || !(aFullData || this._checkPrivacyLevel(uri.schemeIs("https"))))
1391  continue;
1392 
1393  let storage, storageItemCount = 0;
1394  try {
1395  var principal = Cc["@mozilla.org/scriptsecuritymanager;1"].
1396  getService(Ci.nsIScriptSecurityManager).
1397  getCodebasePrincipal(uri);
1398 
1399  // Using getSessionStorageForPrincipal instead of getSessionStorageForURI
1400  // just to be able to pass aCreate = false, that avoids creation of the
1401  // sessionStorage object for the page earlier than the page really
1402  // requires it. It was causing problems while accessing a storage when
1403  // a page later changed its domain.
1404  storage = aDocShell.getSessionStorageForPrincipal(principal, false);
1405  if (storage)
1406  storageItemCount = storage.length;
1407  }
1408  catch (ex) { /* sessionStorage might throw if it's turned off, see bug 458954 */ }
1409  if (storageItemCount == 0)
1410  continue;
1411 
1412  let data = storageData[domain] = {};
1413  for (let j = 0; j < storageItemCount; j++) {
1414  try {
1415  let key = storage.key(j);
1416  let item = storage.getItem(key);
1417  data[key] = item;
1418  }
1419  catch (ex) { /* XXXzeniko this currently throws for secured items (cf. bug 442048) */ }
1420  }
1421  hasContent = true;
1422  }
1423 
1424  if (hasContent)
1425  aTabData.storage = storageData;
1426  },
1427 
1434  _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) {
1435  var browsers = aWindow.getBrowser().browsers;
1436  for (var i = 0; i < browsers.length; i++) {
1437  try {
1438  var tabData = this._windows[aWindow.__SSi].tabs[i];
1439  if (browsers[i].__SS_data &&
1440  browsers[i].__SS_data._tabStillLoading)
1441  continue; // ignore incompletely initialized tabs
1442  this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
1443  }
1444  catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
1445  }
1446  },
1447 
1460  _updateTextAndScrollDataForTab:
1461  function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
1462  var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
1463  // entry data needn't exist for tabs just initialized with an incomplete session state
1464  if (!aTabData.entries[tabIndex])
1465  return;
1466 
1467  let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
1468  this._getSelectedPageStyle(aBrowser.contentWindow);
1469  if (selectedPageStyle)
1470  aTabData.pageStyle = selectedPageStyle;
1471  else if (aTabData.pageStyle)
1472  delete aTabData.pageStyle;
1473 
1474  this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
1475  aTabData.entries[tabIndex],
1476  !aTabData._formDataSaved, aFullData);
1477  aTabData._formDataSaved = true;
1478  if (aBrowser.currentURI.spec == "about:config")
1479  aTabData.entries[tabIndex].formdata = {
1480  "#textbox": aBrowser.contentDocument.getElementById("textbox").wrappedJSObject.value
1481  };
1482  },
1483 
1499  function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
1501  for (var i = 0; i < aContent.frames.length; i++) {
1502  if (aData.children && aData.children[i])
1503  this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
1504  aData.children[i], aUpdateFormData, aFullData);
1505  }
1506  var isHTTPS = this._getURIFromString((aContent.parent || aContent).
1507  document.location.href).schemeIs("https");
1508  if (aFullData || this._checkPrivacyLevel(isHTTPS) ||
1509  aContent.top.document.location.href == "about:sessionrestore") {
1510  if (aFullData || aUpdateFormData) {
1511  let formData = this._collectFormDataForFrame(aContent.document);
1512  if (formData)
1513  aData.formdata = formData;
1514  else if (aData.formdata)
1515  delete aData.formdata;
1516  }
1517 
1518  // designMode is undefined e.g. for XUL documents (as about:config)
1519  if ((aContent.document.designMode || "") == "on") {
1520  if (aData.innerHTML === undefined && !aFullData) {
1521  // we get no "input" events from iframes - listen for keypress here
1522  let _this = this;
1523  aContent.addEventListener("keypress", function(aEvent) {
1524  _this.saveStateDelayed(aWindow, 3000);
1525  }, true);
1526  }
1527  aData.innerHTML = aContent.document.body.innerHTML;
1528  }
1529  }
1530 
1531  // get scroll position from nsIDOMWindowUtils, since it allows avoiding a
1532  // flush of layout
1533  let domWindowUtils = aContent.QueryInterface(Ci.nsIInterfaceRequestor)
1534  .getInterface(Ci.nsIDOMWindowUtils);
1535  let scrollX = {}, scrollY = {};
1536  domWindowUtils.getScrollXY(false, scrollX, scrollY);
1537  aData.scroll = scrollX.value + "," + scrollY.value;
1538  },
1539 
1546  _getSelectedPageStyle: function sss_getSelectedPageStyle(aContent) {
1547  const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i;
1548  for (let i = 0; i < aContent.document.styleSheets.length; i++) {
1549  let ss = aContent.document.styleSheets[i];
1550  let media = ss.media.mediaText;
1551  if (!ss.disabled && ss.title && (!media || forScreen.test(media)))
1552  return ss.title
1553  }
1554  for (let i = 0; i < aContent.frames.length; i++) {
1555  let selectedPageStyle = this._getSelectedPageStyle(aContent.frames[i]);
1556  if (selectedPageStyle)
1557  return selectedPageStyle;
1558  }
1559  return "";
1560  },
1561 
1567  _collectFormDataForFrame: function sss_collectFormDataForFrame(aDocument) {
1568  let formNodes = aDocument.evaluate(XPathHelper.restorableFormNodes, aDocument,
1569  XPathHelper.resolveNS,
1570  Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
1571  let node = formNodes.iterateNext();
1572  if (!node)
1573  return null;
1574 
1577 
1578  let data = {};
1579  do {
1580  // Only generate a limited number of XPath expressions for perf reasons (cf. bug 477564)
1581  if (!node.id && ++generatedCount > MAX_GENERATED_XPATHS)
1582  continue;
1583 
1584  let id = node.id ? "#" + node.id : XPathHelper.generate(node);
1585  if (node instanceof Ci.nsIDOMHTMLInputElement) {
1586  if (node.type != "file")
1587  data[id] = node.type == "checkbox" || node.type == "radio" ? node.checked : node.value;
1588  else
1589  data[id] = { type: "file", fileList: node.mozGetFileNameArray({}) };
1590  }
1591  else if (node instanceof Ci.nsIDOMHTMLTextAreaElement)
1592  data[id] = node.value;
1593  else if (!node.multiple)
1594  data[id] = node.selectedIndex;
1595  else {
1596  let options = Array.map(node.options, function(aOpt, aIx) aOpt.selected ? aIx : -1);
1597  data[id] = options.filter(function(aIx) aIx >= 0);
1598  }
1599  } while ((node = formNodes.iterateNext()));
1600 
1601  return data;
1602  },
1603 
1609  _updateCookieHosts: function sss_updateCookieHosts(aWindow) {
1610  var hosts = this._windows[aWindow.__SSi]._hosts = {};
1611 
1612  // get the domain for each URL
1613  function extractHosts(aEntry) {
1614  if (/^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.test(aEntry.url)) {
1615  if (!hosts[RegExp.$1] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) {
1616  hosts[RegExp.$1] = true;
1617  }
1618  }
1619  else if (/^file:\/\/([^\/]*)/.test(aEntry.url)) {
1620  hosts[RegExp.$1] = true;
1621  }
1622  if (aEntry.children) {
1623  aEntry.children.forEach(extractHosts);
1624  }
1625  }
1626 
1627  var _this = this;
1628  this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); });
1629  },
1630 
1636  _updateCookies: function sss_updateCookies(aWindows) {
1637  function addCookieToHash(aHash, aHost, aPath, aName, aCookie) {
1638  // lazily build up a 3-dimensional hash, with
1639  // aHost, aPath, and aName as keys
1640  if (!aHash[aHost])
1641  aHash[aHost] = {};
1642  if (!aHash[aHost][aPath])
1643  aHash[aHost][aPath] = {};
1645  }
1646 
1647  var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
1648  // collect the cookies per window
1649  for (var i = 0; i < aWindows.length; i++)
1650  aWindows[i].cookies = [];
1651 
1652  var jscookies = {};
1653  var _this = this;
1654  // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
1655  var MAX_EXPIRY = Math.pow(2, 62);
1656  aWindows.forEach(function(aWindow) {
1657  for (var host in aWindow._hosts) {
1658  var list = cm.getCookiesFromHost(host);
1659  while (list.hasMoreElements()) {
1660  var cookie = list.getNext().QueryInterface(Ci.nsICookie2);
1661  if (cookie.isSession && _this._checkPrivacyLevel(cookie.isSecure)) {
1662  // use the cookie's host, path, and name as keys into a hash,
1663  // to make sure we serialize each cookie only once
1664  if (!(cookie.host in jscookies &&
1665  cookie.path in jscookies[cookie.host] &&
1666  cookie.name in jscookies[cookie.host][cookie.path])) {
1667  var jscookie = { "host": cookie.host, "value": cookie.value };
1668  // only add attributes with non-default values (saving a few bits)
1669  if (cookie.path) jscookie.path = cookie.path;
1670  if (cookie.name) jscookie.name = cookie.name;
1671  if (cookie.isSecure) jscookie.secure = true;
1672  if (cookie.isHttpOnly) jscookie.httponly = true;
1673  if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry;
1674 
1675  addCookieToHash(jscookies, cookie.host, cookie.path, cookie.name, jscookie);
1676  }
1677  aWindow.cookies.push(jscookies[cookie.host][cookie.path][cookie.name]);
1678  }
1679  }
1680  }
1681  });
1682 
1683  // don't include empty cookie sections
1684  for (i = 0; i < aWindows.length; i++)
1685  if (aWindows[i].cookies.length == 0)
1686  delete aWindows[i].cookies;
1687  },
1688 
1694  _updateWindowFeatures: function sss_updateWindowFeatures(aWindow) {
1695  var winData = this._windows[aWindow.__SSi];
1696 
1697  WINDOW_ATTRIBUTES.forEach(function(aAttr) {
1698  winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
1699  }, this);
1700 
1701  var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
1702  return aWindow[aItem] && !aWindow[aItem].visible;
1703  });
1704  if (hidden.length != 0)
1705  winData.hidden = hidden.join(",");
1706  else if (winData.hidden)
1707  delete winData.hidden;
1708 
1709  var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
1710  if (sidebar)
1711  winData.sidebar = sidebar;
1712  else if (winData.sidebar)
1713  delete winData.sidebar;
1714  },
1715 
1722  _getCurrentState: function sss_getCurrentState(aUpdateAll) {
1723  var activeWindow = this._getMostRecentBrowserWindow();
1724 
1725  if (this._loadState == STATE_RUNNING) {
1726  // update the data for all windows with activities since the last save operation
1727  this._forEachBrowserWindow(function(aWindow) {
1728  if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
1729  return;
1730  if (aUpdateAll || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) {
1731  this._collectWindowData(aWindow);
1732  }
1733  else { // always update the window features (whose change alone never triggers a save operation)
1734  this._updateWindowFeatures(aWindow);
1735  }
1736  }, this);
1737  this._dirtyWindows = [];
1738  }
1739 
1740  // collect the data for all windows
1741  var total = [], windows = [];
1742  var nonPopupCount = 0;
1743  var ix;
1744  for (ix in this._windows) {
1745  total.push(this._windows[ix]);
1746  windows.push(ix);
1747  if (!this._windows[ix].isPopup)
1748  nonPopupCount++;
1749  }
1750  this._updateCookies(total);
1751 
1752  // collect the data for all windows yet to be restored
1753  for (ix in this._statesToRestore) {
1754  for each (let winData in this._statesToRestore[ix].windows) {
1755  total.push(winData);
1756  if (!winData.isPopup)
1757  nonPopupCount++;
1758  }
1759  }
1760 
1761  // shallow copy this._closedWindows to preserve current state
1762  let lastClosedWindowsCopy = this._closedWindows.slice();
1763 
1764 #ifndef XP_MACOSX
1765  // if no non-popup browser window remains open, return the state of the last closed window(s)
1766  if (nonPopupCount == 0 && lastClosedWindowsCopy.length > 0) {
1767  // prepend the last non-popup browser window, so that if the user loads more tabs
1768  // at startup we don't accidentally add them to a popup window
1769  do {
1770  total.unshift(lastClosedWindowsCopy.shift())
1771  } while (total[0].isPopup)
1772  }
1773 #endif
1774 
1775  if (activeWindow) {
1776  this.activeWindowSSiCache = activeWindow.__SSi || "";
1777  }
1778  ix = this.activeWindowSSiCache ? windows.indexOf(this.activeWindowSSiCache) : -1;
1779 
1780  return { windows: total, selectedWindow: ix + 1, _closedWindows: lastClosedWindowsCopy };
1781  },
1782 
1789  _getWindowState: function sss_getWindowState(aWindow) {
1790  if (!this._isWindowLoaded(aWindow))
1791  return this._statesToRestore[aWindow.__SS_restoreID];
1792 
1793  if (this._loadState == STATE_RUNNING) {
1794  this._collectWindowData(aWindow);
1795  }
1796 
1797  var total = [this._windows[aWindow.__SSi]];
1798  this._updateCookies(total);
1799 
1800  return { windows: total };
1801  },
1802 
1803  _collectWindowData: function sss_collectWindowData(aWindow) {
1804  if (!this._isWindowLoaded(aWindow))
1805  return;
1806 
1807  // update the internal state data for this window
1808  this._saveWindowHistory(aWindow);
1809  this._updateTextAndScrollData(aWindow);
1810  this._updateCookieHosts(aWindow);
1811  this._updateWindowFeatures(aWindow);
1812 
1813  this._dirtyWindows[aWindow.__SSi] = false;
1814  },
1815 
1816 /* ........ Restoring Functionality .............. */
1817 
1829  restoreWindow: function sss_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) {
1830  if (!aFollowUp) {
1831  this.windowToFocus = aWindow;
1832  }
1833  // initialize window if necessary
1834  if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
1835  this.onLoad(aWindow);
1836 
1837  try {
1838  var root = typeof aState == "string" ? this._safeEval(aState) : aState;
1839  if (!root.windows[0]) {
1841  return; // nothing to restore
1842  }
1843  }
1844  catch (ex) { // invalid state object - don't restore anything
1845  debug(ex);
1847  return;
1848  }
1849 
1850  if (root._closedWindows)
1851  this._closedWindows = root._closedWindows;
1852 
1853  var winData;
1854  if (!aState.selectedWindow) {
1855  aState.selectedWindow = 0;
1856  }
1857  // open new windows for all further window entries of a multi-window session
1858  // (unless they don't contain any tab data)
1859  for (var w = 1; w < root.windows.length; w++) {
1860  winData = root.windows[w];
1861  if (winData && winData.tabs && winData.tabs[0]) {
1862  var window = this._openWindowWithState({ windows: [winData] });
1863  if (w == aState.selectedWindow - 1) {
1864  this.windowToFocus = window;
1865  }
1866  }
1867  }
1868  winData = root.windows[0];
1869  if (!winData.tabs) {
1870  winData.tabs = [];
1871  }
1872  // don't restore a single blank tab when we've had an external
1873  // URL passed in for loading at startup (cf. bug 357419)
1874  else if (root._firstTabs && !aOverwriteTabs && winData.tabs.length == 1 &&
1875  (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
1876  winData.tabs = [];
1877  }
1878 
1879  var tabbrowser = aWindow.gBrowser;
1880  var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
1881  var newTabCount = winData.tabs.length;
1882  var tabs = [];
1883 
1884  // disable smooth scrolling while adding, moving, removing and selecting tabs
1885  var tabstrip = tabbrowser.tabContainer.mTabstrip;
1886  var smoothScroll = tabstrip.smoothScroll;
1887  tabstrip.smoothScroll = false;
1888 
1889  // make sure that the selected tab won't be closed in order to
1890  // prevent unnecessary flickering
1891  if (aOverwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
1892  tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
1893 
1894  for (var t = 0; t < newTabCount; t++) {
1895  tabs.push(t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab());
1896  // when resuming at startup: add additionally requested pages to the end
1897  if (!aOverwriteTabs && root._firstTabs) {
1898  tabbrowser.moveTabTo(tabs[t], t);
1899  }
1900  }
1901 
1902  // when overwriting tabs, remove all superflous ones
1903  if (aOverwriteTabs && newTabCount < openTabCount) {
1904  Array.slice(tabbrowser.mTabs, newTabCount, openTabCount)
1905  .forEach(tabbrowser.removeTab, tabbrowser);
1906  }
1907 
1908  if (aOverwriteTabs) {
1909  this.restoreWindowFeatures(aWindow, winData);
1910  delete this._windows[aWindow.__SSi].extData;
1911  }
1912  if (winData.cookies) {
1913  this.restoreCookies(winData.cookies);
1914  }
1915  if (winData.extData) {
1916  if (!this._windows[aWindow.__SSi].extData) {
1917  this._windows[aWindow.__SSi].extData = {};
1918  }
1919  for (var key in winData.extData) {
1920  this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
1921  }
1922  }
1923  if (aOverwriteTabs || root._firstTabs) {
1924  this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || [];
1925  }
1926 
1927  this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs,
1928  (aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0);
1929 
1930  // set smoothScroll back to the original value
1931  tabstrip.smoothScroll = smoothScroll;
1932 
1934  },
1935 
1952  function sss_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount) {
1953  var tabbrowser = aWindow.getBrowser();
1954 
1955  // make sure that all browsers and their histories are available
1956  // - if one's not, resume this check in 100ms (repeat at most 10 times)
1957  for (var t = aIx; t < aTabs.length; t++) {
1958  try {
1959  if (!tabbrowser.getBrowserForTab(aTabs[t]).webNavigation.sessionHistory) {
1960  throw new Error();
1961  }
1962  }
1963  catch (ex) { // in case browser or history aren't ready yet
1964  if (aCount < 10) {
1965  var restoreHistoryFunc = function(self) {
1966  self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount + 1);
1967  }
1968  aWindow.setTimeout(restoreHistoryFunc, 100, this);
1969  return;
1970  }
1971  }
1972  }
1973 
1974  // mark the tabs as loading
1975  for (t = 0; t < aTabs.length; t++) {
1976  var tab = aTabs[t];
1977  var browser = tabbrowser.getBrowserForTab(tab);
1978 
1979  aTabData[t]._tabStillLoading = true;
1980  if (!aTabData[t].entries || aTabData[t].entries.length == 0) {
1981  // make sure to blank out this tab's content
1982  // (just purging the tab's history won't be enough)
1983  browser.contentDocument.location = "about:blank";
1984  continue;
1985  }
1986 
1987  browser.stop(); // in case about:blank isn't done yet
1988 
1989  tab.setAttribute("busy", "true");
1990  tabbrowser.updateIcon(tab);
1991  tabbrowser.setTabTitleLoading(tab);
1992 
1993  // wall-paper fix for bug 439675: make sure that the URL to be loaded
1994  // is always visible in the address bar
1995  let activeIndex = (aTabData[t].index || aTabData[t].entries.length) - 1;
1996  let activePageData = aTabData[t].entries[activeIndex] || null;
1997  browser.userTypedValue = activePageData ? activePageData.url || null : null;
1998 
1999  // keep the data around to prevent dataloss in case
2000  // a tab gets closed before it's been properly restored
2001  browser.__SS_data = aTabData[t];
2002  }
2003 
2004  if (aTabs.length > 0) {
2005  // Determine if we can optimize & load visible tabs first
2006  let maxVisibleTabs = Math.ceil(tabbrowser.tabContainer.mTabstrip.scrollClientSize /
2007  aTabs[0].clientWidth);
2008 
2009  // make sure we restore visible tabs first, if there are enough
2010  if (maxVisibleTabs < aTabs.length && aSelectTab > 1) {
2011  let firstVisibleTab = 0;
2012  if (aTabs.length - maxVisibleTabs > aSelectTab) {
2013  // aSelectTab is leftmost since we scroll to it when possible
2014  firstVisibleTab = aSelectTab - 1;
2015  } else {
2016  // aSelectTab is rightmost or no more room to scroll right
2017  firstVisibleTab = aTabs.length - maxVisibleTabs;
2018  }
2019  aTabs = aTabs.splice(firstVisibleTab, maxVisibleTabs).concat(aTabs);
2020  aTabData = aTabData.splice(firstVisibleTab, maxVisibleTabs).concat(aTabData);
2021  aSelectTab -= firstVisibleTab;
2022  }
2023 
2024  // make sure to restore the selected tab first (if any)
2025  if (aSelectTab-- && aTabs[aSelectTab]) {
2026  aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]);
2027  aTabData.unshift(aTabData.splice(aSelectTab, 1)[0]);
2028  tabbrowser.selectedTab = aTabs[0];
2029  }
2030  }
2031 
2032  if (!this._isWindowLoaded(aWindow)) {
2033  // from now on, the data will come from the actual window
2034  delete this._statesToRestore[aWindow.__SS_restoreID];
2035  delete aWindow.__SS_restoreID;
2036  }
2037 
2038  // helper hash for ensuring unique frame IDs
2039  var idMap = { used: {} };
2040  this.restoreHistory(aWindow, aTabs, aTabData, idMap);
2041  },
2042 
2054  restoreHistory: function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap) {
2055  var _this = this;
2056  while (aTabs.length > 0 && (!aTabData[0]._tabStillLoading || !aTabs[0].parentNode)) {
2057  aTabs.shift(); // this tab got removed before being completely restored
2058  aTabData.shift();
2059  }
2060  if (aTabs.length == 0) {
2061  return; // no more tabs to restore
2062  }
2063 
2064  var tab = aTabs.shift();
2065  var tabData = aTabData.shift();
2066 
2067  var browser = aWindow.getBrowser().getBrowserForTab(tab);
2068  var history = browser.webNavigation.sessionHistory;
2069 
2070  if (history.count > 0) {
2071  history.PurgeHistory(history.count);
2072  }
2073  history.QueryInterface(Ci.nsISHistoryInternal);
2074 
2075  if (!tabData.entries) {
2076  tabData.entries = [];
2077  }
2078  if (tabData.extData) {
2079  tab.__SS_extdata = {};
2080  for (let key in tabData.extData)
2081  tab.__SS_extdata[key] = tabData.extData[key];
2082  }
2083  else
2084  delete tab.__SS_extdata;
2085 
2086  for (var i = 0; i < tabData.entries.length; i++) {
2087  //XXXzpao Wallpaper patch for bug 514751
2088  if (!tabData.entries[i].url)
2089  continue;
2090  history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true);
2091  }
2092 
2093  // make sure to reset the capabilities and attributes, in case this tab gets reused
2094  var disallow = (tabData.disallow)?tabData.disallow.split(","):[];
2095  CAPABILITIES.forEach(function(aCapability) {
2096  browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1;
2097  });
2098  Array.filter(tab.attributes, function(aAttr) {
2099  return (_this.xulAttributes.indexOf(aAttr.name) > -1);
2100  }).forEach(tab.removeAttribute, tab);
2101  if (tabData.xultab) {
2102  // restore attributes from the legacy Firefox 2.0/3.0 format
2103  tabData.xultab.split(" ").forEach(function(aAttr) {
2104  if (/^([^\s=]+)=(.*)/.test(aAttr)) {
2105  tab.setAttribute(RegExp.$1, decodeURI(RegExp.$2));
2106  }
2107  });
2108  }
2109  for (let name in tabData.attributes)
2110  tab.setAttribute(name, tabData.attributes[name]);
2111 
2112  if (tabData.storage && browser.docShell instanceof Ci.nsIDocShell)
2113  this._deserializeSessionStorage(tabData.storage, browser.docShell);
2114 
2115  // notify the tabbrowser that the tab chrome has been restored
2116  var event = aWindow.document.createEvent("Events");
2117  event.initEvent("SSTabRestoring", true, false);
2118  tab.dispatchEvent(event);
2119 
2120  let activeIndex = (tabData.index || tabData.entries.length) - 1;
2121  if (activeIndex >= tabData.entries.length)
2122  activeIndex = tabData.entries.length - 1;
2123  try {
2124  if (activeIndex >= 0)
2125  browser.webNavigation.gotoIndex(activeIndex);
2126  }
2127  catch (ex) {
2128  // ignore page load errors
2129  tab.removeAttribute("busy");
2130  }
2131 
2132  if (tabData.entries.length > 0) {
2133  // restore those aspects of the currently active documents
2134  // which are not preserved in the plain history entries
2135  // (mainly scroll state and text data)
2136  browser.__SS_restore_data = tabData.entries[activeIndex] || {};
2137  browser.__SS_restore_text = tabData.text || "";
2138  browser.__SS_restore_pageStyle = tabData.pageStyle || "";
2139  browser.__SS_restore_tab = tab;
2140  browser.__SS_restore = this.restoreDocument_proxy;
2141  browser.addEventListener("load", browser.__SS_restore, true);
2142  }
2143 
2144  // Handle userTypedValue. Setting userTypedValue seems to update gURLbar
2145  // as needed. Calling loadURI will cancel form filling in restoreDocument_proxy
2146  if (tabData.userTypedValue) {
2147  browser.userTypedValue = tabData.userTypedValue;
2148  if (tabData.userTypedClear)
2149  browser.loadURI(tabData.userTypedValue, null, null, true);
2150  }
2151 
2152  aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap); }, 0);
2153  },
2154 
2163  _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) {
2164  var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
2165  createInstance(Ci.nsISHEntry);
2166 
2167  var ioService = Cc["@mozilla.org/network/io-service;1"].
2168  getService(Ci.nsIIOService);
2169  shEntry.setURI(ioService.newURI(aEntry.url, null, null));
2170  shEntry.setTitle(aEntry.title || aEntry.url);
2171  if (aEntry.subframe)
2172  shEntry.setIsSubFrame(aEntry.subframe || false);
2173  shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
2174  if (aEntry.contentType)
2175  shEntry.contentType = aEntry.contentType;
2176  if (aEntry.referrer)
2177  shEntry.referrerURI = ioService.newURI(aEntry.referrer, null, null);
2178 
2179  if (aEntry.cacheKey) {
2180  var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
2181  createInstance(Ci.nsISupportsPRUint32);
2182  cacheKey.data = aEntry.cacheKey;
2183  shEntry.cacheKey = cacheKey;
2184  }
2185 
2186  if (aEntry.ID) {
2187  // get a new unique ID for this frame (since the one from the last
2188  // start might already be in use)
2189  var id = aIdMap[aEntry.ID] || 0;
2190  if (!id) {
2191  for (id = Date.now(); id in aIdMap.used; id++);
2192  aIdMap[aEntry.ID] = id;
2193  aIdMap.used[id] = true;
2194  }
2195  shEntry.ID = id;
2196  }
2197 
2198  if (aEntry.scroll) {
2199  var scrollPos = (aEntry.scroll || "0,0").split(",");
2200  scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
2201  shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
2202  }
2203 
2205  if (aEntry.postdata_b64) { // Firefox 3
2206  postdata = atob(aEntry.postdata_b64);
2207  } else if (aEntry.postdata) { // Firefox 2
2208  postdata = aEntry.postdata;
2209  }
2210 
2211  if (postdata) {
2212  var stream = Cc["@mozilla.org/io/string-input-stream;1"].
2213  createInstance(Ci.nsIStringInputStream);
2214  stream.setData(postdata, postdata.length);
2215  shEntry.postData = stream;
2216  }
2217 
2218  if (aEntry.owner_b64) { // Firefox 3
2219  var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
2220  createInstance(Ci.nsIStringInputStream);
2221  var binaryData = atob(aEntry.owner_b64);
2222  ownerInput.setData(binaryData, binaryData.length);
2223  var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
2224  createInstance(Ci.nsIObjectInputStream);
2225  binaryStream.setInputStream(ownerInput);
2226  try { // Catch possible deserialization exceptions
2227  shEntry.owner = binaryStream.readObject(true);
2228  } catch (ex) { debug(ex); }
2229  } else if (aEntry.ownerURI) { // Firefox 2
2230  var uriObj = ioService.newURI(aEntry.ownerURI, null, null);
2231  shEntry.owner = Cc["@mozilla.org/scriptsecuritymanager;1"].
2232  getService(Ci.nsIScriptSecurityManager).
2233  getCodebasePrincipal(uriObj);
2234  }
2235 
2236  if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
2237  for (var i = 0; i < aEntry.children.length; i++) {
2238  //XXXzpao Wallpaper patch for bug 514751
2239  if (!aEntry.children[i].url)
2240  continue;
2241  shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i);
2242  }
2243  }
2244 
2245  return shEntry;
2246  },
2247 
2255  _deserializeSessionStorage: function sss_deserializeSessionStorage(aStorageData, aDocShell) {
2256  let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
2257  for (let url in aStorageData) {
2258  let uri = ioService.newURI(url, null, null);
2259  let storage = aDocShell.getSessionStorageForURI(uri);
2260  for (let key in aStorageData[url]) {
2261  try {
2262  storage.setItem(key, aStorageData[url][key]);
2263  }
2264  catch (ex) { Cu.reportError(ex); } // throws e.g. for URIs that can't have sessionStorage
2265  }
2266  }
2267  },
2268 
2272  restoreDocument_proxy: function sss_restoreDocument_proxy(aEvent) {
2273  // wait for the top frame to be loaded completely
2274  if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView || aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
2275  return;
2276  }
2277 
2278  // always call this before injecting content into a document!
2279  function hasExpectedURL(aDocument, aURL)
2280  !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, "");
2281 
2282  // restore text data saved by Firefox 2.0/3.0
2283  var textArray = this.__SS_restore_text ? this.__SS_restore_text.split(" ") : [];
2284  function restoreTextData(aContent, aPrefix, aURL) {
2285  textArray.forEach(function(aEntry) {
2286  if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry) &&
2287  RegExp.$1 == aPrefix && hasExpectedURL(aContent.document, aURL)) {
2288  var document = aContent.document;
2289  var node = RegExp.$2 ? document.getElementById(RegExp.$3) : document.getElementsByName(RegExp.$3)[0] || null;
2290  if (node && "value" in node && node.type != "file") {
2291  node.value = decodeURI(RegExp.$4);
2292 
2293  var event = document.createEvent("UIEvents");
2294  event.initUIEvent("input", true, true, aContent, 0);
2295  node.dispatchEvent(event);
2296  }
2297  }
2298  });
2299  }
2300 
2301  function restoreFormData(aDocument, aData, aURL) {
2302  for (let key in aData) {
2303  if (!hasExpectedURL(aDocument, aURL))
2304  return;
2305 
2306  let node = key.charAt(0) == "#" ? aDocument.getElementById(key.slice(1)) :
2307  XPathHelper.resolve(aDocument, key);
2308  if (!node)
2309  continue;
2310 
2311  let value = aData[key];
2312  if (typeof value == "string" && node.type != "file") {
2313  if (node.value == value)
2314  continue; // don't dispatch an input event for no change
2315 
2316  node.value = value;
2317 
2318  let event = aDocument.createEvent("UIEvents");
2319  event.initUIEvent("input", true, true, aDocument.defaultView, 0);
2320  node.dispatchEvent(event);
2321  }
2322  else if (typeof value == "boolean")
2323  node.checked = value;
2324  else if (typeof value == "number")
2325  try {
2326  node.selectedIndex = value;
2327  } catch (ex) { /* throws for invalid indices */ }
2328  else if (value && value.fileList && value.type == "file" && node.type == "file")
2329  node.mozSetFileNameArray(value.fileList, value.fileList.length);
2330  else if (value && typeof value.indexOf == "function" && node.options) {
2331  Array.forEach(node.options, function(aOpt, aIx) {
2332  aOpt.selected = value.indexOf(aIx) > -1;
2333  });
2334  }
2335  // NB: dispatching "change" events might have unintended side-effects
2336  }
2337  }
2338 
2339  let selectedPageStyle = this.__SS_restore_pageStyle;
2340  let window = this.ownerDocument.defaultView;
2342  if (aData.formdata)
2343  restoreFormData(aContent.document, aData.formdata, aData.url);
2344  else
2345  restoreTextData(aContent, aPrefix, aData.url);
2346  if (aData.innerHTML) {
2347  window.setTimeout(function() {
2348  if (aContent.document.designMode == "on" &&
2349  hasExpectedURL(aContent.document, aData.url)) {
2350  aContent.document.body.innerHTML = aData.innerHTML;
2351  }
2352  }, 0);
2353  }
2354  if (aData.scroll && /(\d+),(\d+)/.test(aData.scroll)) {
2355  aContent.scrollTo(RegExp.$1, RegExp.$2);
2356  }
2357  Array.forEach(aContent.document.styleSheets, function(aSS) {
2358  aSS.disabled = aSS.title && aSS.title != selectedPageStyle;
2359  });
2360  for (var i = 0; i < aContent.frames.length; i++) {
2361  if (aData.children && aData.children[i] &&
2362  hasExpectedURL(aContent.document, aData.url)) {
2363  restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], aPrefix + i + "|");
2364  }
2365  }
2366  }
2367 
2368  // don't restore text data and scrolling state if the user has navigated
2369  // away before the loading completed (except for in-page navigation)
2370  if (hasExpectedURL(aEvent.originalTarget, this.__SS_restore_data.url)) {
2371  var content = aEvent.originalTarget.defaultView;
2372  if (this.currentURI.spec == "about:config") {
2373  // unwrap the document for about:config because otherwise the properties
2374  // of the XBL bindings - as the textbox - aren't accessible (see bug 350718)
2375  content = content.wrappedJSObject;
2376  }
2378  this.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle";
2379 
2380  // notify the tabbrowser that this document has been completely restored
2381  var event = this.ownerDocument.createEvent("Events");
2382  event.initEvent("SSTabRestored", true, false);
2383  this.__SS_restore_tab.dispatchEvent(event);
2384  }
2385 
2386  this.removeEventListener("load", this.__SS_restore, true);
2387  delete this.__SS_restore_data;
2388  delete this.__SS_restore_text;
2390  delete this.__SS_restore_tab;
2391  delete this.__SS_restore;
2392  },
2393 
2401  restoreWindowFeatures: function sss_restoreWindowFeatures(aWindow, aWinData) {
2402  var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
2403  WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
2404  aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
2405  });
2406 
2407  if (aWinData.isPopup) {
2408  this._windows[aWindow.__SSi].isPopup = true;
2409  if (aWindow.gURLBar) {
2410  aWindow.gURLBar.readOnly = true;
2411  aWindow.gURLBar.setAttribute("enablehistory", "false");
2412  }
2413  }
2414  else {
2415  delete this._windows[aWindow.__SSi].isPopup;
2416  if (aWindow.gURLBar) {
2417  aWindow.gURLBar.readOnly = false;
2418  aWindow.gURLBar.setAttribute("enablehistory", "true");
2419  }
2420  }
2421 
2422  var _this = this;
2423  aWindow.setTimeout(function() {
2424  _this.restoreDimensions.apply(_this, [aWindow, aWinData.width || 0,
2425  aWinData.height || 0, "screenX" in aWinData ? aWinData.screenX : NaN,
2426  "screenY" in aWinData ? aWinData.screenY : NaN,
2427  aWinData.sizemode || "", aWinData.sidebar || ""]);
2428  }, 0);
2429  },
2430 
2446  restoreDimensions: function sss_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
2447  var win = aWindow;
2448  var _this = this;
2449  function win_(aName) { return _this._getWindowDimension(win, aName); }
2450 
2451  // only modify those aspects which aren't correct yet
2452  if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
2453  aWindow.resizeTo(aWidth, aHeight);
2454  }
2455  if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
2456  aWindow.moveTo(aLeft, aTop);
2457  }
2458  if (aSizeMode && win_("sizemode") != aSizeMode)
2459  {
2460  switch (aSizeMode)
2461  {
2462  case "maximized":
2463  aWindow.maximize();
2464  break;
2465  case "minimized":
2466  aWindow.minimize();
2467  break;
2468  case "normal":
2469  aWindow.restore();
2470  break;
2471  }
2472  }
2473  var sidebar = aWindow.document.getElementById("sidebar-box");
2474  if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
2475  aWindow.toggleSidebar(aSidebar);
2476  }
2477  // since resizing/moving a window brings it to the foreground,
2478  // we might want to re-focus the last focused window
2479  if (this.windowToFocus && this.windowToFocus.content) {
2480  this.windowToFocus.content.focus();
2481  }
2482  },
2483 
2489  restoreCookies: function sss_restoreCookies(aCookies) {
2490  if (aCookies.count && aCookies.domain1) {
2491  // convert to the new cookie serialization format
2492  var converted = [];
2493  for (var i = 1; i <= aCookies.count; i++) {
2494  // for simplicity we only accept the format we produced ourselves
2495  var parsed = aCookies["value" + i].match(/^([^=;]+)=([^;]*);(?:domain=[^;]+;)?(?:path=([^;]*);)?(secure;)?(httponly;)?/);
2496  if (parsed && /^https?:\/\/([^\/]+)/.test(aCookies["domain" + i]))
2497  converted.push({
2498  host: RegExp.$1, path: parsed[3], name: parsed[1], value: parsed[2],
2499  secure: parsed[4], httponly: parsed[5]
2500  });
2501  }
2502  aCookies = converted;
2503  }
2504 
2505  var cookieManager = Cc["@mozilla.org/cookiemanager;1"].
2506  getService(Ci.nsICookieManager2);
2507  // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
2508  var MAX_EXPIRY = Math.pow(2, 62);
2509  for (i = 0; i < aCookies.length; i++) {
2510  var cookie = aCookies[i];
2511  try {
2512  cookieManager.add(cookie.host, cookie.path || "", cookie.name || "", cookie.value, !!cookie.secure, !!cookie.httponly, true, "expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
2513  }
2514  catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
2515  }
2516  },
2517 
2518 /* ........ Disk Access .............. */
2519 
2528  saveStateDelayed: function sss_saveStateDelayed(aWindow, aDelay) {
2529  if (aWindow) {
2530  this._dirtyWindows[aWindow.__SSi] = true;
2531  }
2532 
2533  if (!this._saveTimer && this._resume_from_crash &&
2534  !this._inPrivateBrowsing) {
2535  // interval until the next disk operation is allowed
2536  var minimalDelay = this._lastSaveTime + this._interval - Date.now();
2537 
2538  // if we have to wait, set a timer, otherwise saveState directly
2539  aDelay = Math.max(minimalDelay, aDelay || 2000);
2540  if (aDelay > 0) {
2541  this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
2542  this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
2543  }
2544  else {
2545  this.saveState();
2546  }
2547  }
2548  },
2549 
2555  saveState: function sss_saveState(aUpdateAll) {
2556  // if crash recovery is disabled, only save session resuming information
2557  if (!this._resume_from_crash && this._loadState == STATE_RUNNING)
2558  return;
2559 
2560  // if we're in private browsing mode, do nothing
2561  if (this._inPrivateBrowsing)
2562  return;
2563 
2564  var oState = this._getCurrentState(aUpdateAll);
2565  oState.session = {
2566  state: this._loadState == STATE_RUNNING ? STATE_RUNNING_STR : STATE_STOPPED_STR,
2567  lastUpdate: Date.now()
2568  };
2569  if (this._recentCrashes)
2570  oState.session.recentCrashes = this._recentCrashes;
2571 
2572  this._saveStateObject(oState);
2573  },
2574 
2578  _saveStateObject: function sss_saveStateObject(aStateObj) {
2579  var stateString = Cc["@mozilla.org/supports-string;1"].
2580  createInstance(Ci.nsISupportsString);
2581  // parentheses are for backwards compatibility with Firefox 2.0 and 3.0
2582  stateString.data = "(" + this._toJSONString(aStateObj) + ")";
2583 
2584  this._observerService.notifyObservers(stateString,
2585  "sessionstore-state-write", "");
2586 
2587  // don't touch the file if an observer has deleted all state data
2588  if (stateString.data)
2589  this._writeFile(this._sessionFile, stateString.data);
2590 
2591  this._lastSaveTime = Date.now();
2592  },
2593 
2597  _clearDisk: function sss_clearDisk() {
2598  if (this._sessionFile.exists()) {
2599  try {
2600  this._sessionFile.remove(false);
2601  }
2602  catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
2603  }
2604  if (this._sessionFileBackup.exists()) {
2605  try {
2606  this._sessionFileBackup.remove(false);
2607  }
2608  catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
2609  }
2610  },
2611 
2612 /* ........ Auxiliary Functions .............. */
2613 
2620  _forEachBrowserWindow: function sss_forEachBrowserWindow(aFunc) {
2621  var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
2622  getService(Ci.nsIWindowMediator);
2623  var windowsEnum = windowMediator.getEnumerator("navigator:browser");
2624 
2625  while (windowsEnum.hasMoreElements()) {
2626  var window = windowsEnum.getNext();
2627  if (window.__SSi && !window.closed) {
2628  aFunc.call(this, window);
2629  }
2630  }
2631  },
2632 
2637  _getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() {
2638  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
2639  getService(Ci.nsIWindowMediator);
2640 
2641  var win = wm.getMostRecentWindow("navigator:browser");
2642  if (!win)
2643  return null;
2644  if (!win.closed)
2645  return win;
2646 
2647 #ifdef BROKEN_WM_Z_ORDER
2648  win = null;
2649  var windowsEnum = wm.getEnumerator("navigator:browser");
2650  // this is oldest to newest, so this gets a bit ugly
2651  while (windowsEnum.hasMoreElements()) {
2652  let nextWin = windowsEnum.getNext();
2653  if (!nextWin.closed)
2654  win = nextWin;
2655  }
2656  return win;
2657 #else
2658  var windowsEnum = wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
2659  while (windowsEnum.hasMoreElements()) {
2660  win = windowsEnum.getNext();
2661  if (!win.closed)
2662  return win;
2663  }
2664  return null;
2665 #endif
2666  },
2667 
2674  _openWindowWithState: function sss_openWindowWithState(aState) {
2675  var argString = Cc["@mozilla.org/supports-string;1"].
2676  createInstance(Ci.nsISupportsString);
2677  argString.data = "";
2678 
2679  //XXXzeniko shouldn't it be possible to set the window's dimensions here (as feature)?
2680  var window = Cc["@mozilla.org/embedcomp/window-watcher;1"].
2681  getService(Ci.nsIWindowWatcher).
2682  openWindow(null, this._prefBranch.getCharPref("chromeURL"), "_blank",
2683  "chrome,dialog=no,all", argString);
2684 
2685  do {
2686  var ID = "window" + Math.random();
2687  } while (ID in this._statesToRestore);
2688  this._statesToRestore[(window.__SS_restoreID = ID)] = aState;
2689 
2690  return window;
2691  },
2692 
2697  _doResumeSession: function sss_doResumeSession() {
2698  if (this._clearingOnShutdown)
2699  return false;
2700 
2701  return this._prefBranch.getIntPref("startup.page") == 3 ||
2702  this._prefBranch.getBoolPref("sessionstore.resume_session_once");
2703  },
2704 
2711  _isCmdLineEmpty: function sss_isCmdLineEmpty(aWindow) {
2712  var defaultArgs = Cc["@mozilla.org/browser/clh;1"].
2713  getService(Ci.nsIBrowserHandler).defaultArgs;
2714  if (aWindow.arguments && aWindow.arguments[0] &&
2715  aWindow.arguments[0] == defaultArgs)
2716  aWindow.arguments[0] = null;
2717 
2718  return !aWindow.arguments || !aWindow.arguments[0];
2719  },
2720 
2728  _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) {
2729  return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
2730  },
2731 
2743  _getWindowDimension: function sss_getWindowDimension(aWindow, aAttribute) {
2744  if (aAttribute == "sizemode") {
2745  switch (aWindow.windowState) {
2746  case aWindow.STATE_FULLSCREEN:
2747  case aWindow.STATE_MAXIMIZED:
2748  return "maximized";
2749  case aWindow.STATE_MINIMIZED:
2750  return "minimized";
2751  default:
2752  return "normal";
2753  }
2754  }
2755 
2756  var dimension;
2757  switch (aAttribute) {
2758  case "width":
2759  dimension = aWindow.outerWidth;
2760  break;
2761  case "height":
2762  dimension = aWindow.outerHeight;
2763  break;
2764  default:
2765  dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
2766  break;
2767  }
2768 
2769  if (aWindow.windowState == aWindow.STATE_NORMAL) {
2770  return dimension;
2771  }
2772  return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
2773  },
2774 
2780  _getURIFromString: function sss_getURIFromString(aString) {
2781  var ioService = Cc["@mozilla.org/network/io-service;1"].
2782  getService(Ci.nsIIOService);
2783  return ioService.newURI(aString, null, null);
2784  },
2785 
2789  _updateCrashReportURL: function sss_updateCrashReportURL(aWindow) {
2790  if (!Ci.nsICrashReporter) {
2791  // if breakpad isn't built, don't bother next time at all
2792  this._updateCrashReportURL = function(aWindow) {};
2793  return;
2794  }
2795  try {
2796  var currentURI = aWindow.getBrowser().currentURI.clone();
2797  // if the current URI contains a username/password, remove it
2798  try {
2799  currentURI.userPass = "";
2800  }
2801  catch (ex) { } // ignore failures on about: URIs
2802 
2803  var cr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsICrashReporter);
2804  cr.annotateCrashReport("URL", currentURI.spec);
2805  }
2806  catch (ex) {
2807  // don't make noise when crashreporter is built but not enabled
2808  if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED)
2809  debug(ex);
2810  }
2811  },
2812 
2818  _needsRestorePage: function sss_needsRestorePage(aState, aRecentCrashes) {
2819  const SIX_HOURS_IN_MS = 6 * 60 * 60 * 1000;
2820 
2821  // don't display the page when there's nothing to restore
2822  let winData = aState.windows || null;
2823  if (!winData || winData.length == 0)
2824  return false;
2825 
2826  // don't wrap a single about:sessionrestore page
2827  if (winData.length == 1 && winData[0].tabs &&
2828  winData[0].tabs.length == 1 && winData[0].tabs[0].entries &&
2829  winData[0].tabs[0].entries.length == 1 &&
2830  winData[0].tabs[0].entries[0].url == "about:sessionrestore")
2831  return false;
2832 
2833  // don't automatically restore in Safe Mode
2834  let XRE = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
2835  if (XRE.inSafeMode)
2836  return true;
2837 
2839  this._prefBranch.getIntPref("sessionstore.max_resumed_crashes");
2840  let sessionAge = aState.session && aState.session.lastUpdate &&
2841  (Date.now() - aState.session.lastUpdate);
2842 
2843  return max_resumed_crashes != -1 &&
2844  (aRecentCrashes > max_resumed_crashes ||
2845  sessionAge && sessionAge >= SIX_HOURS_IN_MS);
2846  },
2847 
2851  _safeEval: function sss_safeEval(aStr) {
2852  return Cu.evalInSandbox(aStr, new Cu.Sandbox("about:blank"));
2853  },
2854 
2864  _toJSONString: function sss_toJSONString(aJSObject) {
2865  // XXXzeniko drop the following keys used only for internal bookkeeping:
2866  // _tabStillLoading, _hosts, _formDataSaved
2867  let jsonString = JSON.stringify(aJSObject);
2868 
2869  if (/[\u2028\u2029]/.test(jsonString)) {
2870  // work-around for bug 485563 until we can use JSON.parse
2871  // instead of evalInSandbox everywhere
2872  jsonString = jsonString.replace(/[\u2028\u2029]/g,
2873  function($0) "\\u" + $0.charCodeAt(0).toString(16));
2874  }
2875 
2876  return jsonString;
2877  },
2878 
2880  function sss_sendRestoreCompletedNotifications(aOnWindowClose) {
2881  if (this._restoreCount && !aOnWindowClose)
2882  this._restoreCount--;
2883 
2884  if (this._restoreCount == 0 && this._closingWindows.length == 0) {
2885  // This was the last window restored at startup, notify observers.
2886  this._observerService.notifyObservers(null,
2887  this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
2888  "");
2889  this._browserSetState = false;
2890  }
2891  },
2892 
2899  _isWindowLoaded: function sss_isWindowLoaded(aWindow) {
2900  return !aWindow.__SS_restoreID;
2901  },
2902 
2911  _replaceLoadingTitle : function sss_replaceLoadingTitle(aString, aTabbrowser, aTab) {
2912  if (aString == aTabbrowser.mStringBundle.getString("tabs.loading")) {
2913  aTabbrowser.setTabTitle(aTab);
2914  [aString, aTab.label] = [aTab.label, aString];
2915  }
2916  return aString;
2917  },
2918 
2924  _capClosedWindows : function sss_capClosedWindows() {
2925  let maxWindowsUndo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
2926  if (this._closedWindows.length <= maxWindowsUndo)
2927  return;
2928  let spliceTo = maxWindowsUndo;
2929 #ifndef XP_MACOSX
2931  // try to find a non-popup window in this._closedWindows
2932  while (normalWindowIndex < this._closedWindows.length &&
2933  !!this._closedWindows[normalWindowIndex].isPopup)
2934  normalWindowIndex++;
2935  if (normalWindowIndex >= maxWindowsUndo)
2936  spliceTo = normalWindowIndex + 1;
2937 #endif
2938  this._closedWindows.splice(spliceTo);
2939  },
2940 
2941 /* ........ Storage API .............. */
2942 
2950  _writeFile: function sss_writeFile(aFile, aData) {
2951  // Initialize the file output stream.
2952  var ostream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
2953  createInstance(Ci.nsIFileOutputStream);
2954  ostream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
2955 
2956  // Obtain a converter to convert our data to a UTF-8 encoded input stream.
2957  var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
2958  createInstance(Ci.nsIScriptableUnicodeConverter);
2959  converter.charset = "UTF-8";
2960 
2961  // Asynchronously copy the data to the file.
2962  var istream = converter.convertToInputStream(aData);
2963  var self = this;
2964  NetUtil.asyncCopy(istream, ostream, function(rc) {
2965  if (Components.isSuccessCode(rc)) {
2966  self._observerService.notifyObservers(null,
2967  "sessionstore-state-write-complete",
2968  "");
2969  }
2970  });
2971  }
2972 };
2973 
2975  // these two hashes should be kept in sync
2976  namespaceURIs: { "xhtml": "http://www.w3.org/1999/xhtml" },
2977  namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" },
2978 
2982  generate: function sss_xph_generate(aNode) {
2983  // have we reached the document node already?
2984  if (!aNode.parentNode)
2985  return "";
2986 
2987  let prefix = this.namespacePrefixes[aNode.namespaceURI] || null;
2988  let tag = (prefix ? prefix + ":" : "") + this.escapeName(aNode.localName);
2989 
2990  // stop once we've found a tag with an ID
2991  if (aNode.id)
2992  return "//" + tag + "[@id=" + this.quoteArgument(aNode.id) + "]";
2993 
2994  // count the number of previous sibling nodes of the same tag
2995  // (and possible also the same name)
2996  let count = 0;
2997  let nName = aNode.name || null;
2998  for (let n = aNode; (n = n.previousSibling); )
2999  if (n.localName == aNode.localName && n.namespaceURI == aNode.namespaceURI &&
3000  (!nName || n.name == nName))
3001  count++;
3002 
3003  // recurse until hitting either the document node or an ID'd node
3004  return this.generate(aNode.parentNode) + "/" + tag +
3005  (nName ? "[@name=" + this.quoteArgument(nName) + "]" : "") +
3006  (count ? "[" + (count + 1) + "]" : "");
3007  },
3008 
3012  resolve: function sss_xph_resolve(aDocument, aQuery) {
3013  let xptype = Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
3014  return aDocument.evaluate(aQuery, aDocument, this.resolveNS, xptype, null).singleNodeValue;
3015  },
3016 
3020  resolveNS: function sss_xph_resolveNS(aPrefix) {
3021  return XPathHelper.namespaceURIs[aPrefix] || null;
3022  },
3023 
3027  escapeName: function sss_xph_escapeName(aName) {
3028  // we can't just use the node's local name, if it contains
3029  // special characters (cf. bug 485482)
3030  return /^\w+$/.test(aName) ? aName :
3031  "*[local-name()=" + this.quoteArgument(aName) + "]";
3032  },
3033 
3037  quoteArgument: function sss_xph_quoteArgument(aArg) {
3038  return !/'/.test(aArg) ? "'" + aArg + "'" :
3039  !/"/.test(aArg) ? '"' + aArg + '"' :
3040  "concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')";
3041  },
3042 
3046  get restorableFormNodes() {
3047  // for a comprehensive list of all available <INPUT> types see
3048  // http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable
3049  let ignoreTypes = ["password", "hidden", "button", "image", "submit", "reset"];
3050  // XXXzeniko work-around until lower-case has been implemented (bug 398389)
3051  let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"';
3052  let ignore = "not(translate(@type, " + toLowerCase + ")='" +
3053  ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')";
3054  let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" +
3055  "//input[" + ignore + "]|//xhtml:input[" + ignore + "]";
3056 
3057  delete this.restorableFormNodes;
3058  return (this.restorableFormNodes = formNodesXPath);
3059  }
3060 };
3061 
3062 // see nsPrivateBrowsingService.js
3063 String.prototype.hasRootDomain = function hasRootDomain(aDomain)
3064 {
3065  let index = this.indexOf(aDomain);
3066  if (index == -1)
3067  return false;
3068 
3069  if (this == aDomain)
3070  return true;
3071 
3072  let prevChar = this[index - 1];
3073  return (index == (this.length - aDomain.length)) &&
3074  (prevChar == "." || prevChar == "/");
3075 }
3076 
3077 function NSGetModule(aComMgr, aFileSpec)
3078  XPCOMUtils.generateModule([SessionStoreService]);
_collectFormDataForFrame XPathHelper Ci nsIDOMXPathResult null
classDescription entry
Definition: FeedWriter.js:1427
var total
this restoreHistory(aWindow, aTabs, aTabData, idMap)
const NOTIFY_WINDOWS_RESTORED
function restoreTextDataAndScrolling(aContent, aData, aPrefix)
restoreDimensions aSidebar
_updateCookies aPath
const Cu
const Cc
var MAX_EXPIRY
let normalWindowIndex
this _updateTextAndScrollData(aWindow)
var istream
_dialogDatepicker pos
function debug(aMsg)
var windows
restoreDimensions aWidth
_collectFormDataForFrame XPathHelper resolveNS
const OBSERVING
getService(Ci.nsIWindowMediator)
var history
restoreWindow aFollowUp
aHash[aHost][aPath][aName]
saveStateDelayed aDelay
var sidebar
const STATE_QUITTING
let activeIndex
var ix
let scrollX
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
restoreDimensions aSizeMode
var event
var jscookies
var converter
_updateCookies aHost
var tabstrip
function undoCloseWindow(aIndex)
Definition: browser.js:6380
let sessionAge
let domWindowUtils
let selectedPageStyle
this _dirtyWindows[aWindow.__SSi]
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
var ioService
function restoreFormData(aDocument, aData, aURL)
return shEntry
delete this __SS_restore_data
var tab
this _saveWindowHistory(aWindow)
this _updateCookies(total)
systray _prefBranch
Definition: mainwin.js:150
restoreDimensions aLeft
let window
let generatedCount
SessionStoreService aDocShell
restoreDimensions aHeight
var disallow
const Ci
const STATE_RUNNING
delete this __SS_restore_tab
function d(s)
ostream init(aFile, 0x02|0x08|0x20, 0600, 0)
let spliceTo
let hasContent
this _window
Definition: FeedWriter.js:1158
let XRE
var t
var count
Definition: test_bug7406.js:32
_collectFormDataForFrame aDocument
const NOTIFY_BROWSER_STATE_RESTORED
let lastClosedWindowsCopy
var openTabCount
var browser
_updateTextAndScrollDataForTab aTabData
aCookies
var idMap
var tabbrowser
function restoreTextData(aContent, aPrefix, aURL)
var oState
restoreDimensions aTop
let max_resumed_crashes
_updateTextAndScrollDataForFrame aContent
const Cr
var tabs
Lastfm onLoad
Definition: mini.js:36
var postdata
let id
var _this
let _windows
this _updateCookieHosts(aWindow)
var win
delete this __SS_restore
const STATE_RUNNING_STR
function win_(aName)
if(hasContent) aTabData.storage
const MAX_GENERATED_XPATHS
SessionStoreService _serializeSessionStorage
restoreHistory aIdMap
this _windows[aWindow.__SSi] tabs forEach(function(aTabData){aTabData.entries.forEach(extractHosts);})
const WINDOW_ATTRIBUTES
_updateCookies aCookie
function openWindow(aOnloadCallback)
this _lastSaveTime
_replaceLoadingTitle aTabbrowser
this _statesToRestore[(window.__SS_restoreID=ID)]
this restoreHistoryPrecursor(aWindow, tabs, winData.tabs,(aOverwriteTabs?(parseInt(winData.selected)||1):0), 0, 0)
restoreHistoryPrecursor aIx
var windowsEnum
let node
return!aWindow arguments!aWindow arguments[0]
history QueryInterface(Ci.nsISHistoryInternal)
this _saveStateObject(oState)
_updateCookies aName
delete this __SS_restore_pageStyle
createInstance(Ci.nsISupportsString)
var uri
Definition: FeedWriter.js:1135
function url(spec)
_updateTextAndScrollDataForTab aBrowser
this _sendRestoreCompletedNotifications()
countRef value
Definition: FeedWriter.js:1423
saveStateDelayed saveState
delete this __SS_restore_text
SessionStoreService aHistory
var smoothScroll
var newTabCount
const PRIVACY_NONE
var cr
foldersync options
Definition: options.js:13
const WINDOW_HIDEABLE_FEATURES
var JSON
sbDeviceFirmwareAutoCheckForUpdate prototype classID
_replaceLoadingTitle aTab
var nonPopupCount
_needsRestorePage aRecentCrashes
let scrollY
return jsonString
var cookieManager
function onTabClose(event)
var hidden
function hasExpectedURL(aDocument, aURL)!aURL||aURL.replace(/var textArray
restoreHistoryPrecursor aCount
var tabIndex
this removeEventListener("load", this.__SS_restore, true)
const CAPABILITIES
dataSBGenres SBProperties tag
Definition: tuner2.js:871
function extractHosts(aEntry)
_updateTextAndScrollDataForFrame aUpdateFormData
restoreWindow aState
_getWindowDimension aAttribute
restoreWindow aOverwriteTabs
const STATE_STOPPED_STR
__defineGetter__("NetUtil", function(){delete this.NetUtil;Cu.import("resource://gre/modules/NetUtil.jsm");return NetUtil;})
this _updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow, aTabData.entries[tabIndex],!aTabData._formDataSaved, aFullData)
const PRIVACY_FULL
_getSelectedPageStyle s i
const STATE_STOPPED
let data
function SessionStoreService()
SessionStoreService aFullData
var cm
restoreHistoryPrecursor aSelectTab
restoreHistoryPrecursor aTabs
this _updateWindowFeatures(aWindow)
var winData
_updateTextAndScrollDataForFrame aData
var tabData
let XPathHelper
var file
sbDeviceFirmwareAutoCheckForUpdate prototype observe
const PRIVACY_ENCRYPTED
function undoCloseTab(aIndex)
Definition: browser.js:6350
restoreWindowFeatures aWinData