fune/browser/components/sessionstore/content/aboutSessionRestore.js
Cristina Horotan 5f4356e527 Backed out 9 changesets (bug 1810141) for several test failures on a CLOSED TREE
Backed out changeset 8781a0d1254d (bug 1810141)
Backed out changeset 131037295784 (bug 1810141)
Backed out changeset 3852fbe290f4 (bug 1810141)
Backed out changeset 118f131a524a (bug 1810141)
Backed out changeset ab5d76846e10 (bug 1810141)
Backed out changeset dce3aa683445 (bug 1810141)
Backed out changeset 4dc41d90dbb3 (bug 1810141)
Backed out changeset 50b57ba1a061 (bug 1810141)
Backed out changeset 569de94781e4 (bug 1810141)
2023-02-13 16:05:30 +02:00

460 lines
12 KiB
JavaScript

/* 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";
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
});
var gStateObject;
var gTreeData;
var gTreeInitialized = false;
// Page initialization
window.onload = function() {
let toggleTabs = document.getElementById("tabsToggle");
if (toggleTabs) {
let tabList = document.getElementById("tabList");
let toggleHiddenTabs = () => {
toggleTabs.classList.toggle("tabs-hidden");
tabList.hidden = toggleTabs.classList.contains("tabs-hidden");
initTreeView();
};
toggleTabs.onclick = toggleHiddenTabs;
}
// 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);
}
}
var tabListTree = document.getElementById("tabList");
tabListTree.addEventListener("click", onListClick);
tabListTree.addEventListener("keydown", onListKeyDown);
var errorCancelButton = document.getElementById("errorCancel");
// aboutSessionRestore.js is included aboutSessionRestore.xhtml
// and aboutWelcomeBack.xhtml, but the latter does not have an
// errorCancel button.
if (errorCancelButton) {
errorCancelButton.addEventListener("command", startNewSession);
}
var errorTryAgainButton = document.getElementById("errorTryAgain");
errorTryAgainButton.addEventListener("command", restoreSession);
// 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) {
errorTryAgainButton.disabled = true;
return;
}
gStateObject = JSON.parse(sessionData.value);
// 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();
errorTryAgainButton.focus({ focusVisible: false });
};
function isTreeViewVisible() {
return !document.getElementById("tabList").hidden;
}
async function initTreeView() {
if (gTreeInitialized || !isTreeViewVisible()) {
return;
}
var tabList = document.getElementById("tabList");
let l10nIds = [];
for (
let labelIndex = 0;
labelIndex < gStateObject.windows.length;
labelIndex++
) {
l10nIds.push({
id: "restore-page-window-label",
args: { windowNumber: labelIndex + 1 },
});
}
let winLabels = await document.l10n.formatValues(l10nIds);
gTreeData = [];
gStateObject.windows.forEach(function(aWinData, aIx) {
var winState = {
label: winLabels[aIx],
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);
gTreeInitialized = true;
}
// User actions
function updateTabListVisibility() {
document.getElementById("tabList").hidden = !document.getElementById(
"radioRestoreChoose"
).checked;
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 top = getBrowserWindow();
// if there's only this page open, reuse the window for restoring the session
if (top.gBrowser.tabs.length == 1) {
SessionStore.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"
);
Services.obs.addObserver(function observe(win, topic) {
if (win != newWindow) {
return;
}
Services.obs.removeObserver(observe, topic);
SessionStore.setWindowState(newWindow, stateString, true);
let tabbrowser = top.gBrowser;
let browser = window.docShell.chromeEventHandler;
let tab = tabbrowser.getTabForBrowser(browser);
tabbrowser.removeTab(tab);
}, "browser-delayed-startup-finished");
}
function startNewSession() {
if (Services.prefs.getIntPref("browser.startup.page") == 0) {
getBrowserWindow().gBrowser.loadURI("about:blank", {
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
{}
),
});
} else {
getBrowserWindow().BrowserHome();
}
}
function onListClick(aEvent) {
// don't react to right-clicks
if (aEvent.button == 2) {
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.browsingContext.topChromeWindow;
}
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").
let state = false;
if (item.parent.tabs.every(isChecked)) {
state = true;
} else if (item.parent.tabs.some(isChecked)) {
state = 0;
}
item.parent.checked = state;
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.addWebTab();
var item = gTreeData[aIx];
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;
SessionStore.setTabState(newTab, JSON.stringify(tabState));
// respect the preference as to whether to select the tab (the Shift key inverses)
if (
Services.prefs.getBoolPref("browser.tabs.loadInBackground") != !aShifted
) {
tabbrowser.selectedTab = newTab;
}
}
// Tree controller
var treeView = {
treeBox: null,
selection: null,
get rowCount() {
return gTreeData.length;
},
setTree(treeBox) {
this.treeBox = treeBox;
},
getCellText(idx, column) {
return gTreeData[idx].label;
},
isContainer(idx) {
return "open" in gTreeData[idx];
},
getCellValue(idx, column) {
return gTreeData[idx].checked;
},
isContainerOpen(idx) {
return gTreeData[idx].open;
},
isContainerEmpty(idx) {
return false;
},
isSeparator(idx) {
return false;
},
isSorted() {
return false;
},
isEditable(idx, column) {
return false;
},
canDrop(idx, orientation, dt) {
return false;
},
getLevel(idx) {
return this.isContainer(idx) ? 0 : 1;
},
getParentIndex(idx) {
if (!this.isContainer(idx)) {
for (var t = idx - 1; t >= 0; t--) {
if (this.isContainer(t)) {
return t;
}
}
}
return -1;
},
hasNextSibling(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(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);
/* eslint-disable no-empty */
for (
var t = idx + 1;
t < gTreeData.length && this.getLevel(t) > thisLevel;
t++
) {}
/* eslint-disable no-empty */
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(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(idx) {
var winState = gTreeData[idx].parent || gTreeData[idx];
if (winState.ix % 2 != 0) {
return "alternate";
}
return "";
},
getImageSrc(idx, column) {
if (column.id == "title") {
return gTreeData[idx].src || null;
}
return null;
},
cycleHeader(column) {},
cycleCell(idx, column) {},
selectionChanged() {},
getColumnProperties(column) {
return "";
},
};