Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const Cc = Components.classes;
6 const Ci = Components.interfaces;
7 const Cu = Components.utils;
9 var gStateObject;
10 var gTreeData;
12 // Page initialization
14 window.onload = function() {
15 // the crashed session state is kept inside a textbox so that SessionStore picks it up
16 // (for when the tab is closed or the session crashes right again)
17 var sessionData = document.getElementById("sessionData");
18 if (!sessionData.value) {
19 document.getElementById("errorTryAgain").disabled = true;
20 return;
21 }
23 // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0)
24 if (sessionData.value.charAt(0) == '(')
25 sessionData.value = sessionData.value.slice(1, -1);
26 try {
27 gStateObject = JSON.parse(sessionData.value);
28 }
29 catch (exJSON) {
30 var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'});
31 gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s);
32 // If we couldn't parse the string with JSON.parse originally, make sure
33 // that the value in the textbox will be parsable.
34 sessionData.value = JSON.stringify(gStateObject);
35 }
37 // make sure the data is tracked to be restored in case of a subsequent crash
38 var event = document.createEvent("UIEvents");
39 event.initUIEvent("input", true, true, window, 0);
40 sessionData.dispatchEvent(event);
42 initTreeView();
44 document.getElementById("errorTryAgain").focus();
45 };
47 function initTreeView() {
48 var tabList = document.getElementById("tabList");
49 var winLabel = tabList.getAttribute("_window_label");
51 gTreeData = [];
52 gStateObject.windows.forEach(function(aWinData, aIx) {
53 var winState = {
54 label: winLabel.replace("%S", (aIx + 1)),
55 open: true,
56 checked: true,
57 ix: aIx
58 };
59 winState.tabs = aWinData.tabs.map(function(aTabData) {
60 var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" };
61 var iconURL = aTabData.attributes && aTabData.attributes.image || null;
62 // don't initiate a connection just to fetch a favicon (see bug 462863)
63 if (/^https?:/.test(iconURL))
64 iconURL = "moz-anno:favicon:" + iconURL;
65 return {
66 label: entry.title || entry.url,
67 checked: true,
68 src: iconURL,
69 parent: winState
70 };
71 });
72 gTreeData.push(winState);
73 for each (var tab in winState.tabs)
74 gTreeData.push(tab);
75 }, this);
77 tabList.view = treeView;
78 tabList.view.selection.select(0);
79 }
81 // User actions
83 function restoreSession() {
84 document.getElementById("errorTryAgain").disabled = true;
86 // remove all unselected tabs from the state before restoring it
87 var ix = gStateObject.windows.length - 1;
88 for (var t = gTreeData.length - 1; t >= 0; t--) {
89 if (treeView.isContainer(t)) {
90 if (gTreeData[t].checked === 0)
91 // this window will be restored partially
92 gStateObject.windows[ix].tabs =
93 gStateObject.windows[ix].tabs.filter(function(aTabData, aIx)
94 gTreeData[t].tabs[aIx].checked);
95 else if (!gTreeData[t].checked)
96 // this window won't be restored at all
97 gStateObject.windows.splice(ix, 1);
98 ix--;
99 }
100 }
101 var stateString = JSON.stringify(gStateObject);
103 var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
104 var top = getBrowserWindow();
106 // if there's only this page open, reuse the window for restoring the session
107 if (top.gBrowser.tabs.length == 1) {
108 ss.setWindowState(top, stateString, true);
109 return;
110 }
112 // restore the session into a new window and close the current tab
113 var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all");
114 newWindow.addEventListener("load", function() {
115 newWindow.removeEventListener("load", arguments.callee, true);
116 ss.setWindowState(newWindow, stateString, true);
118 var tabbrowser = top.gBrowser;
119 var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
120 tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
121 }, true);
122 }
124 function startNewSession() {
125 var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
126 if (prefBranch.getIntPref("browser.startup.page") == 0)
127 getBrowserWindow().gBrowser.loadURI("about:blank");
128 else
129 getBrowserWindow().BrowserHome();
130 }
132 function onListClick(aEvent) {
133 // don't react to right-clicks
134 if (aEvent.button == 2)
135 return;
137 var row = {}, col = {};
138 treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY, row, col, {});
139 if (col.value) {
140 // Restore this specific tab in the same window for middle/double/accel clicking
141 // on a tab's title.
142 #ifdef XP_MACOSX
143 let accelKey = aEvent.metaKey;
144 #else
145 let accelKey = aEvent.ctrlKey;
146 #endif
147 if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) &&
148 col.value.id == "title" &&
149 !treeView.isContainer(row.value)) {
150 restoreSingleTab(row.value, aEvent.shiftKey);
151 aEvent.stopPropagation();
152 }
153 else if (col.value.id == "restore")
154 toggleRowChecked(row.value);
155 }
156 }
158 function onListKeyDown(aEvent) {
159 switch (aEvent.keyCode)
160 {
161 case KeyEvent.DOM_VK_SPACE:
162 toggleRowChecked(document.getElementById("tabList").currentIndex);
163 break;
164 case KeyEvent.DOM_VK_RETURN:
165 var ix = document.getElementById("tabList").currentIndex;
166 if (aEvent.ctrlKey && !treeView.isContainer(ix))
167 restoreSingleTab(ix, aEvent.shiftKey);
168 break;
169 }
170 }
172 // Helper functions
174 function getBrowserWindow() {
175 return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
176 .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
177 .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
178 }
180 function toggleRowChecked(aIx) {
181 var item = gTreeData[aIx];
182 item.checked = !item.checked;
183 treeView.treeBox.invalidateRow(aIx);
185 function isChecked(aItem) aItem.checked;
187 if (treeView.isContainer(aIx)) {
188 // (un)check all tabs of this window as well
189 for each (var tab in item.tabs) {
190 tab.checked = item.checked;
191 treeView.treeBox.invalidateRow(gTreeData.indexOf(tab));
192 }
193 }
194 else {
195 // update the window's checkmark as well (0 means "partially checked")
196 item.parent.checked = item.parent.tabs.every(isChecked) ? true :
197 item.parent.tabs.some(isChecked) ? 0 : false;
198 treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
199 }
201 document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
202 }
204 function restoreSingleTab(aIx, aShifted) {
205 var tabbrowser = getBrowserWindow().gBrowser;
206 var newTab = tabbrowser.addTab();
207 var item = gTreeData[aIx];
209 var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
210 var tabState = gStateObject.windows[item.parent.ix]
211 .tabs[aIx - gTreeData.indexOf(item.parent) - 1];
212 // ensure tab would be visible on the tabstrip.
213 tabState.hidden = false;
214 ss.setTabState(newTab, JSON.stringify(tabState));
216 // respect the preference as to whether to select the tab (the Shift key inverses)
217 var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
218 if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)
219 tabbrowser.selectedTab = newTab;
220 }
222 // Tree controller
224 var treeView = {
225 treeBox: null,
226 selection: null,
228 get rowCount() { return gTreeData.length; },
229 setTree: function(treeBox) { this.treeBox = treeBox; },
230 getCellText: function(idx, column) { return gTreeData[idx].label; },
231 isContainer: function(idx) { return "open" in gTreeData[idx]; },
232 getCellValue: function(idx, column){ return gTreeData[idx].checked; },
233 isContainerOpen: function(idx) { return gTreeData[idx].open; },
234 isContainerEmpty: function(idx) { return false; },
235 isSeparator: function(idx) { return false; },
236 isSorted: function() { return false; },
237 isEditable: function(idx, column) { return false; },
238 canDrop: function(idx, orientation, dt) { return false; },
239 getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; },
241 getParentIndex: function(idx) {
242 if (!this.isContainer(idx))
243 for (var t = idx - 1; t >= 0 ; t--)
244 if (this.isContainer(t))
245 return t;
246 return -1;
247 },
249 hasNextSibling: function(idx, after) {
250 var thisLevel = this.getLevel(idx);
251 for (var t = after + 1; t < gTreeData.length; t++)
252 if (this.getLevel(t) <= thisLevel)
253 return this.getLevel(t) == thisLevel;
254 return false;
255 },
257 toggleOpenState: function(idx) {
258 if (!this.isContainer(idx))
259 return;
260 var item = gTreeData[idx];
261 if (item.open) {
262 // remove this window's tab rows from the view
263 var thisLevel = this.getLevel(idx);
264 for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++);
265 var deletecount = t - idx - 1;
266 gTreeData.splice(idx + 1, deletecount);
267 this.treeBox.rowCountChanged(idx + 1, -deletecount);
268 }
269 else {
270 // add this window's tab rows to the view
271 var toinsert = gTreeData[idx].tabs;
272 for (var i = 0; i < toinsert.length; i++)
273 gTreeData.splice(idx + i + 1, 0, toinsert[i]);
274 this.treeBox.rowCountChanged(idx + 1, toinsert.length);
275 }
276 item.open = !item.open;
277 this.treeBox.invalidateRow(idx);
278 },
280 getCellProperties: function(idx, column) {
281 if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0)
282 return "partial";
283 if (column.id == "title")
284 return this.getImageSrc(idx, column) ? "icon" : "noicon";
286 return "";
287 },
289 getRowProperties: function(idx) {
290 var winState = gTreeData[idx].parent || gTreeData[idx];
291 if (winState.ix % 2 != 0)
292 return "alternate";
294 return "";
295 },
297 getImageSrc: function(idx, column) {
298 if (column.id == "title")
299 return gTreeData[idx].src || null;
300 return null;
301 },
303 getProgressMode : function(idx, column) { },
304 cycleHeader: function(column) { },
305 cycleCell: function(idx, column) { },
306 selectionChanged: function() { },
307 performAction: function(action) { },
308 performActionOnCell: function(action, index, column) { },
309 getColumnProperties: function(column) { return ""; }
310 };