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 /* eslint no-unused-vars: ["error", {args: "none"}] */
10 ChromeUtils.defineESModuleGetters(lazy, {
11 ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
12 LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
13 LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
16 let autoCompleteListeners = new Set();
18 export class AutoCompleteChild extends JSWindowActorChild {
23 this._popupOpen = false;
26 static addPopupStateListener(listener) {
27 autoCompleteListeners.add(listener);
30 static removePopupStateListener(listener) {
31 autoCompleteListeners.delete(listener);
34 receiveMessage(message) {
35 switch (message.name) {
36 case "FormAutoComplete:HandleEnter": {
37 this.selectedIndex = message.data.selectedIndex;
40 "@mozilla.org/autocomplete/controller;1"
41 ].getService(Ci.nsIAutoCompleteController);
42 controller.handleEnter(message.data.isPopupSelection);
46 case "FormAutoComplete:PopupClosed": {
47 this._popupOpen = false;
48 this.notifyListeners(message.name, message.data);
52 case "FormAutoComplete:PopupOpened": {
53 this._popupOpen = true;
54 this.notifyListeners(message.name, message.data);
58 case "FormAutoComplete:Focus": {
59 // XXX See bug 1582722
60 // Before bug 1573836, the messages here didn't match
61 // ("FormAutoComplete:Focus" versus "FormAutoComplete:RequestFocus")
62 // so this was never called. However this._input is actually a
63 // nsIAutoCompleteInput, which doesn't have a focus() method, so it
64 // wouldn't have worked anyway. So for now, I have just disabled this.
75 notifyListeners(messageName, data) {
76 for (let listener of autoCompleteListeners) {
78 listener.popupStateChanged(messageName, data, this.contentWindow);
89 set selectedIndex(index) {
90 this.sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
94 // selectedIndex getter must be synchronous because we need the
95 // correct value when the controller is in controller::HandleEnter.
96 // We can't easily just let the parent inform us the new value every
97 // time it changes because not every action that can change the
98 // selectedIndex is trivial to catch (e.g. moving the mouse over the
100 let selectedIndexResult = Services.cpmm.sendSyncMessage(
101 "FormAutoComplete:GetSelectedIndex",
103 browsingContext: this.browsingContext,
108 selectedIndexResult.length != 1 ||
109 !Number.isInteger(selectedIndexResult[0])
111 throw new Error("Invalid autocomplete selectedIndex");
113 return selectedIndexResult[0];
117 return this._popupOpen;
120 openAutocompletePopup(input, element) {
121 if (this._popupOpen || !input) {
125 let rect = lazy.LayoutUtils.getElementBoundingScreenRect(element);
126 let window = element.ownerGlobal;
127 let dir = window.getComputedStyle(element).direction;
128 let results = this.getResultsFromController(input);
129 let formOrigin = lazy.LoginHelper.getLoginOrigin(
130 element.ownerDocument.documentURI
132 let inputElementIdentifier = lazy.ContentDOMReference.get(element);
134 this.sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {
138 inputElementIdentifier,
146 // We set this here instead of just waiting for the
147 // PopupClosed message to do it so that we don't end
148 // up in a state where the content thinks that a popup
149 // is open when it isn't (or soon won't be).
150 this._popupOpen = false;
151 this.sendAsyncMessage("FormAutoComplete:ClosePopup", {});
155 if (this._popupOpen) {
156 let results = this.getResultsFromController(this._input);
157 this.sendAsyncMessage("FormAutoComplete:Invalidate", { results });
161 selectBy(reverse, page) {
162 Services.cpmm.sendSyncMessage("FormAutoComplete:SelectBy", {
163 browsingContext: this.browsingContext,
169 getResultsFromController(inputField) {
176 let controller = inputField.controller;
177 if (!(controller instanceof Ci.nsIAutoCompleteController)) {
181 for (let i = 0; i < controller.matchCount; ++i) {
183 result.value = controller.getValueAt(i);
184 result.label = controller.getLabelAt(i);
185 result.comment = controller.getCommentAt(i);
186 result.style = controller.getStyleAt(i);
187 result.image = controller.getImageAt(i);
188 results.push(result);
195 AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
196 "nsIAutoCompletePopup",