Bug 1845017 - Disable the TestPHCExhaustion test r=glandium
[gecko.git] / browser / actors / FormValidationParent.sys.mjs
blobe95a8e86fb7ff41495badfd53e16effd66b9bbbf
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/. */
5 /*
6  * Chrome side handling of form validation popup.
7  */
9 const lazy = {};
11 ChromeUtils.defineESModuleGetters(lazy, {
12   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
13 });
15 class PopupShownObserver {
16   _weakContext = null;
18   constructor(browsingContext) {
19     this._weakContext = Cu.getWeakReference(browsingContext);
20   }
22   observe(subject, topic, data) {
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   }
35   QueryInterface = ChromeUtils.generateQI([
36     Ci.nsIObserver,
37     Ci.nsISupportsWeakReference,
38   ]);
41 export class FormValidationParent extends JSWindowActorParent {
42   constructor() {
43     super();
45     this._panel = null;
46     this._obs = null;
47   }
49   static hasOpenPopups() {
50     for (let win of lazy.BrowserWindowTracker.orderedWindows) {
51       let popups = win.document.querySelectorAll("panel,menupopup");
52       for (let popup of popups) {
53         let { state } = popup;
54         if (state == "open" || state == "showing") {
55           return true;
56         }
57       }
58     }
59     return false;
60   }
62   /*
63    * Public apis
64    */
66   uninit() {
67     this._panel = null;
68     this._obs = null;
69   }
71   hidePopup() {
72     this._hidePopup();
73   }
75   /*
76    * Events
77    */
79   receiveMessage(aMessage) {
80     switch (aMessage.name) {
81       case "FormValidation:ShowPopup":
82         let browser = this.browsingContext.top.embedderElement;
83         let window = browser.ownerGlobal;
84         let data = aMessage.data;
85         let tabBrowser = window.gBrowser;
87         // target is the <browser>, make sure we're receiving a message
88         // from the foreground tab.
89         if (tabBrowser && browser != tabBrowser.selectedBrowser) {
90           return;
91         }
93         if (FormValidationParent.hasOpenPopups()) {
94           return;
95         }
97         this._showPopup(browser, data);
98         break;
99       case "FormValidation:HidePopup":
100         this._hidePopup();
101         break;
102     }
103   }
105   handleEvent(aEvent) {
106     switch (aEvent.type) {
107       case "FullZoomChange":
108       case "TextZoomChange":
109       case "scroll":
110         this._hidePopup();
111         break;
112       case "popuphidden":
113         this._onPopupHidden(aEvent);
114         break;
115     }
116   }
118   /*
119    * Internal
120    */
122   _onPopupHidden(aEvent) {
123     aEvent.originalTarget.removeEventListener("popuphidden", this, true);
124     Services.obs.removeObserver(this._obs, "popup-shown");
125     let tabBrowser = aEvent.originalTarget.ownerGlobal.gBrowser;
126     tabBrowser.selectedBrowser.removeEventListener("scroll", this, true);
127     tabBrowser.selectedBrowser.removeEventListener("FullZoomChange", this);
128     tabBrowser.selectedBrowser.removeEventListener("TextZoomChange", this);
130     this._obs = null;
131     this._panel = null;
132   }
134   /*
135    * Shows the form validation popup at a specified position or updates the
136    * messaging and position if the popup is already displayed.
137    *
138    * @aBrowser - Browser element that requests the popup.
139    * @aPanelData - Object that contains popup information
140    *  aPanelData stucture detail:
141    *   screenRect - the screen rect of the target element.
142    *   position - popup positional string constants.
143    *   message - the form element validation message text.
144    */
145   _showPopup(aBrowser, aPanelData) {
146     let previouslyShown = !!this._panel;
147     this._panel = this._getAndMaybeCreatePanel();
148     this._panel.firstChild.textContent = aPanelData.message;
150     // Display the panel if it isn't already visible.
151     if (previouslyShown) {
152       return;
153     }
154     // Cleanup after the popup is hidden
155     this._panel.addEventListener("popuphidden", this, true);
156     // Hide ourselves if other popups shown
157     this._obs = new PopupShownObserver(this.browsingContext);
158     Services.obs.addObserver(this._obs, "popup-shown", true);
160     // Hide if the user scrolls the page
161     aBrowser.addEventListener("scroll", this, true);
162     aBrowser.addEventListener("FullZoomChange", this);
163     aBrowser.addEventListener("TextZoomChange", this);
165     aBrowser.constrainPopup(this._panel);
167     // Open the popup
168     let rect = aPanelData.screenRect;
169     this._panel.openPopupAtScreenRect(
170       aPanelData.position,
171       rect.left,
172       rect.top,
173       rect.width,
174       rect.height,
175       false,
176       false
177     );
178   }
180   /*
181    * Hide the popup if currently displayed. Will fire an event to onPopupHiding
182    * above if visible.
183    */
184   _hidePopup() {
185     this._panel?.hidePopup();
186   }
188   _getAndMaybeCreatePanel() {
189     // Lazy load the invalid form popup the first time we need to display it.
190     if (!this._panel) {
191       let browser = this.browsingContext.top.embedderElement;
192       let window = browser.ownerGlobal;
193       let template = window.document.getElementById("invalidFormTemplate");
194       if (template) {
195         template.replaceWith(template.content);
196       }
197       this._panel = window.document.getElementById("invalid-form-popup");
198     }
200     return this._panel;
201   }