From 3d4a698edbe7cf87c4892866ec9f51eb1e209378 Mon Sep 17 00:00:00 2001 From: Abdoulaye Oumar Ly Date: Tue, 2 Jul 2019 13:25:41 +0000 Subject: [PATCH] Bug 1560171 - Merge SelectParent.jsm and SelectParentHelper.jsm files. r=NeilDeakin Differential Revision: https://phabricator.services.mozilla.com/D36272 --HG-- extra : moz-landing-system : lando --- toolkit/actors/SelectParent.jsm | 573 +++++++++++++++++- .../content/widgets/browser-custom-element.js | 4 +- toolkit/modules/SelectParentHelper.jsm | 557 ----------------- toolkit/modules/moz.build | 1 - 4 files changed, 556 insertions(+), 579 deletions(-) delete mode 100644 toolkit/modules/SelectParentHelper.jsm diff --git a/toolkit/actors/SelectParent.jsm b/toolkit/actors/SelectParent.jsm index fa001c0fd75db..7765ae1a1c57d 100644 --- a/toolkit/actors/SelectParent.jsm +++ b/toolkit/actors/SelectParent.jsm @@ -4,10 +4,555 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -var EXPORTED_SYMBOLS = ["SelectParent"]; +var EXPORTED_SYMBOLS = [ + "SelectParent", + "SelectParentHelper", +]; -ChromeUtils.defineModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); +const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); +const {Services} = ChromeUtils.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; + +// The properties that we should respect only when the item is not active. +const PROPERTIES_RESET_WHEN_ACTIVE = [ + "color", + "background-color", + "text-shadow", +]; + +const customStylingEnabled = Services.prefs.getBoolPref("dom.forms.select.customstyling"); + +var SelectParentHelper = { + /** + * `populate` takes the `menulist` element and a list of `items` and generates + * a popup list of options. + * + * If `customStylingEnabled` is set to `true`, the function will also + * style the select and its popup trying to prevent the text + * and background to end up in the same color. + * + * All `ua*` variables represent the color values for the default colors + * for their respective form elements used by the user agent. + * The `select*` variables represent the color values defined for the + * particular backgroundColor to transparent, + // but they don't intend to change the popup to transparent. + if (customStylingEnabled && + selectStyle["background-color"] != uaStyle["background-color"]) { + let color = selectStyle["background-color"]; + selectStyle["background-image"] = `linear-gradient(${color}, ${color});`; + selectBackgroundSet = true; + } + + if (selectStyle.color == selectStyle["background-color"]) { + selectStyle.color = uaStyle.color; + } + + if (customStylingEnabled) { + if (selectStyle["text-shadow"] != "none") { + sheet.insertRule(`#ContentSelectDropdown > menupopup > [_moz-menuactive="true"] { + text-shadow: none; + }`, 0); + } + + let ruleBody = ""; + for (let property in selectStyle) { + if (property == "background-color" || property == "direction") + continue; // Handled above, or before. + if (selectStyle[property] != uaStyle[property]) { + ruleBody += `${property}: ${selectStyle[property]};`; + } + } + if (ruleBody) { + sheet.insertRule(`#ContentSelectDropdown > menupopup { + ${ruleBody} + }`, 0); + sheet.insertRule(`#ContentSelectDropdown > menupopup > :not([_moz-menuactive="true"]) { + color: inherit; + }`, 0); + } + } + + // We only set the `customoptionstyling` if the background has been + // manually set. This prevents the overlap between moz-appearance and + // background-color. `color` and `text-shadow` do not interfere with it. + if (selectBackgroundSet) { + menulist.menupopup.setAttribute("customoptionstyling", "true"); + } else { + menulist.menupopup.removeAttribute("customoptionstyling"); + } + + this._currentZoom = zoom; + this._currentMenulist = menulist; + this.populateChildren(menulist, items, uniqueItemStyles, selectedIndex, zoom, + selectStyle, selectBackgroundSet, sheet); + }, + + open(browser, menulist, rect, isOpenedViaTouch, selectParentActor) { + this._actor = selectParentActor; + menulist.hidden = false; + this._currentBrowser = browser; + this._closedWithEnter = false; + this._selectRect = rect; + this._registerListeners(browser, menulist.menupopup); + + let win = browser.ownerGlobal; + + // Set the maximum height to show exactly MAX_ROWS items. + let menupopup = menulist.menupopup; + let firstItem = menupopup.firstElementChild; + while (firstItem && firstItem.hidden) { + firstItem = firstItem.nextElementSibling; + } + + if (firstItem) { + let itemHeight = firstItem.getBoundingClientRect().height; + + // Include the padding and border on the popup. + let cs = win.getComputedStyle(menupopup); + let bpHeight = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth) + + parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom); + menupopup.style.maxHeight = (itemHeight * MAX_ROWS + bpHeight) + "px"; + } + + menupopup.classList.toggle("isOpenedViaTouch", isOpenedViaTouch); + + if (browser.getAttribute("selectmenuconstrained") != "false") { + let constraintRect = browser.getBoundingClientRect(); + constraintRect = new win.DOMRect(constraintRect.left + win.mozInnerScreenX, + constraintRect.top + win.mozInnerScreenY, + constraintRect.width, constraintRect.height); + menupopup.setConstraintRect(constraintRect); + } else { + menupopup.setConstraintRect(new win.DOMRect(0, 0, 0, 0)); + } + menupopup.openPopupAtScreenRect(AppConstants.platform == "macosx" ? "selection" : "after_start", rect.left, rect.top, rect.width, rect.height, false, false); + }, + + hide(menulist, browser) { + if (this._currentBrowser == browser) { + menulist.menupopup.hidePopup(); + } + }, + + handleEvent(event) { + switch (event.type) { + case "mouseup": + function inRect(rect, x, y) { + return x >= rect.left && x <= rect.left + rect.width && y >= rect.top && y <= rect.top + rect.height; + } + + let x = event.screenX, y = event.screenY; + let onAnchor = !inRect(this._currentMenulist.menupopup.getOuterScreenRect(), x, y) && + inRect(this._selectRect, x, y) && this._currentMenulist.menupopup.state == "open"; + this._actor.sendAsyncMessage("Forms:MouseUp", { onAnchor }); + break; + + case "mouseover": + this._actor.sendAsyncMessage("Forms:MouseOver", {}); + + break; + + case "mouseout": + this._actor.sendAsyncMessage("Forms:MouseOut", {}); + break; + + case "keydown": + if (event.keyCode == event.DOM_VK_RETURN) { + this._closedWithEnter = true; + } + break; + + case "command": + if (event.target.hasAttribute("value")) { + this._actor.sendAsyncMessage("Forms:SelectDropDownItem", { + value: event.target.value, + closedWithEnter: this._closedWithEnter, + }); + } + break; + + case "fullscreen": + if (this._currentMenulist) { + this._currentMenulist.menupopup.hidePopup(); + } + break; + + case "popuphidden": + this._actor.sendAsyncMessage("Forms:DismissedDropDown", {}); + let popup = event.target; + this._unregisterListeners(this._currentBrowser, popup); + popup.parentNode.hidden = true; + this._currentBrowser = null; + this._currentMenulist = null; + this._selectRect = null; + this._currentZoom = 1; + this._actor = null; + break; + } + }, + + receiveMessage(msg) { + if (!this._currentBrowser) { + return; + } + + if (msg.name == "Forms:UpdateDropDown") { + // Sanity check - we'd better know what the currently + // opened menulist is, and what browser it belongs to... + if (!this._currentMenulist) { + return; + } + + let scrollBox = this._currentMenulist.menupopup.scrollBox.scrollbox; + let scrollTop = scrollBox.scrollTop; + + let options = msg.data.options; + let selectedIndex = msg.data.selectedIndex; + this.populate(this._currentMenulist, options.options, options.uniqueStyles, + selectedIndex, this._currentZoom, msg.data.defaultStyle, + msg.data.style); + + // Restore scroll position to what it was prior to the update. + scrollBox.scrollTop = scrollTop; + } else if (msg.name == "Forms:BlurDropDown-Ping") { + this._actor.sendAsyncMessage("Forms:BlurDropDown-Pong", {}); + } + }, + + _registerListeners(browser, popup) { + popup.addEventListener("command", this); + popup.addEventListener("popuphidden", this); + popup.addEventListener("mouseover", this); + popup.addEventListener("mouseout", this); + browser.ownerGlobal.addEventListener("mouseup", this, true); + browser.ownerGlobal.addEventListener("keydown", this, true); + browser.ownerGlobal.addEventListener("fullscreen", this, true); + }, + + _unregisterListeners(browser, popup) { + popup.removeEventListener("command", this); + popup.removeEventListener("popuphidden", this); + popup.removeEventListener("mouseover", this); + popup.removeEventListener("mouseout", this); + browser.ownerGlobal.removeEventListener("mouseup", this, true); + browser.ownerGlobal.removeEventListener("keydown", this, true); + browser.ownerGlobal.removeEventListener("fullscreen", this, true); + }, + + /** + * `populateChildren` creates all elements for the popup menu + * based on the list of