Bug 1796551 [wpt PR 36570] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / toolkit / actors / AutoCompleteChild.jsm
blobde3023b6a90c28e4180daca90f2610704dc217b0
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 lazy = {};
12 ChromeUtils.defineESModuleGetters(lazy, {
13   ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
14   LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
15 });
17 ChromeUtils.defineModuleGetter(
18   lazy,
19   "LoginHelper",
20   "resource://gre/modules/LoginHelper.jsm"
23 let autoCompleteListeners = new Set();
25 class AutoCompleteChild extends JSWindowActorChild {
26   constructor() {
27     super();
29     this._input = null;
30     this._popupOpen = false;
31   }
33   static addPopupStateListener(listener) {
34     autoCompleteListeners.add(listener);
35   }
37   static removePopupStateListener(listener) {
38     autoCompleteListeners.delete(listener);
39   }
41   receiveMessage(message) {
42     switch (message.name) {
43       case "FormAutoComplete:HandleEnter": {
44         this.selectedIndex = message.data.selectedIndex;
46         let controller = Cc[
47           "@mozilla.org/autocomplete/controller;1"
48         ].getService(Ci.nsIAutoCompleteController);
49         controller.handleEnter(message.data.isPopupSelection);
50         break;
51       }
53       case "FormAutoComplete:PopupClosed": {
54         this._popupOpen = false;
55         this.notifyListeners(message.name, message.data);
56         break;
57       }
59       case "FormAutoComplete:PopupOpened": {
60         this._popupOpen = true;
61         this.notifyListeners(message.name, message.data);
62         break;
63       }
65       case "FormAutoComplete:Focus": {
66         // XXX See bug 1582722
67         // Before bug 1573836, the messages here didn't match
68         // ("FormAutoComplete:Focus" versus "FormAutoComplete:RequestFocus")
69         // so this was never called. However this._input is actually a
70         // nsIAutoCompleteInput, which doesn't have a focus() method, so it
71         // wouldn't have worked anyway. So for now, I have just disabled this.
72         /*
73         if (this._input) {
74           this._input.focus();
75         }
76         */
77         break;
78       }
79     }
80   }
82   notifyListeners(messageName, data) {
83     for (let listener of autoCompleteListeners) {
84       try {
85         listener.popupStateChanged(messageName, data, this.contentWindow);
86       } catch (ex) {
87         Cu.reportError(ex);
88       }
89     }
90   }
92   get input() {
93     return this._input;
94   }
96   set selectedIndex(index) {
97     this.sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
98   }
100   get selectedIndex() {
101     // selectedIndex getter must be synchronous because we need the
102     // correct value when the controller is in controller::HandleEnter.
103     // We can't easily just let the parent inform us the new value every
104     // time it changes because not every action that can change the
105     // selectedIndex is trivial to catch (e.g. moving the mouse over the
106     // list).
107     let selectedIndexResult = Services.cpmm.sendSyncMessage(
108       "FormAutoComplete:GetSelectedIndex",
109       {
110         browsingContext: this.browsingContext,
111       }
112     );
114     if (
115       selectedIndexResult.length != 1 ||
116       !Number.isInteger(selectedIndexResult[0])
117     ) {
118       throw new Error("Invalid autocomplete selectedIndex");
119     }
120     return selectedIndexResult[0];
121   }
123   get popupOpen() {
124     return this._popupOpen;
125   }
127   openAutocompletePopup(input, element) {
128     if (this._popupOpen || !input) {
129       return;
130     }
132     let rect = lazy.LayoutUtils.getElementBoundingScreenRect(element);
133     let window = element.ownerGlobal;
134     let dir = window.getComputedStyle(element).direction;
135     let results = this.getResultsFromController(input);
136     let formOrigin = lazy.LoginHelper.getLoginOrigin(
137       element.ownerDocument.documentURI
138     );
139     let inputElementIdentifier = lazy.ContentDOMReference.get(element);
141     this.sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {
142       results,
143       rect,
144       dir,
145       inputElementIdentifier,
146       formOrigin,
147     });
149     this._input = input;
150   }
152   closePopup() {
153     // We set this here instead of just waiting for the
154     // PopupClosed message to do it so that we don't end
155     // up in a state where the content thinks that a popup
156     // is open when it isn't (or soon won't be).
157     this._popupOpen = false;
158     this.sendAsyncMessage("FormAutoComplete:ClosePopup", {});
159   }
161   invalidate() {
162     if (this._popupOpen) {
163       let results = this.getResultsFromController(this._input);
164       this.sendAsyncMessage("FormAutoComplete:Invalidate", { results });
165     }
166   }
168   selectBy(reverse, page) {
169     Services.cpmm.sendSyncMessage("FormAutoComplete:SelectBy", {
170       browsingContext: this.browsingContext,
171       reverse,
172       page,
173     });
174   }
176   getResultsFromController(inputField) {
177     let results = [];
179     if (!inputField) {
180       return results;
181     }
183     let controller = inputField.controller;
184     if (!(controller instanceof Ci.nsIAutoCompleteController)) {
185       return results;
186     }
188     for (let i = 0; i < controller.matchCount; ++i) {
189       let result = {};
190       result.value = controller.getValueAt(i);
191       result.label = controller.getLabelAt(i);
192       result.comment = controller.getCommentAt(i);
193       result.style = controller.getStyleAt(i);
194       result.image = controller.getImageAt(i);
195       results.push(result);
196     }
198     return results;
199   }
202 AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
203   "nsIAutoCompletePopup",