tor-browser

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

FormValidationParent.sys.mjs (5851B)


      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 * Chrome side handling of form validation popup.
      7 */
      8 
      9 const lazy = {};
     10 
     11 ChromeUtils.defineESModuleGetters(lazy, {
     12  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     13 });
     14 
     15 class PopupShownObserver {
     16  _weakContext = null;
     17 
     18  constructor(browsingContext) {
     19    this._weakContext = Cu.getWeakReference(browsingContext);
     20  }
     21 
     22  observe(subject, topic) {
     23    let ctxt = this._weakContext.get();
     24    let actor = ctxt.currentWindowGlobal?.getExistingActor("FormValidation");
     25    if (!actor) {
     26      Services.obs.removeObserver(this, "popup-shown");
     27      return;
     28    }
     29    // If any panel besides ourselves shows, hide ourselves again.
     30    if (topic == "popup-shown" && subject != actor._panel) {
     31      actor._hidePopup();
     32    }
     33  }
     34 
     35  QueryInterface = ChromeUtils.generateQI([
     36    Ci.nsIObserver,
     37    Ci.nsISupportsWeakReference,
     38  ]);
     39 }
     40 
     41 export class FormValidationParent extends JSWindowActorParent {
     42  constructor() {
     43    super();
     44 
     45    this._panel = null;
     46    this._obs = null;
     47  }
     48 
     49  static hasOpenPopups(ownPanel = null) {
     50    for (let win of lazy.BrowserWindowTracker.orderedWindows) {
     51      let popups = win.document.querySelectorAll("panel,menupopup");
     52      for (let popup of popups) {
     53        if (popup == ownPanel) {
     54          continue; // Skip our own panel if provided.
     55        }
     56        let { state } = popup;
     57        if (state == "open" || state == "showing") {
     58          return true;
     59        }
     60      }
     61    }
     62    return false;
     63  }
     64 
     65  /*
     66   * Public apis
     67   */
     68 
     69  uninit() {
     70    this._panel = null;
     71    this._obs = null;
     72  }
     73 
     74  hidePopup() {
     75    this._hidePopup();
     76  }
     77 
     78  /*
     79   * Events
     80   */
     81 
     82  receiveMessage(aMessage) {
     83    switch (aMessage.name) {
     84      case "FormValidation:ShowPopup": {
     85        let browser = this.browsingContext.top.embedderElement;
     86        let window = browser.ownerGlobal;
     87        let data = aMessage.data;
     88        let tabBrowser = window.gBrowser;
     89 
     90        // target is the <browser>, make sure we're receiving a message
     91        // from the foreground tab.
     92        if (tabBrowser && browser != tabBrowser.selectedBrowser) {
     93          return;
     94        }
     95 
     96        // If any other popups are open, we don't show the form validation
     97        // popup. We have to fall through for our own popup to make sure the
     98        // popup is updated if we are asked to reshow it with a different
     99        // message or for a different element.
    100        if (FormValidationParent.hasOpenPopups(this._panel)) {
    101          return;
    102        }
    103 
    104        this._showPopup(browser, data);
    105        break;
    106      }
    107      case "FormValidation:HidePopup":
    108        this._hidePopup();
    109        break;
    110    }
    111  }
    112 
    113  handleEvent(aEvent) {
    114    switch (aEvent.type) {
    115      case "FullZoomChange":
    116      case "TextZoomChange":
    117      case "scroll":
    118        this._hidePopup();
    119        break;
    120      case "popuphidden":
    121        this._onPopupHidden(aEvent);
    122        break;
    123    }
    124  }
    125 
    126  /*
    127   * Internal
    128   */
    129 
    130  _onPopupHidden(aEvent) {
    131    aEvent.originalTarget.removeEventListener("popuphidden", this, true);
    132    Services.obs.removeObserver(this._obs, "popup-shown");
    133    let tabBrowser = aEvent.originalTarget.ownerGlobal.gBrowser;
    134    tabBrowser.selectedBrowser.removeEventListener("scroll", this, true);
    135    tabBrowser.selectedBrowser.removeEventListener("FullZoomChange", this);
    136    tabBrowser.selectedBrowser.removeEventListener("TextZoomChange", this);
    137 
    138    this._obs = null;
    139    this._panel = null;
    140  }
    141 
    142  /**
    143   * Shows the form validation popup at a specified position or updates the
    144   * messaging and position if the popup is already displayed.
    145   *
    146   * @param {MozBrowser} aBrowser - Browser element that requests the popup.
    147   * @param {object} aPanelData - Object that contains popup information
    148   *  aPanelData stucture detail:
    149   *   screenRect - the screen rect of the target element.
    150   *   position - popup positional string constants.
    151   *   message - the form element validation message text.
    152   */
    153  _showPopup(aBrowser, aPanelData) {
    154    let previouslyShown = !!this._panel;
    155    this._panel = this._getAndMaybeCreatePanel();
    156    this._panel.firstChild.textContent = aPanelData.message;
    157 
    158    // Display the panel if it isn't already visible.
    159    if (previouslyShown) {
    160      return;
    161    }
    162    // Cleanup after the popup is hidden
    163    this._panel.addEventListener("popuphidden", this, true);
    164    // Hide ourselves if other popups shown
    165    this._obs = new PopupShownObserver(this.browsingContext);
    166    Services.obs.addObserver(this._obs, "popup-shown", true);
    167 
    168    // Hide if the user scrolls the page
    169    aBrowser.addEventListener("scroll", this, true);
    170    aBrowser.addEventListener("FullZoomChange", this);
    171    aBrowser.addEventListener("TextZoomChange", this);
    172 
    173    aBrowser.constrainPopup(this._panel);
    174 
    175    // Open the popup
    176    let rect = aPanelData.screenRect;
    177    this._panel.openPopupAtScreenRect(
    178      aPanelData.position,
    179      rect.left,
    180      rect.top,
    181      rect.width,
    182      rect.height,
    183      false,
    184      false
    185    );
    186  }
    187 
    188  /*
    189   * Hide the popup if currently displayed. Will fire an event to onPopupHiding
    190   * above if visible.
    191   */
    192  _hidePopup() {
    193    this._panel?.hidePopup();
    194  }
    195 
    196  _getAndMaybeCreatePanel() {
    197    // Lazy load the invalid form popup the first time we need to display it.
    198    if (!this._panel) {
    199      let browser = this.browsingContext.top.embedderElement;
    200      let window = browser.ownerGlobal;
    201      let template = window.document.getElementById("invalidFormTemplate");
    202      if (template) {
    203        template.replaceWith(template.content);
    204      }
    205      this._panel = window.document.getElementById("invalid-form-popup");
    206    }
    207 
    208    return this._panel;
    209  }
    210 }