tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FormValidationChild.sys.mjs (5842B)


      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/. */
      4 
      5 /**
      6 * Handles the validation callback from nsIFormFillController and
      7 * the display of the help panel on invalid elements.
      8 */
      9 
     10 import { LayoutUtils } from "resource://gre/modules/LayoutUtils.sys.mjs";
     11 
     12 export class FormValidationChild extends JSWindowActorChild {
     13  constructor() {
     14    super();
     15    this._validationMessage = "";
     16    this._element = null;
     17  }
     18 
     19  /*
     20   * Events
     21   */
     22 
     23  handleEvent(aEvent) {
     24    switch (aEvent.type) {
     25      case "MozInvalidForm":
     26        aEvent.preventDefault();
     27        this.notifyInvalidSubmit(aEvent.detail);
     28        break;
     29      case "pageshow":
     30        if (this._isRootDocumentEvent(aEvent)) {
     31          this._hidePopup();
     32        }
     33        break;
     34      case "pagehide":
     35        // Act as if the element is being blurred. This will remove any
     36        // listeners and hide the popup.
     37        this._onBlur();
     38        break;
     39      case "input":
     40        this._onInput(aEvent);
     41        break;
     42      case "blur":
     43        this._onBlur(aEvent);
     44        break;
     45    }
     46  }
     47 
     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) {
     54        return;
     55      }
     56 
     57      if (
     58        !(
     59          ChromeUtils.getClassName(element) === "HTMLInputElement" ||
     60          ChromeUtils.getClassName(element) === "HTMLTextAreaElement" ||
     61          ChromeUtils.getClassName(element) === "HTMLSelectElement" ||
     62          ChromeUtils.getClassName(element) === "HTMLButtonElement" ||
     63          element.isFormAssociatedCustomElement
     64        )
     65      ) {
     66        continue;
     67      }
     68 
     69      let validationMessage = element.isFormAssociatedCustomElement
     70        ? element.internals.validationMessage
     71        : element.validationMessage;
     72 
     73      if (element.isFormAssociatedCustomElement) {
     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;
     81      }
     82 
     83      if (!element || !Services.focus.elementIsFocusable(element, 0)) {
     84        continue;
     85      }
     86 
     87      // Update validation message before showing notification
     88      this._validationMessage = validationMessage;
     89 
     90      // Don't connect up to the same element more than once.
     91      if (this._element == element) {
     92        this._showPopup(element);
     93        break;
     94      }
     95      this._element = element;
     96 
     97      element.focus();
     98 
     99      // Watch for input changes which may change the validation message.
    100      element.addEventListener("input", this);
    101 
    102      // Watch for focus changes so we can disconnect our listeners and
    103      // hide the popup.
    104      element.addEventListener("blur", this);
    105 
    106      this._showPopup(element);
    107      break;
    108    }
    109  }
    110 
    111  /*
    112   * Internal
    113   */
    114 
    115  /*
    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
    118   * becomes valid.
    119   */
    120  _onInput(aEvent) {
    121    let element = aEvent.originalTarget;
    122 
    123    // If the form input is now valid, hide the popup.
    124    if (element.validity.valid) {
    125      this._hidePopup();
    126      return;
    127    }
    128 
    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);
    134    }
    135  }
    136 
    137  /*
    138   * Blur event handler in which we disconnect from the form element and
    139   * hide the popup.
    140   */
    141  _onBlur() {
    142    if (this._element) {
    143      this._element.removeEventListener("input", this);
    144      this._element.removeEventListener("blur", this);
    145    }
    146    this._hidePopup();
    147    this._element = null;
    148  }
    149 
    150  /*
    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.
    154   */
    155  _showPopup(aElement) {
    156    // Collect positional information and show the popup
    157    let panelData = {};
    158 
    159    panelData.message = this._validationMessage;
    160 
    161    panelData.screenRect = LayoutUtils.getElementBoundingScreenRect(aElement);
    162 
    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.
    165    if (
    166      aElement.tagName == "INPUT" &&
    167      (aElement.type == "radio" || aElement.type == "checkbox")
    168    ) {
    169      panelData.position = "bottomcenter topleft";
    170    } else {
    171      panelData.position = "after_start";
    172    }
    173    this.sendAsyncMessage("FormValidation:ShowPopup", panelData);
    174 
    175    aElement.ownerGlobal.addEventListener("pagehide", this, {
    176      mozSystemGroup: true,
    177    });
    178  }
    179 
    180  _hidePopup() {
    181    this.sendAsyncMessage("FormValidation:HidePopup", {});
    182    this._element.ownerGlobal.removeEventListener("pagehide", this, {
    183      mozSystemGroup: true,
    184    });
    185  }
    186 
    187  _isRootDocumentEvent(aEvent) {
    188    if (this.contentWindow == null) {
    189      return true;
    190    }
    191    let target = aEvent.originalTarget;
    192    return (
    193      target == this.document ||
    194      (target.ownerDocument && target.ownerDocument == this.document)
    195    );
    196  }
    197 }