Bug 1760439 [wpt PR 33220] - Implement FedCM permission delegates in content_shell...
[gecko.git] / toolkit / actors / AutoCompleteChild.jsm
bloba10dd9c0fe5867e80ef56166fbcd1d3e4393b779
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(
13   this,
14   "LayoutUtils",
15   "resource://gre/modules/LayoutUtils.jsm"
18 ChromeUtils.defineModuleGetter(
19   this,
20   "ContentDOMReference",
21   "resource://gre/modules/ContentDOMReference.jsm"
24 ChromeUtils.defineModuleGetter(
25   this,
26   "LoginHelper",
27   "resource://gre/modules/LoginHelper.jsm"
30 let autoCompleteListeners = new Set();
32 class AutoCompleteChild extends JSWindowActorChild {
33   constructor() {
34     super();
36     this._input = null;
37     this._popupOpen = false;
38   }
40   static addPopupStateListener(listener) {
41     autoCompleteListeners.add(listener);
42   }
44   static removePopupStateListener(listener) {
45     autoCompleteListeners.delete(listener);
46   }
48   receiveMessage(message) {
49     switch (message.name) {
50       case "FormAutoComplete:HandleEnter": {
51         this.selectedIndex = message.data.selectedIndex;
53         let controller = Cc[
54           "@mozilla.org/autocomplete/controller;1"
55         ].getService(Ci.nsIAutoCompleteController);
56         controller.handleEnter(message.data.isPopupSelection);
57         break;
58       }
60       case "FormAutoComplete:PopupClosed": {
61         this._popupOpen = false;
62         this.notifyListeners(message.name, message.data);
63         break;
64       }
66       case "FormAutoComplete:PopupOpened": {
67         this._popupOpen = true;
68         this.notifyListeners(message.name, message.data);
69         break;
70       }
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.
79         /*
80         if (this._input) {
81           this._input.focus();
82         }
83         */
84         break;
85       }
86     }
87   }
89   notifyListeners(messageName, data) {
90     for (let listener of autoCompleteListeners) {
91       try {
92         listener.popupStateChanged(messageName, data, this.contentWindow);
93       } catch (ex) {
94         Cu.reportError(ex);
95       }
96     }
97   }
99   get input() {
100     return this._input;
101   }
103   set selectedIndex(index) {
104     this.sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
105   }
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
113     // list).
114     let selectedIndexResult = Services.cpmm.sendSyncMessage(
115       "FormAutoComplete:GetSelectedIndex",
116       {
117         browsingContext: this.browsingContext,
118       }
119     );
121     if (
122       selectedIndexResult.length != 1 ||
123       !Number.isInteger(selectedIndexResult[0])
124     ) {
125       throw new Error("Invalid autocomplete selectedIndex");
126     }
127     return selectedIndexResult[0];
128   }
130   get popupOpen() {
131     return this._popupOpen;
132   }
134   openAutocompletePopup(input, element) {
135     if (this._popupOpen || !input) {
136       return;
137     }
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
145     );
146     let inputElementIdentifier = ContentDOMReference.get(element);
148     this.sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {
149       results,
150       rect,
151       dir,
152       inputElementIdentifier,
153       formOrigin,
154     });
156     this._input = input;
157   }
159   closePopup() {
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", {});
166   }
168   invalidate() {
169     if (this._popupOpen) {
170       let results = this.getResultsFromController(this._input);
171       this.sendAsyncMessage("FormAutoComplete:Invalidate", { results });
172     }
173   }
175   selectBy(reverse, page) {
176     Services.cpmm.sendSyncMessage("FormAutoComplete:SelectBy", {
177       browsingContext: this.browsingContext,
178       reverse,
179       page,
180     });
181   }
183   getResultsFromController(inputField) {
184     let results = [];
186     if (!inputField) {
187       return results;
188     }
190     let controller = inputField.controller;
191     if (!(controller instanceof Ci.nsIAutoCompleteController)) {
192       return results;
193     }
195     for (let i = 0; i < controller.matchCount; ++i) {
196       let result = {};
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);
203     }
205     return results;
206   }
209 AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
210   "nsIAutoCompletePopup",