Source code

Revision control

Other Tools

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
"use strict";
5
6
/**
7
* Handles the validation callback from nsIFormFillController and
8
* the display of the help panel on invalid elements.
9
*/
10
11
var EXPORTED_SYMBOLS = ["FormValidationChild"];
12
13
const { BrowserUtils } = ChromeUtils.import(
15
);
16
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
17
18
class FormValidationChild extends JSWindowActorChild {
19
constructor() {
20
super();
21
this._validationMessage = "";
22
this._element = null;
23
this._addedPageShowListener = false;
24
}
25
26
/*
27
* Events
28
*/
29
30
handleEvent(aEvent) {
31
if (!this._addedPageShowListener) {
32
// Listening to ‘pageshow’ event is only relevant
33
// if an invalid form popup was open. So we add
34
// a listener here and not during registration to
35
// avoid a premature instantiation of the actor.
36
this.contentWindow.addEventListener("pageshow", this);
37
this._addedPageShowListener = true;
38
}
39
40
switch (aEvent.type) {
41
case "MozInvalidForm":
42
aEvent.preventDefault();
43
this.notifyInvalidSubmit(aEvent.target, aEvent.detail);
44
break;
45
case "pageshow":
46
if (this._isRootDocumentEvent(aEvent)) {
47
this._hidePopup();
48
}
49
break;
50
case "input":
51
this._onInput(aEvent);
52
break;
53
case "blur":
54
this._onBlur(aEvent);
55
break;
56
}
57
}
58
59
/*
60
* nsIFormSubmitObserver
61
*/
62
63
notifyInvalidSubmit(aFormElement, aInvalidElements) {
64
// Show a validation message on the first focusable element.
65
for (let element of aInvalidElements) {
66
// Insure that this is the FormSubmitObserver associated with the
67
// element / window this notification is about.
68
if (this.contentWindow != element.ownerGlobal.document.defaultView) {
69
return;
70
}
71
72
if (
73
!(
74
ChromeUtils.getClassName(element) === "HTMLInputElement" ||
75
ChromeUtils.getClassName(element) === "HTMLTextAreaElement" ||
76
ChromeUtils.getClassName(element) === "HTMLSelectElement" ||
77
ChromeUtils.getClassName(element) === "HTMLButtonElement"
78
)
79
) {
80
continue;
81
}
82
83
if (!Services.focus.elementIsFocusable(element, 0)) {
84
continue;
85
}
86
87
// Update validation message before showing notification
88
this._validationMessage = element.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(aEvent) {
142
aEvent.originalTarget.removeEventListener("input", this);
143
aEvent.originalTarget.removeEventListener("blur", this);
144
this._element = null;
145
this._hidePopup();
146
}
147
148
/*
149
* Send the show popup message to chrome with appropriate position
150
* information. Can be called repetitively to update the currently
151
* displayed popup position and text.
152
*/
153
_showPopup(aElement) {
154
// Collect positional information and show the popup
155
let panelData = {};
156
157
panelData.message = this._validationMessage;
158
159
// Note, this is relative to the browser and needs to be translated
160
// in chrome.
161
panelData.contentRect = BrowserUtils.getElementBoundingRect(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
let offset = 0;
166
167
if (
168
aElement.tagName == "INPUT" &&
169
(aElement.type == "radio" || aElement.type == "checkbox")
170
) {
171
panelData.position = "bottomcenter topleft";
172
} else {
173
let win = aElement.ownerGlobal;
174
let style = win.getComputedStyle(aElement);
175
if (style.direction == "rtl") {
176
offset =
177
parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
178
} else {
179
offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
180
}
181
let zoomFactor = this.contentWindow.windowUtils.fullZoom;
182
panelData.offset = Math.round(offset * zoomFactor);
183
panelData.position = "after_start";
184
}
185
this.sendAsyncMessage("FormValidation:ShowPopup", panelData);
186
}
187
188
_hidePopup() {
189
this.sendAsyncMessage("FormValidation:HidePopup", {});
190
}
191
192
_isRootDocumentEvent(aEvent) {
193
if (this.contentWindow == null) {
194
return true;
195
}
196
let target = aEvent.originalTarget;
197
return (
198
target == this.document ||
199
(target.ownerDocument && target.ownerDocument == this.document)
200
);
201
}
202
}