mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			193 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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)
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |