Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 * Responsible for filling in form information.
7 * - displays select popups
8 * - Provides autocomplete box for input fields.
9 */
11 var FormHelperUI = {
12 _debugEvents: false,
13 _currentBrowser: null,
14 _currentElement: null,
15 _currentCaretRect: null,
16 _currentElementRect: null,
17 _open: false,
19 type: "form",
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);
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);
35 // Listen some events to show/hide arrows
36 Elements.browsers.addEventListener("PanBegin", this, false);
37 Elements.browsers.addEventListener("PanFinished", this, false);
38 },
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 }
53 this._currentBrowser = Browser.selectedBrowser;
54 this._currentCaretRect = null;
56 let lastElement = this._currentElement || null;
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 };
71 this._updateContainerForSelect(lastElement, this._currentElement);
72 this._showAutoCompleteSuggestions(this._currentElement);
74 // Prevent the view to scroll automatically while typing
75 this._currentBrowser.scrollSync = false;
77 this._open = true;
78 },
80 hide: function formHelperHide() {
81 this._open = false;
83 SelectHelperUI.hide();
84 AutofillMenuUI.hide();
86 // Restore the scroll synchonisation
87 if (this._currentBrowser)
88 this._currentBrowser.scrollSync = true;
90 // reset current Element and Caret Rect
91 this._currentElementRect = null;
92 this._currentCaretRect = null;
94 this._updateContainerForSelect(this._currentElement, null);
95 if (this._currentBrowser)
96 this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Closed", { });
97 },
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 },
113 /*
114 * Events
115 */
117 handleEvent: function formHelperHandleEvent(aEvent) {
118 if (this._debugEvents) Util.dumpLn(aEvent.type);
120 if (!this._open)
121 return;
123 switch (aEvent.type) {
124 case "TabSelect":
125 case "TabClose":
126 case "PanBegin":
127 case "SizeChanged":
128 this.hide();
129 break;
131 case "URLChanged":
132 if (aEvent.detail && aEvent.target == getBrowser())
133 this.hide();
134 break;
135 }
136 },
138 receiveMessage: function formHelperReceiveMessage(aMessage) {
139 if (this._debugEvents) Util.dumpLn(aMessage.name);
140 let json = aMessage.json;
142 switch (aMessage.name) {
143 case "FormAssist:Show":
144 this._onShowRequest(json);
145 break;
147 case "FormAssist:Hide":
148 this.hide();
149 break;
151 case "FormAssist:AutoComplete":
152 this.show(json.current);
153 break;
154 }
155 },
157 doAutoComplete: function formHelperDoAutoComplete(aData) {
158 this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:AutoComplete",
159 { value: aData });
160 },
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 }
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 };
181 this._getAutoCompleteSuggestions(aElement.value, aElement, resultsAvailable);
182 },
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);
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);
204 // Do not show the value if it is the current one in the input field
205 if (value == aSearchString)
206 continue;
208 // Supply a label and value, since they can differ for datalist suggestions
209 suggestions.push({ label: value, value: value });
210 }
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 }
219 aCallback(suggestions);
220 };
222 // Send the query
223 this._formAutoCompleteService.autoCompleteSearchAsync(aElement.name || aElement.id,
224 aSearchString, aElement, null,
225 resultsAvailable);
226 },
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);
235 if (currentHasChoices)
236 SelectHelperUI.show(aCurrentElement.choices, aCurrentElement.title, aCurrentElement.rect);
237 else if (lastHasChoices)
238 SelectHelperUI.hide();
239 },
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 };