/* 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.EXPORTED_SYMBOLS = [ "SelectParentHelper" ]; const {utils: Cu} = Components; const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {}); const {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); // Maximum number of rows to display in the select dropdown. const MAX_ROWS = 20; // Minimum elements required to show select search const SEARCH_MINIMUM_ELEMENTS = 40; var currentBrowser = null; var currentMenulist = null; var currentZoom = 1; var closedWithEnter = false; var selectRect; var customStylingEnabled = Services.prefs.getBoolPref("dom.forms.select.customstyling"); var usedSelectBackgroundColor; this.SelectParentHelper = { populate(menulist, items, selectedIndex, zoom, uaBackgroundColor, uaColor, uaSelectBackgroundColor, uaSelectColor, selectBackgroundColor, selectColor, selectTextShadow) { // Clear the current contents of the popup menulist.menupopup.textContent = ""; let stylesheet = menulist.querySelector("#ContentSelectDropdownScopedStylesheet"); if (stylesheet) { stylesheet.remove(); } let doc = menulist.ownerDocument; let sheet; if (customStylingEnabled) { stylesheet = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); stylesheet.setAttribute("id", "ContentSelectDropdownScopedStylesheet"); stylesheet.scoped = true; stylesheet.hidden = true; stylesheet = menulist.appendChild(stylesheet); sheet = stylesheet.sheet; } let ruleBody = ""; // Some webpages set the in content, that means that the -moz_activemenu // may have been removed from the selected item. Since that's normally only // set for the initially selected on popupshowing for the menulist, and we // don't want to close and re-open the popup, we manually set it here. menulist.menuBoxObject.activeChild = item; } item.setAttribute("value", option.index); if (parentElement) { item.classList.add("contentSelectDropdown-ingroup") } } } // Check if search pref is enabled, if this is the first time iterating through // the dropdown, and if the list is long enough for a search element to be added. if (Services.prefs.getBoolPref("dom.forms.selectSearch") && addSearch && element.childElementCount > SEARCH_MINIMUM_ELEMENTS) { // Add a search text field as the first element of the dropdown let searchbox = element.ownerDocument.createElement("textbox"); searchbox.setAttribute("type", "search"); searchbox.addEventListener("input", onSearchInput); searchbox.addEventListener("focus", onSearchFocus); searchbox.addEventListener("blur", onSearchBlur); searchbox.addEventListener("command", onSearchInput); // Handle special keys for exiting search searchbox.addEventListener("keydown", function(event) { if (event.defaultPrevented) { return; } switch (event.key) { case "Escape": searchbox.parentElement.hidePopup(); break; case "ArrowDown": case "Enter": case "Tab": searchbox.blur(); if (searchbox.nextSibling.localName == "menuitem" && !searchbox.nextSibling.hidden) { menulist.menuBoxObject.activeChild = searchbox.nextSibling; } else { var currentOption = searchbox.nextSibling; while (currentOption && (currentOption.localName != "menuitem" || currentOption.hidden)) { currentOption = currentOption.nextSibling; } if (currentOption) { menulist.menuBoxObject.activeChild = currentOption; } else { searchbox.focus(); } } break; default: return; } event.preventDefault(); }, true); element.insertBefore(searchbox, element.childNodes[0]); } return nthChildIndex; } function onSearchInput() { let searchObj = this; // Get input from search field, set to all lower case for comparison let input = searchObj.value.toLowerCase(); // Get all items in dropdown (could be options or optgroups) let menupopup = searchObj.parentElement; let menuItems = menupopup.querySelectorAll("menuitem, menucaption"); // Flag used to detect any group headers with no visible options. // These group headers should be hidden. let allHidden = true; // Keep a reference to the previous group header (menucaption) to go back // and set to hidden if all options within are hidden. let prevCaption = null; for (let currentItem of menuItems) { // Make sure we don't show any options that were hidden by page content if (!currentItem.hiddenByContent) { // Get label and tooltip (title) from option and change to // lower case for comparison let itemLabel = currentItem.getAttribute("label").toLowerCase(); let itemTooltip = currentItem.getAttribute("title").toLowerCase(); // If search input is empty, all options should be shown if (!input) { currentItem.hidden = false; } else if (currentItem.localName == "menucaption") { if (prevCaption != null) { prevCaption.hidden = allHidden; } prevCaption = currentItem; allHidden = true; } else { if (!currentItem.classList.contains("contentSelectDropdown-ingroup") && currentItem.previousSibling.classList.contains("contentSelectDropdown-ingroup")) { if (prevCaption != null) { prevCaption.hidden = allHidden; } prevCaption = null; allHidden = true; } if (itemLabel.includes(input) || itemTooltip.includes(input)) { currentItem.hidden = false; allHidden = false; } else { currentItem.hidden = true; } } if (prevCaption != null) { prevCaption.hidden = allHidden; } } } } function onSearchFocus() { let searchObj = this; let menupopup = searchObj.parentElement; menupopup.parentElement.menuBoxObject.activeChild = null; menupopup.setAttribute("ignorekeys", "true"); currentBrowser.messageManager.sendAsyncMessage("Forms:SearchFocused", {}); } function onSearchBlur() { let searchObj = this; let menupopup = searchObj.parentElement; menupopup.setAttribute("ignorekeys", "false"); }