1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * Handles the validation callback from nsIFormFillController and
7 * the display of the help panel on invalid elements.
10 import { LayoutUtils } from "resource://gre/modules/LayoutUtils.sys.mjs";
12 export class FormValidationChild extends JSWindowActorChild {
15 this._validationMessage = "";
24 switch (aEvent.type) {
25 case "MozInvalidForm":
26 aEvent.preventDefault();
27 this.notifyInvalidSubmit(aEvent.detail);
30 if (this._isRootDocumentEvent(aEvent)) {
35 // Act as if the element is being blurred. This will remove any
36 // listeners and hide the popup.
40 this._onInput(aEvent);
48 notifyInvalidSubmit(aInvalidElements) {
49 // Show a validation message on the first focusable element.
50 for (let element of aInvalidElements) {
51 // Insure that this is the FormSubmitObserver associated with the
52 // element / window this notification is about.
53 if (this.contentWindow != element.ownerGlobal.document.defaultView) {
59 ChromeUtils.getClassName(element) === "HTMLInputElement" ||
60 ChromeUtils.getClassName(element) === "HTMLTextAreaElement" ||
61 ChromeUtils.getClassName(element) === "HTMLSelectElement" ||
62 ChromeUtils.getClassName(element) === "HTMLButtonElement" ||
63 element.isFormAssociatedCustomElements
69 let validationMessage = element.isFormAssociatedCustomElements
70 ? element.internals.validationMessage
71 : element.validationMessage;
73 if (element.isFormAssociatedCustomElements) {
74 // For element that are form-associated custom elements, user agents
75 // should use their validation anchor instead.
76 // It is not clear how constraint validation should work for FACE in
77 // spec if the validation anchor is null, see
78 // https://github.com/whatwg/html/issues/10155. Blink seems fallback to
79 // FACE itself when validation anchor is null, which looks reasonable.
80 element = element.internals.validationAnchor || element;
83 if (!element || !Services.focus.elementIsFocusable(element, 0)) {
87 // Update validation message before showing notification
88 this._validationMessage = validationMessage;
90 // Don't connect up to the same element more than once.
91 if (this._element == element) {
92 this._showPopup(element);
95 this._element = element;
99 // Watch for input changes which may change the validation message.
100 element.addEventListener("input", this);
102 // Watch for focus changes so we can disconnect our listeners and
104 element.addEventListener("blur", this);
106 this._showPopup(element);
116 * Handles input changes on the form element we've associated a popup
117 * with. Updates the validation message or closes the popup if form data
121 let element = aEvent.originalTarget;
123 // If the form input is now valid, hide the popup.
124 if (element.validity.valid) {
129 // If the element is still invalid for a new reason, we should update
130 // the popup error message.
131 if (this._validationMessage != element.validationMessage) {
132 this._validationMessage = element.validationMessage;
133 this._showPopup(element);
138 * Blur event handler in which we disconnect from the form element and
143 this._element.removeEventListener("input", this);
144 this._element.removeEventListener("blur", this);
147 this._element = null;
151 * Send the show popup message to chrome with appropriate position
152 * information. Can be called repetitively to update the currently
153 * displayed popup position and text.
155 _showPopup(aElement) {
156 // Collect positional information and show the popup
159 panelData.message = this._validationMessage;
161 panelData.screenRect = LayoutUtils.getElementBoundingScreenRect(aElement);
163 // We want to show the popup at the middle of checkbox and radio buttons
164 // and where the content begin for the other elements.
166 aElement.tagName == "INPUT" &&
167 (aElement.type == "radio" || aElement.type == "checkbox")
169 panelData.position = "bottomcenter topleft";
171 panelData.position = "after_start";
173 this.sendAsyncMessage("FormValidation:ShowPopup", panelData);
175 aElement.ownerGlobal.addEventListener("pagehide", this, {
176 mozSystemGroup: true,
181 this.sendAsyncMessage("FormValidation:HidePopup", {});
182 this._element.ownerGlobal.removeEventListener("pagehide", this, {
183 mozSystemGroup: true,
187 _isRootDocumentEvent(aEvent) {
188 if (this.contentWindow == null) {
191 let target = aEvent.originalTarget;
193 target == this.document ||
194 (target.ownerDocument && target.ownerDocument == this.document)