diff options
Diffstat (limited to 'browser/components/sidebar/browser-sidebar.js')
-rw-r--r-- | browser/components/sidebar/browser-sidebar.js | 424 |
1 files changed, 341 insertions, 83 deletions
diff --git a/browser/components/sidebar/browser-sidebar.js b/browser/components/sidebar/browser-sidebar.js index 55664f8cfc..6cbac7c082 100644 --- a/browser/components/sidebar/browser-sidebar.js +++ b/browser/components/sidebar/browser-sidebar.js @@ -3,65 +3,127 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** - * SidebarUI controls showing and hiding the browser sidebar. + * SidebarController handles logic such as toggling sidebar panels, + * dynamically adding menubar menu items for the View -> Sidebar menu, + * and provides APIs for sidebar extensions, etc. */ -var SidebarUI = { +var SidebarController = { + makeSidebar({ elementId, ...rest }) { + return { + get sourceL10nEl() { + return document.getElementById(elementId); + }, + get title() { + return document.getElementById(elementId).getAttribute("label"); + }, + ...rest, + }; + }, + get sidebars() { if (this._sidebars) { return this._sidebars; } - function makeSidebar({ elementId, ...rest }) { - return { - get sourceL10nEl() { - return document.getElementById(elementId); - }, - get title() { - return document.getElementById(elementId).getAttribute("label"); - }, - ...rest, - }; - } - - return (this._sidebars = new Map([ - [ - "viewBookmarksSidebar", - makeSidebar({ - elementId: "sidebar-switcher-bookmarks", - url: "chrome://browser/content/places/bookmarksSidebar.xhtml", - menuId: "menu_bookmarksSidebar", - }), - ], + this._sidebars = new Map([ [ "viewHistorySidebar", - makeSidebar({ + this.makeSidebar({ elementId: "sidebar-switcher-history", url: this.sidebarRevampEnabled ? "chrome://browser/content/sidebar/sidebar-history.html" : "chrome://browser/content/places/historySidebar.xhtml", menuId: "menu_historySidebar", triggerButtonId: "appMenuViewHistorySidebar", + keyId: "key_gotoHistory", + menuL10nId: "menu-view-history-button", + revampL10nId: "sidebar-menu-history", + icon: `url("chrome://browser/content/firefoxview/view-history.svg")`, }), ], [ "viewTabsSidebar", - makeSidebar({ + this.makeSidebar({ elementId: "sidebar-switcher-tabs", url: this.sidebarRevampEnabled ? "chrome://browser/content/sidebar/sidebar-syncedtabs.html" : "chrome://browser/content/syncedtabs/sidebar.xhtml", menuId: "menu_tabsSidebar", + classAttribute: "sync-ui-item", + menuL10nId: "menu-view-synced-tabs-sidebar", + revampL10nId: "sidebar-menu-synced-tabs", + icon: `url("chrome://browser/content/firefoxview/view-syncedtabs.svg")`, }), ], - [ - "viewMegalistSidebar", - makeSidebar({ - elementId: "sidebar-switcher-megalist", - url: "chrome://global/content/megalist/megalist.html", - menuId: "menu_megalistSidebar", - }), - ], - ])); + ]); + + if (!this.sidebarRevampEnabled) { + this._sidebars.set( + "viewBookmarksSidebar", + this.makeSidebar({ + elementId: "sidebar-switcher-bookmarks", + url: "chrome://browser/content/places/bookmarksSidebar.xhtml", + menuId: "menu_bookmarksSidebar", + keyId: "viewBookmarksSidebarKb", + menuL10nId: "menu-view-bookmarks", + revampL10nId: "sidebar-menu-bookmarks", + }) + ); + if (this.megalistEnabled) { + this._sidebars.set( + "viewMegalistSidebar", + this.makeSidebar({ + elementId: "sidebar-switcher-megalist", + url: "chrome://global/content/megalist/megalist.html", + menuId: "menu_megalistSidebar", + menuL10nId: "menu-view-megalist-sidebar", + revampL10nId: "sidebar-menu-megalist", + }) + ); + } + } else { + this._sidebars.set( + "viewCustomizeSidebar", + this.makeSidebar({ + url: "chrome://browser/content/sidebar/sidebar-customize.html", + revampL10nId: "sidebar-menu-customize", + icon: `url("chrome://browser/skin/preferences/category-general.svg")`, + }) + ); + } + + return this._sidebars; + }, + + /** + * Returns a map of tools and extensions for use in the sidebar + */ + get toolsAndExtensions() { + if (this._toolsAndExtensions) { + return this._toolsAndExtensions; + } + + this._toolsAndExtensions = new Map(); + this.getSidebarPanels(["viewHistorySidebar", "viewTabsSidebar"]).forEach( + tool => { + this._toolsAndExtensions.set(tool.commandID, { + view: tool.commandID, + icon: tool.icon, + l10nId: tool.revampL10nId, + disabled: false, + }); + } + ); + this.getExtensions().forEach(extension => { + this._toolsAndExtensions.set(extension.commandID, { + view: extension.commandID, + extensionId: extension.extensionId, + icon: extension.icon, + tooltiptext: extension.label, + disabled: false, + }); + }); + return this._toolsAndExtensions; }, // Avoid getting the browser element from init() to avoid triggering the @@ -120,9 +182,18 @@ var SidebarUI = { this._switcherTarget = document.getElementById("sidebar-switcher-target"); this._switcherArrow = document.getElementById("sidebar-switcher-arrow"); + const menubar = document.getElementById("viewSidebarMenu"); + for (const [commandID, sidebar] of this.sidebars.entries()) { + if (!Object.hasOwn(sidebar, "extensionId")) { + // registerExtension() already creates menu items for extensions. + const menuitem = this.createMenuItem(commandID, sidebar); + menubar.appendChild(menuitem); + } + } + if (this.sidebarRevampEnabled) { - await import("chrome://browser/content/sidebar/sidebar-launcher.mjs"); - document.getElementById("sidebar-launcher").hidden = false; + await import("chrome://browser/content/sidebar/sidebar-main.mjs"); + document.getElementById("sidebar-main").hidden = false; document.getElementById("sidebar-header").hidden = true; } else { this._switcherTarget.addEventListener("command", () => { @@ -144,12 +215,7 @@ var SidebarUI = { const sideMenuPopupItem = document.getElementById( "sidebar-switcher-megalist" ); - sideMenuPopupItem.style.display = Services.prefs.getBoolPref( - "browser.megalist.enabled", - false - ) - ? "" - : "none"; + sideMenuPopupItem.style.display = this.megalistEnabled ? "" : "none"; }, setMegalistMenubarVisibility(aEvent) { @@ -160,10 +226,7 @@ var SidebarUI = { // Show the megalist item if enabled const megalistItem = popup.querySelector("#menu_megalistSidebar"); - megalistItem.hidden = !Services.prefs.getBoolPref( - "browser.megalist.enabled", - false - ); + megalistItem.hidden = !this.megalistEnabled; }, uninit() { @@ -172,22 +235,6 @@ var SidebarUI = { let enumerator = Services.wm.getEnumerator("navigator:browser"); if (!enumerator.hasMoreElements()) { let xulStore = Services.xulStore; - xulStore.persist(this._box, "sidebarcommand"); - - if (this._box.hasAttribute("positionend")) { - xulStore.persist(this._box, "positionend"); - } else { - xulStore.removeValue( - document.documentURI, - "sidebar-box", - "positionend" - ); - } - if (this._box.hasAttribute("checked")) { - xulStore.persist(this._box, "checked"); - } else { - xulStore.removeValue(document.documentURI, "sidebar-box", "checked"); - } xulStore.persist(this._box, "style"); xulStore.persist(this._title, "value"); @@ -338,30 +385,30 @@ var SidebarUI = { [...browser.children].forEach((node, i) => { node.style.order = i + 1; }); - let sidebarLauncher = document.querySelector("sidebar-launcher"); + let sidebarMain = document.querySelector("sidebar-main"); if (!this._positionStart) { - // DOM ordering is: sidebar-launcher | sidebar-box | splitter | appcontent | - // Want to display as: | appcontent | splitter | sidebar-box | sidebar-launcher - // So we just swap box and appcontent ordering and move sidebar-launcher to the end + // DOM ordering is: sidebar-main | sidebar-box | splitter | appcontent | + // Want to display as: | appcontent | splitter | sidebar-box | sidebar-main + // So we just swap box and appcontent ordering and move sidebar-main to the end let appcontent = document.getElementById("appcontent"); let boxOrdinal = this._box.style.order; this._box.style.order = appcontent.style.order; appcontent.style.order = boxOrdinal; // the launcher should be on the right of the sidebar-box - sidebarLauncher.style.order = parseInt(this._box.style.order) + 1; + sidebarMain.style.order = parseInt(this._box.style.order) + 1; // Indicate we've switched ordering to the box this._box.setAttribute("positionend", true); - sidebarLauncher.setAttribute("positionend", true); + sidebarMain.setAttribute("positionend", true); } else { this._box.removeAttribute("positionend"); - sidebarLauncher.removeAttribute("positionend"); + sidebarMain.removeAttribute("positionend"); } this.hideSwitcherPanel(); - let content = SidebarUI.browser.contentWindow; + let content = SidebarController.browser.contentWindow; if (content && content.updatePosition) { content.updatePosition(); } @@ -378,7 +425,7 @@ var SidebarUI = { // If the opener had a sidebar, open the same sidebar in our window. // The opener can be the hidden window too, if we're coming from the state // where no windows are open, and the hidden window has no sidebar box. - let sourceUI = sourceWindow.SidebarUI; + let sourceUI = sourceWindow.SidebarController; if (!sourceUI || !sourceUI._box) { // no source UI or no _box means we also can't adopt the state. return false; @@ -543,17 +590,212 @@ var SidebarUI = { _loadSidebarExtension(commandID) { let sidebar = this.sidebars.get(commandID); - let { extensionId } = sidebar; - if (extensionId) { - SidebarUI.browser.contentWindow.loadPanel( - extensionId, - sidebar.panel, - sidebar.browserStyle - ); + if (typeof sidebar.onload === "function") { + sidebar.onload(); } }, /** + * Sets the disabled property for a tool when customizing sidebar options + * + * @param {string} commandID + */ + toggleTool(commandID) { + let toggledTool = this.toolsAndExtensions.get(commandID); + toggledTool.disabled = !toggledTool.disabled; + if (!toggledTool.disabled) { + // If re-enabling tool, remove from the map and add it to the end + this.toolsAndExtensions.delete(commandID); + this.toolsAndExtensions.set(commandID, toggledTool); + } + window.dispatchEvent(new CustomEvent("SidebarItemChanged")); + }, + + addOrUpdateExtension(commandID, extension) { + if (this.toolsAndExtensions.has(commandID)) { + // Update existing extension + let extensionToUpdate = this.toolsAndExtensions.get(commandID); + extensionToUpdate.icon = extension.icon; + extensionToUpdate.tooltiptext = extension.label; + window.dispatchEvent(new CustomEvent("SidebarItemChanged")); + } else { + // Add new extension + this.toolsAndExtensions.set(commandID, { + view: commandID, + extensionId: extension.extensionId, + icon: extension.icon, + tooltiptext: extension.label, + disabled: false, + }); + window.dispatchEvent(new CustomEvent("SidebarItemAdded")); + } + }, + + /** + * Add menu items for a browser extension. Add the extension to the + * `sidebars` map. + * + * @param {string} commandID + * @param {object} props + */ + registerExtension(commandID, props) { + const sidebar = { + title: props.title, + url: "chrome://browser/content/webext-panels.xhtml", + menuId: props.menuId, + switcherMenuId: `sidebarswitcher_menu_${commandID}`, + keyId: `ext-key-id-${commandID}`, + label: props.title, + icon: props.icon, + classAttribute: "menuitem-iconic webextension-menuitem", + // The following properties are specific to extensions + extensionId: props.extensionId, + onload: props.onload, + }; + this.sidebars.set(commandID, sidebar); + + // Insert a menuitem for View->Show Sidebars. + const menuitem = this.createMenuItem(commandID, sidebar); + document.getElementById("viewSidebarMenu").appendChild(menuitem); + this.addOrUpdateExtension(commandID, sidebar); + + if (!this.sidebarRevampEnabled) { + // Insert a toolbarbutton for the sidebar dropdown selector. + let switcherMenuitem = this.createMenuItem(commandID, sidebar); + switcherMenuitem.setAttribute("id", sidebar.switcherMenuId); + switcherMenuitem.removeAttribute("type"); + + let separator = document.getElementById("sidebar-extensions-separator"); + separator.parentNode.insertBefore(switcherMenuitem, separator); + } + this._setExtensionAttributes( + commandID, + { icon: props.icon, label: props.title }, + sidebar + ); + }, + + /** + * Create a menu item for the View>Sidebars submenu in the menubar. + * + * @param {string} commandID + * @param {object} sidebar + * @returns {Element} + */ + createMenuItem(commandID, sidebar) { + const menuitem = document.createXULElement("menuitem"); + menuitem.setAttribute("id", sidebar.menuId); + menuitem.setAttribute("type", "checkbox"); + menuitem.addEventListener("command", () => this.toggle(commandID)); + if (sidebar.classAttribute) { + menuitem.setAttribute("class", sidebar.classAttribute); + } + if (sidebar.keyId) { + menuitem.setAttribute("key", sidebar.keyId); + } + if (sidebar.menuL10nId) { + menuitem.dataset.l10nId = sidebar.menuL10nId; + } + return menuitem; + }, + + /** + * Update attributes on all existing menu items for a browser extension. + * + * @param {string} commandID + * @param {object} attributes + * @param {string} attributes.icon + * @param {string} attributes.label + * @param {boolean} needsRefresh + */ + setExtensionAttributes(commandID, attributes, needsRefresh) { + const sidebar = this.sidebars.get(commandID); + this._setExtensionAttributes(commandID, attributes, sidebar, needsRefresh); + this.addOrUpdateExtension(commandID, sidebar); + }, + + _setExtensionAttributes( + commandID, + { icon, label }, + sidebar, + needsRefresh = false + ) { + sidebar.icon = icon; + sidebar.label = label; + + const updateAttributes = el => { + el.style.setProperty("--webextension-menuitem-image", sidebar.icon); + el.setAttribute("label", sidebar.label); + }; + + updateAttributes(document.getElementById(sidebar.menuId), sidebar); + const switcherMenu = document.getElementById(sidebar.switcherMenuId); + if (switcherMenu) { + updateAttributes(switcherMenu, sidebar); + } + if (this.initialized && this.currentID === commandID) { + // Update the sidebar if this extension is the current sidebar. + updateAttributes(this._switcherTarget, sidebar); + this.title = label; + if (this.isOpen && needsRefresh) { + this.show(commandID); + } + } + }, + + /** + * Retrieve the list of registered browser extensions. + * + * @returns {Array} + */ + getExtensions() { + const extensions = []; + for (const [commandID, sidebar] of this.sidebars.entries()) { + if (Object.hasOwn(sidebar, "extensionId")) { + extensions.push({ commandID, ...sidebar }); + } + } + return extensions; + }, + + /** + * Retrieve the list of sidebar panels + * + * @param {Array} commandIds + * @returns {Array} + */ + getSidebarPanels(commandIds) { + const tools = []; + for (const commandID of commandIds) { + const sidebar = this.sidebars.get(commandID); + if (sidebar) { + tools.push({ commandID, ...sidebar }); + } + } + return tools; + }, + + /** + * Remove a browser extension. + * + * @param {string} commandID + */ + removeExtension(commandID) { + const sidebar = this.sidebars.get(commandID); + if (!sidebar) { + return; + } + if (this.currentID === commandID) { + this.hide(); + } + document.getElementById(sidebar.menuId)?.remove(); + document.getElementById(sidebar.switcherMenuId)?.remove(); + this.sidebars.delete(commandID); + this.toolsAndExtensions.delete(commandID); + window.dispatchEvent(new CustomEvent("SidebarItemRemoved")); + }, + + /** * Show the sidebar. * * This wraps the internal method, including a ping to telemetry. @@ -632,7 +874,17 @@ var SidebarUI = { this._box.setAttribute("checked", "true"); this._box.setAttribute("sidebarcommand", commandID); - let { url, title, sourceL10nEl } = this.sidebars.get(commandID); + let { icon, url, title, sourceL10nEl } = this.sidebars.get(commandID); + if (icon) { + this._switcherTarget.style.setProperty( + "--webextension-menuitem-image", + icon + ); + } else { + this._switcherTarget.style.removeProperty( + "--webextension-menuitem-image" + ); + } // use to live update <tree> elements if the locale changes this.lastOpenedId = commandID; @@ -730,15 +982,21 @@ var SidebarUI = { // Add getters related to the position here, since we will want them // available for both startDelayedLoad and init. XPCOMUtils.defineLazyPreferenceGetter( - SidebarUI, + SidebarController, "_positionStart", - SidebarUI.POSITION_START_PREF, + SidebarController.POSITION_START_PREF, true, - SidebarUI.setPosition.bind(SidebarUI) + SidebarController.setPosition.bind(SidebarController) ); XPCOMUtils.defineLazyPreferenceGetter( - SidebarUI, + SidebarController, "sidebarRevampEnabled", "sidebar.revamp", false ); +XPCOMUtils.defineLazyPreferenceGetter( + SidebarController, + "megalistEnabled", + "browser.megalist.enabled", + false +); |