diff options
Diffstat (limited to 'browser/actors/FormValidationParent.sys.mjs')
-rw-r--r-- | browser/actors/FormValidationParent.sys.mjs | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/browser/actors/FormValidationParent.sys.mjs b/browser/actors/FormValidationParent.sys.mjs new file mode 100644 index 0000000000..6c23a28097 --- /dev/null +++ b/browser/actors/FormValidationParent.sys.mjs @@ -0,0 +1,204 @@ +/* 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/. */ + +/* + * Chrome side handling of form validation popup. + */ + +const lazy = {}; + +ChromeUtils.defineModuleGetter( + lazy, + "BrowserWindowTracker", + "resource:///modules/BrowserWindowTracker.jsm" +); + +class PopupShownObserver { + _weakContext = null; + + constructor(browsingContext) { + this._weakContext = Cu.getWeakReference(browsingContext); + } + + observe(subject, topic, data) { + let ctxt = this._weakContext.get(); + let actor = ctxt.currentWindowGlobal?.getExistingActor("FormValidation"); + if (!actor) { + Services.obs.removeObserver(this, "popup-shown"); + return; + } + // If any panel besides ourselves shows, hide ourselves again. + if (topic == "popup-shown" && subject != actor._panel) { + actor._hidePopup(); + } + } + + QueryInterface = ChromeUtils.generateQI([ + Ci.nsIObserver, + Ci.nsISupportsWeakReference, + ]); +} + +export class FormValidationParent extends JSWindowActorParent { + constructor() { + super(); + + this._panel = null; + this._obs = null; + } + + static hasOpenPopups() { + for (let win of lazy.BrowserWindowTracker.orderedWindows) { + let popups = win.document.querySelectorAll("panel,menupopup"); + for (let popup of popups) { + let { state } = popup; + if (state == "open" || state == "showing") { + return true; + } + } + } + return false; + } + + /* + * Public apis + */ + + uninit() { + this._panel = null; + this._obs = null; + } + + hidePopup() { + this._hidePopup(); + } + + /* + * Events + */ + + receiveMessage(aMessage) { + switch (aMessage.name) { + case "FormValidation:ShowPopup": + let browser = this.browsingContext.top.embedderElement; + let window = browser.ownerGlobal; + let data = aMessage.data; + let tabBrowser = window.gBrowser; + + // target is the <browser>, make sure we're receiving a message + // from the foreground tab. + if (tabBrowser && browser != tabBrowser.selectedBrowser) { + return; + } + + if (FormValidationParent.hasOpenPopups()) { + return; + } + + this._showPopup(browser, data); + break; + case "FormValidation:HidePopup": + this._hidePopup(); + break; + } + } + + handleEvent(aEvent) { + switch (aEvent.type) { + case "FullZoomChange": + case "TextZoomChange": + case "scroll": + this._hidePopup(); + break; + case "popuphidden": + this._onPopupHidden(aEvent); + break; + } + } + + /* + * Internal + */ + + _onPopupHidden(aEvent) { + aEvent.originalTarget.removeEventListener("popuphidden", this, true); + Services.obs.removeObserver(this._obs, "popup-shown"); + let tabBrowser = aEvent.originalTarget.ownerGlobal.gBrowser; + tabBrowser.selectedBrowser.removeEventListener("scroll", this, true); + tabBrowser.selectedBrowser.removeEventListener("FullZoomChange", this); + tabBrowser.selectedBrowser.removeEventListener("TextZoomChange", this); + + this._obs = null; + this._panel = null; + } + + /* + * Shows the form validation popup at a specified position or updates the + * messaging and position if the popup is already displayed. + * + * @aBrowser - Browser element that requests the popup. + * @aPanelData - Object that contains popup information + * aPanelData stucture detail: + * screenRect - the screen rect of the target element. + * position - popup positional string constants. + * message - the form element validation message text. + */ + _showPopup(aBrowser, aPanelData) { + let previouslyShown = !!this._panel; + this._panel = this._getAndMaybeCreatePanel(); + this._panel.firstChild.textContent = aPanelData.message; + + // Display the panel if it isn't already visible. + if (previouslyShown) { + return; + } + // Cleanup after the popup is hidden + this._panel.addEventListener("popuphidden", this, true); + // Hide ourselves if other popups shown + this._obs = new PopupShownObserver(this.browsingContext); + Services.obs.addObserver(this._obs, "popup-shown", true); + + // Hide if the user scrolls the page + aBrowser.addEventListener("scroll", this, true); + aBrowser.addEventListener("FullZoomChange", this); + aBrowser.addEventListener("TextZoomChange", this); + + aBrowser.constrainPopup(this._panel); + + // Open the popup + let rect = aPanelData.screenRect; + this._panel.openPopupAtScreenRect( + aPanelData.position, + rect.left, + rect.top, + rect.width, + rect.height, + false, + false + ); + } + + /* + * Hide the popup if currently displayed. Will fire an event to onPopupHiding + * above if visible. + */ + _hidePopup() { + this._panel?.hidePopup(); + } + + _getAndMaybeCreatePanel() { + // Lazy load the invalid form popup the first time we need to display it. + if (!this._panel) { + let browser = this.browsingContext.top.embedderElement; + let window = browser.ownerGlobal; + let template = window.document.getElementById("invalidFormTemplate"); + if (template) { + template.replaceWith(template.content); + } + this._panel = window.document.getElementById("invalid-form-popup"); + } + + return this._panel; + } +} |