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