|
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 |
|
5 /* |
|
6 * Responsible for filling in form information. |
|
7 * - displays select popups |
|
8 * - Provides autocomplete box for input fields. |
|
9 */ |
|
10 |
|
11 var FormHelperUI = { |
|
12 _debugEvents: false, |
|
13 _currentBrowser: null, |
|
14 _currentElement: null, |
|
15 _currentCaretRect: null, |
|
16 _currentElementRect: null, |
|
17 _open: false, |
|
18 |
|
19 type: "form", |
|
20 |
|
21 init: function formHelperInit() { |
|
22 // Listen for form assistant messages from content |
|
23 messageManager.addMessageListener("FormAssist:Show", this); |
|
24 messageManager.addMessageListener("FormAssist:Hide", this); |
|
25 messageManager.addMessageListener("FormAssist:Update", this); |
|
26 messageManager.addMessageListener("FormAssist:AutoComplete", this); |
|
27 |
|
28 // Listen for events where form assistant should be closed or updated |
|
29 let tabs = Elements.tabList; |
|
30 tabs.addEventListener("TabSelect", this, true); |
|
31 tabs.addEventListener("TabClose", this, true); |
|
32 Elements.browsers.addEventListener("URLChanged", this, true); |
|
33 Elements.browsers.addEventListener("SizeChanged", this, true); |
|
34 |
|
35 // Listen some events to show/hide arrows |
|
36 Elements.browsers.addEventListener("PanBegin", this, false); |
|
37 Elements.browsers.addEventListener("PanFinished", this, false); |
|
38 }, |
|
39 |
|
40 /* |
|
41 * Open of the form helper proper. Currently doesn't display anything |
|
42 * on metro since the nav buttons are off. |
|
43 */ |
|
44 show: function formHelperShow(aElement) { |
|
45 // Delay the operation until all resize operations generated by the |
|
46 // keyboard apparition are done. |
|
47 if (!InputSourceHelper.isPrecise && aElement.editable && |
|
48 ContentAreaObserver.isKeyboardTransitioning) { |
|
49 this._waitForKeyboard(aElement); |
|
50 return; |
|
51 } |
|
52 |
|
53 this._currentBrowser = Browser.selectedBrowser; |
|
54 this._currentCaretRect = null; |
|
55 |
|
56 let lastElement = this._currentElement || null; |
|
57 |
|
58 this._currentElement = { |
|
59 id: aElement.id, |
|
60 name: aElement.name, |
|
61 title: aElement.title, |
|
62 value: aElement.value, |
|
63 maxLength: aElement.maxLength, |
|
64 type: aElement.type, |
|
65 choices: aElement.choices, |
|
66 isAutocomplete: aElement.isAutocomplete, |
|
67 list: aElement.list, |
|
68 rect: aElement.rect |
|
69 }; |
|
70 |
|
71 this._updateContainerForSelect(lastElement, this._currentElement); |
|
72 this._showAutoCompleteSuggestions(this._currentElement); |
|
73 |
|
74 // Prevent the view to scroll automatically while typing |
|
75 this._currentBrowser.scrollSync = false; |
|
76 |
|
77 this._open = true; |
|
78 }, |
|
79 |
|
80 hide: function formHelperHide() { |
|
81 this._open = false; |
|
82 |
|
83 SelectHelperUI.hide(); |
|
84 AutofillMenuUI.hide(); |
|
85 |
|
86 // Restore the scroll synchonisation |
|
87 if (this._currentBrowser) |
|
88 this._currentBrowser.scrollSync = true; |
|
89 |
|
90 // reset current Element and Caret Rect |
|
91 this._currentElementRect = null; |
|
92 this._currentCaretRect = null; |
|
93 |
|
94 this._updateContainerForSelect(this._currentElement, null); |
|
95 if (this._currentBrowser) |
|
96 this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Closed", { }); |
|
97 }, |
|
98 |
|
99 _onShowRequest: function _onShowRequest(aJsonMsg) { |
|
100 if (aJsonMsg.current.choices) { |
|
101 // Note, aJsonMsg.current.rect is translated from browser to client |
|
102 // in the SelectHelperUI code. |
|
103 SelectHelperUI.show(aJsonMsg.current.choices, aJsonMsg.current.title, |
|
104 aJsonMsg.current.rect); |
|
105 } else { |
|
106 this._currentBrowser = getBrowser(); |
|
107 this._currentElementRect = |
|
108 Rect.fromRect(this._currentBrowser.rectBrowserToClient(aJsonMsg.current.rect)); |
|
109 this.show(aJsonMsg.current); |
|
110 } |
|
111 }, |
|
112 |
|
113 /* |
|
114 * Events |
|
115 */ |
|
116 |
|
117 handleEvent: function formHelperHandleEvent(aEvent) { |
|
118 if (this._debugEvents) Util.dumpLn(aEvent.type); |
|
119 |
|
120 if (!this._open) |
|
121 return; |
|
122 |
|
123 switch (aEvent.type) { |
|
124 case "TabSelect": |
|
125 case "TabClose": |
|
126 case "PanBegin": |
|
127 case "SizeChanged": |
|
128 this.hide(); |
|
129 break; |
|
130 |
|
131 case "URLChanged": |
|
132 if (aEvent.detail && aEvent.target == getBrowser()) |
|
133 this.hide(); |
|
134 break; |
|
135 } |
|
136 }, |
|
137 |
|
138 receiveMessage: function formHelperReceiveMessage(aMessage) { |
|
139 if (this._debugEvents) Util.dumpLn(aMessage.name); |
|
140 let json = aMessage.json; |
|
141 |
|
142 switch (aMessage.name) { |
|
143 case "FormAssist:Show": |
|
144 this._onShowRequest(json); |
|
145 break; |
|
146 |
|
147 case "FormAssist:Hide": |
|
148 this.hide(); |
|
149 break; |
|
150 |
|
151 case "FormAssist:AutoComplete": |
|
152 this.show(json.current); |
|
153 break; |
|
154 } |
|
155 }, |
|
156 |
|
157 doAutoComplete: function formHelperDoAutoComplete(aData) { |
|
158 this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:AutoComplete", |
|
159 { value: aData }); |
|
160 }, |
|
161 |
|
162 /* |
|
163 * Retrieves autocomplete suggestions asynchronously for an element from the |
|
164 * form autocomplete service and updates and displays the autocompletepopup. |
|
165 * |
|
166 * @param aElement form input element |
|
167 */ |
|
168 _showAutoCompleteSuggestions: function (aElement) { |
|
169 if (!aElement.isAutocomplete) { |
|
170 return; |
|
171 } |
|
172 |
|
173 let resultsAvailable = autoCompleteSuggestions => { |
|
174 // Return false if there are no suggestions to show |
|
175 if (!autoCompleteSuggestions.length) { |
|
176 return; |
|
177 } |
|
178 AutofillMenuUI.show(this._currentElementRect, autoCompleteSuggestions); |
|
179 }; |
|
180 |
|
181 this._getAutoCompleteSuggestions(aElement.value, aElement, resultsAvailable); |
|
182 }, |
|
183 |
|
184 /* |
|
185 * Retrieves autocomplete suggestions for an element from the form |
|
186 * autocomplete service. |
|
187 * |
|
188 * @param aSearchString text entered into form |
|
189 * @param aElement form input element |
|
190 * @param aCallback(array_of_suggestions) called when results are available. |
|
191 */ |
|
192 _getAutoCompleteSuggestions: function (aSearchString, aElement, aCallback) { |
|
193 // Cache the form autocomplete service for future use |
|
194 if (!this._formAutoCompleteService) |
|
195 this._formAutoCompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"]. |
|
196 getService(Ci.nsIFormAutoComplete); |
|
197 |
|
198 // results callback |
|
199 let resultsAvailable = function (results) { |
|
200 let suggestions = []; |
|
201 for (let idx = 0; idx < results.matchCount; idx++) { |
|
202 let value = results.getValueAt(idx); |
|
203 |
|
204 // Do not show the value if it is the current one in the input field |
|
205 if (value == aSearchString) |
|
206 continue; |
|
207 |
|
208 // Supply a label and value, since they can differ for datalist suggestions |
|
209 suggestions.push({ label: value, value: value }); |
|
210 } |
|
211 |
|
212 // Add the datalist elements provided by the website, note that the |
|
213 // displayed value can differ from the real value of the element. |
|
214 let options = aElement.list; |
|
215 for (let idx = 0; idx < options.length; idx++) { |
|
216 suggestions.push(options[idx]); |
|
217 } |
|
218 |
|
219 aCallback(suggestions); |
|
220 }; |
|
221 |
|
222 // Send the query |
|
223 this._formAutoCompleteService.autoCompleteSearchAsync(aElement.name || aElement.id, |
|
224 aSearchString, aElement, null, |
|
225 resultsAvailable); |
|
226 }, |
|
227 |
|
228 /* |
|
229 * Setup for displaying the selection choices menu |
|
230 */ |
|
231 _updateContainerForSelect: function _formHelperUpdateContainerForSelect(aLastElement, aCurrentElement) { |
|
232 let lastHasChoices = aLastElement && (aLastElement.choices != null); |
|
233 let currentHasChoices = aCurrentElement && (aCurrentElement.choices != null); |
|
234 |
|
235 if (currentHasChoices) |
|
236 SelectHelperUI.show(aCurrentElement.choices, aCurrentElement.title, aCurrentElement.rect); |
|
237 else if (lastHasChoices) |
|
238 SelectHelperUI.hide(); |
|
239 }, |
|
240 |
|
241 _waitForKeyboard: function formHelperWaitForKeyboard(aElement) { |
|
242 let self = this; |
|
243 window.addEventListener("KeyboardChanged", function(aEvent) { |
|
244 window.removeEventListener("KeyboardChanged", arguments.callee, false); |
|
245 self._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Update", {}); |
|
246 }, false); |
|
247 } |
|
248 }; |
|
249 |