michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["SessionHistory"]; michael@0: michael@0: const Cu = Components.utils; michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Utils", michael@0: "resource:///modules/sessionstore/Utils.jsm"); michael@0: michael@0: function debug(msg) { michael@0: Services.console.logStringMessage("SessionHistory: " + msg); michael@0: } michael@0: michael@0: /** michael@0: * The external API exported by this module. michael@0: */ michael@0: this.SessionHistory = Object.freeze({ michael@0: isEmpty: function (docShell) { michael@0: return SessionHistoryInternal.isEmpty(docShell); michael@0: }, michael@0: michael@0: collect: function (docShell) { michael@0: return SessionHistoryInternal.collect(docShell); michael@0: }, michael@0: michael@0: restore: function (docShell, tabData) { michael@0: SessionHistoryInternal.restore(docShell, tabData); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * The internal API for the SessionHistory module. michael@0: */ michael@0: let SessionHistoryInternal = { michael@0: /** michael@0: * Returns whether the given docShell's session history is empty. michael@0: * michael@0: * @param docShell michael@0: * The docShell that owns the session history. michael@0: */ michael@0: isEmpty: function (docShell) { michael@0: let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation); michael@0: let history = webNavigation.sessionHistory; michael@0: if (!webNavigation.currentURI) { michael@0: return true; michael@0: } michael@0: let uri = webNavigation.currentURI.spec; michael@0: return uri == "about:blank" && history.count == 0; michael@0: }, michael@0: michael@0: /** michael@0: * Collects session history data for a given docShell. michael@0: * michael@0: * @param docShell michael@0: * The docShell that owns the session history. michael@0: */ michael@0: collect: function (docShell) { michael@0: let data = {entries: []}; michael@0: let isPinned = docShell.isAppTab; michael@0: let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation); michael@0: let history = webNavigation.sessionHistory; michael@0: michael@0: if (history && history.count > 0) { michael@0: let oldest; michael@0: let maxSerializeBack = michael@0: Services.prefs.getIntPref("browser.sessionstore.max_serialize_back"); michael@0: if (maxSerializeBack >= 0) { michael@0: oldest = Math.max(0, history.index - maxSerializeBack); michael@0: } else { // History.getEntryAtIndex(0, ...) is the oldest. michael@0: oldest = 0; michael@0: } michael@0: michael@0: let newest; michael@0: let maxSerializeFwd = michael@0: Services.prefs.getIntPref("browser.sessionstore.max_serialize_forward"); michael@0: if (maxSerializeFwd >= 0) { michael@0: newest = Math.min(history.count - 1, history.index + maxSerializeFwd); michael@0: } else { // History.getEntryAtIndex(history.count - 1, ...) is the newest. michael@0: newest = history.count - 1; michael@0: } michael@0: michael@0: try { michael@0: for (let i = oldest; i <= newest; i++) { michael@0: let shEntry = history.getEntryAtIndex(i, false); michael@0: let entry = this.serializeEntry(shEntry, isPinned); michael@0: data.entries.push(entry); michael@0: } michael@0: } catch (ex) { michael@0: // In some cases, getEntryAtIndex will throw. This seems to be due to michael@0: // history.count being higher than it should be. By doing this in a michael@0: // try-catch, we'll update history to where it breaks, print an error michael@0: // message, and still save sessionstore.js. michael@0: debug("SessionStore failed gathering complete history " + michael@0: "for the focused window/tab. See bug 669196."); michael@0: } michael@0: michael@0: // Set the one-based index of the currently active tab, michael@0: // ensuring it isn't out of bounds if an exception was thrown above. michael@0: data.index = Math.min(history.index - oldest + 1, data.entries.length); michael@0: } michael@0: michael@0: // If either the session history isn't available yet or doesn't have any michael@0: // valid entries, make sure we at least include the current page. michael@0: if (data.entries.length == 0) { michael@0: let uri = webNavigation.currentURI.spec; michael@0: let body = webNavigation.document.body; michael@0: // We landed here because the history is inaccessible or there are no michael@0: // history entries. In that case we should at least record the docShell's michael@0: // current URL as a single history entry. If the URL is not about:blank michael@0: // or it's a blank tab that was modified (like a custom newtab page), michael@0: // record it. For about:blank we explicitly want an empty array without michael@0: // an 'index' property to denote that there are no history entries. michael@0: if (uri != "about:blank" || (body && body.hasChildNodes())) { michael@0: data.entries.push({ url: uri }); michael@0: data.index = 1; michael@0: } michael@0: } michael@0: michael@0: return data; michael@0: }, michael@0: michael@0: /** michael@0: * Determines whether a given session history entry has been added dynamically. michael@0: * michael@0: * @param shEntry michael@0: * The session history entry. michael@0: * @return bool michael@0: */ michael@0: isDynamic: function (shEntry) { michael@0: // shEntry.isDynamicallyAdded() is true for dynamically added michael@0: //