Software: Apache/2.2.16 (Debian). PHP/5.3.3-7+squeeze19 uname -a: Linux mail.tri-specialutilitydistrict.com 2.6.32-5-amd64 #1 SMP Tue May 13 16:34:35 UTC uid=33(www-data) gid=33(www-data) groups=33(www-data) Safe-mode: OFF (not secure) /usr/lib/iceweasel/components/ drwxr-xr-x |
Viewing file: Select action/file-type: /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the nsSessionStore component. * * The Initial Developer of the Original Code is * Simon Bünzli <zeniko@gmail.com> * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Dietrich Ayala <dietrich@mozilla.com> * Ehsan Akhgari <ehsan.akhgari@gmail.com> * Paul O’Shannessy <paul@oshannessy.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /** * Session Storage and Restoration * * Overview * This service keeps track of a user's session, storing the various bits * required to return the browser to its current state. The relevant data is * stored in memory, and is periodically saved to disk in a file in the * profile directory. The service is started at first window load, in * delayedStartup, and will restore the session from the data received from * the nsSessionStartup service. */ /* :::::::: Constants and Helpers ::::::::::::::: */ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; const STATE_STOPPED = 0; const STATE_RUNNING = 1; const STATE_QUITTING = -1; const STATE_STOPPED_STR = "stopped"; const STATE_RUNNING_STR = "running"; const PRIVACY_NONE = 0; const PRIVACY_ENCRYPTED = 1; const PRIVACY_FULL = 2; const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored"; const INTERFACES = [Ci.nsISessionStore, Ci.nsIDOMEventListener, Ci.nsIObserver, Ci.nsISupportsWeakReference, Ci.nsISessionStore_MOZILLA_1_9_1, Ci.nsIClassInfo]; // global notifications observed const OBSERVING = [ "domwindowopened", "domwindowclosed", "quit-application-requested", "quit-application-granted", "quit-application", "browser:purge-session-history", "private-browsing", "browser:purge-domain-data", "private-browsing-change-granted" ]; /* XUL Window properties to (re)store Restored in restoreDimensions() */ const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"]; /* Hideable window features to (re)store Restored in restoreWindowFeatures() */ const WINDOW_HIDEABLE_FEATURES = [ "menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars" ]; /* docShell capabilities to (re)store Restored in restoreHistory() eg: browser.docShell["allow" + aCapability] = false; */ const CAPABILITIES = [ "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images" ]; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); function debug(aMsg) { aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n"); Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService) .logStringMessage(aMsg); } /* :::::::: The Service ::::::::::::::: */ function SessionStoreService() { } SessionStoreService.prototype = { classDescription: "Browser Session Store Service", contractID: "@mozilla.org/browser/sessionstore;1", classID: Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}"), QueryInterface: XPCOMUtils.generateQI(INTERFACES), // extra requirements for nsIClassInfo getInterfaces: function sss_getInterfaces(aCountRef) { aCountRef.value = INTERFACES.length; return INTERFACES; }, getHelperForLanguage: function sss_getHelperForLanguage (aLanguage) null, implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, flags: Ci.nsIClassInfo.SINGLETON, // xul:tab attributes to (re)store (extensions might want to hook in here); // the favicon is always saved for the about:sessionrestore page xulAttributes: ["image"], // set default load state _loadState: STATE_STOPPED, // minimal interval between two save operations (in milliseconds) _interval: 10000, // when crash recovery is disabled, session data is not written to disk _resume_from_crash: true, // During the initial restore tracks the number of windows yet to be restored _restoreCount: 0, // time in milliseconds (Date.now()) when the session was last written to file _lastSaveTime: 0, // states for all currently opened windows _windows: {}, // states for all recently closed windows _closedWindows: [], // not-"dirty" windows usually don't need to have their data updated _dirtyWindows: {}, // collection of session states yet to be restored _statesToRestore: {}, // counts the number of crashes since the last clean start _recentCrashes: 0, // whether we are in private browsing mode _inPrivateBrowsing: false, // whether we clearing history on shutdown _clearingOnShutdown: false, /* ........ Global Event Handlers .............. */ /** * Initialize the component */ init: function sss_init(aWindow) { if (!aWindow || this._loadState == STATE_RUNNING) { // make sure that all browser windows which try to initialize // SessionStore are really tracked by it if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi])) this.onLoad(aWindow); return; } this._prefBranch = Cc["@mozilla.org/preferences-service;1"]. getService(Ci.nsIPrefService).getBranch("browser."); this._prefBranch.QueryInterface(Ci.nsIPrefBranch2); var observerService = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); OBSERVING.forEach(function(aTopic) { observerService.addObserver(this, aTopic, true); }, this); var pbs = Cc["@mozilla.org/privatebrowsing;1"]. getService(Ci.nsIPrivateBrowsingService); this._inPrivateBrowsing = pbs.privateBrowsingEnabled; // get interval from prefs - used often, so caching/observing instead of fetching on-demand this._interval = this._prefBranch.getIntPref("sessionstore.interval"); this._prefBranch.addObserver("sessionstore.interval", this, true); // get crash recovery state from prefs and allow for proper reaction to state changes this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash"); this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true); // observe prefs changes so we can modify stored data to match this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true); this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true); // this pref is only read at startup, so no need to observe it this._sessionhistory_max_entries = this._prefBranch.getIntPref("sessionhistory.max_entries"); // get file references var dirService = Cc["@mozilla.org/file/directory_service;1"]. getService(Ci.nsIProperties); this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile); this._sessionFileBackup = this._sessionFile.clone(); this._sessionFile.append("sessionstore.js"); this._sessionFileBackup.append("sessionstore.bak"); // get string containing session state var iniString; try { var ss = Cc["@mozilla.org/browser/sessionstartup;1"]. getService(Ci.nsISessionStartup); if (ss.doRestore()) iniString = ss.state; } catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok if (iniString) { try { // parse the session state into JS objects this._initialState = this._safeEval("(" + iniString + ")"); let lastSessionCrashed = this._initialState.session && this._initialState.session.state && this._initialState.session.state == STATE_RUNNING_STR; if (lastSessionCrashed) { this._recentCrashes = (this._initialState.session && this._initialState.session.recentCrashes || 0) + 1; if (this._needsRestorePage(this._initialState, this._recentCrashes)) { // replace the crashed session with a restore-page-only session let pageData = { url: "about:sessionrestore", formdata: { "#sessionData": iniString } }; this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] }; } } // make sure that at least the first window doesn't have anything hidden delete this._initialState.windows[0].hidden; // Since nothing is hidden in the first window, it cannot be a popup delete this._initialState.windows[0].isPopup; } catch (ex) { debug("The session file is invalid: " + ex); } } // remove the session data files if crash recovery is disabled if (!this._resume_from_crash) this._clearDisk(); else { // create a backup if the session data file exists try { if (this._sessionFileBackup.exists()) this._sessionFileBackup.remove(false); if (this._sessionFile.exists()) this._sessionFile.copyTo(null, this._sessionFileBackup.leafName); } catch (ex) { Cu.reportError(ex); } // file was write-locked? } // at this point, we've as good as resumed the session, so we can // clear the resume_session_once flag, if it's set if (this._loadState != STATE_QUITTING && this._prefBranch.getBoolPref("sessionstore.resume_session_once")) this._prefBranch.setBoolPref("sessionstore.resume_session_once", false); // As this is called at delayedStartup, restoration must be initiated here this.onLoad(aWindow); }, /** * Called on application shutdown, after notifications: * quit-application-granted, quit-application */ _uninit: function sss_uninit() { if (this._doResumeSession()) { // save all data for session resuming this.saveState(true); } else { // discard all session related data this._clearDisk(); } // Make sure to break our cycle with the save timer if (this._saveTimer) { this._saveTimer.cancel(); this._saveTimer = null; } }, /** * Handle notifications */ observe: function sss_observe(aSubject, aTopic, aData) { // for event listeners var _this = this; switch (aTopic) { case "domwindowopened": // catch new windows aSubject.addEventListener("load", function(aEvent) { aEvent.currentTarget.removeEventListener("load", arguments.callee, false); _this.onLoad(aEvent.currentTarget); }, false); break; case "domwindowclosed": // catch closed windows this.onClose(aSubject); break; case "quit-application-requested": // get a current snapshot of all windows this._forEachBrowserWindow(function(aWindow) { this._collectWindowData(aWindow); }); this._dirtyWindows = []; break; case "quit-application-granted": // freeze the data at what we've got (ignoring closing windows) this._loadState = STATE_QUITTING; break; case "quit-application": if (aData == "restart") { this._prefBranch.setBoolPref("sessionstore.resume_session_once", true); this._clearingOnShutdown = false; } this._loadState = STATE_QUITTING; // just to be sure this._uninit(); break; case "browser:purge-session-history": // catch sanitization let openWindows = {}; this._forEachBrowserWindow(function(aWindow) { Array.forEach(aWindow.getBrowser().browsers, function(aBrowser) { delete aBrowser.parentNode.__SS_data; }); openWindows[aWindow.__SSi] = true; }); // also clear all data about closed tabs and windows for (let ix in this._windows) { if (ix in openWindows) this._windows[ix]._closedTabs = []; else delete this._windows[ix]; } // also clear all data about closed windows this._closedWindows = []; this._clearDisk(); // give the tabbrowsers a chance to clear their histories first var win = this._getMostRecentBrowserWindow(); if (win) win.setTimeout(function() { _this.saveState(true); }, 0); else if (this._loadState == STATE_RUNNING) this.saveState(true); // Delete the private browsing backed up state, if any if ("_stateBackup" in this) delete this._stateBackup; if (this._loadState == STATE_QUITTING) this._clearingOnShutdown = true; break; case "browser:purge-domain-data": // does a session history entry contain a url for the given domain? function containsDomain(aEntry) { try { if (this._getURIFromString(aEntry.url).host.hasRootDomain(aData)) return true; } catch (ex) { /* url had no host at all */ } return aEntry.children && aEntry.children.some(containsDomain, this); } // remove all closed tabs containing a reference to the given domain for (let ix in this._windows) { let closedTabs = this._windows[ix]._closedTabs; for (let i = closedTabs.length - 1; i >= 0; i--) { if (closedTabs[i].state.entries.some(containsDomain, this)) closedTabs.splice(i, 1); } } // remove all open & closed tabs containing a reference to the given // domain in closed windows for (let ix = this._closedWindows.length - 1; ix >= 0; ix--) { let closedTabs = this._closedWindows[ix]._closedTabs; let openTabs = this._closedWindows[ix].tabs; let openTabCount = openTabs.length; for (let i = closedTabs.length - 1; i >= 0; i--) if (closedTabs[i].state.entries.some(containsDomain, this)) closedTabs.splice(i, 1); for (let j = openTabs.length - 1; j >= 0; j--) { if (openTabs[j].entries.some(containsDomain, this)) { openTabs.splice(j, 1); if (this._closedWindows[ix].selected > j) this._closedWindows[ix].selected--; } } if (openTabs.length == 0) { this._closedWindows.splice(ix, 1); } else if (openTabs.length != openTabCount) { // Adjust the window's title if we removed an open tab let selectedTab = openTabs[this._closedWindows[ix].selected - 1]; // some duplication from restoreHistory - make sure we get the correct title let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1; if (activeIndex >= selectedTab.entries.length) activeIndex = selectedTab.entries.length - 1; this._closedWindows[ix].title = selectedTab.entries[activeIndex].title; } } if (this._loadState == STATE_RUNNING) this.saveState(true); break; case "nsPref:changed": // catch pref changes switch (aData) { // if the user decreases the max number of closed tabs they want // preserved update our internal states to match that max case "sessionstore.max_tabs_undo": for (let ix in this._windows) { this._windows[ix]._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo")); } break; case "sessionstore.max_windows_undo": this._capClosedWindows(); break; case "sessionstore.interval": this._interval = this._prefBranch.getIntPref("sessionstore.interval"); // reset timer and save if (this._saveTimer) { this._saveTimer.cancel(); this._saveTimer = null; } this.saveStateDelayed(null, -1); break; case "sessionstore.resume_from_crash": this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash"); // either create the file with crash recovery information or remove it // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead) if (this._resume_from_crash) this.saveState(true); else if (this._loadState == STATE_RUNNING) this._clearDisk(); break; } break; case "timer-callback": // timer call back for delayed saving this._saveTimer = null; this.saveState(); break; case "private-browsing": switch (aData) { case "enter": this._inPrivateBrowsing = true; break; case "exit": aSubject.QueryInterface(Ci.nsISupportsPRBool); let quitting = aSubject.data; if (quitting) { // save the backed up state with session set to stopped, // otherwise resuming next time would look like a crash if ("_stateBackup" in this) { var oState = this._stateBackup; oState.session = { state: STATE_STOPPED_STR }; this._saveStateObject(oState); } // make sure to restore the non-private session upon resuming this._prefBranch.setBoolPref("sessionstore.resume_session_once", true); } else this._inPrivateBrowsing = false; delete this._stateBackup; break; } break; case "private-browsing-change-granted": if (aData == "enter") { this.saveState(true); this._stateBackup = this._safeEval(this._getCurrentState(true).toSource()); } break; } }, /* ........ Window Event Handlers .............. */ /** * Implement nsIDOMEventListener for handling various window and tab events */ handleEvent: function sss_handleEvent(aEvent) { switch (aEvent.type) { case "load": case "pageshow": this.onTabLoad(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent); break; case "change": case "input": case "DOMAutoComplete": this.onTabInput(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget); break; case "scroll": this.onTabScroll(aEvent.currentTarget.ownerDocument.defaultView); break; case "TabOpen": case "TabClose": let target = aEvent.originalTarget; let panelID = target.linkedPanel; let ownerDoc = target.ownerDocument; let bindingParent = ownerDoc.getBindingParent(target); let tabpanel = ownerDoc.getAnonymousElementByAttribute(bindingParent, "id", panelID); if (aEvent.type == "TabOpen") { this.onTabAdd(aEvent.currentTarget.ownerDocument.defaultView, tabpanel); } else { // aEvent.detail determines if the tab was closed by moving to a different window if (!aEvent.detail) this.onTabClose(aEvent.currentTarget.ownerDocument.defaultView, aEvent.originalTarget); this.onTabRemove(aEvent.currentTarget.ownerDocument.defaultView, tabpanel); } break; case "TabSelect": var tabpanels = aEvent.currentTarget.mPanelContainer; this.onTabSelect(aEvent.currentTarget.ownerDocument.defaultView, tabpanels); break; } }, /** * If it's the first window load since app start... * - determine if we're reloading after a crash or a forced-restart * - restore window state * - restart downloads * Set up event listeners for this window's tabs * @param aWindow * Window reference */ onLoad: function sss_onLoad(aWindow) { // return if window has already been initialized if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi]) return; // ignore non-browser windows and windows opened while shutting down if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState == STATE_QUITTING) return; // assign it a unique identifier (timestamp) aWindow.__SSi = "window" + Date.now(); // and create its data object this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [] }; if (!aWindow.toolbar.visible) this._windows[aWindow.__SSi].isPopup = true; // perform additional initialization when the first window is loading if (this._loadState == STATE_STOPPED) { this._loadState = STATE_RUNNING; this._lastSaveTime = Date.now(); // restore a crashed session resp. resume the last session if requested if (this._initialState) { // make sure that the restored tabs are first in the window this._initialState._firstTabs = true; this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0; this.restoreWindow(aWindow, this._initialState, this._isCmdLineEmpty(aWindow)); delete this._initialState; // mark ourselves as running this.saveState(true); } else { // Nothing to restore, notify observers things are complete. var observerService = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, ""); //@line 593 "/tmp/buildd/iceweasel-3.5.16/browser/components/sessionstore/src/nsSessionStore.js" // the next delayed save request should execute immediately this._lastSaveTime -= this._interval; //@line 600 "/tmp/buildd/iceweasel-3.5.16/browser/components/sessionstore/src/nsSessionStore.js" } } // this window was opened by _openWindowWithState else if (!this._isWindowLoaded(aWindow)) { let followUp = this._statesToRestore[aWindow.__SS_restoreID].windows.length == 1; this.restoreWindow(aWindow, this._statesToRestore[aWindow.__SS_restoreID], true, followUp); } var tabbrowser = aWindow.getBrowser(); var tabpanels = tabbrowser.mPanelContainer; // add tab change listeners to all already existing tabs for (var i = 0; i < tabpanels.childNodes.length; i++) { this.onTabAdd(aWindow, tabpanels.childNodes[i], true); } // notification of tab add/remove/selection tabbrowser.addEventListener("TabOpen", this, true); tabbrowser.addEventListener("TabClose", this, true); tabbrowser.addEventListener("TabSelect", this, true); }, /** * On window close... * - remove event listeners from tabs * - save all window data * @param aWindow * Window reference */ onClose: function sss_onClose(aWindow) { // this window was about to be restored - conserve its original data, if any let isFullyLoaded = this._isWindowLoaded(aWindow); if (!isFullyLoaded) { if (!aWindow.__SSi) aWindow.__SSi = "window" + Date.now(); this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID]; delete this._statesToRestore[aWindow.__SS_restoreID]; delete aWindow.__SS_restoreID; } // ignore windows not tracked by SessionStore if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) { return; } if (this.windowToFocus && this.windowToFocus == aWindow) { delete this.windowToFocus; } var tabbrowser = aWindow.getBrowser(); var tabpanels = tabbrowser.mPanelContainer; tabbrowser.removeEventListener("TabOpen", this, true); tabbrowser.removeEventListener("TabClose", this, true); tabbrowser.removeEventListener("TabSelect", this, true); let winData = this._windows[aWindow.__SSi]; if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down // update all window data for a last time this._collectWindowData(aWindow); if (isFullyLoaded) { winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label; winData.title = this._replaceLoadingTitle(winData.title, tabbrowser, tabbrowser.selectedTab); this._updateCookies([winData]); } // save the window if it has multiple tabs or a single tab with entries if (winData.tabs.length > 1 || (winData.tabs.length == 1 && winData.tabs[0].entries.length > 0)) { this._closedWindows.unshift(winData); this._capClosedWindows(); } // clear this window from the list delete this._windows[aWindow.__SSi]; // save the state without this window to disk this.saveStateDelayed(); } for (var i = 0; i < tabpanels.childNodes.length; i++) { this.onTabRemove(aWindow, tabpanels.childNodes[i], true); } // cache the window state until the window is completely gone aWindow.__SS_dyingCache = winData; delete aWindow.__SSi; }, /** * set up listeners for a new tab * @param aWindow * Window reference * @param aPanel * TabPanel reference * @param aNoNotification * bool Do not save state if we're updating an existing tab */ onTabAdd: function sss_onTabAdd(aWindow, aPanel, aNoNotification) { aPanel.addEventListener("load", this, true); aPanel.addEventListener("pageshow", this, true); aPanel.addEventListener("change", this, true); aPanel.addEventListener("input", this, true); aPanel.addEventListener("DOMAutoComplete", this, true); aPanel.addEventListener("scroll", this, true); if (!aNoNotification) { this.saveStateDelayed(aWindow); } }, /** * remove listeners for a tab * @param aWindow * Window reference * @param aPanel * TabPanel reference * @param aNoNotification * bool Do not save state if we're updating an existing tab */ onTabRemove: function sss_onTabRemove(aWindow, aPanel, aNoNotification) { aPanel.removeEventListener("load", this, true); aPanel.removeEventListener("pageshow", this, true); aPanel.removeEventListener("change", this, true); aPanel.removeEventListener("input", this, true); aPanel.removeEventListener("DOMAutoComplete", this, true); aPanel.removeEventListener("scroll", this, true); delete aPanel.__SS_data; if (!aNoNotification) { this.saveStateDelayed(aWindow); } }, /** * When a tab closes, collect its properties * @param aWindow * Window reference * @param aTab * TabPanel reference */ onTabClose: function sss_onTabClose(aWindow, aTab) { // notify the tabbrowser that the tab state will be retrieved for the last time // (so that extension authors can easily set data on soon-to-be-closed tabs) var event = aWindow.document.createEvent("Events"); event.initEvent("SSTabClosing", true, false); aTab.dispatchEvent(event); var maxTabsUndo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo"); // don't update our internal state if we don't have to if (maxTabsUndo == 0) { return; } // make sure that the tab related data is up-to-date var tabState = this._collectTabData(aTab); this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState); // store closed-tab data for undo if (tabState.entries.length > 0) { let tabTitle = aTab.label; let tabbrowser = aWindow.gBrowser; tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab); this._windows[aWindow.__SSi]._closedTabs.unshift({ state: tabState, title: tabTitle, image: aTab.getAttribute("image"), pos: aTab._tPos }); var length = this._windows[aWindow.__SSi]._closedTabs.length; if (length > maxTabsUndo) this._windows[aWindow.__SSi]._closedTabs.splice(maxTabsUndo, length - maxTabsUndo); } }, /** * When a tab loads, save state. * @param aWindow * Window reference * @param aPanel * TabPanel reference * @param aEvent * Event obj */ onTabLoad: function sss_onTabLoad(aWindow, aPanel, aEvent) { // react on "load" and solitary "pageshow" events (the first "pageshow" // following "load" is too late for deleting the data caches) if (aEvent.type != "load" && !aEvent.persisted) { return; } delete aPanel.__SS_data; this.saveStateDelayed(aWindow); // attempt to update the current URL we send in a crash report this._updateCrashReportURL(aWindow); }, /** * Called when a tabpanel sends the "input" notification * @param aWindow * Window reference * @param aPanel * TabPanel reference */ onTabInput: function sss_onTabInput(aWindow, aPanel) { if (aPanel.__SS_data) delete aPanel.__SS_data._formDataSaved; this.saveStateDelayed(aWindow, 3000); }, /** * Called when a tabpanel sends a "scroll" notification * @param aWindow * Window reference */ onTabScroll: function sss_onTabScroll(aWindow) { this.saveStateDelayed(aWindow, 3000); }, /** * When a tab is selected, save session data * @param aWindow * Window reference * @param aPanels * TabPanel reference */ onTabSelect: function sss_onTabSelect(aWindow, aPanels) { if (this._loadState == STATE_RUNNING) { this._windows[aWindow.__SSi].selected = aPanels.selectedIndex; this.saveStateDelayed(aWindow); // attempt to update the current URL we send in a crash report this._updateCrashReportURL(aWindow); } }, /* ........ nsISessionStore API .............. */ getBrowserState: function sss_getBrowserState() { return this._toJSONString(this._getCurrentState()); }, setBrowserState: function sss_setBrowserState(aState) { try { var state = this._safeEval("(" + aState + ")"); } catch (ex) { /* invalid state object - don't restore anything */ } if (!state || !state.windows) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); var window = this._getMostRecentBrowserWindow(); if (!window) { this._openWindowWithState(state); return; } // close all other browser windows this._forEachBrowserWindow(function(aWindow) { if (aWindow != window) { aWindow.close(); } }); // make sure closed window data isn't kept this._closedWindows = []; // restore to the given state this.restoreWindow(window, state, true); }, getWindowState: function sss_getWindowState(aWindow) { if (!aWindow.__SSi && !aWindow.__SS_dyingCache) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); if (!aWindow.__SSi) return this._toJSONString({ windows: [aWindow.__SS_dyingCache] }); return this._toJSONString(this._getWindowState(aWindow)); }, setWindowState: function sss_setWindowState(aWindow, aState, aOverwrite) { if (!aWindow.__SSi) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite); }, getTabState: function sss_getTabState(aTab) { if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); var tabState = this._collectTabData(aTab); var window = aTab.ownerDocument.defaultView; this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState); return this._toJSONString(tabState); }, setTabState: function sss_setTabState(aTab, aState) { var tabState = this._safeEval("(" + aState + ")"); if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); var window = aTab.ownerDocument.defaultView; this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0); }, duplicateTab: function sss_duplicateTab(aWindow, aTab) { if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi || !aWindow.getBrowser) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); var tabState = this._collectTabData(aTab, true); var sourceWindow = aTab.ownerDocument.defaultView; this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true); var newTab = aWindow.getBrowser().addTab(); this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0); return newTab; }, getClosedTabCount: function sss_getClosedTabCount(aWindow) { if (!aWindow.__SSi && aWindow.__SS_dyingCache) return aWindow.__SS_dyingCache._closedTabs.length; if (!aWindow.__SSi) // XXXzeniko shouldn't we throw here? return 0; // not a browser window, or not otherwise tracked by SS. return this._windows[aWindow.__SSi]._closedTabs.length; }, getClosedTabData: function sss_getClosedTabDataAt(aWindow) { if (!aWindow.__SSi && !aWindow.__SS_dyingCache) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); if (!aWindow.__SSi) return this._toJSONString(aWindow.__SS_dyingCache._closedTabs); return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs); }, undoCloseTab: function sss_undoCloseTab(aWindow, aIndex) { if (!aWindow.__SSi) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); var closedTabs = this._windows[aWindow.__SSi]._closedTabs; // default to the most-recently closed tab aIndex = aIndex || 0; if (!(aIndex in closedTabs)) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); // fetch the data of closed tab, while removing it from the array let closedTab = closedTabs.splice(aIndex, 1).shift(); let closedTabState = closedTab.state; // create a new tab let browser = aWindow.gBrowser; let tab = browser.addTab(); // restore the tab's position browser.moveTabTo(tab, closedTab.pos); // restore tab content this.restoreHistoryPrecursor(aWindow, [tab], [closedTabState], 1, 0, 0); // focus the tab's content area let content = browser.getBrowserForTab(tab).contentWindow; aWindow.setTimeout(function() { content.focus(); }, 0); return tab; }, getClosedWindowCount: function sss_getClosedWindowCount() { return this._closedWindows.length; }, getClosedWindowData: function sss_getClosedWindowData() { return this._toJSONString(this._closedWindows); }, undoCloseWindow: function sss_undoCloseWindow(aIndex) { // default to the most-recently closed window aIndex = aIndex || 0; if (!aIndex in this._closedWindows) throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); // reopen the window let state = { windows: this._closedWindows.splice(aIndex, 1) }; let window = this._openWindowWithState(state); this.windowToFocus = window; return window; }, getWindowValue: function sss_getWindowValue(aWindow, aKey) { if (aWindow.__SSi) { var data = this._windows[aWindow.__SSi].extData || {}; return data[aKey] || ""; } if (aWindow.__SS_dyingCache) { data = aWindow.__SS_dyingCache.extData || {}; return data[aKey] || ""; } throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); }, setWindowValue: function sss_setWindowValue(aWindow, aKey, aStringValue) { if (aWindow.__SSi) { if (!this._windows[aWindow.__SSi].extData) { this._windows[aWindow.__SSi].extData = {}; } this._windows[aWindow.__SSi].extData[aKey] = aStringValue; this.saveStateDelayed(aWindow); } else { throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); } }, deleteWindowValue: function sss_deleteWindowValue(aWindow, aKey) { if (aWindow.__SSi && this._windows[aWindow.__SSi].extData && this._windows[aWindow.__SSi].extData[aKey]) delete this._windows[aWindow.__SSi].extData[aKey]; else throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); }, getTabValue: function sss_getTabValue(aTab, aKey) { var data = aTab.__SS_extdata || {}; return data[aKey] || ""; }, setTabValue: function sss_setTabValue(aTab, aKey, aStringValue) { if (!aTab.__SS_extdata) { aTab.__SS_extdata = {}; } aTab.__SS_extdata[aKey] = aStringValue; this.saveStateDelayed(aTab.ownerDocument.defaultView); }, deleteTabValue: function sss_deleteTabValue(aTab, aKey) { if (aTab.__SS_extdata && aTab.__SS_extdata[aKey]) delete aTab.__SS_extdata[aKey]; else throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG); }, persistTabAttribute: function sss_persistTabAttribute(aName) { if (this.xulAttributes.indexOf(aName) != -1) return; // this attribute is already being tracked this.xulAttributes.push(aName); this.saveStateDelayed(); }, /* ........ Saving Functionality .............. */ /** * Store all session data for a window * @param aWindow * Window reference */ _saveWindowHistory: function sss_saveWindowHistory(aWindow) { var tabbrowser = aWindow.getBrowser(); var tabs = tabbrowser.mTabs; var tabsData = this._windows[aWindow.__SSi].tabs = []; for (var i = 0; i < tabs.length; i++) tabsData.push(this._collectTabData(tabs[i])); this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1; }, /** * Collect data related to a single tab * @param aTab * tabbrowser tab * @param aFullData * always return privacy sensitive data (use with care) * @returns object */ _collectTabData: function sss_collectTabData(aTab, aFullData) { var tabData = { entries: [] }; var browser = aTab.linkedBrowser; if (!browser || !browser.currentURI) // can happen when calling this function right after .addTab() return tabData; else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tabStillLoading) // use the data to be restored when the tab hasn't been completely loaded return browser.parentNode.__SS_data; var history = null; try { history = browser.sessionHistory; } catch (ex) { } // this could happen if we catch a tab during (de)initialization // XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse // data even when we shouldn't (e.g. Back, different anchor) if (history && browser.parentNode.__SS_data && browser.parentNode.__SS_data.entries[history.index] && history.index < this._sessionhistory_max_entries - 1 && !aFullData) { tabData = browser.parentNode.__SS_data; tabData.index = history.index + 1; } else if (history && history.count > 0) { for (var j = 0; j < history.count; j++) tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false), aFullData)); tabData.index = history.index + 1; // make sure not to cache privacy sensitive data which shouldn't get out if (!aFullData) browser.parentNode.__SS_data = tabData; } else if (browser.currentURI.spec != "about:blank" || browser.contentDocument.body.hasChildNodes()) { tabData.entries[0] = { url: browser.currentURI.spec }; tabData.index = 1; } var disallow = []; for (var i = 0; i < CAPABILITIES.length; i++) if (!browser.docShell["allow" + CAPABILITIES[i]]) disallow.push(CAPABILITIES[i]); if (disallow.length > 0) tabData.disallow = disallow.join(","); else if (tabData.disallow) delete tabData.disallow; if (this.xulAttributes.length > 0) { tabData.attributes = {}; Array.forEach(aTab.attributes, function(aAttr) { if (this.xulAttributes.indexOf(aAttr.name) > -1) tabData.attributes[aAttr.name] = aAttr.value; }, this); } if (aTab.__SS_extdata) tabData.extData = aTab.__SS_extdata; else if (tabData.extData) delete tabData.extData; if (history && browser.docShell instanceof Ci.nsIDocShell) this._serializeSessionStorage(tabData, history, browser.docShell, aFullData); return tabData; }, /** * Get an object that is a serialized representation of a History entry * Used for data storage * @param aEntry * nsISHEntry instance * @param aFullData * always return privacy sensitive data (use with care) * @returns object */ _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) { var entry = { url: aEntry.URI.spec }; if (aEntry.title && aEntry.title != entry.url) { entry.title = aEntry.title; } if (aEntry.isSubFrame) { entry.subframe = true; } if (!(aEntry instanceof Ci.nsISHEntry)) { return entry; } var cacheKey = aEntry.cacheKey; if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0) { // XXXbz would be better to have cache keys implement // nsISerializable or something. entry.cacheKey = cacheKey.data; } entry.ID = aEntry.ID; if (aEntry.contentType) entry.contentType = aEntry.contentType; var x = {}, y = {}; aEntry.getScrollPosition(x, y); if (x.value != 0 || y.value != 0) entry.scroll = x.value + "," + y.value; try { var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata"); if (aEntry.postData && (aFullData || prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) { aEntry.postData.QueryInterface(Ci.nsISeekableStream). seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); var stream = Cc["@mozilla.org/binaryinputstream;1"]. createInstance(Ci.nsIBinaryInputStream); stream.setInputStream(aEntry.postData); var postBytes = stream.readByteArray(stream.available()); var postdata = String.fromCharCode.apply(null, postBytes); if (aFullData || prefPostdata == -1 || postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <= prefPostdata) { // We can stop doing base64 encoding once our serialization into JSON // is guaranteed to handle all chars in strings, including embedded // nulls. entry.postdata_b64 = btoa(postdata); } } } catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right if (aEntry.owner) { // Not catching anything specific here, just possible errors // from writeCompoundObject and the like. try { var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"]. createInstance(Ci.nsIObjectOutputStream); var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); pipe.init(false, false, 0, 0xffffffff, null); binaryStream.setOutputStream(pipe.outputStream); binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true); binaryStream.close(); // Now we want to read the data from the pipe's input end and encode it. var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"]. createInstance(Ci.nsIBinaryInputStream); scriptableStream.setInputStream(pipe.inputStream); var ownerBytes = scriptableStream.readByteArray(scriptableStream.available()); // We can stop doing base64 encoding once our serialization into JSON // is guaranteed to handle all chars in strings, including embedded // nulls. entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes)); } catch (ex) { debug(ex); } } if (!(aEntry instanceof Ci.nsISHContainer)) { return entry; } if (aEntry.childCount > 0) { entry.children = []; for (var i = 0; i < aEntry.childCount; i++) { var child = aEntry.GetChildAt(i); if (child) { entry.children.push(this._serializeHistoryEntry(child, aFullData)); } else { // to maintain the correct frame order, insert a dummy entry entry.children.push({ url: "about:blank" }); } // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595) if (/^wyciwyg:\/\//.test(entry.children[i].url)) { delete entry.children; break; } } } return entry; }, /** * Updates all sessionStorage "super cookies" * @param aTabData * The data object for a specific tab * @param aHistory * That tab's session history * @param aDocShell * That tab's docshell (containing the sessionStorage) * @param aFullData * always return privacy sensitive data (use with care) */ _serializeSessionStorage: function sss_serializeSessionStorage(aTabData, aHistory, aDocShell, aFullData) { let storageData = {}; let hasContent = false; aDocShell.QueryInterface(Ci.nsIDocShell_MOZILLA_1_9_1_SessionStorage); for (let i = 0; i < aHistory.count; i++) { let uri = aHistory.getEntryAtIndex(i, false).URI; // sessionStorage is saved per origin (cf. nsDocShell::GetSessionStorageForURI) let domain = uri.spec; try { if (uri.host) domain = uri.prePath; } catch (ex) { /* this throws for host-less URIs (such as about: or jar:) */ } if (storageData[domain] || !(aFullData || this._checkPrivacyLevel(uri.schemeIs("https")))) continue; let storage, storageItemCount = 0; try { var principal = Cc["@mozilla.org/scriptsecuritymanager;1"]. getService(Ci.nsIScriptSecurityManager). getCodebasePrincipal(uri); // Using getSessionStorageForPrincipal instead of getSessionStorageForURI // just to be able to pass aCreate = false, that avoids creation of the // sessionStorage object for the page earlier than the page really // requires it. It was causing problems while accessing a storage when // a page later changed its domain. storage = aDocShell.getSessionStorageForPrincipal(principal, false); if (storage) storageItemCount = storage.length; } catch (ex) { /* sessionStorage might throw if it's turned off, see bug 458954 */ } if (storageItemCount == 0) continue; let data = storageData[domain] = {}; for (let j = 0; j < storageItemCount; j++) { try { let key = storage.key(j); let item = storage.getItem(key); data[key] = item; } catch (ex) { /* XXXzeniko this currently throws for secured items (cf. bug 442048) */ } } hasContent = true; } if (hasContent) aTabData.storage = storageData; }, /** * go through all tabs and store the current scroll positions * and innerHTML content of WYSIWYG editors * @param aWindow * Window reference */ _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) { var browsers = aWindow.getBrowser().browsers; for (var i = 0; i < browsers.length; i++) { try { var tabData = this._windows[aWindow.__SSi].tabs[i]; if (browsers[i].parentNode.__SS_data && browsers[i].parentNode.__SS_data._tabStillLoading) continue; // ignore incompletely initialized tabs this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData); } catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time) } }, /** * go through all frames and store the current scroll positions * and innerHTML content of WYSIWYG editors * @param aWindow * Window reference * @param aBrowser * single browser reference * @param aTabData * tabData object to add the information to * @param aFullData * always return privacy sensitive data (use with care) */ _updateTextAndScrollDataForTab: function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) { var tabIndex = (aTabData.index || aTabData.entries.length) - 1; // entry data needn't exist for tabs just initialized with an incomplete session state if (!aTabData.entries[tabIndex]) return; let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" : this._getSelectedPageStyle(aBrowser.contentWindow); if (selectedPageStyle) aTabData.pageStyle = selectedPageStyle; else if (aTabData.pageStyle) delete aTabData.pageStyle; this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow, aTabData.entries[tabIndex], !aTabData._formDataSaved, aFullData); aTabData._formDataSaved = true; if (aBrowser.currentURI.spec == "about:config") aTabData.entries[tabIndex].formdata = { "#textbox": aBrowser.contentDocument.getElementById("textbox").wrappedJSObject.value }; }, /** * go through all subframes and store all form data, the current * scroll positions and innerHTML content of WYSIWYG editors * @param aWindow * Window reference * @param aContent * frame reference * @param aData * part of a tabData object to add the information to * @param aUpdateFormData * update all form data for this tab * @param aFullData * always return privacy sensitive data (use with care) */ _updateTextAndScrollDataForFrame: function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData, aUpdateFormData, aFullData) { for (var i = 0; i < aContent.frames.length; i++) { if (aData.children && aData.children[i]) this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i], aData.children[i], aUpdateFormData, aFullData); } var isHTTPS = this._getURIFromString((aContent.parent || aContent). document.location.href).schemeIs("https"); if (aFullData || this._checkPrivacyLevel(isHTTPS) || aContent.top.document.location.href == "about:sessionrestore") { if (aFullData || aUpdateFormData) { let formData = this._collectFormDataForFrame(aContent.document); if (formData) aData.formdata = formData; else if (aData.formdata) delete aData.formdata; } // designMode is undefined e.g. for XUL documents (as about:config) if ((aContent.document.designMode || "") == "on") { if (aData.innerHTML === undefined && !aFullData) { // we get no "input" events from iframes - listen for keypress here let _this = this; aContent.addEventListener("keypress", function(aEvent) { _this.saveStateDelayed(aWindow, 3000); }, true); } aData.innerHTML = aContent.document.body.innerHTML; } } // get scroll position from nsIDOMWindowUtils, since it allows avoiding a // flush of layout let domWindowUtils = aContent.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); let scrollX = {}, scrollY = {}; domWindowUtils.getScrollXY(false, scrollX, scrollY); aData.scroll = scrollX.value + "," + scrollY.value; }, /** * determine the title of the currently enabled style sheet (if any) * and recurse through the frameset if necessary * @param aContent is a frame reference * @returns the title style sheet determined to be enabled (empty string if none) */ _getSelectedPageStyle: function sss_getSelectedPageStyle(aContent) { const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i; for (let i = 0; i < aContent.document.styleSheets.length; i++) { let ss = aContent.document.styleSheets[i]; let media = ss.media.mediaText; if (!ss.disabled && ss.title && (!media || forScreen.test(media))) return ss.title } for (let i = 0; i < aContent.frames.length; i++) { let selectedPageStyle = this._getSelectedPageStyle(aContent.frames[i]); if (selectedPageStyle) return selectedPageStyle; } return ""; }, /** * collect the state of all form elements * @param aDocument * document reference */ _collectFormDataForFrame: function sss_collectFormDataForFrame(aDocument) { let formNodes = aDocument.evaluate(XPathHelper.restorableFormNodes, aDocument, XPathHelper.resolveNS, Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); let node = formNodes.iterateNext(); if (!node) return null; const MAX_GENERATED_XPATHS = 100; let generatedCount = 0; let data = {}; do { // Only generate a limited number of XPath expressions for perf reasons (cf. bug 477564) if (!node.id && ++generatedCount > MAX_GENERATED_XPATHS) continue; let id = node.id ? "#" + node.id : XPathHelper.generate(node); if (node instanceof Ci.nsIDOMHTMLInputElement) { if (node.type != "file") data[id] = node.type == "checkbox" || node.type == "radio" ? node.checked : node.value; else data[id] = { type: "file", value: node.value }; } else if (node instanceof Ci.nsIDOMHTMLTextAreaElement) data[id] = node.value; else if (!node.multiple) data[id] = node.selectedIndex; else { let options = Array.map(node.options, function(aOpt, aIx) aOpt.selected ? aIx : -1); data[id] = options.filter(function(aIx) aIx >= 0); } } while ((node = formNodes.iterateNext())); return data; }, /** * store all hosts for a URL * @param aWindow * Window reference */ _updateCookieHosts: function sss_updateCookieHosts(aWindow) { var hosts = this._windows[aWindow.__SSi]._hosts = {}; // get all possible subdomain levels for a given URL var _this = this; function extractHosts(aEntry) { if (/^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.test(aEntry.url) && !hosts[RegExp.$1] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) { var host = RegExp.$1; var ix; for (ix = host.indexOf(".") + 1; ix; ix = host.indexOf(".", ix) + 1) { hosts[host.substr(ix)] = true; } hosts[host] = true; } else if (/^file:\/\/([^\/]*)/.test(aEntry.url)) { hosts[RegExp.$1] = true; } if (aEntry.children) { aEntry.children.forEach(extractHosts); } } this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); }); }, /** * Serialize cookie data * @param aWindows * array of Window references */ _updateCookies: function sss_updateCookies(aWindows) { var cookiesEnum = Cc["@mozilla.org/cookiemanager;1"]. getService(Ci.nsICookieManager).enumerator; // collect the cookies per window for (var i = 0; i < aWindows.length; i++) aWindows[i].cookies = []; // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision var MAX_EXPIRY = Math.pow(2, 62); while (cookiesEnum.hasMoreElements()) { var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2); if (cookie.isSession && this._checkPrivacyLevel(cookie.isSecure)) { var jscookie = null; aWindows.forEach(function(aWindow) { if (aWindow._hosts && aWindow._hosts[cookie.rawHost]) { // serialize the cookie when it's first needed if (!jscookie) { jscookie = { host: cookie.host, value: cookie.value }; // only add attributes with non-default values (saving a few bits) if (cookie.path) jscookie.path = cookie.path; if (cookie.name) jscookie.name = cookie.name; if (cookie.isSecure) jscookie.secure = true; if (cookie.isHttpOnly) jscookie.httponly = true; if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry; } aWindow.cookies.push(jscookie); } }); } } // don't include empty cookie sections for (i = 0; i < aWindows.length; i++) if (aWindows[i].cookies.length == 0) delete aWindows[i].cookies; }, /** * Store window dimensions, visibility, sidebar * @param aWindow * Window reference */ _updateWindowFeatures: function sss_updateWindowFeatures(aWindow) { var winData = this._windows[aWindow.__SSi]; WINDOW_ATTRIBUTES.forEach(function(aAttr) { winData[aAttr] = this._getWindowDimension(aWindow, aAttr); }, this); var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) { return aWindow[aItem] && !aWindow[aItem].visible; }); if (hidden.length != 0) winData.hidden = hidden.join(","); else if (winData.hidden) delete winData.hidden; var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand"); if (sidebar) winData.sidebar = sidebar; else if (winData.sidebar) delete winData.sidebar; }, /** * serialize session data as Ini-formatted string * @param aUpdateAll * Bool update all windows * @returns string */ _getCurrentState: function sss_getCurrentState(aUpdateAll) { var activeWindow = this._getMostRecentBrowserWindow(); if (this._loadState == STATE_RUNNING) { // update the data for all windows with activities since the last save operation this._forEachBrowserWindow(function(aWindow) { if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore return; if (aUpdateAll || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) { this._collectWindowData(aWindow); } else { // always update the window features (whose change alone never triggers a save operation) this._updateWindowFeatures(aWindow); } }, this); this._dirtyWindows = []; } // collect the data for all windows var total = [], windows = []; var nonPopupCount = 0; var ix; for (ix in this._windows) { total.push(this._windows[ix]); windows.push(ix); if (!this._windows[ix].isPopup) nonPopupCount++; } this._updateCookies(total); // collect the data for all windows yet to be restored for (ix in this._statesToRestore) { for each (let winData in this._statesToRestore[ix].windows) { total.push(winData); if (!winData.isPopup) nonPopupCount++; } } // shallow copy this._closedWindows to preserve current state let lastClosedWindowsCopy = this._closedWindows.slice(); //@line 1659 "/tmp/buildd/iceweasel-3.5.16/browser/components/sessionstore/src/nsSessionStore.js" // if no non-popup browser window remains open, return the state of the last closed window(s) if (nonPopupCount == 0 && lastClosedWindowsCopy.length > 0) { // prepend the last non-popup browser window, so that if the user loads more tabs // at startup we don't accidentally add them to a popup window do { total.unshift(lastClosedWindowsCopy.shift()) } while (total[0].isPopup) } //@line 1668 "/tmp/buildd/iceweasel-3.5.16/browser/components/sessionstore/src/nsSessionStore.js" if (activeWindow) { this.activeWindowSSiCache = activeWindow.__SSi || ""; } ix = this.activeWindowSSiCache ? windows.indexOf(this.activeWindowSSiCache) : -1; return { windows: total, selectedWindow: ix + 1, _closedWindows: lastClosedWindowsCopy }; }, /** * serialize session data for a window * @param aWindow * Window reference * @returns string */ _getWindowState: function sss_getWindowState(aWindow) { if (!this._isWindowLoaded(aWindow)) return this._statesToRestore[aWindow.__SS_restoreID]; if (this._loadState == STATE_RUNNING) { this._collectWindowData(aWindow); } var total = [this._windows[aWindow.__SSi]]; this._updateCookies(total); return { windows: total }; }, _collectWindowData: function sss_collectWindowData(aWindow) { if (!this._isWindowLoaded(aWindow)) return; // update the internal state data for this window this._saveWindowHistory(aWindow); this._updateTextAndScrollData(aWindow); this._updateCookieHosts(aWindow); this._updateWindowFeatures(aWindow); this._dirtyWindows[aWindow.__SSi] = false; }, /* ........ Restoring Functionality .............. */ /** * restore features to a single window * @param aWindow * Window reference * @param aState * JS object or its eval'able source * @param aOverwriteTabs * bool overwrite existing tabs w/ new ones * @param aFollowUp * bool this isn't the restoration of the first window */ restoreWindow: function sss_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) { if (!aFollowUp) { this.windowToFocus = aWindow; } // initialize window if necessary if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi])) this.onLoad(aWindow); try { var root = typeof aState == "string" ? this._safeEval(aState) : aState; if (!root.windows[0]) { this._notifyIfAllWindowsRestored(); return; // nothing to restore } } catch (ex) { // invalid state object - don't restore anything debug(ex); this._notifyIfAllWindowsRestored(); return; } if (root._closedWindows) this._closedWindows = root._closedWindows; var winData; if (!aState.selectedWindow) { aState.selectedWindow = 0; } // open new windows for all further window entries of a multi-window session // (unless they don't contain any tab data) for (var w = 1; w < root.windows.length; w++) { winData = root.windows[w]; if (winData && winData.tabs && winData.tabs[0]) { var window = this._openWindowWithState({ windows: [winData] }); if (w == aState.selectedWindow - 1) { this.windowToFocus = window; } } } winData = root.windows[0]; if (!winData.tabs) { winData.tabs = []; } // don't restore a single blank tab when we've had an external // URL passed in for loading at startup (cf. bug 357419) else if (root._firstTabs && !aOverwriteTabs && winData.tabs.length == 1 && (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) { winData.tabs = []; } var tabbrowser = aWindow.gBrowser; var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1; var newTabCount = winData.tabs.length; var tabs = []; // disable smooth scrolling while adding, moving, removing and selecting tabs var tabstrip = tabbrowser.tabContainer.mTabstrip; var smoothScroll = tabstrip.smoothScroll; tabstrip.smoothScroll = false; // make sure that the selected tab won't be closed in order to // prevent unnecessary flickering if (aOverwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount) tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1); for (var t = 0; t < newTabCount; t++) { tabs.push(t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab()); // when resuming at startup: add additionally requested pages to the end if (!aOverwriteTabs && root._firstTabs) { tabbrowser.moveTabTo(tabs[t], t); } } // when overwriting tabs, remove all superflous ones if (aOverwriteTabs && newTabCount < openTabCount) { Array.slice(tabbrowser.mTabs, newTabCount, openTabCount) .forEach(tabbrowser.removeTab, tabbrowser); } if (aOverwriteTabs) { this.restoreWindowFeatures(aWindow, winData); } if (winData.cookies) { this.restoreCookies(winData.cookies); } if (winData.extData) { if (aOverwriteTabs || !this._windows[aWindow.__SSi].extData) { this._windows[aWindow.__SSi].extData = {}; } for (var key in winData.extData) { this._windows[aWindow.__SSi].extData[key] = winData.extData[key]; } } if (winData._closedTabs && (root._firstTabs || aOverwriteTabs)) { this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs; } this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs, (aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0); // set smoothScroll back to the original value tabstrip.smoothScroll = smoothScroll; this._notifyIfAllWindowsRestored(); }, /** * Manage history restoration for a window * @param aWindow * Window to restore the tabs into * @param aTabs * Array of tab references * @param aTabData * Array of tab data * @param aSelectTab * Index of selected tab * @param aIx * Index of the next tab to check readyness for * @param aCount * Counter for number of times delaying b/c browser or history aren't ready */ restoreHistoryPrecursor: function sss_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount) { var tabbrowser = aWindow.getBrowser(); // make sure that all browsers and their histories are available // - if one's not, resume this check in 100ms (repeat at most 10 times) for (var t = aIx; t < aTabs.length; t++) { try { if (!tabbrowser.getBrowserForTab(aTabs[t]).webNavigation.sessionHistory) { throw new Error(); } } catch (ex) { // in case browser or history aren't ready yet if (aCount < 10) { var restoreHistoryFunc = function(self) { self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount + 1); } aWindow.setTimeout(restoreHistoryFunc, 100, this); return; } } } // mark the tabs as loading for (t = 0; t < aTabs.length; t++) { var tab = aTabs[t]; var browser = tabbrowser.getBrowserForTab(tab); aTabData[t]._tabStillLoading = true; if (!aTabData[t].entries || aTabData[t].entries.length == 0) { // make sure to blank out this tab's content // (just purging the tab's history won't be enough) browser.contentDocument.location = "about:blank"; continue; } browser.stop(); // in case about:blank isn't done yet tab.setAttribute("busy", "true"); tabbrowser.updateIcon(tab); tabbrowser.setTabTitleLoading(tab); // wall-paper fix for bug 439675: make sure that the URL to be loaded // is always visible in the address bar let activeIndex = (aTabData[t].index || aTabData[t].entries.length) - 1; let activePageData = aTabData[t].entries[activeIndex] || null; browser.userTypedValue = activePageData ? activePageData.url || null : null; // keep the data around to prevent dataloss in case // a tab gets closed before it's been properly restored browser.parentNode.__SS_data = aTabData[t]; } if (aTabs.length > 0) { // Determine if we can optimize & load visible tabs first let tabScrollBoxObject = tabbrowser.tabContainer.mTabstrip.scrollBoxObject; let tabBoxObject = aTabs[0].boxObject; let maxVisibleTabs = Math.ceil(tabScrollBoxObject.width / tabBoxObject.width); // make sure we restore visible tabs first, if there are enough if (maxVisibleTabs < aTabs.length && aSelectTab > 1) { let firstVisibleTab = 0; if (aTabs.length - maxVisibleTabs > aSelectTab) { // aSelectTab is leftmost since we scroll to it when possible firstVisibleTab = aSelectTab - 1; } else { // aSelectTab is rightmost or no more room to scroll right firstVisibleTab = aTabs.length - maxVisibleTabs; } aTabs = aTabs.splice(firstVisibleTab, maxVisibleTabs).concat(aTabs); aTabData = aTabData.splice(firstVisibleTab, maxVisibleTabs).concat(aTabData); aSelectTab -= firstVisibleTab; } // make sure to restore the selected tab first (if any) if (aSelectTab-- && aTabs[aSelectTab]) { aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]); aTabData.unshift(aTabData.splice(aSelectTab, 1)[0]); tabbrowser.selectedTab = aTabs[0]; } } if (!this._isWindowLoaded(aWindow)) { // from now on, the data will come from the actual window delete this._statesToRestore[aWindow.__SS_restoreID]; delete aWindow.__SS_restoreID; } // helper hash for ensuring unique frame IDs var idMap = { used: {} }; this.restoreHistory(aWindow, aTabs, aTabData, idMap); }, /** * Restory history for a window * @param aWindow * Window reference * @param aTabs * Array of tab references * @param aTabData * Array of tab data * @param aIdMap * Hash for ensuring unique frame IDs */ restoreHistory: function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap) { var _this = this; while (aTabs.length > 0 && (!aTabData[0]._tabStillLoading || !aTabs[0].parentNode)) { aTabs.shift(); // this tab got removed before being completely restored aTabData.shift(); } if (aTabs.length == 0) { return; // no more tabs to restore } var tab = aTabs.shift(); var tabData = aTabData.shift(); var browser = aWindow.getBrowser().getBrowserForTab(tab); var history = browser.webNavigation.sessionHistory; if (history.count > 0) { history.PurgeHistory(history.count); } history.QueryInterface(Ci.nsISHistoryInternal); if (!tabData.entries) { tabData.entries = []; } if (tabData.extData) { tab.__SS_extdata = {}; for (let key in tabData.extData) tab.__SS_extdata[key] = tabData.extData[key]; } else delete tab.__SS_extdata; for (var i = 0; i < tabData.entries.length; i++) { history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true); } // make sure to reset the capabilities and attributes, in case this tab gets reused var disallow = (tabData.disallow)?tabData.disallow.split(","):[]; CAPABILITIES.forEach(function(aCapability) { browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1; }); Array.filter(tab.attributes, function(aAttr) { return (_this.xulAttributes.indexOf(aAttr.name) > -1); }).forEach(tab.removeAttribute, tab); if (tabData.xultab) { // restore attributes from the legacy Firefox 2.0/3.0 format tabData.xultab.split(" ").forEach(function(aAttr) { if (/^([^\s=]+)=(.*)/.test(aAttr)) { tab.setAttribute(RegExp.$1, decodeURI(RegExp.$2)); } }); } for (let name in tabData.attributes) tab.setAttribute(name, tabData.attributes[name]); if (tabData.storage && browser.docShell instanceof Ci.nsIDocShell) this._deserializeSessionStorage(tabData.storage, browser.docShell); // notify the tabbrowser that the tab chrome has been restored var event = aWindow.document.createEvent("Events"); event.initEvent("SSTabRestoring", true, false); tab.dispatchEvent(event); let activeIndex = (tabData.index || tabData.entries.length) - 1; if (activeIndex >= tabData.entries.length) activeIndex = tabData.entries.length - 1; try { if (activeIndex >= 0) browser.webNavigation.gotoIndex(activeIndex); } catch (ex) { // ignore page load errors tab.removeAttribute("busy"); } if (tabData.entries.length > 0) { // restore those aspects of the currently active documents // which are not preserved in the plain history entries // (mainly scroll state and text data) browser.__SS_restore_data = tabData.entries[activeIndex] || {}; browser.__SS_restore_text = tabData.text || ""; browser.__SS_restore_pageStyle = tabData.pageStyle || ""; browser.__SS_restore_tab = tab; browser.__SS_restore = this.restoreDocument_proxy; browser.addEventListener("load", browser.__SS_restore, true); } aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap); }, 0); }, /** * expands serialized history data into a session-history-entry instance * @param aEntry * Object containing serialized history data for a URL * @param aIdMap * Hash for ensuring unique frame IDs * @returns nsISHEntry */ _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) { var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"]. createInstance(Ci.nsISHEntry); var ioService = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService); shEntry.setURI(ioService.newURI(aEntry.url, null, null)); shEntry.setTitle(aEntry.title || aEntry.url); if (aEntry.subframe) shEntry.setIsSubFrame(aEntry.subframe || false); shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory; if (aEntry.contentType) shEntry.contentType = aEntry.contentType; if (aEntry.cacheKey) { var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"]. createInstance(Ci.nsISupportsPRUint32); cacheKey.data = aEntry.cacheKey; shEntry.cacheKey = cacheKey; } if (aEntry.ID) { // get a new unique ID for this frame (since the one from the last // start might already be in use) var id = aIdMap[aEntry.ID] || 0; if (!id) { for (id = Date.now(); id in aIdMap.used; id++); aIdMap[aEntry.ID] = id; aIdMap.used[id] = true; } shEntry.ID = id; } if (aEntry.scroll) { var scrollPos = (aEntry.scroll || "0,0").split(","); scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0]; shEntry.setScrollPosition(scrollPos[0], scrollPos[1]); } var postdata; if (aEntry.postdata_b64) { // Firefox 3 postdata = atob(aEntry.postdata_b64); } else if (aEntry.postdata) { // Firefox 2 postdata = aEntry.postdata; } if (postdata) { var stream = Cc["@mozilla.org/io/string-input-stream;1"]. createInstance(Ci.nsIStringInputStream); stream.setData(postdata, postdata.length); shEntry.postData = stream; } if (aEntry.owner_b64) { // Firefox 3 var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"]. createInstance(Ci.nsIStringInputStream); var binaryData = atob(aEntry.owner_b64); ownerInput.setData(binaryData, binaryData.length); var binaryStream = Cc["@mozilla.org/binaryinputstream;1"]. createInstance(Ci.nsIObjectInputStream); binaryStream.setInputStream(ownerInput); try { // Catch possible deserialization exceptions shEntry.owner = binaryStream.readObject(true); } catch (ex) { debug(ex); } } else if (aEntry.ownerURI) { // Firefox 2 var uriObj = ioService.newURI(aEntry.ownerURI, null, null); shEntry.owner = Cc["@mozilla.org/scriptsecuritymanager;1"]. getService(Ci.nsIScriptSecurityManager). getCodebasePrincipal(uriObj); } if (aEntry.children && shEntry instanceof Ci.nsISHContainer) { for (var i = 0; i < aEntry.children.length; i++) { shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i); } } return shEntry; }, /** * restores all sessionStorage "super cookies" * @param aStorageData * Storage data to be restored * @param aDocShell * A tab's docshell (containing the sessionStorage) */ _deserializeSessionStorage: function sss_deserializeSessionStorage(aStorageData, aDocShell) { let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); for (let url in aStorageData) { let uri = ioService.newURI(url, null, null); let docShell_191 = aDocShell.QueryInterface(Ci.nsIDocShell_MOZILLA_1_9_1_SessionStorage); let storage = docShell_191.getSessionStorageForURI(uri); for (let key in aStorageData[url]) { try { storage.setItem(key, aStorageData[url][key]); } catch (ex) { Cu.reportError(ex); } // throws e.g. for URIs that can't have sessionStorage } } }, /** * Restore properties to a loaded document */ restoreDocument_proxy: function sss_restoreDocument_proxy(aEvent) { // wait for the top frame to be loaded completely if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView || aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) { return; } // always call this before injecting content into a document! function hasExpectedURL(aDocument, aURL) !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, ""); // restore text data saved by Firefox 2.0/3.0 var textArray = this.__SS_restore_text ? this.__SS_restore_text.split(" ") : []; function restoreTextData(aContent, aPrefix, aURL) { textArray.forEach(function(aEntry) { if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry) && RegExp.$1 == aPrefix && hasExpectedURL(aContent.document, aURL)) { var document = aContent.document; var node = RegExp.$2 ? document.getElementById(RegExp.$3) : document.getElementsByName(RegExp.$3)[0] || null; if (node && "value" in node && node.type != "file") { node.value = decodeURI(RegExp.$4); var event = document.createEvent("UIEvents"); event.initUIEvent("input", true, true, aContent, 0); node.dispatchEvent(event); } } }); } function restoreFormData(aDocument, aData, aURL) { for (let key in aData) { if (!hasExpectedURL(aDocument, aURL)) return; let node = key.charAt(0) == "#" ? aDocument.getElementById(key.slice(1)) : XPathHelper.resolve(aDocument, key); if (!node) continue; let value = aData[key]; if (typeof value == "string" && node.type != "file") { if (node.value == value) continue; // don't dispatch an input event for no change node.value = value; let event = aDocument.createEvent("UIEvents"); event.initUIEvent("input", true, true, aDocument.defaultView, 0); node.dispatchEvent(event); } else if (typeof value == "boolean") node.checked = value; else if (typeof value == "number") try { node.selectedIndex = value; } catch (ex) { /* throws for invalid indices */ } else if (value && value.type && value.type == node.type) node.value = value.value; else if (value && typeof value.indexOf == "function" && node.options) { Array.forEach(node.options, function(aOpt, aIx) { aOpt.selected = value.indexOf(aIx) > -1; }); } // NB: dispatching "change" events might have unintended side-effects } } let selectedPageStyle = this.__SS_restore_pageStyle; let window = this.ownerDocument.defaultView; function restoreTextDataAndScrolling(aContent, aData, aPrefix) { if (aData.formdata) restoreFormData(aContent.document, aData.formdata, aData.url); else restoreTextData(aContent, aPrefix, aData.url); if (aData.innerHTML) { window.setTimeout(function() { if (aContent.document.designMode == "on" && hasExpectedURL(aContent.document, aData.url)) { aContent.document.body.innerHTML = aData.innerHTML; } }, 0); } if (aData.scroll && /(\d+),(\d+)/.test(aData.scroll)) { aContent.scrollTo(RegExp.$1, RegExp.$2); } Array.forEach(aContent.document.styleSheets, function(aSS) { aSS.disabled = aSS.title && aSS.title != selectedPageStyle; }); for (var i = 0; i < aContent.frames.length; i++) { if (aData.children && aData.children[i] && hasExpectedURL(aContent.document, aData.url)) { restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], aPrefix + i + "|"); } } } // don't restore text data and scrolling state if the user has navigated // away before the loading completed (except for in-page navigation) if (hasExpectedURL(aEvent.originalTarget, this.__SS_restore_data.url)) { var content = aEvent.originalTarget.defaultView; if (this.currentURI.spec == "about:config") { // unwrap the document for about:config because otherwise the properties // of the XBL bindings - as the textbox - aren't accessible (see bug 350718) content = content.wrappedJSObject; } restoreTextDataAndScrolling(content, this.__SS_restore_data, ""); this.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle"; // notify the tabbrowser that this document has been completely restored var event = this.ownerDocument.createEvent("Events"); event.initEvent("SSTabRestored", true, false); this.__SS_restore_tab.dispatchEvent(event); } this.removeEventListener("load", this.__SS_restore, true); delete this.__SS_restore_data; delete this.__SS_restore_text; delete this.__SS_restore_pageStyle; delete this.__SS_restore_tab; delete this.__SS_restore; }, /** * Restore visibility and dimension features to a window * @param aWindow * Window reference * @param aWinData * Object containing session data for the window */ restoreWindowFeatures: function sss_restoreWindowFeatures(aWindow, aWinData) { var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[]; WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) { aWindow[aItem].visible = hidden.indexOf(aItem) == -1; }); if (aWinData.isPopup) { this._windows[aWindow.__SSi].isPopup = true; if (aWindow.gURLBar) { aWindow.gURLBar.readOnly = true; aWindow.gURLBar.setAttribute("enablehistory", "false"); } } else { delete this._windows[aWindow.__SSi].isPopup; if (aWindow.gURLBar) { aWindow.gURLBar.readOnly = false; aWindow.gURLBar.setAttribute("enablehistory", "true"); } } var _this = this; aWindow.setTimeout(function() { _this.restoreDimensions.apply(_this, [aWindow, aWinData.width || 0, aWinData.height || 0, "screenX" in aWinData ? aWinData.screenX : NaN, "screenY" in aWinData ? aWinData.screenY : NaN, aWinData.sizemode || "", aWinData.sidebar || ""]); }, 0); }, /** * Restore a window's dimensions * @param aWidth * Window width * @param aHeight * Window height * @param aLeft * Window left * @param aTop * Window top * @param aSizeMode * Window size mode (eg: maximized) * @param aSidebar * Sidebar command */ restoreDimensions: function sss_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) { var win = aWindow; var _this = this; function win_(aName) { return _this._getWindowDimension(win, aName); } // only modify those aspects which aren't correct yet if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) { aWindow.resizeTo(aWidth, aHeight); } if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) { aWindow.moveTo((aLeft < -aWidth) ? 0 : aLeft, (aTop < -aHeight) ? 0 : aTop); } if (aSizeMode && win_("sizemode") != aSizeMode) { switch (aSizeMode) { case "maximized": aWindow.maximize(); break; case "minimized": case "normal": aWindow.restore(); break; } } var sidebar = aWindow.document.getElementById("sidebar-box"); if (sidebar.getAttribute("sidebarcommand") != aSidebar) { aWindow.toggleSidebar(aSidebar); } // since resizing/moving a window brings it to the foreground, // we might want to re-focus the last focused window if (this.windowToFocus) { this.windowToFocus.content.focus(); } }, /** * Restores cookies (accepting both Firefox 2.0 and current format) * @param aCookies * Array of cookie objects */ restoreCookies: function sss_restoreCookies(aCookies) { if (aCookies.count && aCookies.domain1) { // convert to the new cookie serialization format var converted = []; for (var i = 1; i <= aCookies.count; i++) { // for simplicity we only accept the format we produced ourselves var parsed = aCookies["value" + i].match(/^([^=;]+)=([^;]*);(?:domain=[^;]+;)?(?:path=([^;]*);)?(secure;)?(httponly;)?/); if (parsed && /^https?:\/\/([^\/]+)/.test(aCookies["domain" + i])) converted.push({ host: RegExp.$1, path: parsed[3], name: parsed[1], value: parsed[2], secure: parsed[4], httponly: parsed[5] }); } aCookies = converted; } var cookieManager = Cc["@mozilla.org/cookiemanager;1"]. getService(Ci.nsICookieManager2); // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision var MAX_EXPIRY = Math.pow(2, 62); for (i = 0; i < aCookies.length; i++) { var cookie = aCookies[i]; try { cookieManager.add(cookie.host, cookie.path || "", cookie.name || "", cookie.value, !!cookie.secure, !!cookie.httponly, true, "expiry" in cookie ? cookie.expiry : MAX_EXPIRY); } catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering } }, /* ........ Disk Access .............. */ /** * save state delayed by N ms * marks window as dirty (i.e. data update can't be skipped) * @param aWindow * Window reference * @param aDelay * Milliseconds to delay */ saveStateDelayed: function sss_saveStateDelayed(aWindow, aDelay) { if (aWindow) { this._dirtyWindows[aWindow.__SSi] = true; } if (!this._saveTimer && this._resume_from_crash && !this._inPrivateBrowsing) { // interval until the next disk operation is allowed var minimalDelay = this._lastSaveTime + this._interval - Date.now(); // if we have to wait, set a timer, otherwise saveState directly aDelay = Math.max(minimalDelay, aDelay || 2000); if (aDelay > 0) { this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT); } else { this.saveState(); } } }, /** * save state to disk * @param aUpdateAll * Bool update all windows */ saveState: function sss_saveState(aUpdateAll) { // if crash recovery is disabled, only save session resuming information if (!this._resume_from_crash && this._loadState == STATE_RUNNING) return; // if we're in private browsing mode, do nothing if (this._inPrivateBrowsing) return; var oState = this._getCurrentState(aUpdateAll); oState.session = { state: this._loadState == STATE_RUNNING ? STATE_RUNNING_STR : STATE_STOPPED_STR, lastUpdate: Date.now() }; if (this._recentCrashes) oState.session.recentCrashes = this._recentCrashes; this._saveStateObject(oState); }, /** * write a state object to disk */ _saveStateObject: function sss_saveStateObject(aStateObj) { var stateString = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); // parentheses are for backwards compatibility with Firefox 2.0 and 3.0 stateString.data = "(" + this._toJSONString(aStateObj) + ")"; var observerService = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); observerService.notifyObservers(stateString, "sessionstore-state-write", ""); // don't touch the file if an observer has deleted all state data if (stateString.data) this._writeFile(this._sessionFile, stateString.data); this._lastSaveTime = Date.now(); }, /** * delete session datafile and backup */ _clearDisk: function sss_clearDisk() { if (this._sessionFile.exists()) { try { this._sessionFile.remove(false); } catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now? } if (this._sessionFileBackup.exists()) { try { this._sessionFileBackup.remove(false); } catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now? } }, /* ........ Auxiliary Functions .............. */ /** * call a callback for all currently opened browser windows * (might miss the most recent one) * @param aFunc * Callback each window is passed to */ _forEachBrowserWindow: function sss_forEachBrowserWindow(aFunc) { var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. getService(Ci.nsIWindowMediator); var windowsEnum = windowMediator.getEnumerator("navigator:browser"); while (windowsEnum.hasMoreElements()) { var window = windowsEnum.getNext(); if (window.__SSi) { aFunc.call(this, window); } } }, /** * Returns most recent window * @returns Window reference */ _getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() { var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. getService(Ci.nsIWindowMediator); return windowMediator.getMostRecentWindow("navigator:browser"); }, /** * open a new browser window for a given session state * called when restoring a multi-window session * @param aState * Object containing session data */ _openWindowWithState: function sss_openWindowWithState(aState) { var argString = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); argString.data = ""; //XXXzeniko shouldn't it be possible to set the window's dimensions here (as feature)? var window = Cc["@mozilla.org/embedcomp/window-watcher;1"]. getService(Ci.nsIWindowWatcher). openWindow(null, this._prefBranch.getCharPref("chromeURL"), "_blank", "chrome,dialog=no,all", argString); do { var ID = "window" + Math.random(); } while (ID in this._statesToRestore); this._statesToRestore[(window.__SS_restoreID = ID)] = aState; return window; }, /** * Whether or not to resume session, if not recovering from a crash. * @returns bool */ _doResumeSession: function sss_doResumeSession() { if (this._clearingOnShutdown) return false; return this._prefBranch.getIntPref("startup.page") == 3 || this._prefBranch.getBoolPref("sessionstore.resume_session_once"); }, /** * whether the user wants to load any other page at startup * (except the homepage) - needed for determining whether to overwrite the current tabs * C.f.: nsBrowserContentHandler's defaultArgs implementation. * @returns bool */ _isCmdLineEmpty: function sss_isCmdLineEmpty(aWindow) { var defaultArgs = Cc["@mozilla.org/browser/clh;1"]. getService(Ci.nsIBrowserHandler).defaultArgs; if (aWindow.arguments && aWindow.arguments[0] && aWindow.arguments[0] == defaultArgs) aWindow.arguments[0] = null; return !aWindow.arguments || !aWindow.arguments[0]; }, /** * don't save sensitive data if the user doesn't want to * (distinguishes between encrypted and non-encrypted sites) * @param aIsHTTPS * Bool is encrypted * @returns bool */ _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) { return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL); }, /** * on popup windows, the XULWindow's attributes seem not to be set correctly * we use thus JSDOMWindow attributes for sizemode and normal window attributes * (and hope for reasonable values when maximized/minimized - since then * outerWidth/outerHeight aren't the dimensions of the restored window) * @param aWindow * Window reference * @param aAttribute * String sizemode | width | height | other window attribute * @returns string */ _getWindowDimension: function sss_getWindowDimension(aWindow, aAttribute) { if (aAttribute == "sizemode") { switch (aWindow.windowState) { case aWindow.STATE_MAXIMIZED: return "maximized"; case aWindow.STATE_MINIMIZED: return "minimized"; default: return "normal"; } } var dimension; switch (aAttribute) { case "width": dimension = aWindow.outerWidth; break; case "height": dimension = aWindow.outerHeight; break; default: dimension = aAttribute in aWindow ? aWindow[aAttribute] : ""; break; } if (aWindow.windowState == aWindow.STATE_NORMAL) { return dimension; } return aWindow.document.documentElement.getAttribute(aAttribute) || dimension; }, /** * Get nsIURI from string * @param string * @returns nsIURI */ _getURIFromString: function sss_getURIFromString(aString) { var ioService = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService); return ioService.newURI(aString, null, null); }, /** * Annotate a breakpad crash report with the currently selected tab's URL. */ _updateCrashReportURL: function sss_updateCrashReportURL(aWindow) { if (!Ci.nsICrashReporter) { // if breakpad isn't built, don't bother next time at all this._updateCrashReportURL = function(aWindow) {}; return; } try { var currentURI = aWindow.getBrowser().currentURI.clone(); // if the current URI contains a username/password, remove it try { currentURI.userPass = ""; } catch (ex) { } // ignore failures on about: URIs var cr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsICrashReporter); cr.annotateCrashReport("URL", currentURI.spec); } catch (ex) { // don't make noise when crashreporter is built but not enabled if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) debug(ex); } }, /** * @param aState is a session state * @param aRecentCrashes is the number of consecutive crashes * @returns whether a restore page will be needed for the session state */ _needsRestorePage: function sss_needsRestorePage(aState, aRecentCrashes) { const SIX_HOURS_IN_MS = 6 * 60 * 60 * 1000; // don't display the page when there's nothing to restore let winData = aState.windows || null; if (!winData || winData.length == 0) return false; // don't wrap a single about:sessionrestore page if (winData.length == 1 && winData[0].tabs && winData[0].tabs.length == 1 && winData[0].tabs[0].entries && winData[0].tabs[0].entries.length == 1 && winData[0].tabs[0].entries[0].url == "about:sessionrestore") return false; // don't automatically restore in Safe Mode let XRE = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); if (XRE.inSafeMode) return true; let max_resumed_crashes = this._prefBranch.getIntPref("sessionstore.max_resumed_crashes"); let sessionAge = aState.session && aState.session.lastUpdate && (Date.now() - aState.session.lastUpdate); return max_resumed_crashes != -1 && (aRecentCrashes > max_resumed_crashes || sessionAge && sessionAge >= SIX_HOURS_IN_MS); }, /** * safe eval'ing */ _safeEval: function sss_safeEval(aStr) { return Cu.evalInSandbox(aStr, new Cu.Sandbox("about:blank")); }, /** * Converts a JavaScript object into a JSON string * (see http://www.json.org/ for more information). * * The inverse operation consists of JSON.parse(JSON_string). * * @param aJSObject is the object to be converted * @returns the object's JSON representation */ _toJSONString: function sss_toJSONString(aJSObject) { // XXXzeniko drop the following keys used only for internal bookkeeping: // _tabStillLoading, _hosts, _formDataSaved let jsonString = JSON.stringify(aJSObject); if (/[\u2028\u2029]/.test(jsonString)) { // work-around for bug 485563 until we can use JSON.parse // instead of evalInSandbox everywhere jsonString = jsonString.replace(/[\u2028\u2029]/g, function($0) "\\u" + $0.charCodeAt(0).toString(16)); } return jsonString; }, _notifyIfAllWindowsRestored: function sss_notifyIfAllWindowsRestored() { if (this._restoreCount) { this._restoreCount--; if (this._restoreCount == 0) { // This was the last window restored at startup, notify observers. var observerService = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, ""); } } }, /** * @param aWindow * Window reference * @returns whether this window's data is still cached in _statesToRestore * because it's not fully loaded yet */ _isWindowLoaded: function sss_isWindowLoaded(aWindow) { return !aWindow.__SS_restoreID; }, /** * Replace "Loading..." with the tab label (with minimal side-effects) * @param aString is the string the title is stored in * @param aTabbrowser is a tabbrowser object, containing aTab * @param aTab is the tab whose title we're updating & using * * @returns aString that has been updated with the new title */ _replaceLoadingTitle : function sss_replaceLoadingTitle(aString, aTabbrowser, aTab) { if (aString == aTabbrowser.mStringBundle.getString("tabs.loading")) { aTabbrowser.setTabTitle(aTab); [aString, aTab.label] = [aTab.label, aString]; } return aString; }, /** * Resize this._closedWindows to the value of the pref, except in the case * where we don't have any non-popup windows on Windows and Linux. Then we must * resize such that we have at least one non-popup window. */ _capClosedWindows : function sss_capClosedWindows() { let maxWindowsUndo = this._prefBranch.getIntPref("sessionstore.max_windows_undo"); if (this._closedWindows.length <= maxWindowsUndo) return; let spliceTo = maxWindowsUndo; //@line 2780 "/tmp/buildd/iceweasel-3.5.16/browser/components/sessionstore/src/nsSessionStore.js" let normalWindowIndex = 0; // try to find a non-popup window in this._closedWindows while (normalWindowIndex < this._closedWindows.length && !!this._closedWindows[normalWindowIndex].isPopup) normalWindowIndex++; if (normalWindowIndex >= maxWindowsUndo) spliceTo = normalWindowIndex + 1; //@line 2788 "/tmp/buildd/iceweasel-3.5.16/browser/components/sessionstore/src/nsSessionStore.js" this._closedWindows.splice(spliceTo); }, /* ........ Storage API .............. */ /** * write file to disk * @param aFile * nsIFile * @param aData * String data */ _writeFile: function sss_writeFile(aFile, aData) { // init stream var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"]. createInstance(Ci.nsIFileOutputStream); stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0); // convert to UTF-8 var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; var convertedData = converter.ConvertFromUnicode(aData); convertedData += converter.Finish(); // write and close stream stream.write(convertedData, convertedData.length); if (stream instanceof Ci.nsISafeOutputStream) { stream.finish(); } else { stream.close(); } } }; let XPathHelper = { // these two hashes should be kept in sync namespaceURIs: { "xhtml": "http://www.w3.org/1999/xhtml" }, namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" }, /** * Generates an approximate XPath query to an (X)HTML node */ generate: function sss_xph_generate(aNode) { // have we reached the document node already? if (!aNode.parentNode) return ""; let prefix = this.namespacePrefixes[aNode.namespaceURI] || null; let tag = (prefix ? prefix + ":" : "") + this.escapeName(aNode.localName); // stop once we've found a tag with an ID if (aNode.id) return "//" + tag + "[@id=" + this.quoteArgument(aNode.id) + "]"; // count the number of previous sibling nodes of the same tag // (and possible also the same name) let count = 0; let nName = aNode.name || null; for (let n = aNode; (n = n.previousSibling); ) if (n.localName == aNode.localName && n.namespaceURI == aNode.namespaceURI && (!nName || n.name == nName)) count++; // recurse until hitting either the document node or an ID'd node return this.generate(aNode.parentNode) + "/" + tag + (nName ? "[@name=" + this.quoteArgument(nName) + "]" : "") + (count ? "[" + (count + 1) + "]" : ""); }, /** * Resolves an XPath query generated by XPathHelper.generate */ resolve: function sss_xph_resolve(aDocument, aQuery) { let xptype = Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE; return aDocument.evaluate(aQuery, aDocument, this.resolveNS, xptype, null).singleNodeValue; }, /** * Namespace resolver for the above XPath resolver */ resolveNS: function sss_xph_resolveNS(aPrefix) { return XPathHelper.namespaceURIs[aPrefix] || null; }, /** * @returns valid XPath for the given node (usually just the local name itself) */ escapeName: function sss_xph_escapeName(aName) { // we can't just use the node's local name, if it contains // special characters (cf. bug 485482) return /^\w+$/.test(aName) ? aName : "*[local-name()=" + this.quoteArgument(aName) + "]"; }, /** * @returns a properly quoted string to insert into an XPath query */ quoteArgument: function sss_xph_quoteArgument(aArg) { return !/'/.test(aArg) ? "'" + aArg + "'" : !/"/.test(aArg) ? '"' + aArg + '"' : "concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')"; }, /** * @returns an XPath query to all savable form field nodes */ get restorableFormNodes() { // for a comprehensive list of all available <INPUT> types see // http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable let ignoreTypes = ["password", "hidden", "button", "image", "submit", "reset"]; // XXXzeniko work-around until lower-case has been implemented (bug 398389) let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"'; let ignore = "not(translate(@type, " + toLowerCase + ")='" + ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')"; let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" + "//input[" + ignore + "]|//xhtml:input[" + ignore + "]"; delete this.restorableFormNodes; return (this.restorableFormNodes = formNodesXPath); } }; // see nsPrivateBrowsingService.js String.prototype.hasRootDomain = function hasRootDomain(aDomain) { let index = this.indexOf(aDomain); if (index == -1) return false; if (this == aDomain) return true; let prevChar = this[index - 1]; return (index == (this.length - aDomain.length)) && (prevChar == "." || prevChar == "/"); } function NSGetModule(aComMgr, aFileSpec) XPCOMUtils.generateModule([SessionStoreService]); |
:: Command execute :: | |
--[ c99shell v. 2.0 [PHP 7 Update] [25.02.2019] maintained by KaizenLouie | C99Shell Github | Generation time: 0.0261 ]-- |