diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/components/sessionstore/content/aboutSessionRestore.js | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/sessionstore/content/aboutSessionRestore.js')
-rw-r--r-- | browser/components/sessionstore/content/aboutSessionRestore.js | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/browser/components/sessionstore/content/aboutSessionRestore.js b/browser/components/sessionstore/content/aboutSessionRestore.js new file mode 100644 index 0000000000..321bb9d0db --- /dev/null +++ b/browser/components/sessionstore/content/aboutSessionRestore.js @@ -0,0 +1,460 @@ +/* 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 ""; + }, +}; |