michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Responsible for filling in form information. michael@0: * - displays select popups michael@0: * - Provides autocomplete box for input fields. michael@0: */ michael@0: michael@0: var FormHelperUI = { michael@0: _debugEvents: false, michael@0: _currentBrowser: null, michael@0: _currentElement: null, michael@0: _currentCaretRect: null, michael@0: _currentElementRect: null, michael@0: _open: false, michael@0: michael@0: type: "form", michael@0: michael@0: init: function formHelperInit() { michael@0: // Listen for form assistant messages from content michael@0: messageManager.addMessageListener("FormAssist:Show", this); michael@0: messageManager.addMessageListener("FormAssist:Hide", this); michael@0: messageManager.addMessageListener("FormAssist:Update", this); michael@0: messageManager.addMessageListener("FormAssist:AutoComplete", this); michael@0: michael@0: // Listen for events where form assistant should be closed or updated michael@0: let tabs = Elements.tabList; michael@0: tabs.addEventListener("TabSelect", this, true); michael@0: tabs.addEventListener("TabClose", this, true); michael@0: Elements.browsers.addEventListener("URLChanged", this, true); michael@0: Elements.browsers.addEventListener("SizeChanged", this, true); michael@0: michael@0: // Listen some events to show/hide arrows michael@0: Elements.browsers.addEventListener("PanBegin", this, false); michael@0: Elements.browsers.addEventListener("PanFinished", this, false); michael@0: }, michael@0: michael@0: /* michael@0: * Open of the form helper proper. Currently doesn't display anything michael@0: * on metro since the nav buttons are off. michael@0: */ michael@0: show: function formHelperShow(aElement) { michael@0: // Delay the operation until all resize operations generated by the michael@0: // keyboard apparition are done. michael@0: if (!InputSourceHelper.isPrecise && aElement.editable && michael@0: ContentAreaObserver.isKeyboardTransitioning) { michael@0: this._waitForKeyboard(aElement); michael@0: return; michael@0: } michael@0: michael@0: this._currentBrowser = Browser.selectedBrowser; michael@0: this._currentCaretRect = null; michael@0: michael@0: let lastElement = this._currentElement || null; michael@0: michael@0: this._currentElement = { michael@0: id: aElement.id, michael@0: name: aElement.name, michael@0: title: aElement.title, michael@0: value: aElement.value, michael@0: maxLength: aElement.maxLength, michael@0: type: aElement.type, michael@0: choices: aElement.choices, michael@0: isAutocomplete: aElement.isAutocomplete, michael@0: list: aElement.list, michael@0: rect: aElement.rect michael@0: }; michael@0: michael@0: this._updateContainerForSelect(lastElement, this._currentElement); michael@0: this._showAutoCompleteSuggestions(this._currentElement); michael@0: michael@0: // Prevent the view to scroll automatically while typing michael@0: this._currentBrowser.scrollSync = false; michael@0: michael@0: this._open = true; michael@0: }, michael@0: michael@0: hide: function formHelperHide() { michael@0: this._open = false; michael@0: michael@0: SelectHelperUI.hide(); michael@0: AutofillMenuUI.hide(); michael@0: michael@0: // Restore the scroll synchonisation michael@0: if (this._currentBrowser) michael@0: this._currentBrowser.scrollSync = true; michael@0: michael@0: // reset current Element and Caret Rect michael@0: this._currentElementRect = null; michael@0: this._currentCaretRect = null; michael@0: michael@0: this._updateContainerForSelect(this._currentElement, null); michael@0: if (this._currentBrowser) michael@0: this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Closed", { }); michael@0: }, michael@0: michael@0: _onShowRequest: function _onShowRequest(aJsonMsg) { michael@0: if (aJsonMsg.current.choices) { michael@0: // Note, aJsonMsg.current.rect is translated from browser to client michael@0: // in the SelectHelperUI code. michael@0: SelectHelperUI.show(aJsonMsg.current.choices, aJsonMsg.current.title, michael@0: aJsonMsg.current.rect); michael@0: } else { michael@0: this._currentBrowser = getBrowser(); michael@0: this._currentElementRect = michael@0: Rect.fromRect(this._currentBrowser.rectBrowserToClient(aJsonMsg.current.rect)); michael@0: this.show(aJsonMsg.current); michael@0: } michael@0: }, michael@0: michael@0: /* michael@0: * Events michael@0: */ michael@0: michael@0: handleEvent: function formHelperHandleEvent(aEvent) { michael@0: if (this._debugEvents) Util.dumpLn(aEvent.type); michael@0: michael@0: if (!this._open) michael@0: return; michael@0: michael@0: switch (aEvent.type) { michael@0: case "TabSelect": michael@0: case "TabClose": michael@0: case "PanBegin": michael@0: case "SizeChanged": michael@0: this.hide(); michael@0: break; michael@0: michael@0: case "URLChanged": michael@0: if (aEvent.detail && aEvent.target == getBrowser()) michael@0: this.hide(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: receiveMessage: function formHelperReceiveMessage(aMessage) { michael@0: if (this._debugEvents) Util.dumpLn(aMessage.name); michael@0: let json = aMessage.json; michael@0: michael@0: switch (aMessage.name) { michael@0: case "FormAssist:Show": michael@0: this._onShowRequest(json); michael@0: break; michael@0: michael@0: case "FormAssist:Hide": michael@0: this.hide(); michael@0: break; michael@0: michael@0: case "FormAssist:AutoComplete": michael@0: this.show(json.current); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: doAutoComplete: function formHelperDoAutoComplete(aData) { michael@0: this._currentBrowser.messageManager.sendAsyncMessage("FormAssist:AutoComplete", michael@0: { value: aData }); michael@0: }, michael@0: michael@0: /* michael@0: * Retrieves autocomplete suggestions asynchronously for an element from the michael@0: * form autocomplete service and updates and displays the autocompletepopup. michael@0: * michael@0: * @param aElement form input element michael@0: */ michael@0: _showAutoCompleteSuggestions: function (aElement) { michael@0: if (!aElement.isAutocomplete) { michael@0: return; michael@0: } michael@0: michael@0: let resultsAvailable = autoCompleteSuggestions => { michael@0: // Return false if there are no suggestions to show michael@0: if (!autoCompleteSuggestions.length) { michael@0: return; michael@0: } michael@0: AutofillMenuUI.show(this._currentElementRect, autoCompleteSuggestions); michael@0: }; michael@0: michael@0: this._getAutoCompleteSuggestions(aElement.value, aElement, resultsAvailable); michael@0: }, michael@0: michael@0: /* michael@0: * Retrieves autocomplete suggestions for an element from the form michael@0: * autocomplete service. michael@0: * michael@0: * @param aSearchString text entered into form michael@0: * @param aElement form input element michael@0: * @param aCallback(array_of_suggestions) called when results are available. michael@0: */ michael@0: _getAutoCompleteSuggestions: function (aSearchString, aElement, aCallback) { michael@0: // Cache the form autocomplete service for future use michael@0: if (!this._formAutoCompleteService) michael@0: this._formAutoCompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"]. michael@0: getService(Ci.nsIFormAutoComplete); michael@0: michael@0: // results callback michael@0: let resultsAvailable = function (results) { michael@0: let suggestions = []; michael@0: for (let idx = 0; idx < results.matchCount; idx++) { michael@0: let value = results.getValueAt(idx); michael@0: michael@0: // Do not show the value if it is the current one in the input field michael@0: if (value == aSearchString) michael@0: continue; michael@0: michael@0: // Supply a label and value, since they can differ for datalist suggestions michael@0: suggestions.push({ label: value, value: value }); michael@0: } michael@0: michael@0: // Add the datalist elements provided by the website, note that the michael@0: // displayed value can differ from the real value of the element. michael@0: let options = aElement.list; michael@0: for (let idx = 0; idx < options.length; idx++) { michael@0: suggestions.push(options[idx]); michael@0: } michael@0: michael@0: aCallback(suggestions); michael@0: }; michael@0: michael@0: // Send the query michael@0: this._formAutoCompleteService.autoCompleteSearchAsync(aElement.name || aElement.id, michael@0: aSearchString, aElement, null, michael@0: resultsAvailable); michael@0: }, michael@0: michael@0: /* michael@0: * Setup for displaying the selection choices menu michael@0: */ michael@0: _updateContainerForSelect: function _formHelperUpdateContainerForSelect(aLastElement, aCurrentElement) { michael@0: let lastHasChoices = aLastElement && (aLastElement.choices != null); michael@0: let currentHasChoices = aCurrentElement && (aCurrentElement.choices != null); michael@0: michael@0: if (currentHasChoices) michael@0: SelectHelperUI.show(aCurrentElement.choices, aCurrentElement.title, aCurrentElement.rect); michael@0: else if (lastHasChoices) michael@0: SelectHelperUI.hide(); michael@0: }, michael@0: michael@0: _waitForKeyboard: function formHelperWaitForKeyboard(aElement) { michael@0: let self = this; michael@0: window.addEventListener("KeyboardChanged", function(aEvent) { michael@0: window.removeEventListener("KeyboardChanged", arguments.callee, false); michael@0: self._currentBrowser.messageManager.sendAsyncMessage("FormAssist:Update", {}); michael@0: }, false); michael@0: } michael@0: }; michael@0: