diff options
Diffstat (limited to 'browser/actors/FormValidationChild.sys.mjs')
-rw-r--r-- | browser/actors/FormValidationChild.sys.mjs | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/browser/actors/FormValidationChild.sys.mjs b/browser/actors/FormValidationChild.sys.mjs new file mode 100644 index 0000000000..6fa2e3c90d --- /dev/null +++ b/browser/actors/FormValidationChild.sys.mjs @@ -0,0 +1,193 @@ +/* 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/. */ + +/** + * Handles the validation callback from nsIFormFillController and + * the display of the help panel on invalid elements. + */ + +import { LayoutUtils } from "resource://gre/modules/LayoutUtils.sys.mjs"; + +export class FormValidationChild extends JSWindowActorChild { + constructor() { + super(); + this._validationMessage = ""; + this._element = null; + } + + /* + * Events + */ + + handleEvent(aEvent) { + switch (aEvent.type) { + case "MozInvalidForm": + aEvent.preventDefault(); + this.notifyInvalidSubmit(aEvent.detail); + break; + case "pageshow": + if (this._isRootDocumentEvent(aEvent)) { + this._hidePopup(); + } + break; + case "pagehide": + // Act as if the element is being blurred. This will remove any + // listeners and hide the popup. + this._onBlur(); + break; + case "input": + this._onInput(aEvent); + break; + case "blur": + this._onBlur(aEvent); + break; + } + } + + notifyInvalidSubmit(aInvalidElements) { + // Show a validation message on the first focusable element. + for (let element of aInvalidElements) { + // Insure that this is the FormSubmitObserver associated with the + // element / window this notification is about. + if (this.contentWindow != element.ownerGlobal.document.defaultView) { + return; + } + + if ( + !( + ChromeUtils.getClassName(element) === "HTMLInputElement" || + ChromeUtils.getClassName(element) === "HTMLTextAreaElement" || + ChromeUtils.getClassName(element) === "HTMLSelectElement" || + ChromeUtils.getClassName(element) === "HTMLButtonElement" || + element.isFormAssociatedCustomElements + ) + ) { + continue; + } + + let validationMessage = element.isFormAssociatedCustomElements + ? element.internals.validationMessage + : element.validationMessage; + + if (element.isFormAssociatedCustomElements) { + // For element that are form-associated custom elements, user agents + // should use their validation anchor instead. + element = element.internals.validationAnchor; + } + + 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(aEvent) { + 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) + ); + } +} |