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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: var gStateObject; michael@0: var gTreeData; michael@0: michael@0: // Page initialization michael@0: michael@0: window.onload = function() { michael@0: // the crashed session state is kept inside a textbox so that SessionStore picks it up michael@0: // (for when the tab is closed or the session crashes right again) michael@0: var sessionData = document.getElementById("sessionData"); michael@0: if (!sessionData.value) { michael@0: document.getElementById("errorTryAgain").disabled = true; michael@0: return; michael@0: } michael@0: michael@0: // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0) michael@0: if (sessionData.value.charAt(0) == '(') michael@0: sessionData.value = sessionData.value.slice(1, -1); michael@0: try { michael@0: gStateObject = JSON.parse(sessionData.value); michael@0: } michael@0: catch (exJSON) { michael@0: var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'}); michael@0: gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s); michael@0: // If we couldn't parse the string with JSON.parse originally, make sure michael@0: // that the value in the textbox will be parsable. michael@0: sessionData.value = JSON.stringify(gStateObject); michael@0: } michael@0: michael@0: // make sure the data is tracked to be restored in case of a subsequent crash michael@0: var event = document.createEvent("UIEvents"); michael@0: event.initUIEvent("input", true, true, window, 0); michael@0: sessionData.dispatchEvent(event); michael@0: michael@0: initTreeView(); michael@0: michael@0: document.getElementById("errorTryAgain").focus(); michael@0: }; michael@0: michael@0: function initTreeView() { michael@0: var tabList = document.getElementById("tabList"); michael@0: var winLabel = tabList.getAttribute("_window_label"); michael@0: michael@0: gTreeData = []; michael@0: gStateObject.windows.forEach(function(aWinData, aIx) { michael@0: var winState = { michael@0: label: winLabel.replace("%S", (aIx + 1)), michael@0: open: true, michael@0: checked: true, michael@0: ix: aIx michael@0: }; michael@0: winState.tabs = aWinData.tabs.map(function(aTabData) { michael@0: var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" }; michael@0: var iconURL = aTabData.attributes && aTabData.attributes.image || null; michael@0: // don't initiate a connection just to fetch a favicon (see bug 462863) michael@0: if (/^https?:/.test(iconURL)) michael@0: iconURL = "moz-anno:favicon:" + iconURL; michael@0: return { michael@0: label: entry.title || entry.url, michael@0: checked: true, michael@0: src: iconURL, michael@0: parent: winState michael@0: }; michael@0: }); michael@0: gTreeData.push(winState); michael@0: for each (var tab in winState.tabs) michael@0: gTreeData.push(tab); michael@0: }, this); michael@0: michael@0: tabList.view = treeView; michael@0: tabList.view.selection.select(0); michael@0: } michael@0: michael@0: // User actions michael@0: michael@0: function restoreSession() { michael@0: document.getElementById("errorTryAgain").disabled = true; michael@0: michael@0: // remove all unselected tabs from the state before restoring it michael@0: var ix = gStateObject.windows.length - 1; michael@0: for (var t = gTreeData.length - 1; t >= 0; t--) { michael@0: if (treeView.isContainer(t)) { michael@0: if (gTreeData[t].checked === 0) michael@0: // this window will be restored partially michael@0: gStateObject.windows[ix].tabs = michael@0: gStateObject.windows[ix].tabs.filter(function(aTabData, aIx) michael@0: gTreeData[t].tabs[aIx].checked); michael@0: else if (!gTreeData[t].checked) michael@0: // this window won't be restored at all michael@0: gStateObject.windows.splice(ix, 1); michael@0: ix--; michael@0: } michael@0: } michael@0: var stateString = JSON.stringify(gStateObject); michael@0: michael@0: var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); michael@0: var top = getBrowserWindow(); michael@0: michael@0: // if there's only this page open, reuse the window for restoring the session michael@0: if (top.gBrowser.tabs.length == 1) { michael@0: ss.setWindowState(top, stateString, true); michael@0: return; michael@0: } michael@0: michael@0: // restore the session into a new window and close the current tab michael@0: var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all"); michael@0: newWindow.addEventListener("load", function() { michael@0: newWindow.removeEventListener("load", arguments.callee, true); michael@0: ss.setWindowState(newWindow, stateString, true); michael@0: michael@0: var tabbrowser = top.gBrowser; michael@0: var tabIndex = tabbrowser.getBrowserIndexForDocument(document); michael@0: tabbrowser.removeTab(tabbrowser.tabs[tabIndex]); michael@0: }, true); michael@0: } michael@0: michael@0: function startNewSession() { michael@0: var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); michael@0: if (prefBranch.getIntPref("browser.startup.page") == 0) michael@0: getBrowserWindow().gBrowser.loadURI("about:blank"); michael@0: else michael@0: getBrowserWindow().BrowserHome(); michael@0: } michael@0: michael@0: function onListClick(aEvent) { michael@0: // don't react to right-clicks michael@0: if (aEvent.button == 2) michael@0: return; michael@0: michael@0: var row = {}, col = {}; michael@0: treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY, row, col, {}); michael@0: if (col.value) { michael@0: // Restore this specific tab in the same window for middle/double/accel clicking michael@0: // on a tab's title. michael@0: #ifdef XP_MACOSX michael@0: let accelKey = aEvent.metaKey; michael@0: #else michael@0: let accelKey = aEvent.ctrlKey; michael@0: #endif michael@0: if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) && michael@0: col.value.id == "title" && michael@0: !treeView.isContainer(row.value)) { michael@0: restoreSingleTab(row.value, aEvent.shiftKey); michael@0: aEvent.stopPropagation(); michael@0: } michael@0: else if (col.value.id == "restore") michael@0: toggleRowChecked(row.value); michael@0: } michael@0: } michael@0: michael@0: function onListKeyDown(aEvent) { michael@0: switch (aEvent.keyCode) michael@0: { michael@0: case KeyEvent.DOM_VK_SPACE: michael@0: toggleRowChecked(document.getElementById("tabList").currentIndex); michael@0: break; michael@0: case KeyEvent.DOM_VK_RETURN: michael@0: var ix = document.getElementById("tabList").currentIndex; michael@0: if (aEvent.ctrlKey && !treeView.isContainer(ix)) michael@0: restoreSingleTab(ix, aEvent.shiftKey); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Helper functions michael@0: michael@0: function getBrowserWindow() { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); michael@0: } michael@0: michael@0: function toggleRowChecked(aIx) { michael@0: var item = gTreeData[aIx]; michael@0: item.checked = !item.checked; michael@0: treeView.treeBox.invalidateRow(aIx); michael@0: michael@0: function isChecked(aItem) aItem.checked; michael@0: michael@0: if (treeView.isContainer(aIx)) { michael@0: // (un)check all tabs of this window as well michael@0: for each (var tab in item.tabs) { michael@0: tab.checked = item.checked; michael@0: treeView.treeBox.invalidateRow(gTreeData.indexOf(tab)); michael@0: } michael@0: } michael@0: else { michael@0: // update the window's checkmark as well (0 means "partially checked") michael@0: item.parent.checked = item.parent.tabs.every(isChecked) ? true : michael@0: item.parent.tabs.some(isChecked) ? 0 : false; michael@0: treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent)); michael@0: } michael@0: michael@0: document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked); michael@0: } michael@0: michael@0: function restoreSingleTab(aIx, aShifted) { michael@0: var tabbrowser = getBrowserWindow().gBrowser; michael@0: var newTab = tabbrowser.addTab(); michael@0: var item = gTreeData[aIx]; michael@0: michael@0: var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); michael@0: var tabState = gStateObject.windows[item.parent.ix] michael@0: .tabs[aIx - gTreeData.indexOf(item.parent) - 1]; michael@0: // ensure tab would be visible on the tabstrip. michael@0: tabState.hidden = false; michael@0: ss.setTabState(newTab, JSON.stringify(tabState)); michael@0: michael@0: // respect the preference as to whether to select the tab (the Shift key inverses) michael@0: var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); michael@0: if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted) michael@0: tabbrowser.selectedTab = newTab; michael@0: } michael@0: michael@0: // Tree controller michael@0: michael@0: var treeView = { michael@0: treeBox: null, michael@0: selection: null, michael@0: michael@0: get rowCount() { return gTreeData.length; }, michael@0: setTree: function(treeBox) { this.treeBox = treeBox; }, michael@0: getCellText: function(idx, column) { return gTreeData[idx].label; }, michael@0: isContainer: function(idx) { return "open" in gTreeData[idx]; }, michael@0: getCellValue: function(idx, column){ return gTreeData[idx].checked; }, michael@0: isContainerOpen: function(idx) { return gTreeData[idx].open; }, michael@0: isContainerEmpty: function(idx) { return false; }, michael@0: isSeparator: function(idx) { return false; }, michael@0: isSorted: function() { return false; }, michael@0: isEditable: function(idx, column) { return false; }, michael@0: canDrop: function(idx, orientation, dt) { return false; }, michael@0: getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; }, michael@0: michael@0: getParentIndex: function(idx) { michael@0: if (!this.isContainer(idx)) michael@0: for (var t = idx - 1; t >= 0 ; t--) michael@0: if (this.isContainer(t)) michael@0: return t; michael@0: return -1; michael@0: }, michael@0: michael@0: hasNextSibling: function(idx, after) { michael@0: var thisLevel = this.getLevel(idx); michael@0: for (var t = after + 1; t < gTreeData.length; t++) michael@0: if (this.getLevel(t) <= thisLevel) michael@0: return this.getLevel(t) == thisLevel; michael@0: return false; michael@0: }, michael@0: michael@0: toggleOpenState: function(idx) { michael@0: if (!this.isContainer(idx)) michael@0: return; michael@0: var item = gTreeData[idx]; michael@0: if (item.open) { michael@0: // remove this window's tab rows from the view michael@0: var thisLevel = this.getLevel(idx); michael@0: for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++); michael@0: var deletecount = t - idx - 1; michael@0: gTreeData.splice(idx + 1, deletecount); michael@0: this.treeBox.rowCountChanged(idx + 1, -deletecount); michael@0: } michael@0: else { michael@0: // add this window's tab rows to the view michael@0: var toinsert = gTreeData[idx].tabs; michael@0: for (var i = 0; i < toinsert.length; i++) michael@0: gTreeData.splice(idx + i + 1, 0, toinsert[i]); michael@0: this.treeBox.rowCountChanged(idx + 1, toinsert.length); michael@0: } michael@0: item.open = !item.open; michael@0: this.treeBox.invalidateRow(idx); michael@0: }, michael@0: michael@0: getCellProperties: function(idx, column) { michael@0: if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0) michael@0: return "partial"; michael@0: if (column.id == "title") michael@0: return this.getImageSrc(idx, column) ? "icon" : "noicon"; michael@0: michael@0: return ""; michael@0: }, michael@0: michael@0: getRowProperties: function(idx) { michael@0: var winState = gTreeData[idx].parent || gTreeData[idx]; michael@0: if (winState.ix % 2 != 0) michael@0: return "alternate"; michael@0: michael@0: return ""; michael@0: }, michael@0: michael@0: getImageSrc: function(idx, column) { michael@0: if (column.id == "title") michael@0: return gTreeData[idx].src || null; michael@0: return null; michael@0: }, michael@0: michael@0: getProgressMode : function(idx, column) { }, michael@0: cycleHeader: function(column) { }, michael@0: cycleCell: function(idx, column) { }, michael@0: selectionChanged: function() { }, michael@0: performAction: function(action) { }, michael@0: performActionOnCell: function(action, index, column) { }, michael@0: getColumnProperties: function(column) { return ""; } michael@0: };