diff options
Diffstat (limited to 'browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.sys.mjs')
-rw-r--r-- | browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.sys.mjs | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.sys.mjs b/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.sys.mjs new file mode 100644 index 0000000000..4d53b166c0 --- /dev/null +++ b/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.sys.mjs @@ -0,0 +1,372 @@ +/* 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/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "l10n", () => { + return new Localization(["browser/recentlyClosed.ftl"], true); +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "closedTabsFromAllWindowsEnabled", + "browser.sessionstore.closedTabsFromAllWindows" +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "closedTabsFromClosedWindowsEnabled", + "browser.sessionstore.closedTabsFromClosedWindows" +); + +export var RecentlyClosedTabsAndWindowsMenuUtils = { + /** + * Builds up a document fragment of UI items for the recently closed tabs. + * @param aWindow + * The window that the tabs were closed in. + * @param aTagName + * The tag name that will be used when creating the UI items. + * @param aPrefixRestoreAll (defaults to false) + * Whether the 'restore all tabs' item is suffixed or prefixed to the list. + * If suffixed (the default) a separator will be inserted before it. + * @returns A document fragment with UI items for each recently closed tab. + */ + getTabsFragment(aWindow, aTagName, aPrefixRestoreAll = false) { + let doc = aWindow.document; + const isPrivate = lazy.PrivateBrowsingUtils.isWindowPrivate(aWindow); + const fragment = doc.createDocumentFragment(); + let isEmpty = true; + + if ( + lazy.SessionStore.getClosedTabCount({ + sourceWindow: aWindow, + closedTabsFromClosedWindows: false, + }) + ) { + isEmpty = false; + const browserWindows = lazy.closedTabsFromAllWindowsEnabled + ? lazy.SessionStore.getWindows(aWindow) + : [aWindow]; + + for (const win of browserWindows) { + const closedTabs = lazy.SessionStore.getClosedTabDataForWindow(win); + for (let i = 0; i < closedTabs.length; i++) { + createEntry( + aTagName, + false, + i, + closedTabs[i], + doc, + closedTabs[i].title, + fragment + ); + } + } + } + + if ( + !isPrivate && + lazy.closedTabsFromClosedWindowsEnabled && + lazy.SessionStore.getClosedTabCountFromClosedWindows() + ) { + isEmpty = false; + const closedTabs = lazy.SessionStore.getClosedTabDataFromClosedWindows(); + for (let i = 0; i < closedTabs.length; i++) { + createEntry( + aTagName, + false, + i, + closedTabs[i], + doc, + closedTabs[i].title, + fragment + ); + } + } + + if (!isEmpty) { + createRestoreAllEntry( + doc, + fragment, + aPrefixRestoreAll, + false, + aTagName == "menuitem" + ? "recently-closed-menu-reopen-all-tabs" + : "recently-closed-panel-reopen-all-tabs", + aTagName + ); + } + return fragment; + }, + + /** + * Builds up a document fragment of UI items for the recently closed windows. + * @param aWindow + * A window that can be used to create the elements and document fragment. + * @param aTagName + * The tag name that will be used when creating the UI items. + * @param aPrefixRestoreAll (defaults to false) + * Whether the 'restore all windows' item is suffixed or prefixed to the list. + * If suffixed (the default) a separator will be inserted before it. + * @returns A document fragment with UI items for each recently closed window. + */ + getWindowsFragment(aWindow, aTagName, aPrefixRestoreAll = false) { + let closedWindowData = lazy.SessionStore.getClosedWindowData(); + let doc = aWindow.document; + let fragment = doc.createDocumentFragment(); + if (closedWindowData.length) { + for (let i = 0; i < closedWindowData.length; i++) { + const { selected, tabs, title } = closedWindowData[i]; + const selectedTab = tabs[selected - 1]; + if (selectedTab) { + const menuLabel = lazy.l10n.formatValueSync( + "recently-closed-undo-close-window-label", + { tabCount: tabs.length - 1, winTitle: title } + ); + createEntry(aTagName, true, i, selectedTab, doc, menuLabel, fragment); + } + } + + createRestoreAllEntry( + doc, + fragment, + aPrefixRestoreAll, + true, + aTagName == "menuitem" + ? "recently-closed-menu-reopen-all-windows" + : "recently-closed-panel-reopen-all-windows", + aTagName + ); + } + return fragment; + }, + + /** + * Handle a command event to re-open all closed tabs + * @param aEvent + * The command event when the user clicks the restore all menu item + */ + onRestoreAllTabsCommand(aEvent) { + const currentWindow = aEvent.target.ownerGlobal; + const browserWindows = lazy.closedTabsFromAllWindowsEnabled + ? lazy.SessionStore.getWindows(currentWindow) + : [currentWindow]; + for (const sourceWindow of browserWindows) { + let count = lazy.SessionStore.getClosedTabCountForWindow(sourceWindow); + while (--count >= 0) { + lazy.SessionStore.undoCloseTab(sourceWindow, 0, currentWindow); + } + } + if (lazy.closedTabsFromClosedWindowsEnabled) { + for (let tabData of lazy.SessionStore.getClosedTabDataFromClosedWindows()) { + lazy.SessionStore.undoClosedTabFromClosedWindow( + { sourceClosedId: tabData.sourceClosedId }, + tabData.closedId, + currentWindow + ); + } + } + }, + + /** + * Handle a command event to re-open all closed windows + * @param aEvent + * The command event when the user clicks the restore all menu item + */ + onRestoreAllWindowsCommand(aEvent) { + const count = lazy.SessionStore.getClosedWindowCount(); + for (let index = 0; index < count; index++) { + lazy.SessionStore.undoCloseWindow(index); + } + }, + + /** + * Re-open a closed tab and put it to the end of the tab strip. + * Used for a middle click. + * @param aEvent + * The event when the user clicks the menu item + */ + _undoCloseMiddleClick(aEvent) { + if (aEvent.button != 1) { + return; + } + if (aEvent.originalTarget.hasAttribute("source-closed-id")) { + lazy.SessionStore.undoClosedTabFromClosedWindow( + { + sourceClosedId: + aEvent.originalTarget.getAttribute("source-closed-id"), + }, + aEvent.originalTarget.getAttribute("value") + ); + } else { + aEvent.view.undoCloseTab( + aEvent.originalTarget.getAttribute("value"), + aEvent.originalTarget.getAttribute("source-window-id") + ); + } + aEvent.view.gBrowser.moveTabToEnd(); + let ancestorPanel = aEvent.target.closest("panel"); + if (ancestorPanel) { + ancestorPanel.hidePopup(); + } + }, +}; + +/** + * Create a UI entry for a recently closed tab or window. + * @param aTagName + * the tag name that will be used when creating the UI entry + * @param aIsWindowsFragment + * whether or not this entry will represent a closed window + * @param aIndex + * the index of the closed tab + * @param aClosedTab + * the closed tab + * @param aDocument + * a document that can be used to create the entry + * @param aMenuLabel + * the label the created entry will have + * @param aFragment + * the fragment the created entry will be in + */ +function createEntry( + aTagName, + aIsWindowsFragment, + aIndex, + aClosedTab, + aDocument, + aMenuLabel, + aFragment +) { + let element = aDocument.createXULElement(aTagName); + + element.setAttribute("label", aMenuLabel); + if (aClosedTab.image) { + const iconURL = lazy.PlacesUIUtils.getImageURL(aClosedTab.image); + element.setAttribute("image", iconURL); + } + + if (aIsWindowsFragment) { + element.setAttribute("oncommand", `undoCloseWindow("${aIndex}");`); + } else if (typeof aClosedTab.sourceClosedId == "number") { + // sourceClosedId is used to look up the closed window to remove it when the tab is restored + let sourceClosedId = aClosedTab.sourceClosedId; + element.setAttribute("source-closed-id", sourceClosedId); + element.setAttribute("value", aClosedTab.closedId); + element.removeAttribute("oncommand"); + element.addEventListener( + "command", + event => { + lazy.SessionStore.undoClosedTabFromClosedWindow( + { sourceClosedId }, + aClosedTab.closedId + ); + }, + { once: true } + ); + } else { + // sourceWindowId is used to look up the closed tab entry to remove it when it is restored + let sourceWindowId = aClosedTab.sourceWindowId; + element.setAttribute("value", aIndex); + element.setAttribute("source-window-id", sourceWindowId); + element.setAttribute( + "oncommand", + `undoCloseTab(${aIndex}, "${sourceWindowId}");` + ); + } + + if (aTagName == "menuitem") { + element.setAttribute( + "class", + "menuitem-iconic bookmark-item menuitem-with-favicon" + ); + } + + // Set the targetURI attribute so it will be shown in tooltip. + // SessionStore uses one-based indexes, so we need to normalize them. + let tabData; + tabData = aIsWindowsFragment ? aClosedTab : aClosedTab.state; + let activeIndex = (tabData.index || tabData.entries.length) - 1; + if (activeIndex >= 0 && tabData.entries[activeIndex]) { + element.setAttribute("targetURI", tabData.entries[activeIndex].url); + } + + // Windows don't open in new tabs and menuitems dispatch command events on + // middle click, so we only need to manually handle middle clicks for + // toolbarbuttons. + if (!aIsWindowsFragment && aTagName != "menuitem") { + element.addEventListener( + "click", + RecentlyClosedTabsAndWindowsMenuUtils._undoCloseMiddleClick + ); + } + + if (aIndex == 0) { + element.setAttribute( + "key", + "key_undoClose" + (aIsWindowsFragment ? "Window" : "Tab") + ); + } + + aFragment.appendChild(element); +} + +/** + * Create an entry to restore all closed windows or tabs. + * @param aDocument + * a document that can be used to create the entry + * @param aFragment + * the fragment the created entry will be in + * @param aPrefixRestoreAll + * whether the 'restore all windows' item is suffixed or prefixed to the list + * If suffixed a separator will be inserted before it. + * @param aIsWindowsFragment + * whether or not this entry will represent a closed window + * @param aRestoreAllLabel + * which localizable string to use for the entry + * @param aEntryCount + * the number of elements to be restored by this entry + * @param aTagName + * the tag name that will be used when creating the UI entry + */ +function createRestoreAllEntry( + aDocument, + aFragment, + aPrefixRestoreAll, + aIsWindowsFragment, + aRestoreAllLabel, + aTagName +) { + let restoreAllElements = aDocument.createXULElement(aTagName); + restoreAllElements.classList.add("restoreallitem"); + + // We cannot use aDocument.l10n.setAttributes because the menubar label is not + // updated in time and displays a blank string (see Bug 1691553). + restoreAllElements.setAttribute( + "label", + lazy.l10n.formatValueSync(aRestoreAllLabel) + ); + + restoreAllElements.addEventListener( + "command", + aIsWindowsFragment + ? RecentlyClosedTabsAndWindowsMenuUtils.onRestoreAllWindowsCommand + : RecentlyClosedTabsAndWindowsMenuUtils.onRestoreAllTabsCommand + ); + + if (aPrefixRestoreAll) { + aFragment.insertBefore(restoreAllElements, aFragment.firstChild); + } else { + aFragment.appendChild(aDocument.createXULElement("menuseparator")); + aFragment.appendChild(restoreAllElements); + } +} |