1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/sessionstore/content/aboutSessionRestore.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,310 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const Cc = Components.classes; 1.9 +const Ci = Components.interfaces; 1.10 +const Cu = Components.utils; 1.11 + 1.12 +var gStateObject; 1.13 +var gTreeData; 1.14 + 1.15 +// Page initialization 1.16 + 1.17 +window.onload = function() { 1.18 + // the crashed session state is kept inside a textbox so that SessionStore picks it up 1.19 + // (for when the tab is closed or the session crashes right again) 1.20 + var sessionData = document.getElementById("sessionData"); 1.21 + if (!sessionData.value) { 1.22 + document.getElementById("errorTryAgain").disabled = true; 1.23 + return; 1.24 + } 1.25 + 1.26 + // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0) 1.27 + if (sessionData.value.charAt(0) == '(') 1.28 + sessionData.value = sessionData.value.slice(1, -1); 1.29 + try { 1.30 + gStateObject = JSON.parse(sessionData.value); 1.31 + } 1.32 + catch (exJSON) { 1.33 + var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'}); 1.34 + gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s); 1.35 + // If we couldn't parse the string with JSON.parse originally, make sure 1.36 + // that the value in the textbox will be parsable. 1.37 + sessionData.value = JSON.stringify(gStateObject); 1.38 + } 1.39 + 1.40 + // make sure the data is tracked to be restored in case of a subsequent crash 1.41 + var event = document.createEvent("UIEvents"); 1.42 + event.initUIEvent("input", true, true, window, 0); 1.43 + sessionData.dispatchEvent(event); 1.44 + 1.45 + initTreeView(); 1.46 + 1.47 + document.getElementById("errorTryAgain").focus(); 1.48 +}; 1.49 + 1.50 +function initTreeView() { 1.51 + var tabList = document.getElementById("tabList"); 1.52 + var winLabel = tabList.getAttribute("_window_label"); 1.53 + 1.54 + gTreeData = []; 1.55 + gStateObject.windows.forEach(function(aWinData, aIx) { 1.56 + var winState = { 1.57 + label: winLabel.replace("%S", (aIx + 1)), 1.58 + open: true, 1.59 + checked: true, 1.60 + ix: aIx 1.61 + }; 1.62 + winState.tabs = aWinData.tabs.map(function(aTabData) { 1.63 + var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" }; 1.64 + var iconURL = aTabData.attributes && aTabData.attributes.image || null; 1.65 + // don't initiate a connection just to fetch a favicon (see bug 462863) 1.66 + if (/^https?:/.test(iconURL)) 1.67 + iconURL = "moz-anno:favicon:" + iconURL; 1.68 + return { 1.69 + label: entry.title || entry.url, 1.70 + checked: true, 1.71 + src: iconURL, 1.72 + parent: winState 1.73 + }; 1.74 + }); 1.75 + gTreeData.push(winState); 1.76 + for each (var tab in winState.tabs) 1.77 + gTreeData.push(tab); 1.78 + }, this); 1.79 + 1.80 + tabList.view = treeView; 1.81 + tabList.view.selection.select(0); 1.82 +} 1.83 + 1.84 +// User actions 1.85 + 1.86 +function restoreSession() { 1.87 + document.getElementById("errorTryAgain").disabled = true; 1.88 + 1.89 + // remove all unselected tabs from the state before restoring it 1.90 + var ix = gStateObject.windows.length - 1; 1.91 + for (var t = gTreeData.length - 1; t >= 0; t--) { 1.92 + if (treeView.isContainer(t)) { 1.93 + if (gTreeData[t].checked === 0) 1.94 + // this window will be restored partially 1.95 + gStateObject.windows[ix].tabs = 1.96 + gStateObject.windows[ix].tabs.filter(function(aTabData, aIx) 1.97 + gTreeData[t].tabs[aIx].checked); 1.98 + else if (!gTreeData[t].checked) 1.99 + // this window won't be restored at all 1.100 + gStateObject.windows.splice(ix, 1); 1.101 + ix--; 1.102 + } 1.103 + } 1.104 + var stateString = JSON.stringify(gStateObject); 1.105 + 1.106 + var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); 1.107 + var top = getBrowserWindow(); 1.108 + 1.109 + // if there's only this page open, reuse the window for restoring the session 1.110 + if (top.gBrowser.tabs.length == 1) { 1.111 + ss.setWindowState(top, stateString, true); 1.112 + return; 1.113 + } 1.114 + 1.115 + // restore the session into a new window and close the current tab 1.116 + var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all"); 1.117 + newWindow.addEventListener("load", function() { 1.118 + newWindow.removeEventListener("load", arguments.callee, true); 1.119 + ss.setWindowState(newWindow, stateString, true); 1.120 + 1.121 + var tabbrowser = top.gBrowser; 1.122 + var tabIndex = tabbrowser.getBrowserIndexForDocument(document); 1.123 + tabbrowser.removeTab(tabbrowser.tabs[tabIndex]); 1.124 + }, true); 1.125 +} 1.126 + 1.127 +function startNewSession() { 1.128 + var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); 1.129 + if (prefBranch.getIntPref("browser.startup.page") == 0) 1.130 + getBrowserWindow().gBrowser.loadURI("about:blank"); 1.131 + else 1.132 + getBrowserWindow().BrowserHome(); 1.133 +} 1.134 + 1.135 +function onListClick(aEvent) { 1.136 + // don't react to right-clicks 1.137 + if (aEvent.button == 2) 1.138 + return; 1.139 + 1.140 + var row = {}, col = {}; 1.141 + treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY, row, col, {}); 1.142 + if (col.value) { 1.143 + // Restore this specific tab in the same window for middle/double/accel clicking 1.144 + // on a tab's title. 1.145 +#ifdef XP_MACOSX 1.146 + let accelKey = aEvent.metaKey; 1.147 +#else 1.148 + let accelKey = aEvent.ctrlKey; 1.149 +#endif 1.150 + if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) && 1.151 + col.value.id == "title" && 1.152 + !treeView.isContainer(row.value)) { 1.153 + restoreSingleTab(row.value, aEvent.shiftKey); 1.154 + aEvent.stopPropagation(); 1.155 + } 1.156 + else if (col.value.id == "restore") 1.157 + toggleRowChecked(row.value); 1.158 + } 1.159 +} 1.160 + 1.161 +function onListKeyDown(aEvent) { 1.162 + switch (aEvent.keyCode) 1.163 + { 1.164 + case KeyEvent.DOM_VK_SPACE: 1.165 + toggleRowChecked(document.getElementById("tabList").currentIndex); 1.166 + break; 1.167 + case KeyEvent.DOM_VK_RETURN: 1.168 + var ix = document.getElementById("tabList").currentIndex; 1.169 + if (aEvent.ctrlKey && !treeView.isContainer(ix)) 1.170 + restoreSingleTab(ix, aEvent.shiftKey); 1.171 + break; 1.172 + } 1.173 +} 1.174 + 1.175 +// Helper functions 1.176 + 1.177 +function getBrowserWindow() { 1.178 + return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) 1.179 + .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem 1.180 + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); 1.181 +} 1.182 + 1.183 +function toggleRowChecked(aIx) { 1.184 + var item = gTreeData[aIx]; 1.185 + item.checked = !item.checked; 1.186 + treeView.treeBox.invalidateRow(aIx); 1.187 + 1.188 + function isChecked(aItem) aItem.checked; 1.189 + 1.190 + if (treeView.isContainer(aIx)) { 1.191 + // (un)check all tabs of this window as well 1.192 + for each (var tab in item.tabs) { 1.193 + tab.checked = item.checked; 1.194 + treeView.treeBox.invalidateRow(gTreeData.indexOf(tab)); 1.195 + } 1.196 + } 1.197 + else { 1.198 + // update the window's checkmark as well (0 means "partially checked") 1.199 + item.parent.checked = item.parent.tabs.every(isChecked) ? true : 1.200 + item.parent.tabs.some(isChecked) ? 0 : false; 1.201 + treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent)); 1.202 + } 1.203 + 1.204 + document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked); 1.205 +} 1.206 + 1.207 +function restoreSingleTab(aIx, aShifted) { 1.208 + var tabbrowser = getBrowserWindow().gBrowser; 1.209 + var newTab = tabbrowser.addTab(); 1.210 + var item = gTreeData[aIx]; 1.211 + 1.212 + var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); 1.213 + var tabState = gStateObject.windows[item.parent.ix] 1.214 + .tabs[aIx - gTreeData.indexOf(item.parent) - 1]; 1.215 + // ensure tab would be visible on the tabstrip. 1.216 + tabState.hidden = false; 1.217 + ss.setTabState(newTab, JSON.stringify(tabState)); 1.218 + 1.219 + // respect the preference as to whether to select the tab (the Shift key inverses) 1.220 + var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); 1.221 + if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted) 1.222 + tabbrowser.selectedTab = newTab; 1.223 +} 1.224 + 1.225 +// Tree controller 1.226 + 1.227 +var treeView = { 1.228 + treeBox: null, 1.229 + selection: null, 1.230 + 1.231 + get rowCount() { return gTreeData.length; }, 1.232 + setTree: function(treeBox) { this.treeBox = treeBox; }, 1.233 + getCellText: function(idx, column) { return gTreeData[idx].label; }, 1.234 + isContainer: function(idx) { return "open" in gTreeData[idx]; }, 1.235 + getCellValue: function(idx, column){ return gTreeData[idx].checked; }, 1.236 + isContainerOpen: function(idx) { return gTreeData[idx].open; }, 1.237 + isContainerEmpty: function(idx) { return false; }, 1.238 + isSeparator: function(idx) { return false; }, 1.239 + isSorted: function() { return false; }, 1.240 + isEditable: function(idx, column) { return false; }, 1.241 + canDrop: function(idx, orientation, dt) { return false; }, 1.242 + getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; }, 1.243 + 1.244 + getParentIndex: function(idx) { 1.245 + if (!this.isContainer(idx)) 1.246 + for (var t = idx - 1; t >= 0 ; t--) 1.247 + if (this.isContainer(t)) 1.248 + return t; 1.249 + return -1; 1.250 + }, 1.251 + 1.252 + hasNextSibling: function(idx, after) { 1.253 + var thisLevel = this.getLevel(idx); 1.254 + for (var t = after + 1; t < gTreeData.length; t++) 1.255 + if (this.getLevel(t) <= thisLevel) 1.256 + return this.getLevel(t) == thisLevel; 1.257 + return false; 1.258 + }, 1.259 + 1.260 + toggleOpenState: function(idx) { 1.261 + if (!this.isContainer(idx)) 1.262 + return; 1.263 + var item = gTreeData[idx]; 1.264 + if (item.open) { 1.265 + // remove this window's tab rows from the view 1.266 + var thisLevel = this.getLevel(idx); 1.267 + for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++); 1.268 + var deletecount = t - idx - 1; 1.269 + gTreeData.splice(idx + 1, deletecount); 1.270 + this.treeBox.rowCountChanged(idx + 1, -deletecount); 1.271 + } 1.272 + else { 1.273 + // add this window's tab rows to the view 1.274 + var toinsert = gTreeData[idx].tabs; 1.275 + for (var i = 0; i < toinsert.length; i++) 1.276 + gTreeData.splice(idx + i + 1, 0, toinsert[i]); 1.277 + this.treeBox.rowCountChanged(idx + 1, toinsert.length); 1.278 + } 1.279 + item.open = !item.open; 1.280 + this.treeBox.invalidateRow(idx); 1.281 + }, 1.282 + 1.283 + getCellProperties: function(idx, column) { 1.284 + if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0) 1.285 + return "partial"; 1.286 + if (column.id == "title") 1.287 + return this.getImageSrc(idx, column) ? "icon" : "noicon"; 1.288 + 1.289 + return ""; 1.290 + }, 1.291 + 1.292 + getRowProperties: function(idx) { 1.293 + var winState = gTreeData[idx].parent || gTreeData[idx]; 1.294 + if (winState.ix % 2 != 0) 1.295 + return "alternate"; 1.296 + 1.297 + return ""; 1.298 + }, 1.299 + 1.300 + getImageSrc: function(idx, column) { 1.301 + if (column.id == "title") 1.302 + return gTreeData[idx].src || null; 1.303 + return null; 1.304 + }, 1.305 + 1.306 + getProgressMode : function(idx, column) { }, 1.307 + cycleHeader: function(column) { }, 1.308 + cycleCell: function(idx, column) { }, 1.309 + selectionChanged: function() { }, 1.310 + performAction: function(action) { }, 1.311 + performActionOnCell: function(action, index, column) { }, 1.312 + getColumnProperties: function(column) { return ""; } 1.313 +};