summaryrefslogtreecommitdiff
path: root/application/basilisk/components/sessionstore/content
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2019-12-16 13:57:01 -0500
committerMatt A. Tobin <email@mattatobin.com>2019-12-16 13:57:01 -0500
commit06494f307850c576868831bd28a61464eab1f359 (patch)
treef281f5c46c3e0b73c7eabe22f02622dc013b0c35 /application/basilisk/components/sessionstore/content
parente7d4713e0765c79feddf2384d343d10595fa5cb3 (diff)
downloaduxp-06494f307850c576868831bd28a61464eab1f359.tar.gz
Remove Basilisk from the Unified XUL Platform repository
Development will proceed at https://github.com/MoonchildProductions/Basilisk
Diffstat (limited to 'application/basilisk/components/sessionstore/content')
-rw-r--r--application/basilisk/components/sessionstore/content/aboutSessionRestore.js373
-rw-r--r--application/basilisk/components/sessionstore/content/aboutSessionRestore.xhtml86
-rw-r--r--application/basilisk/components/sessionstore/content/content-sessionStore.js897
3 files changed, 0 insertions, 1356 deletions
diff --git a/application/basilisk/components/sessionstore/content/aboutSessionRestore.js b/application/basilisk/components/sessionstore/content/aboutSessionRestore.js
deleted file mode 100644
index 8f265235d7..0000000000
--- a/application/basilisk/components/sessionstore/content/aboutSessionRestore.js
+++ /dev/null
@@ -1,373 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-
-var gStateObject;
-var gTreeData;
-
-// Page initialization
-
-window.onload = function() {
- // pages used by this script may have a link that needs to be updated to
- // the in-product link.
- let anchor = document.getElementById("linkMoreTroubleshooting");
- if (anchor) {
- let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
- anchor.setAttribute("href", baseURL + "troubleshooting");
- }
-
- // wire up click handlers for the radio buttons if they exist.
- for (let radioId of ["radioRestoreAll", "radioRestoreChoose"]) {
- let button = document.getElementById(radioId);
- if (button) {
- button.addEventListener("click", updateTabListVisibility);
- }
- }
-
- // the crashed session state is kept inside a textbox so that SessionStore picks it up
- // (for when the tab is closed or the session crashes right again)
- var sessionData = document.getElementById("sessionData");
- if (!sessionData.value) {
- document.getElementById("errorTryAgain").disabled = true;
- return;
- }
-
- try {
- gStateObject = JSON.parse(sessionData.value);
- } catch (e) {
- Cu.reportError(e);
- }
-
- // make sure the data is tracked to be restored in case of a subsequent crash
- var event = document.createEvent("UIEvents");
- event.initUIEvent("input", true, true, window, 0);
- sessionData.dispatchEvent(event);
-
- initTreeView();
-
- document.getElementById("errorTryAgain").focus();
-};
-
-function isTreeViewVisible() {
- let tabList = document.querySelector(".tree-container");
- return tabList.hasAttribute("available");
-}
-
-function initTreeView() {
- // If we aren't visible we initialize as we are made visible (and it's OK
- // to initialize multiple times)
- if (!isTreeViewVisible()) {
- return;
- }
- var tabList = document.getElementById("tabList");
- var winLabel = tabList.getAttribute("_window_label");
-
- gTreeData = [];
- if (gStateObject) {
- gStateObject.windows.forEach(function(aWinData, aIx) {
- var winState = {
- label: winLabel.replace("%S", (aIx + 1)),
- open: true,
- checked: true,
- ix: aIx
- };
- winState.tabs = aWinData.tabs.map(function(aTabData) {
- var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" };
- var iconURL = aTabData.image || null;
- // don't initiate a connection just to fetch a favicon (see bug 462863)
- if (/^https?:/.test(iconURL))
- iconURL = "moz-anno:favicon:" + iconURL;
- return {
- label: entry.title || entry.url,
- checked: true,
- src: iconURL,
- parent: winState
- };
- });
- gTreeData.push(winState);
- for (let tab of winState.tabs)
- gTreeData.push(tab);
- }, this);
- }
-
- tabList.view = treeView;
- tabList.view.selection.select(0);
-}
-
-// User actions
-function updateTabListVisibility() {
- let tabList = document.querySelector(".tree-container");
- let container = document.querySelector(".container");
- if (document.getElementById("radioRestoreChoose").checked) {
- tabList.setAttribute("available", "true");
- container.classList.add("restore-chosen");
- } else {
- tabList.removeAttribute("available");
- container.classList.remove("restore-chosen");
- }
- initTreeView();
-}
-
-function restoreSession() {
- Services.obs.notifyObservers(null, "sessionstore-initiating-manual-restore", "");
- document.getElementById("errorTryAgain").disabled = true;
-
- if (isTreeViewVisible()) {
- if (!gTreeData.some(aItem => aItem.checked)) {
- // This should only be possible when we have no "cancel" button, and thus
- // the "Restore session" button always remains enabled. In that case and
- // when nothing is selected, we just want a new session.
- startNewSession();
- return;
- }
-
- // remove all unselected tabs from the state before restoring it
- var ix = gStateObject.windows.length - 1;
- for (var t = gTreeData.length - 1; t >= 0; t--) {
- if (treeView.isContainer(t)) {
- if (gTreeData[t].checked === 0)
- // this window will be restored partially
- gStateObject.windows[ix].tabs =
- gStateObject.windows[ix].tabs.filter((aTabData, aIx) =>
- gTreeData[t].tabs[aIx].checked);
- else if (!gTreeData[t].checked)
- // this window won't be restored at all
- gStateObject.windows.splice(ix, 1);
- ix--;
- }
- }
- }
- var stateString = JSON.stringify(gStateObject);
-
- var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- var top = getBrowserWindow();
-
- // if there's only this page open, reuse the window for restoring the session
- if (top.gBrowser.tabs.length == 1) {
- ss.setWindowState(top, stateString, true);
- return;
- }
-
- // restore the session into a new window and close the current tab
- var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all");
-
- var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
- obs.addObserver(function observe(win, topic) {
- if (win != newWindow) {
- return;
- }
-
- obs.removeObserver(observe, topic);
- ss.setWindowState(newWindow, stateString, true);
-
- var tabbrowser = top.gBrowser;
- var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
- tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
- }, "browser-delayed-startup-finished", false);
-}
-
-function startNewSession() {
- var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
- if (prefBranch.getIntPref("browser.startup.page") == 0)
- getBrowserWindow().gBrowser.loadURI("about:blank");
- else
- getBrowserWindow().BrowserHome();
-}
-
-function onListClick(aEvent) {
- // don't react to right-clicks
- if (aEvent.button == 2)
- return;
-
- if (!treeView.treeBox) {
- return;
- }
- var cell = treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY);
- if (cell.col) {
- // Restore this specific tab in the same window for middle/double/accel clicking
- // on a tab's title.
- let accelKey = AppConstants.platform == "macosx" ?
- aEvent.metaKey :
- aEvent.ctrlKey;
- if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) &&
- cell.col.id == "title" &&
- !treeView.isContainer(cell.row)) {
- restoreSingleTab(cell.row, aEvent.shiftKey);
- aEvent.stopPropagation();
- }
- else if (cell.col.id == "restore")
- toggleRowChecked(cell.row);
- }
-}
-
-function onListKeyDown(aEvent) {
- switch (aEvent.keyCode)
- {
- case KeyEvent.DOM_VK_SPACE:
- toggleRowChecked(document.getElementById("tabList").currentIndex);
- // Prevent page from scrolling on the space key.
- aEvent.preventDefault();
- break;
- case KeyEvent.DOM_VK_RETURN:
- var ix = document.getElementById("tabList").currentIndex;
- if (aEvent.ctrlKey && !treeView.isContainer(ix))
- restoreSingleTab(ix, aEvent.shiftKey);
- break;
- }
-}
-
-// Helper functions
-
-function getBrowserWindow() {
- return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-}
-
-function toggleRowChecked(aIx) {
- function isChecked(aItem) {
- return aItem.checked;
- }
-
- var item = gTreeData[aIx];
- item.checked = !item.checked;
- treeView.treeBox.invalidateRow(aIx);
-
- if (treeView.isContainer(aIx)) {
- // (un)check all tabs of this window as well
- for (let tab of item.tabs) {
- tab.checked = item.checked;
- treeView.treeBox.invalidateRow(gTreeData.indexOf(tab));
- }
- }
- else {
- // update the window's checkmark as well (0 means "partially checked")
- item.parent.checked = item.parent.tabs.every(isChecked) ? true :
- item.parent.tabs.some(isChecked) ? 0 : false;
- treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
- }
-
- // we only disable the button when there's no cancel button.
- if (document.getElementById("errorCancel")) {
- document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
- }
-}
-
-function restoreSingleTab(aIx, aShifted) {
- var tabbrowser = getBrowserWindow().gBrowser;
- var newTab = tabbrowser.addTab();
- var item = gTreeData[aIx];
-
- var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- var tabState = gStateObject.windows[item.parent.ix]
- .tabs[aIx - gTreeData.indexOf(item.parent) - 1];
- // ensure tab would be visible on the tabstrip.
- tabState.hidden = false;
- ss.setTabState(newTab, JSON.stringify(tabState));
-
- // respect the preference as to whether to select the tab (the Shift key inverses)
- var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
- if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)
- tabbrowser.selectedTab = newTab;
-}
-
-// Tree controller
-
-var treeView = {
- treeBox: null,
- selection: null,
-
- get rowCount() { return gTreeData.length; },
- setTree: function(treeBox) { this.treeBox = treeBox; },
- getCellText: function(idx, column) { return gTreeData[idx].label; },
- isContainer: function(idx) {
- return gTreeData[idx] ? "open" in gTreeData[idx] : false;
- },
- getCellValue: function(idx, column){ return gTreeData[idx].checked; },
- isContainerOpen: function(idx) { return gTreeData[idx].open; },
- isContainerEmpty: function(idx) { return false; },
- isSeparator: function(idx) { return false; },
- isSorted: function() { return false; },
- isEditable: function(idx, column) { return false; },
- canDrop: function(idx, orientation, dt) { return false; },
- getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; },
-
- getParentIndex: function(idx) {
- if (!this.isContainer(idx))
- for (var t = idx - 1; t >= 0 ; t--)
- if (this.isContainer(t))
- return t;
- return -1;
- },
-
- hasNextSibling: function(idx, after) {
- var thisLevel = this.getLevel(idx);
- for (var t = after + 1; t < gTreeData.length; t++)
- if (this.getLevel(t) <= thisLevel)
- return this.getLevel(t) == thisLevel;
- return false;
- },
-
- toggleOpenState: function(idx) {
- if (!this.isContainer(idx))
- return;
- var item = gTreeData[idx];
- if (item.open) {
- // remove this window's tab rows from the view
- var thisLevel = this.getLevel(idx);
- for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++);
- var deletecount = t - idx - 1;
- gTreeData.splice(idx + 1, deletecount);
- this.treeBox.rowCountChanged(idx + 1, -deletecount);
- }
- else {
- // add this window's tab rows to the view
- var toinsert = gTreeData[idx].tabs;
- for (var i = 0; i < toinsert.length; i++)
- gTreeData.splice(idx + i + 1, 0, toinsert[i]);
- this.treeBox.rowCountChanged(idx + 1, toinsert.length);
- }
- item.open = !item.open;
- this.treeBox.invalidateRow(idx);
- },
-
- getCellProperties: function(idx, column) {
- if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0)
- return "partial";
- if (column.id == "title")
- return this.getImageSrc(idx, column) ? "icon" : "noicon";
-
- return "";
- },
-
- getRowProperties: function(idx) {
- var winState = gTreeData[idx].parent || gTreeData[idx];
- if (winState.ix % 2 != 0)
- return "alternate";
-
- return "";
- },
-
- getImageSrc: function(idx, column) {
- if (column.id == "title")
- return gTreeData[idx].src || null;
- return null;
- },
-
- getProgressMode : function(idx, column) { },
- cycleHeader: function(column) { },
- cycleCell: function(idx, column) { },
- selectionChanged: function() { },
- performAction: function(action) { },
- performActionOnCell: function(action, index, column) { },
- getColumnProperties: function(column) { return ""; }
-};
diff --git a/application/basilisk/components/sessionstore/content/aboutSessionRestore.xhtml b/application/basilisk/components/sessionstore/content/aboutSessionRestore.xhtml
deleted file mode 100644
index bcd9084e77..0000000000
--- a/application/basilisk/components/sessionstore/content/aboutSessionRestore.xhtml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-<!DOCTYPE html [
- <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
- %htmlDTD;
- <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
- %netErrorDTD;
- <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
- %globalDTD;
- <!ENTITY % restorepageDTD SYSTEM "chrome://browser/locale/aboutSessionRestore.dtd">
- %restorepageDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- <head>
- <title>&restorepage.tabtitle;</title>
- <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css" media="all"/>
- <link rel="stylesheet" href="chrome://browser/skin/aboutSessionRestore.css" type="text/css" media="all"/>
- <link rel="icon" type="image/png" href="chrome://global/skin/icons/warning-16.png"/>
-
- <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSessionRestore.js"/>
- </head>
-
- <body dir="&locale.dir;">
-
- <div class="container restore-chosen">
-
- <div class="title">
- <h1 class="title-text">&restorepage.errorTitle;</h1>
- </div>
- <div class="description">
- <p>&restorepage.problemDesc;</p>
-
- <div id="errorLongDesc">
- <p>&restorepage.tryThis;</p>
- <ul>
- <li>&restorepage.restoreSome;</li>
- <li>&restorepage.startNew;</li>
- </ul>
- </div>
- </div>
- <div class="tree-container" available="true">
- <xul:tree id="tabList" seltype="single" hidecolumnpicker="true"
- onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
- _window_label="&restorepage.windowLabel;">
- <xul:treecols>
- <xul:treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
- <xul:splitter class="tree-splitter"/>
- <xul:treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
- </xul:treecols>
- <xul:treechildren flex="1"/>
- </xul:tree>
- </div>
- <div class="button-container">
-#ifdef XP_UNIX
- <xul:button id="errorCancel"
- label="&restorepage.closeButton;"
- accesskey="&restorepage.close.access;"
- oncommand="startNewSession();"/>
- <xul:button class="primary"
- id="errorTryAgain"
- label="&restorepage.tryagainButton;"
- accesskey="&restorepage.restore.access;"
- oncommand="restoreSession();"/>
-#else
- <xul:button class="primary"
- id="errorTryAgain"
- label="&restorepage.tryagainButton;"
- accesskey="&restorepage.restore.access;"
- oncommand="restoreSession();"/>
- <xul:button id="errorCancel"
- label="&restorepage.closeButton;"
- accesskey="&restorepage.close.access;"
- oncommand="startNewSession();"/>
-#endif
- </div>
- <!-- holds the session data for when the tab is closed -->
- <input type="text" id="sessionData" style="display: none;"/>
- </div>
-
- </body>
-</html>
diff --git a/application/basilisk/components/sessionstore/content/content-sessionStore.js b/application/basilisk/components/sessionstore/content/content-sessionStore.js
deleted file mode 100644
index 858e35750e..0000000000
--- a/application/basilisk/components/sessionstore/content/content-sessionStore.js
+++ /dev/null
@@ -1,897 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-function debug(msg) {
- Services.console.logStringMessage("SessionStoreContent: " + msg);
-}
-
-var Cu = Components.utils;
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-Cu.import("resource://gre/modules/Timer.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "FormData",
- "resource://gre/modules/FormData.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
- "resource://gre/modules/Preferences.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
- "resource:///modules/sessionstore/DocShellCapabilities.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
- "resource:///modules/sessionstore/PageStyle.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
- "resource://gre/modules/ScrollPosition.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
- "resource:///modules/sessionstore/SessionHistory.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
- "resource:///modules/sessionstore/SessionStorage.jsm");
-
-Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
-var gFrameTree = new FrameTree(this);
-
-Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
-XPCOMUtils.defineLazyGetter(this, 'gContentRestore',
- () => { return new ContentRestore(this) });
-
-// The current epoch.
-var gCurrentEpoch = 0;
-
-// A bound to the size of data to store for DOM Storage.
-const DOM_STORAGE_MAX_CHARS = 10000000; // 10M characters
-
-// This pref controls whether or not we send updates to the parent on a timeout
-// or not, and should only be used for tests or debugging.
-const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
-
-const kNoIndex = Number.MAX_SAFE_INTEGER;
-const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
-
-/**
- * Returns a lazy function that will evaluate the given
- * function |fn| only once and cache its return value.
- */
-function createLazy(fn) {
- let cached = false;
- let cachedValue = null;
-
- return function lazy() {
- if (!cached) {
- cachedValue = fn();
- cached = true;
- }
-
- return cachedValue;
- };
-}
-
-/**
- * Listens for and handles content events that we need for the
- * session store service to be notified of state changes in content.
- */
-var EventListener = {
-
- init: function () {
- addEventListener("load", this, true);
- },
-
- handleEvent: function (event) {
- // Ignore load events from subframes.
- if (event.target != content.document) {
- return;
- }
-
- if (content.document.documentURI.startsWith("about:reader")) {
- if (event.type == "load" &&
- !content.document.body.classList.contains("loaded")) {
- // Don't restore the scroll position of an about:reader page at this
- // point; listen for the custom event dispatched from AboutReader.jsm.
- content.addEventListener("AboutReaderContentReady", this);
- return;
- }
-
- content.removeEventListener("AboutReaderContentReady", this);
- }
-
- // Restore the form data and scroll position. If we're not currently
- // restoring a tab state then this call will simply be a noop.
- gContentRestore.restoreDocument();
- }
-};
-
-/**
- * Listens for and handles messages sent by the session store service.
- */
-var MessageListener = {
-
- MESSAGES: [
- "SessionStore:restoreHistory",
- "SessionStore:restoreTabContent",
- "SessionStore:resetRestore",
- "SessionStore:flush",
- ],
-
- init: function () {
- this.MESSAGES.forEach(m => addMessageListener(m, this));
- },
-
- receiveMessage: function ({name, data}) {
- // The docShell might be gone. Don't process messages,
- // that will just lead to errors anyway.
- if (!docShell) {
- return;
- }
-
- // A fresh tab always starts with epoch=0. The parent has the ability to
- // override that to signal a new era in this tab's life. This enables it
- // to ignore async messages that were already sent but not yet received
- // and would otherwise confuse the internal tab state.
- if (data.epoch && data.epoch != gCurrentEpoch) {
- gCurrentEpoch = data.epoch;
- }
-
- switch (name) {
- case "SessionStore:restoreHistory":
- this.restoreHistory(data);
- break;
- case "SessionStore:restoreTabContent":
- this.restoreTabContent(data);
- break;
- case "SessionStore:resetRestore":
- gContentRestore.resetRestore();
- break;
- case "SessionStore:flush":
- this.flush(data);
- break;
- default:
- debug("received unknown message '" + name + "'");
- break;
- }
- },
-
- restoreHistory({epoch, tabData, loadArguments, isRemotenessUpdate}) {
- gContentRestore.restoreHistory(tabData, loadArguments, {
- // Note: The callbacks passed here will only be used when a load starts
- // that was not initiated by sessionstore itself. This can happen when
- // some code calls browser.loadURI() or browser.reload() on a pending
- // browser/tab.
-
- onLoadStarted() {
- // Notify the parent that the tab is no longer pending.
- sendSyncMessage("SessionStore:restoreTabContentStarted", {epoch});
- },
-
- onLoadFinished() {
- // Tell SessionStore.jsm that it may want to restore some more tabs,
- // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
- sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch});
- }
- });
-
- // When restoreHistory finishes, we send a synchronous message to
- // SessionStore.jsm so that it can run SSTabRestoring. Users of
- // SSTabRestoring seem to get confused if chrome and content are out of
- // sync about the state of the restore (particularly regarding
- // docShell.currentURI). Using a synchronous message is the easiest way
- // to temporarily synchronize them.
- sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch, isRemotenessUpdate});
- },
-
- restoreTabContent({loadArguments, isRemotenessUpdate}) {
- let epoch = gCurrentEpoch;
-
- // We need to pass the value of didStartLoad back to SessionStore.jsm.
- let didStartLoad = gContentRestore.restoreTabContent(loadArguments, isRemotenessUpdate, () => {
- // Tell SessionStore.jsm that it may want to restore some more tabs,
- // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
- sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
- });
-
- sendAsyncMessage("SessionStore:restoreTabContentStarted", {epoch, isRemotenessUpdate});
-
- if (!didStartLoad) {
- // Pretend that the load succeeded so that event handlers fire correctly.
- sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
- }
- },
-
- flush({id}) {
- // Flush the message queue, send the latest updates.
- MessageQueue.send({flushID: id});
- }
-};
-
-/**
- * Listens for changes to the session history. Whenever the user navigates
- * we will collect URLs and everything belonging to session history.
- *
- * Causes a SessionStore:update message to be sent that contains the current
- * session history.
- *
- * Example:
- * {entries: [{url: "about:mozilla", ...}, ...], index: 1}
- */
-var SessionHistoryListener = {
- init: function () {
- // The frame tree observer is needed to handle initial subframe loads.
- // It will redundantly invalidate with the SHistoryListener in some cases
- // but these invalidations are very cheap.
- gFrameTree.addObserver(this);
-
- // By adding the SHistoryListener immediately, we will unfortunately be
- // notified of every history entry as the tab is restored. We don't bother
- // waiting to add the listener later because these notifications are cheap.
- // We will likely only collect once since we are batching collection on
- // a delay.
- docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.
- addSHistoryListener(this);
-
- // Collect data if we start with a non-empty shistory.
- if (!SessionHistory.isEmpty(docShell)) {
- this.collect();
- // When a tab is detached from the window, for the new window there is a
- // new SessionHistoryListener created. Normally it is empty at this point
- // but in a test env. the initial about:blank might have a children in which
- // case we fire off a history message here with about:blank in it. If we
- // don't do it ASAP then there is going to be a browser swap and the parent
- // will be all confused by that message.
- MessageQueue.send();
- }
-
- // Listen for page title changes.
- addEventListener("DOMTitleChanged", this);
- },
-
- uninit: function () {
- let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
- if (sessionHistory) {
- sessionHistory.removeSHistoryListener(this);
- }
- },
-
- collect: function () {
- this._fromIdx = kNoIndex;
- if (docShell) {
- MessageQueue.push("history", () => SessionHistory.collect(docShell));
- }
- },
-
- _fromIdx: kNoIndex,
-
- // History can grow relatively big with the nested elements, so if we don't have to, we
- // don't want to send the entire history all the time. For a simple optimization
- // we keep track of the smallest index from after any change has occured and we just send
- // the elements from that index. If something more complicated happens we just clear it
- // and send the entire history. We always send the additional info like the current selected
- // index (so for going back and forth between history entries we set the index to kLastIndex
- // if nothing else changed send an empty array and the additonal info like the selected index)
- collectFrom: function (idx) {
- if (this._fromIdx <= idx) {
- // If we already know that we need to update history fromn index N we can ignore any changes
- // tha happened with an element with index larger than N.
- // Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
- // here, and in case of navigation in the history back and forth we use kLastIndex which ignores
- // only the subsequent navigations, but not any new elements added.
- return;
- }
-
- this._fromIdx = idx;
- MessageQueue.push("historychange", () => {
- if (this._fromIdx === kNoIndex) {
- return null;
- }
-
- let history = SessionHistory.collect(docShell);
- if (kLastIndex == idx) {
- history.entries = [];
- } else {
- history.entries.splice(0, this._fromIdx + 1);
- }
-
- history.fromIdx = this._fromIdx;
-
- this._fromIdx = kNoIndex;
- return history;
- });
- },
-
- handleEvent(event) {
- this.collect();
- },
-
- onFrameTreeCollected: function () {
- this.collect();
- },
-
- onFrameTreeReset: function () {
- this.collect();
- },
-
- OnHistoryNewEntry: function (newURI, oldIndex) {
- this.collectFrom(oldIndex);
- },
-
- OnHistoryGoBack: function (backURI) {
- this.collectFrom(kLastIndex);
- return true;
- },
-
- OnHistoryGoForward: function (forwardURI) {
- this.collectFrom(kLastIndex);
- return true;
- },
-
- OnHistoryGotoIndex: function (index, gotoURI) {
- this.collectFrom(kLastIndex);
- return true;
- },
-
- OnHistoryPurge: function (numEntries) {
- this.collect();
- return true;
- },
-
- OnHistoryReload: function (reloadURI, reloadFlags) {
- this.collect();
- return true;
- },
-
- OnHistoryReplaceEntry: function (index) {
- this.collect();
- },
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsISHistoryListener,
- Ci.nsISupportsWeakReference
- ])
-};
-
-/**
- * Listens for scroll position changes. Whenever the user scrolls the top-most
- * frame we update the scroll position and will restore it when requested.
- *
- * Causes a SessionStore:update message to be sent that contains the current
- * scroll positions as a tree of strings. If no frame of the whole frame tree
- * is scrolled this will return null so that we don't tack a property onto
- * the tabData object in the parent process.
- *
- * Example:
- * {scroll: "100,100", children: [null, null, {scroll: "200,200"}]}
- */
-var ScrollPositionListener = {
- init: function () {
- addEventListener("scroll", this);
- gFrameTree.addObserver(this);
- },
-
- handleEvent: function (event) {
- let frame = event.target.defaultView;
-
- // Don't collect scroll data for frames created at or after the load event
- // as SessionStore can't restore scroll data for those.
- if (gFrameTree.contains(frame)) {
- MessageQueue.push("scroll", () => this.collect());
- }
- },
-
- onFrameTreeCollected: function () {
- MessageQueue.push("scroll", () => this.collect());
- },
-
- onFrameTreeReset: function () {
- MessageQueue.push("scroll", () => null);
- },
-
- collect: function () {
- return gFrameTree.map(ScrollPosition.collect);
- }
-};
-
-/**
- * Listens for changes to input elements. Whenever the value of an input
- * element changes we will re-collect data for the current frame tree and send
- * a message to the parent process.
- *
- * Causes a SessionStore:update message to be sent that contains the form data
- * for all reachable frames.
- *
- * Example:
- * {
- * formdata: {url: "http://mozilla.org/", id: {input_id: "input value"}},
- * children: [
- * null,
- * {url: "http://sub.mozilla.org/", id: {input_id: "input value 2"}}
- * ]
- * }
- */
-var FormDataListener = {
- init: function () {
- addEventListener("input", this, true);
- addEventListener("change", this, true);
- gFrameTree.addObserver(this);
- },
-
- handleEvent: function (event) {
- let frame = event.target.ownerGlobal;
-
- // Don't collect form data for frames created at or after the load event
- // as SessionStore can't restore form data for those.
- if (gFrameTree.contains(frame)) {
- MessageQueue.push("formdata", () => this.collect());
- }
- },
-
- onFrameTreeReset: function () {
- MessageQueue.push("formdata", () => null);
- },
-
- collect: function () {
- return gFrameTree.map(FormData.collect);
- }
-};
-
-/**
- * Listens for changes to the page style. Whenever a different page style is
- * selected or author styles are enabled/disabled we send a message with the
- * currently applied style to the chrome process.
- *
- * Causes a SessionStore:update message to be sent that contains the currently
- * selected pageStyle for all reachable frames.
- *
- * Example:
- * {pageStyle: "Dusk", children: [null, {pageStyle: "Mozilla"}]}
- */
-var PageStyleListener = {
- init: function () {
- Services.obs.addObserver(this, "author-style-disabled-changed", false);
- Services.obs.addObserver(this, "style-sheet-applicable-state-changed", false);
- gFrameTree.addObserver(this);
- },
-
- uninit: function () {
- Services.obs.removeObserver(this, "author-style-disabled-changed");
- Services.obs.removeObserver(this, "style-sheet-applicable-state-changed");
- },
-
- observe: function (subject, topic) {
- let frame = subject.defaultView;
-
- if (frame && gFrameTree.contains(frame)) {
- MessageQueue.push("pageStyle", () => this.collect());
- }
- },
-
- collect: function () {
- return PageStyle.collect(docShell, gFrameTree);
- },
-
- onFrameTreeCollected: function () {
- MessageQueue.push("pageStyle", () => this.collect());
- },
-
- onFrameTreeReset: function () {
- MessageQueue.push("pageStyle", () => null);
- }
-};
-
-/**
- * Listens for changes to docShell capabilities. Whenever a new load is started
- * we need to re-check the list of capabilities and send message when it has
- * changed.
- *
- * Causes a SessionStore:update message to be sent that contains the currently
- * disabled docShell capabilities (all nsIDocShell.allow* properties set to
- * false) as a string - i.e. capability names separate by commas.
- */
-var DocShellCapabilitiesListener = {
- /**
- * This field is used to compare the last docShell capabilities to the ones
- * that have just been collected. If nothing changed we won't send a message.
- */
- _latestCapabilities: "",
-
- init: function () {
- gFrameTree.addObserver(this);
- },
-
- /**
- * onFrameTreeReset() is called as soon as we start loading a page.
- */
- onFrameTreeReset: function() {
- // The order of docShell capabilities cannot change while we're running
- // so calling join() without sorting before is totally sufficient.
- let caps = DocShellCapabilities.collect(docShell).join(",");
-
- // Send new data only when the capability list changes.
- if (caps != this._latestCapabilities) {
- this._latestCapabilities = caps;
- MessageQueue.push("disallow", () => caps || null);
- }
- }
-};
-
-/**
- * Listens for changes to the DOMSessionStorage. Whenever new keys are added,
- * existing ones removed or changed, or the storage is cleared we will send a
- * message to the parent process containing up-to-date sessionStorage data.
- *
- * Causes a SessionStore:update message to be sent that contains the current
- * DOMSessionStorage contents. The data is a nested object using host names
- * as keys and per-host DOMSessionStorage data as values.
- */
-var SessionStorageListener = {
- init: function () {
- addEventListener("MozSessionStorageChanged", this, true);
- Services.obs.addObserver(this, "browser:purge-domain-data", false);
- gFrameTree.addObserver(this);
- },
-
- uninit: function () {
- Services.obs.removeObserver(this, "browser:purge-domain-data");
- },
-
- handleEvent: function (event) {
- if (gFrameTree.contains(event.target)) {
- this.collectFromEvent(event);
- }
- },
-
- observe: function () {
- // Collect data on the next tick so that any other observer
- // that needs to purge data can do its work first.
- setTimeout(() => this.collect(), 0);
- },
-
- // Before DOM Storage can be written to disk, it needs to be serialized
- // for sending across frames/processes, then again to be sent across
- // threads, then again to be put in a buffer for the disk. Each of these
- // serializations is an opportunity to OOM and (depending on the site of
- // the OOM), either crash, lose all data for the frame or lose all data
- // for the application.
- //
- // In order to avoid this, compute an estimate of the size of the
- // object, and block SessionStorage items that are too large. As
- // we also don't want to cause an OOM here, we use a quick and memory-
- // efficient approximation: we compute the total sum of string lengths
- // involved in this object.
- estimateStorageSize: function(collected) {
- if (!collected) {
- return 0;
- }
-
- let size = 0;
- for (let host of Object.keys(collected)) {
- size += host.length;
- let perHost = collected[host];
- for (let key of Object.keys(perHost)) {
- size += key.length;
- let perKey = perHost[key];
- size += perKey.length;
- }
- }
-
- return size;
- },
-
- // We don't want to send all the session storage data for all the frames
- // for every change. So if only a few value changed we send them over as
- // a "storagechange" event. If however for some reason before we send these
- // changes we have to send over the entire sessions storage data, we just
- // reset these changes.
- _changes: undefined,
-
- resetChanges: function () {
- this._changes = undefined;
- },
-
- collectFromEvent: function (event) {
- // TODO: we should take browser.sessionstore.dom_storage_limit into an account here.
- if (docShell) {
- let {url, key, newValue} = event;
- let uri = Services.io.newURI(url, null, null);
- let domain = uri.prePath;
- if (!this._changes) {
- this._changes = {};
- }
- if (!this._changes[domain]) {
- this._changes[domain] = {};
- }
- this._changes[domain][key] = newValue;
-
- MessageQueue.push("storagechange", () => {
- let tmp = this._changes;
- // If there were multiple changes we send them merged.
- // First one will collect all the changes the rest of
- // these messages will be ignored.
- this.resetChanges();
- return tmp;
- });
- }
- },
-
- collect: function () {
- if (docShell) {
- // We need the entire session storage, let's reset the pending individual change
- // messages.
- this.resetChanges();
- MessageQueue.push("storage", () => {
- let collected = SessionStorage.collect(docShell, gFrameTree);
-
- if (collected == null) {
- return collected;
- }
-
- let size = this.estimateStorageSize(collected);
-
- MessageQueue.push("telemetry", () => ({ FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS: size }));
- if (size > Preferences.get("browser.sessionstore.dom_storage_limit", DOM_STORAGE_MAX_CHARS)) {
- // Rather than keeping the old storage, which wouldn't match the rest
- // of the state of the page, empty the storage. DOM storage will be
- // recollected the next time and stored if it is now small enough.
- return {};
- }
-
- return collected;
- });
- }
- },
-
- onFrameTreeCollected: function () {
- this.collect();
- },
-
- onFrameTreeReset: function () {
- this.collect();
- }
-};
-
-/**
- * Listen for changes to the privacy status of the tab.
- * By definition, tabs start in non-private mode.
- *
- * Causes a SessionStore:update message to be sent for
- * field "isPrivate". This message contains
- * |true| if the tab is now private
- * |null| if the tab is now public - the field is therefore
- * not saved.
- */
-var PrivacyListener = {
- init: function() {
- docShell.addWeakPrivacyTransitionObserver(this);
-
- // Check that value at startup as it might have
- // been set before the frame script was loaded.
- if (docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing) {
- MessageQueue.push("isPrivate", () => true);
- }
- },
-
- // Ci.nsIPrivacyTransitionObserver
- privateModeChanged: function(enabled) {
- MessageQueue.push("isPrivate", () => enabled || null);
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivacyTransitionObserver,
- Ci.nsISupportsWeakReference])
-};
-
-/**
- * A message queue that takes collected data and will take care of sending it
- * to the chrome process. It allows flushing using synchronous messages and
- * takes care of any race conditions that might occur because of that. Changes
- * will be batched if they're pushed in quick succession to avoid a message
- * flood.
- */
-var MessageQueue = {
- /**
- * A map (string -> lazy fn) holding lazy closures of all queued data
- * collection routines. These functions will return data collected from the
- * docShell.
- */
- _data: new Map(),
-
- /**
- * The delay (in ms) used to delay sending changes after data has been
- * invalidated.
- */
- BATCH_DELAY_MS: 1000,
-
- /**
- * The current timeout ID, null if there is no queue data. We use timeouts
- * to damp a flood of data changes and send lots of changes as one batch.
- */
- _timeout: null,
-
- /**
- * Whether or not sending batched messages on a timer is disabled. This should
- * only be used for debugging or testing. If you need to access this value,
- * you should probably use the timeoutDisabled getter.
- */
- _timeoutDisabled: false,
-
- /**
- * True if batched messages are not being fired on a timer. This should only
- * ever be true when debugging or during tests.
- */
- get timeoutDisabled() {
- return this._timeoutDisabled;
- },
-
- /**
- * Disables sending batched messages on a timer. Also cancels any pending
- * timers.
- */
- set timeoutDisabled(val) {
- this._timeoutDisabled = val;
-
- if (val && this._timeout) {
- clearTimeout(this._timeout);
- this._timeout = null;
- }
-
- return val;
- },
-
- init() {
- this.timeoutDisabled =
- Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
-
- Services.prefs.addObserver(TIMEOUT_DISABLED_PREF, this, false);
- },
-
- uninit() {
- Services.prefs.removeObserver(TIMEOUT_DISABLED_PREF, this);
- },
-
- observe(subject, topic, data) {
- if (topic == "nsPref:changed" && data == TIMEOUT_DISABLED_PREF) {
- this.timeoutDisabled =
- Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
- }
- },
-
- /**
- * Pushes a given |value| onto the queue. The given |key| represents the type
- * of data that is stored and can override data that has been queued before
- * but has not been sent to the parent process, yet.
- *
- * @param key (string)
- * A unique identifier specific to the type of data this is passed.
- * @param fn (function)
- * A function that returns the value that will be sent to the parent
- * process.
- */
- push: function (key, fn) {
- this._data.set(key, createLazy(fn));
-
- if (!this._timeout && !this._timeoutDisabled) {
- // Wait a little before sending the message to batch multiple changes.
- this._timeout = setTimeout(() => this.send(), this.BATCH_DELAY_MS);
- }
- },
-
- /**
- * Sends queued data to the chrome process.
- *
- * @param options (object)
- * {flushID: 123} to specify that this is a flush
- * {isFinal: true} to signal this is the final message sent on unload
- */
- send: function (options = {}) {
- // Looks like we have been called off a timeout after the tab has been
- // closed. The docShell is gone now and we can just return here as there
- // is nothing to do.
- if (!docShell) {
- return;
- }
-
- if (this._timeout) {
- clearTimeout(this._timeout);
- this._timeout = null;
- }
-
- let flushID = (options && options.flushID) || 0;
-
- let durationMs = Date.now();
-
- let data = {};
- let telemetry = {};
- for (let [key, func] of this._data) {
- let value = func();
- if (key == "telemetry") {
- for (let histogramId of Object.keys(value)) {
- telemetry[histogramId] = value[histogramId];
- }
- } else if (value || (key != "storagechange" && key != "historychange")) {
- data[key] = value;
- }
- }
-
- this._data.clear();
-
- durationMs = Date.now() - durationMs;
- telemetry.FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS = durationMs;
-
- try {
- // Send all data to the parent process.
- sendAsyncMessage("SessionStore:update", {
- data, telemetry, flushID,
- isFinal: options.isFinal || false,
- epoch: gCurrentEpoch
- });
- } catch (ex if ex && ex.result == Cr.NS_ERROR_OUT_OF_MEMORY) {
- let telemetry = {
- FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM: 1
- };
- sendAsyncMessage("SessionStore:error", {
- telemetry
- });
- }
- },
-};
-
-EventListener.init();
-MessageListener.init();
-FormDataListener.init();
-PageStyleListener.init();
-SessionHistoryListener.init();
-SessionStorageListener.init();
-ScrollPositionListener.init();
-DocShellCapabilitiesListener.init();
-PrivacyListener.init();
-MessageQueue.init();
-
-function handleRevivedTab() {
- if (!content) {
- removeEventListener("pagehide", handleRevivedTab);
- return;
- }
-
- if (content.document.documentURI.startsWith("about:tabcrashed")) {
- if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
- // Sanity check - we'd better be loading this in a non-remote browser.
- throw new Error("We seem to be navigating away from about:tabcrashed in " +
- "a non-remote browser. This should really never happen.");
- }
-
- removeEventListener("pagehide", handleRevivedTab);
-
- // Notify the parent.
- sendAsyncMessage("SessionStore:crashedTabRevived");
- }
-}
-
-// If we're browsing from the tab crashed UI to a blacklisted URI that keeps
-// this browser non-remote, we'll handle that in a pagehide event.
-addEventListener("pagehide", handleRevivedTab);
-
-addEventListener("unload", () => {
- // Upon frameLoader destruction, send a final update message to
- // the parent and flush all data currently held in the child.
- MessageQueue.send({isFinal: true});
-
- // If we're browsing from the tab crashed UI to a URI that causes the tab
- // to go remote again, we catch this in the unload event handler, because
- // swapping out the non-remote browser for a remote one in
- // tabbrowser.xml's updateBrowserRemoteness doesn't cause the pagehide
- // event to be fired.
- handleRevivedTab();
-
- // Remove all registered nsIObservers.
- PageStyleListener.uninit();
- SessionStorageListener.uninit();
- SessionHistoryListener.uninit();
- MessageQueue.uninit();
-
- // Remove progress listeners.
- gContentRestore.resetRestore();
-
- // We don't need to take care of any gFrameTree observers as the gFrameTree
- // will die with the content script. The same goes for the privacy transition
- // observer that will die with the docShell when the tab is closed.
-});