/* vim: set ts=2 sw=2 sts=2 et tw=80: */ /* 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"; var EXPORTED_SYMBOLS = ["SelectChild", "SelectContentHelper"]; const { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs", LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs", }); const kStateActive = 0x00000001; // ElementState::ACTIVE const kStateHover = 0x00000004; // ElementState::HOVER // Duplicated in SelectParent.jsm // Please keep these lists in sync. const SUPPORTED_OPTION_OPTGROUP_PROPERTIES = [ "direction", "color", "background-color", "text-shadow", "font-family", "font-weight", "font-size", "font-style", ]; const SUPPORTED_SELECT_PROPERTIES = [ ...SUPPORTED_OPTION_OPTGROUP_PROPERTIES, "scrollbar-width", "scrollbar-color", ]; // A process global state for whether or not content thinks // that a while it was open, so // we'll poke a DeferredTask to update the parent sometime // in the very near future. this._updateTimer.arm(); }); this.mut.observe(this.element, { childList: true, subtree: true, attributes: true, }); XPCOMUtils.defineLazyPreferenceGetter( this, "disablePopupAutohide", "ui.popup.disable_autohide", false ); }, uninit() { this.element.openInParentProcess = false; let win = this.element.ownerGlobal; win.removeEventListener("pagehide", this, { mozSystemGroup: true }); this.element.removeEventListener("blur", this, { mozSystemGroup: true }); this.element.removeEventListener("transitionend", this, { mozSystemGroup: true, }); this.element = null; this.actor = null; this.mut.disconnect(); this._updateTimer.disarm(); this._updateTimer = null; gOpen = false; }, showDropDown() { this.element.openInParentProcess = true; this._setupPseudoClassStyles(); let rect = this._getBoundingContentRect(); let computedStyles = getComputedStyles(this.element); let options = this._buildOptionList(); let defaultStyles = this.element.ownerGlobal.getDefaultComputedStyle( this.element ); this.actor.sendAsyncMessage("Forms:ShowDropDown", { isOpenedViaTouch: this.isOpenedViaTouch, options, rect, custom: !this.element.nodePrincipal.isSystemPrincipal, selectedIndex: this.element.selectedIndex, isDarkBackground: ChromeUtils.isDarkBackground(this.element), style: supportedStyles(computedStyles, SUPPORTED_SELECT_PROPERTIES), defaultStyle: supportedStyles(defaultStyles, SUPPORTED_SELECT_PROPERTIES), }); this._clearPseudoClassStyles(); gOpen = true; }, _setupPseudoClassStyles() { if (this._pseudoStylesSetup) { throw new Error("pseudo styles must not be set up yet"); } // Do all of the things that change style at once, before we read // any styles. this._pseudoStylesSetup = true; InspectorUtils.addPseudoClassLock(this.element, ":focus"); let lockedDescendants = (this._lockedDescendants = this.element.querySelectorAll( ":checked" )); for (let child of lockedDescendants) { // Selected options have the :checked pseudo-class, which // we want to disable before calculating the computed // styles since the user agent styles alter the styling // based on :checked. InspectorUtils.addPseudoClassLock(child, ":checked", false); } }, _clearPseudoClassStyles() { if (!this._pseudoStylesSetup) { throw new Error("pseudo styles must be set up already"); } // Undo all of the things that change style at once, after we're // done reading styles. InspectorUtils.clearPseudoClassLocks(this.element); let lockedDescendants = this._lockedDescendants; for (let child of lockedDescendants) { InspectorUtils.clearPseudoClassLocks(child); } this._lockedDescendants = null; this._pseudoStylesSetup = false; }, _getBoundingContentRect() { return lazy.LayoutUtils.getElementBoundingScreenRect(this.element); }, _buildOptionList() { if (!this._pseudoStylesSetup) { throw new Error("pseudo styles must be set up"); } let uniqueStyles = []; let options = buildOptionListForChildren(this.element, uniqueStyles); return { options, uniqueStyles }; }, _update() { // The popup is open. let contentHelper = currentSelectContentHelper.get(this); if (contentHelper) { contentHelper.handleEvent(event); } return; } switch (event.type) { case "mozshowdropdown": { let contentHelper = new SelectContentHelper( event.target, { isOpenedViaTouch: false }, this ); currentSelectContentHelper.set(this, contentHelper); break; } case "mozshowdropdown-sourcetouch": { let contentHelper = new SelectContentHelper( event.target, { isOpenedViaTouch: true }, this ); currentSelectContentHelper.set(this, contentHelper); break; } } } receiveMessage(message) { let contentHelper = currentSelectContentHelper.get(this); if (contentHelper) { contentHelper.receiveMessage(message); } } }