home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / components / nsSessionStore.js < prev    next >
Encoding:
JavaScript  |  2009-07-15  |  103.0 KB  |  2,902 lines

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