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 { ActorChild } = ChromeUtils.import(
15
);
16
const { BrowserUtils } = ChromeUtils.import(
18
);
19
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
20
21
class FormValidationChild extends ActorChild {
22
constructor(dispatcher) {
23
super(dispatcher);
24
25
this._validationMessage = "";
26
this._element = null;
27
28
this.mm.addEventListener("pageshow", this);
29
}
30
31
/*
32
* Events
33
*/
34
35
handleEvent(aEvent) {
36
switch (aEvent.type) {
37
case "MozInvalidForm":
38
aEvent.preventDefault();
39
this.notifyInvalidSubmit(aEvent.target, aEvent.detail);
40
break;
41
case "pageshow":
42
if (this._isRootDocumentEvent(aEvent)) {
43
this._hidePopup();
44
}
45
break;
46
case "input":
47
this._onInput(aEvent);
48
break;
49
case "blur":
50
this._onBlur(aEvent);
51
break;
52
}
53
}
54
55
/*
56
* nsIFormSubmitObserver
57
*/
58
59
notifyInvalidSubmit(aFormElement, aInvalidElements) {
60
// Show a validation message on the first focusable element.
61
for (let element of aInvalidElements) {
62
// Insure that this is the FormSubmitObserver associated with the
63
// element / window this notification is about.
64
if (this.content != element.ownerGlobal.top.document.defaultView) {
65
return;
66
}
67
68
if (
69
!(
70
ChromeUtils.getClassName(element) === "HTMLInputElement" ||
71
ChromeUtils.getClassName(element) === "HTMLTextAreaElement" ||
72
ChromeUtils.getClassName(element) === "HTMLSelectElement" ||
73
ChromeUtils.getClassName(element) === "HTMLButtonElement"
74
)
75
) {
76
continue;
77
}
78
79
if (!Services.focus.elementIsFocusable(element, 0)) {
80
continue;
81
}
82
83
// Update validation message before showing notification
84
this._validationMessage = element.validationMessage;
85
86
// Don't connect up to the same element more than once.
87
if (this._element == element) {
88
this._showPopup(element);
89
break;
90
}
91
this._element = element;
92
93
element.focus();
94
95
// Watch for input changes which may change the validation message.
96
element.addEventListener("input", this);
97
98
// Watch for focus changes so we can disconnect our listeners and
99
// hide the popup.
100
element.addEventListener("blur", this);
101
102
this._showPopup(element);
103
break;
104
}
105
}
106
107
/*
108
* Internal
109
*/
110
111
/*
112
* Handles input changes on the form element we've associated a popup
113
* with. Updates the validation message or closes the popup if form data
114
* becomes valid.
115
*/
116
_onInput(aEvent) {
117
let element = aEvent.originalTarget;
118
119
// If the form input is now valid, hide the popup.
120
if (element.validity.valid) {
121
this._hidePopup();
122
return;
123
}
124
125
// If the element is still invalid for a new reason, we should update
126
// the popup error message.
127
if (this._validationMessage != element.validationMessage) {
128
this._validationMessage = element.validationMessage;
129
this._showPopup(element);
130
}
131
}
132
133
/*
134
* Blur event handler in which we disconnect from the form element and
135
* hide the popup.
136
*/
137
_onBlur(aEvent) {
138
aEvent.originalTarget.removeEventListener("input", this);
139
aEvent.originalTarget.removeEventListener("blur", this);
140
this._element = null;
141
this._hidePopup();
142
}
143
144
/*
145
* Send the show popup message to chrome with appropriate position
146
* information. Can be called repetitively to update the currently
147
* displayed popup position and text.
148
*/
149
_showPopup(aElement) {
150
// Collect positional information and show the popup
151
let panelData = {};
152
153
panelData.message = this._validationMessage;
154
155
// Note, this is relative to the browser and needs to be translated
156
// in chrome.
157
panelData.contentRect = BrowserUtils.getElementBoundingRect(aElement);
158
159
// We want to show the popup at the middle of checkbox and radio buttons
160
// and where the content begin for the other elements.
161
let offset = 0;
162
163
if (
164
aElement.tagName == "INPUT" &&
165
(aElement.type == "radio" || aElement.type == "checkbox")
166
) {
167
panelData.position = "bottomcenter topleft";
168
} else {
169
let win = aElement.ownerGlobal;
170
let style = win.getComputedStyle(aElement);
171
if (style.direction == "rtl") {
172
offset =
173
parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
174
} else {
175
offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
176
}
177
let zoomFactor = this.content.windowUtils.fullZoom;
178
panelData.offset = Math.round(offset * zoomFactor);
179
panelData.position = "after_start";
180
}
181
this.mm.sendAsyncMessage("FormValidation:ShowPopup", panelData);
182
}
183
184
_hidePopup() {
185
this.mm.sendAsyncMessage("FormValidation:HidePopup", {});
186
}
187
188
_isRootDocumentEvent(aEvent) {
189
if (this.content == null) {
190
return true;
191
}
192
let target = aEvent.originalTarget;
193
return (
194
target == this.content.document ||
195
(target.ownerDocument && target.ownerDocument == this.content.document)
196
);
197
}
198
}