element.isFormAssociatedCustomElement ? element.internals.validationMessage : element.validationMessage; if (element.isFormAssociatedCustomElement) { // For element that are form-associated custom elements, user agents // should use their validation anchor instead. // It is not clear how constraint validation should work for FACE in // spec if the validation anchor is null, see // https://github.com/whatwg/html/issues/10155. Blink seems fallback to // FACE itself when validation anchor is null, which looks reasonable. element = element.internals.validationAnchor || element; } if (!element || !Services.focus.elementIsFocusable(element, 0)) { continue; } // Update validation message before showing notification this._validationMessage = validationMessage; // Don't connect up to the same element more than once. if (this._element == element) { this._showPopup(element); break; } this._element = element; element.focus(); // Watch for input changes which may change the validation message. element.addEventListener("input", this); // Watch for focus changes so we can disconnect our listeners and // hide the popup. element.addEventListener("blur", this); this._showPopup(element); break; } } /* * Internal */ /* * Handles input changes on the form element we've associated a popup * with. Updates the validation message or closes the popup if form data * becomes valid. */ _onInput(aEvent) { let element = aEvent.originalTarget; // If the form input is now valid, hide the popup. if (element.validity.valid) { this._hidePopup(); return; } // If the element is still invalid for a new reason, we should update // the popup error message. if (this._validationMessage != element.validationMessage) { this._validationMessage = element.validationMessage; this._showPopup(element); } } /* * Blur event handler in which we disconnect from the form element and * hide the popup. */ _onBlur() { if (this._element) { this._element.removeEventListener("input", this); this._element.removeEventListener("blur", this); } this._hidePopup(); this._element = null; } /* * Send the show popup message to chrome with appropriate position * information. Can be called repetitively to update the currently * displayed popup position and text. */ _showPopup(aElement) { // Collect positional information and show the popup let panelData = {}; panelData.message = this._validationMessage; panelData.screenRect = LayoutUtils.getElementBoundingScreenRect(aElement); // We want to show the popup at the middle of checkbox and radio buttons // and where the content begin for the other elements. if ( aElement.tagName == "INPUT" && (aElement.type == "radio" || aElement.type == "checkbox") ) { panelData.position = "bottomcenter topleft"; } else { panelData.position = "after_start"; } this.sendAsyncMessage("FormValidation:ShowPopup", panelData); aElement.ownerGlobal.addEventListener("pagehide", this, { mozSystemGroup: true, }); } _hidePopup() { this.sendAsyncMessage("FormValidation:HidePopup", {}); this._element.ownerGlobal.removeEventListener("pagehide", this, { mozSystemGroup: true, }); } _isRootDocumentEvent(aEvent) { if (this.contentWindow == null) { return true; } let target = aEvent.originalTarget; return ( target == this.document || (target.ownerDocument && target.ownerDocument == this.document) ); } } PK