Bug 1856666 - run snap tests as cron r=releng-reviewers,ahal
[gecko.git] / toolkit / actors / AutoCompleteChild.sys.mjs
blob4cd9b9f3de97ca3959dd0ac9bca3428d639e8e1a
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"}] */
8 const lazy = {};
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",
14 });
16 let autoCompleteListeners = new Set();
18 export class AutoCompleteChild extends JSWindowActorChild {
19   constructor() {
20     super();
22     this._input = null;
23     this._popupOpen = false;
24   }
26   static addPopupStateListener(listener) {
27     autoCompleteListeners.add(listener);
28   }
30   static removePopupStateListener(listener) {
31     autoCompleteListeners.delete(listener);
32   }
34   receiveMessage(message) {
35     switch (message.name) {
36       case "FormAutoComplete:HandleEnter": {
37         this.selectedIndex = message.data.selectedIndex;
39         let controller = Cc[
40           "@mozilla.org/autocomplete/controller;1"
41         ].getService(Ci.nsIAutoCompleteController);
42         controller.handleEnter(message.data.isPopupSelection);
43         break;
44       }
46       case "FormAutoComplete:PopupClosed": {
47         this._popupOpen = false;
48         this.notifyListeners(message.name, message.data);
49         break;
50       }
52       case "FormAutoComplete:PopupOpened": {
53         this._popupOpen = true;
54         this.notifyListeners(message.name, message.data);
55         break;
56       }
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.
65         /*
66         if (this._input) {
67           this._input.focus();
68         }
69         */
70         break;
71       }
72     }
73   }
75   notifyListeners(messageName, data) {
76     for (let listener of autoCompleteListeners) {
77       try {
78         listener.popupStateChanged(messageName, data, this.contentWindow);
79       } catch (ex) {
80         console.error(ex);
81       }
82     }
83   }
85   get input() {
86     return this._input;
87   }
89   set selectedIndex(index) {
90     this.sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
91   }
93   get selectedIndex() {
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
99     // list).
100     let selectedIndexResult = Services.cpmm.sendSyncMessage(
101       "FormAutoComplete:GetSelectedIndex",
102       {
103         browsingContext: this.browsingContext,
104       }
105     );
107     if (
108       selectedIndexResult.length != 1 ||
109       !Number.isInteger(selectedIndexResult[0])
110     ) {
111       throw new Error("Invalid autocomplete selectedIndex");
112     }
113     return selectedIndexResult[0];
114   }
116   get popupOpen() {
117     return this._popupOpen;
118   }
120   openAutocompletePopup(input, element) {
121     if (this._popupOpen || !input || !element?.isConnected) {
122       return;
123     }
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
131     );
132     let inputElementIdentifier = lazy.ContentDOMReference.get(element);
134     this.sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {
135       results,
136       rect,
137       dir,
138       inputElementIdentifier,
139       formOrigin,
140     });
142     this._input = input;
143   }
145   closePopup() {
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", {});
152   }
154   invalidate() {
155     if (this._popupOpen) {
156       let results = this.getResultsFromController(this._input);
157       this.sendAsyncMessage("FormAutoComplete:Invalidate", { results });
158     }
159   }
161   selectBy(reverse, page) {
162     Services.cpmm.sendSyncMessage("FormAutoComplete:SelectBy", {
163       browsingContext: this.browsingContext,
164       reverse,
165       page,
166     });
167   }
169   getResultsFromController(inputField) {
170     let results = [];
172     if (!inputField) {
173       return results;
174     }
176     let controller = inputField.controller;
177     if (!(controller instanceof Ci.nsIAutoCompleteController)) {
178       return results;
179     }
181     for (let i = 0; i < controller.matchCount; ++i) {
182       let result = {};
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);
189     }
191     return results;
192   }
195 AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
196   "nsIAutoCompletePopup",