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/. */
6 * Handles the validation callback from nsIFormFillController and
7 * the display of the help panel on invalid elements.
12 let Cc = Components.classes;
13 let Ci = Components.interfaces;
14 let Cu = Components.utils;
16 let HTMLInputElement = Ci.nsIDOMHTMLInputElement;
17 let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
18 let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
19 let HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
21 this.EXPORTED_SYMBOLS = [ "FormSubmitObserver" ];
23 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
24 Cu.import("resource://gre/modules/Services.jsm");
25 Cu.import("resource://gre/modules/BrowserUtils.jsm");
27 function FormSubmitObserver(aWindow, aTabChildGlobal) {
28 this.init(aWindow, aTabChildGlobal);
31 FormSubmitObserver.prototype =
33 _validationMessage: "",
41 init: function(aWindow, aTabChildGlobal)
43 this._content = aWindow;
44 this._tab = aTabChildGlobal;
46 this._content.QueryInterface(Ci.nsIInterfaceRequestor)
\r
47 .getInterface(Ci.nsIDocShell)
\r
48 .sameTypeRootTreeItem
\r
49 .QueryInterface(Ci.nsIDocShell)
50 .QueryInterface(Ci.nsIInterfaceRequestor)
51 .getInterface(Ci.nsIContentFrameMessageManager);
53 // nsIFormSubmitObserver callback about invalid forms. See HTMLFormElement
55 Services.obs.addObserver(this, "invalidformsubmit", false);
56 this._tab.addEventListener("pageshow", this, false);
57 this._tab.addEventListener("unload", this, false);
62 Services.obs.removeObserver(this, "invalidformsubmit");
63 this._content.removeEventListener("pageshow", this, false);
64 this._content.removeEventListener("unload", this, false);
75 handleEvent: function (aEvent) {
76 switch (aEvent.type) {
78 if (this._isRootDocumentEvent(aEvent)) {
86 this._onInput(aEvent);
95 * nsIFormSubmitObserver
98 notifyInvalidSubmit : function (aFormElement, aInvalidElements)
100 // We are going to handle invalid form submission attempt by focusing the
101 // first invalid element and show the corresponding validation message in a
102 // panel attached to the element.
103 if (!aInvalidElements.length) {
107 // Insure that this is the FormSubmitObserver associated with the form
108 // element / window this notification is about.
109 if (this._content != aFormElement.ownerDocument.defaultView.top.document.defaultView) {
113 let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
114 if (!(element instanceof HTMLInputElement ||
115 element instanceof HTMLTextAreaElement ||
116 element instanceof HTMLSelectElement ||
117 element instanceof HTMLButtonElement)) {
121 // Don't connect up to the same element more than once.
122 if (this._element == element) {
123 this._showPopup(element);
126 this._element = element;
130 this._validationMessage = element.validationMessage;
132 // Watch for input changes which may change the validation message.
133 element.addEventListener("input", this, false);
135 // Watch for focus changes so we can disconnect our listeners and
137 element.addEventListener("blur", this, false);
139 this._showPopup(element);
147 * Handles input changes on the form element we've associated a popup
148 * with. Updates the validation message or closes the popup if form data
151 _onInput: function (aEvent) {
152 let element = aEvent.originalTarget;
154 // If the form input is now valid, hide the popup.
155 if (element.validity.valid) {
160 // If the element is still invalid for a new reason, we should update
161 // the popup error message.
162 if (this._validationMessage != element.validationMessage) {
163 this._validationMessage = element.validationMessage;
164 this._showPopup(element);
169 * Blur event handler in which we disconnect from the form element and
172 _onBlur: function (aEvent) {
173 aEvent.originalTarget.removeEventListener("input", this, false);
174 aEvent.originalTarget.removeEventListener("blur", this, false);
175 this._element = null;
180 * Send the show popup message to chrome with appropriate position
181 * information. Can be called repetitively to update the currently
182 * displayed popup position and text.
184 _showPopup: function (aElement) {
185 // Collect positional information and show the popup
188 panelData.message = this._validationMessage;
190 // Note, this is relative to the browser and needs to be translated
192 panelData.contentRect = this._msgRect(aElement);
194 // We want to show the popup at the middle of checkbox and radio buttons
195 // and where the content begin for the other elements.
199 if (aElement.tagName == 'INPUT' &&
200 (aElement.type == 'radio' || aElement.type == 'checkbox')) {
201 panelData.position = "bottomcenter topleft";
203 let win = aElement.ownerDocument.defaultView;
204 let style = win.getComputedStyle(aElement, null);
205 if (style.direction == 'rtl') {
206 offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
208 offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
210 let zoomFactor = this._getWindowUtils().fullZoom;
211 panelData.offset = Math.round(offset * zoomFactor);
212 panelData.position = "after_start";
214 this._mm.sendAsyncMessage("FormValidation:ShowPopup", panelData);
217 _hidePopup: function () {
218 this._mm.sendAsyncMessage("FormValidation:HidePopup", {});
221 _getWindowUtils: function () {
\r
222 return this._content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
\r
225 _isRootDocumentEvent: function (aEvent) {
226 if (this._content == null) {
229 let target = aEvent.originalTarget;
230 return (target == this._content.document ||
231 (target.ownerDocument && target.ownerDocument == this._content.document));
235 * Return a message manager rect for the element's bounding client rect
236 * in top level browser coords.
238 _msgRect: function (aElement) {
239 let domRect = aElement.getBoundingClientRect();
240 let zoomFactor = this._getWindowUtils().fullZoom;
241 let { offsetX, offsetY } = BrowserUtils.offsetToTopLevelWindow(this._content, aElement);
243 left: (domRect.left + offsetX) * zoomFactor,
244 top: (domRect.top + offsetY) * zoomFactor,
245 width: domRect.width * zoomFactor,
246 height: domRect.height * zoomFactor
250 QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver])