summaryrefslogtreecommitdiffstats
path: root/webbrowser/components/sessionstore/content
diff options
context:
space:
mode:
Diffstat (limited to 'webbrowser/components/sessionstore/content')
-rw-r--r--webbrowser/components/sessionstore/content/aboutSessionRestore.js320
-rw-r--r--webbrowser/components/sessionstore/content/aboutSessionRestore.xhtml94
-rw-r--r--webbrowser/components/sessionstore/content/content-sessionStore.js40
3 files changed, 454 insertions, 0 deletions
diff --git a/webbrowser/components/sessionstore/content/aboutSessionRestore.js b/webbrowser/components/sessionstore/content/aboutSessionRestore.js
new file mode 100644
index 0000000..2b6f9ea
--- /dev/null
+++ b/webbrowser/components/sessionstore/content/aboutSessionRestore.js
@@ -0,0 +1,320 @@
+/* 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/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+var gStateObject;
+var gTreeData;
+
+// Page initialization
+
+window.onload = function() {
+ // 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;
+ }
+
+ // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0)
+ if (sessionData.value.charAt(0) == '(')
+ sessionData.value = sessionData.value.slice(1, -1);
+ try {
+ gStateObject = JSON.parse(sessionData.value);
+ }
+ catch (exJSON) {
+ var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'});
+ gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s);
+ // If we couldn't parse the string with JSON.parse originally, make sure
+ // that the value in the textbox will be parsable.
+ sessionData.value = JSON.stringify(gStateObject);
+ }
+
+ // 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 initTreeView() {
+ var tabList = document.getElementById("tabList");
+ var winLabel = tabList.getAttribute("_window_label");
+
+ gTreeData = [];
+ 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.attributes && aTabData.attributes.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 restoreSession() {
+ document.getElementById("errorTryAgain").disabled = true;
+
+ // 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(function(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");
+ newWindow.addEventListener("load", function() {
+ newWindow.removeEventListener("load", arguments.callee, true);
+ ss.setWindowState(newWindow, stateString, true);
+
+ var tabbrowser = top.gBrowser;
+ var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
+ tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
+ }, true);
+}
+
+function startNewSession() {
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ if (prefBranch.getIntPref("browser.startup.page") == 0)
+ getBrowserWindow().gBrowser.loadURI("about:logopage");
+ 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.
+#ifdef XP_MACOSX
+ let accelKey = aEvent.metaKey;
+#else
+ let accelKey = aEvent.ctrlKey;
+#endif
+ 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);
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ var ix = document.getElementById("tabList").currentIndex;
+ if (aEvent.ctrlKey && !treeView.isContainer(ix))
+ restoreSingleTab(ix, aEvent.shiftKey);
+ break;
+ case KeyEvent.DOM_VK_UP:
+ case KeyEvent.DOM_VK_DOWN:
+ case KeyEvent.DOM_VK_PAGE_UP:
+ case KeyEvent.DOM_VK_PAGE_DOWN:
+ case KeyEvent.DOM_VK_HOME:
+ case KeyEvent.DOM_VK_END:
+ aEvent.preventDefault(); // else the page scrolls unwantedly
+ 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) {
+ var item = gTreeData[aIx];
+ item.checked = !item.checked;
+ treeView.treeBox.invalidateRow(aIx);
+
+ function isChecked(aItem) aItem.checked;
+
+ 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));
+ }
+
+ 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 "open" in gTreeData[idx]; },
+ 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/webbrowser/components/sessionstore/content/aboutSessionRestore.xhtml b/webbrowser/components/sessionstore/content/aboutSessionRestore.xhtml
new file mode 100644
index 0000000..6b22250
--- /dev/null
+++ b/webbrowser/components/sessionstore/content/aboutSessionRestore.xhtml
@@ -0,0 +1,94 @@
+<?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">
+ <head>
+ <title>&restorepage.tabtitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/netError.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;">
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText">&restorepage.errorTitle;</h1>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText">&restorepage.problemDesc;</p>
+ </div>
+
+ <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
+ <div id="errorLongDesc">
+ <p>&restorepage.tryThis;</p>
+ <ul>
+ <li>&restorepage.restoreSome;</li>
+ <li>&restorepage.startNew;</li>
+ </ul>
+ </div>
+
+ <!-- Short Description -->
+ <div id="errorTrailerDesc">
+ <tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="tabList" flex="1" seltype="single" hidecolumnpicker="true"
+ onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
+ _window_label="&restorepage.windowLabel;">
+ <treecols>
+ <treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
+ <splitter class="tree-splitter"/>
+ <treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ </div>
+ </div>
+
+ <!-- Buttons -->
+ <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="buttons">
+#ifdef XP_UNIX
+ <button id="errorCancel" label="&restorepage.closeButton;"
+ accesskey="&restorepage.close.access;"
+ oncommand="startNewSession();"/>
+ <button id="errorTryAgain" label="&restorepage.tryagainButton;"
+ accesskey="&restorepage.restore.access;"
+ oncommand="restoreSession();"/>
+#else
+ <button id="errorTryAgain" label="&restorepage.tryagainButton;"
+ accesskey="&restorepage.restore.access;"
+ oncommand="restoreSession();"/>
+ <button id="errorCancel" label="&restorepage.closeButton;"
+ accesskey="&restorepage.close.access;"
+ oncommand="startNewSession();"/>
+#endif
+ </hbox>
+ <!-- holds the session data for when the tab is closed -->
+ <input type="text" id="sessionData" style="display: none;"/>
+ </div>
+
+ </body>
+</html>
diff --git a/webbrowser/components/sessionstore/content/content-sessionStore.js b/webbrowser/components/sessionstore/content/content-sessionStore.js
new file mode 100644
index 0000000..e3e956e
--- /dev/null
+++ b/webbrowser/components/sessionstore/content/content-sessionStore.js
@@ -0,0 +1,40 @@
+/* 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/. */
+
+function debug(msg) {
+ Services.console.logStringMessage("SessionStoreContent: " + msg);
+}
+
+/**
+ * Listens for and handles content events that we need for the
+ * session store service to be notified of state changes in content.
+ */
+var EventListener = {
+
+ DOM_EVENTS: [
+ "pageshow", "change", "input"
+ ],
+
+ init: function () {
+ this.DOM_EVENTS.forEach(e => addEventListener(e, this, true));
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "pageshow":
+ if (event.persisted)
+ sendAsyncMessage("SessionStore:pageshow");
+ break;
+ case "input":
+ case "change":
+ sendAsyncMessage("SessionStore:input");
+ break;
+ default:
+ debug("received unknown event '" + event.type + "'");
+ break;
+ }
+ }
+};
+
+EventListener.init();