From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- toolkit/content/widgets/menulist.js | 417 ++++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 toolkit/content/widgets/menulist.js (limited to 'toolkit/content/widgets/menulist.js') diff --git a/toolkit/content/widgets/menulist.js b/toolkit/content/widgets/menulist.js new file mode 100644 index 0000000000..4e66c030f3 --- /dev/null +++ b/toolkit/content/widgets/menulist.js @@ -0,0 +1,417 @@ +/* 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"; + +// This is loaded into all XUL windows. Wrap in a block to prevent +// leaking to window scope. +{ + const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + const MozXULMenuElement = MozElements.MozElementMixin(XULMenuElement); + const MenuBaseControl = MozElements.BaseControlMixin(MozXULMenuElement); + + class MozMenuList extends MenuBaseControl { + constructor() { + super(); + + this.addEventListener( + "command", + event => { + if (event.target.parentNode.parentNode == this) { + this.selectedItem = event.target; + } + }, + true + ); + + this.addEventListener("popupshowing", event => { + if (event.target.parentNode == this) { + this.activeChild = null; + if (this.selectedItem) { + // Not ready for auto-setting the active child in hierarchies yet. + // For now, only do this when the outermost menupopup opens. + this.activeChild = this.mSelectedInternal; + } + } + }); + + this.addEventListener( + "keypress", + event => { + if ( + event.defaultPrevented || + event.altKey || + event.ctrlKey || + event.metaKey + ) { + return; + } + + if ( + AppConstants.platform === "macosx" && + !this.open && + (event.keyCode == KeyEvent.DOM_VK_UP || + event.keyCode == KeyEvent.DOM_VK_DOWN) + ) { + // This should open the menulist on macOS, see + // XULButtonElement::PostHandleEvent. + return; + } + + if ( + event.keyCode == KeyEvent.DOM_VK_UP || + event.keyCode == KeyEvent.DOM_VK_DOWN || + event.keyCode == KeyEvent.DOM_VK_PAGE_UP || + event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN || + event.keyCode == KeyEvent.DOM_VK_HOME || + event.keyCode == KeyEvent.DOM_VK_END || + event.keyCode == KeyEvent.DOM_VK_BACK_SPACE || + event.charCode > 0 + ) { + // Moving relative to an item: start from the currently selected item + this.activeChild = this.mSelectedInternal; + if (this.handleKeyPress(event)) { + this.activeChild.doCommand(); + event.preventDefault(); + } + } + }, + { mozSystemGroup: true } + ); + + this.attachShadow({ mode: "open" }); + } + + static get inheritedAttributes() { + return { + image: "src=image", + "#label": "value=label,crop,accesskey", + "#highlightable-label": "text=label,crop,accesskey", + dropmarker: "disabled,open", + }; + } + + static get markup() { + // Accessibility information of these nodes will be presented + // on XULComboboxAccessible generated from ; + // hide these nodes from the accessibility tree. + return ` + + + + + + + `; + } + + connectedCallback() { + if (this.delayConnectedCallback()) { + return; + } + + if (!this.hasAttribute("popuponly")) { + this.shadowRoot.appendChild(this.constructor.fragment); + this._labelBox = this.shadowRoot.getElementById("label-box"); + this._dropmarker = this.shadowRoot.querySelector("dropmarker"); + this.initializeAttributeInheritance(); + } else { + this.shadowRoot.appendChild(document.createElement("slot")); + } + + this.mSelectedInternal = null; + this.mAttributeObserver = null; + this.setInitialSelection(); + } + + // nsIDOMXULSelectControlElement + set value(val) { + // if the new value is null, we still need to remove the old value + if (val == null) { + this.selectedItem = val; + return; + } + + var arr = null; + var popup = this.menupopup; + if (popup) { + arr = popup.getElementsByAttribute("value", val); + } + + if (arr && arr.item(0)) { + this.selectedItem = arr[0]; + } else { + this.selectedItem = null; + this.setAttribute("value", val); + } + } + + // nsIDOMXULSelectControlElement + get value() { + return this.getAttribute("value"); + } + + // nsIDOMXULMenuListElement + set image(val) { + this.setAttribute("image", val); + } + + // nsIDOMXULMenuListElement + get image() { + return this.getAttribute("image"); + } + + // nsIDOMXULMenuListElement + get label() { + return this.getAttribute("label"); + } + + set description(val) { + this.setAttribute("description", val); + } + + get description() { + return this.getAttribute("description"); + } + + // nsIDOMXULMenuListElement + set open(val) { + this.openMenu(val); + } + + // nsIDOMXULMenuListElement + get open() { + return this.hasAttribute("open"); + } + + // nsIDOMXULSelectControlElement + get itemCount() { + return this.menupopup ? this.menupopup.children.length : 0; + } + + get menupopup() { + var popup = this.firstElementChild; + while (popup && popup.localName != "menupopup") { + popup = popup.nextElementSibling; + } + return popup; + } + + // nsIDOMXULSelectControlElement + set selectedIndex(val) { + var popup = this.menupopup; + if (popup && 0 <= val) { + if (val < popup.children.length) { + this.selectedItem = popup.children[val]; + } + } else { + this.selectedItem = null; + } + } + + // nsIDOMXULSelectControlElement + get selectedIndex() { + // Quick and dirty. We won't deal with hierarchical menulists yet. + if ( + !this.selectedItem || + !this.mSelectedInternal.parentNode || + this.mSelectedInternal.parentNode.parentNode != this + ) { + return -1; + } + + var children = this.mSelectedInternal.parentNode.children; + var i = children.length; + while (i--) { + if (children[i] == this.mSelectedInternal) { + break; + } + } + + return i; + } + + // nsIDOMXULSelectControlElement + set selectedItem(val) { + var oldval = this.mSelectedInternal; + if (oldval == val) { + return; + } + + if (val && !this.contains(val)) { + return; + } + + if (oldval) { + oldval.removeAttribute("selected"); + this.mAttributeObserver.disconnect(); + } + + this.mSelectedInternal = val; + let attributeFilter = ["value", "label", "image", "description"]; + if (val) { + val.setAttribute("selected", "true"); + for (let attr of attributeFilter) { + if (val.hasAttribute(attr)) { + this.setAttribute(attr, val.getAttribute(attr)); + } else { + this.removeAttribute(attr); + } + } + + this.mAttributeObserver = new MutationObserver( + this.handleMutation.bind(this) + ); + this.mAttributeObserver.observe(val, { attributeFilter }); + } else { + for (let attr of attributeFilter) { + this.removeAttribute(attr); + } + } + + var event = document.createEvent("Events"); + event.initEvent("select", true, true); + this.dispatchEvent(event); + + event = document.createEvent("Events"); + event.initEvent("ValueChange", true, true); + this.dispatchEvent(event); + } + + // nsIDOMXULSelectControlElement + get selectedItem() { + return this.mSelectedInternal; + } + + setInitialSelection() { + var popup = this.menupopup; + if (popup) { + var arr = popup.getElementsByAttribute("selected", "true"); + + var editable = this.editable; + var value = this.value; + if (!arr.item(0) && value) { + arr = popup.getElementsByAttribute( + editable ? "label" : "value", + value + ); + } + + if (arr.item(0)) { + this.selectedItem = arr[0]; + } else if (!editable) { + this.selectedIndex = 0; + } + } + } + + contains(item) { + if (!item) { + return false; + } + + var parent = item.parentNode; + return parent && parent.parentNode == this; + } + + handleMutation(aRecords) { + for (let record of aRecords) { + let t = record.target; + if (t == this.mSelectedInternal) { + let attrName = record.attributeName; + switch (attrName) { + case "value": + case "label": + case "image": + case "description": + if (t.hasAttribute(attrName)) { + this.setAttribute(attrName, t.getAttribute(attrName)); + } else { + this.removeAttribute(attrName); + } + } + } + } + } + + // nsIDOMXULSelectControlElement + getIndexOfItem(item) { + var popup = this.menupopup; + if (popup) { + var children = popup.children; + var i = children.length; + while (i--) { + if (children[i] == item) { + return i; + } + } + } + return -1; + } + + // nsIDOMXULSelectControlElement + getItemAtIndex(index) { + var popup = this.menupopup; + if (popup) { + var children = popup.children; + if (index >= 0 && index < children.length) { + return children[index]; + } + } + return null; + } + + appendItem(label, value, description) { + if (!this.menupopup) { + this.appendChild(MozXULElement.parseXULToFragment(``)); + } + + var popup = this.menupopup; + popup.appendChild(MozXULElement.parseXULToFragment(``)); + + var item = popup.lastElementChild; + if (label !== undefined) { + item.setAttribute("label", label); + } + item.setAttribute("value", value); + if (description) { + item.setAttribute("description", description); + } + + return item; + } + + removeAllItems() { + this.selectedItem = null; + var popup = this.menupopup; + if (popup) { + this.removeChild(popup); + } + } + + disconnectedCallback() { + if (this.mAttributeObserver) { + this.mAttributeObserver.disconnect(); + } + + if (this._labelBox) { + this._labelBox.remove(); + this._dropmarker.remove(); + this._labelBox = null; + this._dropmarker = null; + } + } + } + + MenuBaseControl.implementCustomInterface(MozMenuList, [ + Ci.nsIDOMXULMenuListElement, + Ci.nsIDOMXULSelectControlElement, + ]); + + customElements.define("menulist", MozMenuList); +} -- cgit v1.2.3