1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 var EXPORTED_SYMBOLS = ["AutoCompleteChild"];
8 /* eslint no-unused-vars: ["error", {args: "none"}] */
10 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
12 ChromeUtils.defineModuleGetter(
15 "resource://gre/modules/LayoutUtils.jsm"
18 ChromeUtils.defineModuleGetter(
20 "ContentDOMReference",
21 "resource://gre/modules/ContentDOMReference.jsm"
24 ChromeUtils.defineModuleGetter(
27 "resource://gre/modules/LoginHelper.jsm"
30 let autoCompleteListeners = new Set();
32 class AutoCompleteChild extends JSWindowActorChild {
37 this._popupOpen = false;
40 static addPopupStateListener(listener) {
41 autoCompleteListeners.add(listener);
44 static removePopupStateListener(listener) {
45 autoCompleteListeners.delete(listener);
48 receiveMessage(message) {
49 switch (message.name) {
50 case "FormAutoComplete:HandleEnter": {
51 this.selectedIndex = message.data.selectedIndex;
54 "@mozilla.org/autocomplete/controller;1"
55 ].getService(Ci.nsIAutoCompleteController);
56 controller.handleEnter(message.data.isPopupSelection);
60 case "FormAutoComplete:PopupClosed": {
61 this._popupOpen = false;
62 this.notifyListeners(message.name, message.data);
66 case "FormAutoComplete:PopupOpened": {
67 this._popupOpen = true;
68 this.notifyListeners(message.name, message.data);
72 case "FormAutoComplete:Focus": {
73 // XXX See bug 1582722
74 // Before bug 1573836, the messages here didn't match
75 // ("FormAutoComplete:Focus" versus "FormAutoComplete:RequestFocus")
76 // so this was never called. However this._input is actually a
77 // nsIAutoCompleteInput, which doesn't have a focus() method, so it
78 // wouldn't have worked anyway. So for now, I have just disabled this.
89 notifyListeners(messageName, data) {
90 for (let listener of autoCompleteListeners) {
92 listener.popupStateChanged(messageName, data, this.contentWindow);
103 set selectedIndex(index) {
104 this.sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
107 get selectedIndex() {
108 // selectedIndex getter must be synchronous because we need the
109 // correct value when the controller is in controller::HandleEnter.
110 // We can't easily just let the parent inform us the new value every
111 // time it changes because not every action that can change the
112 // selectedIndex is trivial to catch (e.g. moving the mouse over the
114 let selectedIndexResult = Services.cpmm.sendSyncMessage(
115 "FormAutoComplete:GetSelectedIndex",
117 browsingContext: this.browsingContext,
122 selectedIndexResult.length != 1 ||
123 !Number.isInteger(selectedIndexResult[0])
125 throw new Error("Invalid autocomplete selectedIndex");
127 return selectedIndexResult[0];
131 return this._popupOpen;
134 openAutocompletePopup(input, element) {
135 if (this._popupOpen || !input) {
139 let rect = LayoutUtils.getElementBoundingScreenRect(element);
140 let window = element.ownerGlobal;
141 let dir = window.getComputedStyle(element).direction;
142 let results = this.getResultsFromController(input);
143 let formOrigin = LoginHelper.getLoginOrigin(
144 element.ownerDocument.documentURI
146 let inputElementIdentifier = ContentDOMReference.get(element);
148 this.sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {
152 inputElementIdentifier,
160 // We set this here instead of just waiting for the
161 // PopupClosed message to do it so that we don't end
162 // up in a state where the content thinks that a popup
163 // is open when it isn't (or soon won't be).
164 this._popupOpen = false;
165 this.sendAsyncMessage("FormAutoComplete:ClosePopup", {});
169 if (this._popupOpen) {
170 let results = this.getResultsFromController(this._input);
171 this.sendAsyncMessage("FormAutoComplete:Invalidate", { results });
175 selectBy(reverse, page) {
176 Services.cpmm.sendSyncMessage("FormAutoComplete:SelectBy", {
177 browsingContext: this.browsingContext,
183 getResultsFromController(inputField) {
190 let controller = inputField.controller;
191 if (!(controller instanceof Ci.nsIAutoCompleteController)) {
195 for (let i = 0; i < controller.matchCount; ++i) {
197 result.value = controller.getValueAt(i);
198 result.label = controller.getLabelAt(i);
199 result.comment = controller.getCommentAt(i);
200 result.style = controller.getStyleAt(i);
201 result.image = controller.getImageAt(i);
202 results.push(result);
209 AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
210 "nsIAutoCompletePopup",