From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- browser/base/content/browser-sidebar.js | 674 -------------------------------- 1 file changed, 674 deletions(-) delete mode 100644 browser/base/content/browser-sidebar.js (limited to 'browser/base/content/browser-sidebar.js') diff --git a/browser/base/content/browser-sidebar.js b/browser/base/content/browser-sidebar.js deleted file mode 100644 index 2d730700a6..0000000000 --- a/browser/base/content/browser-sidebar.js +++ /dev/null @@ -1,674 +0,0 @@ -/* 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/. */ - -/** - * SidebarUI controls showing and hiding the browser sidebar. - */ -var SidebarUI = { - 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", - }), - ], - [ - "viewHistorySidebar", - makeSidebar({ - elementId: "sidebar-switcher-history", - url: "chrome://browser/content/places/historySidebar.xhtml", - menuId: "menu_historySidebar", - triggerButtonId: "appMenuViewHistorySidebar", - }), - ], - [ - "viewTabsSidebar", - makeSidebar({ - elementId: "sidebar-switcher-tabs", - url: "chrome://browser/content/syncedtabs/sidebar.xhtml", - menuId: "menu_tabsSidebar", - }), - ], - ])); - }, - - // Avoid getting the browser element from init() to avoid triggering the - // constructor during startup if the sidebar is hidden. - get browser() { - if (this._browser) { - return this._browser; - } - return (this._browser = document.getElementById("sidebar")); - }, - POSITION_START_PREF: "sidebar.position_start", - DEFAULT_SIDEBAR_ID: "viewBookmarksSidebar", - - // lastOpenedId is set in show() but unlike currentID it's not cleared out on hide - // and isn't persisted across windows - lastOpenedId: null, - - _box: null, - // The constructor of this label accesses the browser element due to the - // control="sidebar" attribute, so avoid getting this label during startup. - get _title() { - if (this.__title) { - return this.__title; - } - return (this.__title = document.getElementById("sidebar-title")); - }, - _splitter: null, - _reversePositionButton: null, - _switcherPanel: null, - _switcherTarget: null, - _switcherArrow: null, - _inited: false, - - /** - * @type {MutationObserver | null} - */ - _observer: null, - - _initDeferred: Promise.withResolvers(), - - get promiseInitialized() { - return this._initDeferred.promise; - }, - - get initialized() { - return this._inited; - }, - - init() { - this._box = document.getElementById("sidebar-box"); - this._splitter = document.getElementById("sidebar-splitter"); - this._reversePositionButton = document.getElementById( - "sidebar-reverse-position" - ); - this._switcherPanel = document.getElementById("sidebarMenu-popup"); - this._switcherTarget = document.getElementById("sidebar-switcher-target"); - this._switcherArrow = document.getElementById("sidebar-switcher-arrow"); - - this._switcherTarget.addEventListener("command", () => { - this.toggleSwitcherPanel(); - }); - this._switcherTarget.addEventListener("keydown", event => { - this.handleKeydown(event); - }); - - this._inited = true; - - Services.obs.addObserver(this, "intl:app-locales-changed"); - - this._initDeferred.resolve(); - }, - - uninit() { - // If this is the last browser window, persist various values that should be - // remembered for after a restart / reopening a browser window. - 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"); - } - - Services.obs.removeObserver(this, "intl:app-locales-changed"); - - if (this._observer) { - this._observer.disconnect(); - this._observer = null; - } - }, - - /** - * The handler for Services.obs.addObserver. - **/ - observe(_subject, topic, _data) { - switch (topic) { - case "intl:app-locales-changed": { - if (this.isOpen) { - // The component used in history and bookmarks, but it does not - // support live switching the app locale. Reload the entire sidebar to - // invalidate any old text. - this.hide(); - this.showInitially(this.lastOpenedId); - break; - } - } - } - }, - - /** - * Ensure the title stays in sync with the source element, which updates for - * l10n changes. - * - * @param {HTMLElement} [element] - */ - observeTitleChanges(element) { - if (!element) { - return; - } - let observer = this._observer; - if (!observer) { - observer = new MutationObserver(() => { - this.title = this.sidebars.get(this.lastOpenedId).title; - }); - // Re-use the observer. - this._observer = observer; - } - observer.disconnect(); - observer.observe(element, { - attributes: true, - attributeFilter: ["label"], - }); - }, - - /** - * Opens the switcher panel if it's closed, or closes it if it's open. - */ - toggleSwitcherPanel() { - if ( - this._switcherPanel.state == "open" || - this._switcherPanel.state == "showing" - ) { - this.hideSwitcherPanel(); - } else if (this._switcherPanel.state == "closed") { - this.showSwitcherPanel(); - } - }, - - /** - * Handles keydown on the the switcherTarget button - * @param {Event} event - */ - handleKeydown(event) { - switch (event.key) { - case "Enter": - case " ": { - this.toggleSwitcherPanel(); - event.stopPropagation(); - event.preventDefault(); - break; - } - case "Escape": { - this.hideSwitcherPanel(); - event.stopPropagation(); - event.preventDefault(); - break; - } - } - }, - - hideSwitcherPanel() { - this._switcherPanel.hidePopup(); - }, - - showSwitcherPanel() { - this._switcherPanel.addEventListener( - "popuphiding", - () => { - this._switcherTarget.classList.remove("active"); - this._switcherTarget.setAttribute("aria-expanded", false); - }, - { once: true } - ); - - // Combine start/end position with ltr/rtl to set the label in the popup appropriately. - let label = - this._positionStart == RTL_UI - ? gNavigatorBundle.getString("sidebar.moveToLeft") - : gNavigatorBundle.getString("sidebar.moveToRight"); - this._reversePositionButton.setAttribute("label", label); - - // Open the sidebar switcher popup, anchored off the switcher toggle - this._switcherPanel.hidden = false; - this._switcherPanel.openPopup(this._switcherTarget); - - this._switcherTarget.classList.add("active"); - this._switcherTarget.setAttribute("aria-expanded", true); - }, - - updateShortcut({ keyId }) { - let menuitem = this._switcherPanel?.querySelector(`[key="${keyId}"]`); - if (!menuitem) { - // If the menu item doesn't exist yet then the accel text will be set correctly - // upon creation so there's nothing to do now. - return; - } - menuitem.removeAttribute("acceltext"); - }, - - /** - * Change the pref that will trigger a call to setPosition - */ - reversePosition() { - Services.prefs.setBoolPref(this.POSITION_START_PREF, !this._positionStart); - }, - - /** - * Read the positioning pref and position the sidebar and the splitter - * appropriately within the browser container. - */ - setPosition() { - // First reset all ordinals to match DOM ordering. - let browser = document.getElementById("browser"); - [...browser.children].forEach((node, i) => { - node.style.order = i + 1; - }); - - if (!this._positionStart) { - // DOM ordering is: | sidebar-box | splitter | appcontent | - // Want to display as: | appcontent | splitter | sidebar-box | - // So we just swap box and appcontent ordering - let appcontent = document.getElementById("appcontent"); - let boxOrdinal = this._box.style.order; - this._box.style.order = appcontent.style.order; - appcontent.style.order = boxOrdinal; - // Indicate we've switched ordering to the box - this._box.setAttribute("positionend", true); - } else { - this._box.removeAttribute("positionend"); - } - - this.hideSwitcherPanel(); - - let content = SidebarUI.browser.contentWindow; - if (content && content.updatePosition) { - content.updatePosition(); - } - }, - - /** - * Try and adopt the status of the sidebar from another window. - * @param {Window} sourceWindow - Window to use as a source for sidebar status. - * @return true if we adopted the state, or false if the caller should - * initialize the state itself. - */ - adoptFromWindow(sourceWindow) { - // 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; - if (!sourceUI || !sourceUI._box) { - // no source UI or no _box means we also can't adopt the state. - return false; - } - - // Set sidebar command even if hidden, so that we keep the same sidebar - // even if it's currently closed. - let commandID = sourceUI._box.getAttribute("sidebarcommand"); - if (commandID) { - this._box.setAttribute("sidebarcommand", commandID); - } - - if (sourceUI._box.hidden) { - // just hidden means we have adopted the hidden state. - return true; - } - - // dynamically generated sidebars will fail this check, but we still - // consider it adopted. - if (!this.sidebars.has(commandID)) { - return true; - } - - this._box.style.width = sourceUI._box.getBoundingClientRect().width + "px"; - this.showInitially(commandID); - - return true; - }, - - windowPrivacyMatches(w1, w2) { - return ( - PrivateBrowsingUtils.isWindowPrivate(w1) === - PrivateBrowsingUtils.isWindowPrivate(w2) - ); - }, - - /** - * If loading a sidebar was delayed on startup, start the load now. - */ - startDelayedLoad() { - let sourceWindow = window.opener; - // No source window means this is the initial window. If we're being - // opened from another window, check that it is one we might open a sidebar - // for. - if (sourceWindow) { - if ( - sourceWindow.closed || - sourceWindow.location.protocol != "chrome:" || - !this.windowPrivacyMatches(sourceWindow, window) - ) { - return; - } - // Try to adopt the sidebar state from the source window - if (this.adoptFromWindow(sourceWindow)) { - return; - } - } - - // If we're not adopting settings from a parent window, set them now. - let wasOpen = this._box.getAttribute("checked"); - if (!wasOpen) { - return; - } - - let commandID = this._box.getAttribute("sidebarcommand"); - if (commandID && this.sidebars.has(commandID)) { - this.showInitially(commandID); - } else { - this._box.removeAttribute("checked"); - // Remove the |sidebarcommand| attribute, because the element it - // refers to no longer exists, so we should assume this sidebar - // panel has been uninstalled. (249883) - // We use setAttribute rather than removeAttribute so it persists - // correctly. - this._box.setAttribute("sidebarcommand", ""); - // On a startup in which the startup cache was invalidated (e.g. app update) - // extensions will not be started prior to delayedLoad, thus the - // sidebarcommand element will not exist yet. Store the commandID so - // extensions may reopen if necessary. A startup cache invalidation - // can be forced (for testing) by deleting compatibility.ini from the - // profile. - this.lastOpenedId = commandID; - } - }, - - /** - * Fire a "SidebarShown" event on the sidebar to give any interested parties - * a chance to update the button or whatever. - */ - _fireShowEvent() { - let event = new CustomEvent("SidebarShown", { bubbles: true }); - this._switcherTarget.dispatchEvent(event); - }, - - /** - * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar - * a chance to adjust focus as needed. An additional event is needed, because - * we don't want to focus the sidebar when it's opened on startup or in a new - * window, only when the user opens the sidebar. - */ - _fireFocusedEvent() { - let event = new CustomEvent("SidebarFocused", { bubbles: true }); - this.browser.contentWindow.dispatchEvent(event); - }, - - /** - * True if the sidebar is currently open. - */ - get isOpen() { - return !this._box.hidden; - }, - - /** - * The ID of the current sidebar. - */ - get currentID() { - return this.isOpen ? this._box.getAttribute("sidebarcommand") : ""; - }, - - get title() { - return this._title.value; - }, - - set title(value) { - this._title.value = value; - }, - - /** - * Toggle the visibility of the sidebar. If the sidebar is hidden or is open - * with a different commandID, then the sidebar will be opened using the - * specified commandID. Otherwise the sidebar will be hidden. - * - * @param {string} commandID ID of the sidebar. - * @param {DOMNode} [triggerNode] Node, usually a button, that triggered the - * visibility toggling of the sidebar. - * @return {Promise} - */ - toggle(commandID = this.lastOpenedId, triggerNode) { - if ( - CustomizationHandler.isCustomizing() || - CustomizationHandler.isExitingCustomizeMode - ) { - return Promise.resolve(); - } - // First priority for a default value is this.lastOpenedId which is set during show() - // and not reset in hide(), unlike currentID. If show() hasn't been called and we don't - // have a persisted command either, or the command doesn't exist anymore, then - // fallback to a default sidebar. - if (!commandID) { - commandID = this._box.getAttribute("sidebarcommand"); - } - if (!commandID || !this.sidebars.has(commandID)) { - commandID = this.DEFAULT_SIDEBAR_ID; - } - - if (this.isOpen && commandID == this.currentID) { - this.hide(triggerNode); - return Promise.resolve(); - } - return this.show(commandID, triggerNode); - }, - - _loadSidebarExtension(commandID) { - let sidebar = this.sidebars.get(commandID); - let { extensionId } = sidebar; - if (extensionId) { - SidebarUI.browser.contentWindow.loadPanel( - extensionId, - sidebar.panel, - sidebar.browserStyle - ); - } - }, - - /** - * Show the sidebar. - * - * This wraps the internal method, including a ping to telemetry. - * - * @param {string} commandID ID of the sidebar to use. - * @param {DOMNode} [triggerNode] Node, usually a button, that triggered the - * showing of the sidebar. - * @return {Promise} - */ - async show(commandID, triggerNode) { - let panelType = commandID.substring(4, commandID.length - 7); - Services.telemetry.keyedScalarAdd("sidebar.opened", panelType, 1); - - // Extensions without private window access wont be in the - // sidebars map. - if (!this.sidebars.has(commandID)) { - return false; - } - return this._show(commandID).then(() => { - this._loadSidebarExtension(commandID); - - if (triggerNode) { - updateToggleControlLabel(triggerNode); - } - - this._fireFocusedEvent(); - return true; - }); - }, - - /** - * Show the sidebar, without firing the focused event or logging telemetry. - * This is intended to be used when the sidebar is opened automatically - * when a window opens (not triggered by user interaction). - * - * @param {string} commandID ID of the sidebar. - * @return {Promise} - */ - async showInitially(commandID) { - let panelType = commandID.substring(4, commandID.length - 7); - Services.telemetry.keyedScalarAdd("sidebar.opened", panelType, 1); - - // Extensions without private window access wont be in the - // sidebars map. - if (!this.sidebars.has(commandID)) { - return false; - } - return this._show(commandID).then(() => { - this._loadSidebarExtension(commandID); - return true; - }); - }, - - /** - * Implementation for show. Also used internally for sidebars that are shown - * when a window is opened and we don't want to ping telemetry. - * - * @param {string} commandID ID of the sidebar. - * @return {Promise} - */ - _show(commandID) { - return new Promise(resolve => { - this.selectMenuItem(commandID); - - this._box.hidden = this._splitter.hidden = false; - this.setPosition(); - - this.hideSwitcherPanel(); - - this._box.setAttribute("checked", "true"); - this._box.setAttribute("sidebarcommand", commandID); - this.lastOpenedId = commandID; - - let { url, title, sourceL10nEl } = this.sidebars.get(commandID); - this.title = title; - // Keep the title element in sync with any l10n changes. - this.observeTitleChanges(sourceL10nEl); - this.browser.setAttribute("src", url); // kick off async load - - if (this.browser.contentDocument.location.href != url) { - this.browser.addEventListener( - "load", - event => { - // We're handling the 'load' event before it bubbles up to the usual - // (non-capturing) event handlers. Let it bubble up before resolving. - setTimeout(() => { - resolve(); - - // Now that the currentId is updated, fire a show event. - this._fireShowEvent(); - }, 0); - }, - { capture: true, once: true } - ); - } else { - resolve(); - - // Now that the currentId is updated, fire a show event. - this._fireShowEvent(); - } - }); - }, - - /** - * Hide the sidebar. - * - * @param {DOMNode} [triggerNode] Node, usually a button, that triggered the - * hiding of the sidebar. - */ - hide(triggerNode) { - if (!this.isOpen) { - return; - } - - this.hideSwitcherPanel(); - - this.selectMenuItem(""); - - // Replace the document currently displayed in the sidebar with about:blank - // so that we can free memory by unloading the page. We need to explicitly - // create a new content viewer because the old one doesn't get destroyed - // until about:blank has loaded (which does not happen as long as the - // element is hidden). - this.browser.setAttribute("src", "about:blank"); - this.browser.docShell.createAboutBlankDocumentViewer(null, null); - - this._box.removeAttribute("checked"); - this._box.hidden = this._splitter.hidden = true; - - let selBrowser = gBrowser.selectedBrowser; - selBrowser.focus(); - if (triggerNode) { - updateToggleControlLabel(triggerNode); - } - }, - - /** - * Sets the checked state only on the menu items of the specified sidebar, or - * none if the argument is an empty string. - */ - selectMenuItem(commandID) { - for (let [id, { menuId, triggerButtonId }] of this.sidebars) { - let menu = document.getElementById(menuId); - let triggerbutton = - triggerButtonId && document.getElementById(triggerButtonId); - if (id == commandID) { - menu.setAttribute("checked", "true"); - if (triggerbutton) { - triggerbutton.setAttribute("checked", "true"); - updateToggleControlLabel(triggerbutton); - } - } else { - menu.removeAttribute("checked"); - if (triggerbutton) { - triggerbutton.removeAttribute("checked"); - updateToggleControlLabel(triggerbutton); - } - } - } - }, -}; - -// Add getters related to the position here, since we will want them -// available for both startDelayedLoad and init. -XPCOMUtils.defineLazyPreferenceGetter( - SidebarUI, - "_positionStart", - SidebarUI.POSITION_START_PREF, - true, - SidebarUI.setPosition.bind(SidebarUI) -); -- cgit v1.2.3