summaryrefslogtreecommitdiffstats
path: root/toolkit/actors/AutoCompleteChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/actors/AutoCompleteChild.sys.mjs')
-rw-r--r--toolkit/actors/AutoCompleteChild.sys.mjs197
1 files changed, 197 insertions, 0 deletions
diff --git a/toolkit/actors/AutoCompleteChild.sys.mjs b/toolkit/actors/AutoCompleteChild.sys.mjs
new file mode 100644
index 0000000000..a405834ca9
--- /dev/null
+++ b/toolkit/actors/AutoCompleteChild.sys.mjs
@@ -0,0 +1,197 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint no-unused-vars: ["error", {args: "none"}] */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
+ LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
+ LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
+});
+
+let autoCompleteListeners = new Set();
+
+export class AutoCompleteChild extends JSWindowActorChild {
+ constructor() {
+ super();
+
+ this._input = null;
+ this._popupOpen = false;
+ }
+
+ static addPopupStateListener(listener) {
+ autoCompleteListeners.add(listener);
+ }
+
+ static removePopupStateListener(listener) {
+ autoCompleteListeners.delete(listener);
+ }
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "FormAutoComplete:HandleEnter": {
+ this.selectedIndex = message.data.selectedIndex;
+
+ let controller = Cc[
+ "@mozilla.org/autocomplete/controller;1"
+ ].getService(Ci.nsIAutoCompleteController);
+ controller.handleEnter(message.data.isPopupSelection);
+ break;
+ }
+
+ case "FormAutoComplete:PopupClosed": {
+ this._popupOpen = false;
+ this.notifyListeners(message.name, message.data);
+ break;
+ }
+
+ case "FormAutoComplete:PopupOpened": {
+ this._popupOpen = true;
+ this.notifyListeners(message.name, message.data);
+ break;
+ }
+
+ case "FormAutoComplete:Focus": {
+ // XXX See bug 1582722
+ // Before bug 1573836, the messages here didn't match
+ // ("FormAutoComplete:Focus" versus "FormAutoComplete:RequestFocus")
+ // so this was never called. However this._input is actually a
+ // nsIAutoCompleteInput, which doesn't have a focus() method, so it
+ // wouldn't have worked anyway. So for now, I have just disabled this.
+ /*
+ if (this._input) {
+ this._input.focus();
+ }
+ */
+ break;
+ }
+ }
+ }
+
+ notifyListeners(messageName, data) {
+ for (let listener of autoCompleteListeners) {
+ try {
+ listener.popupStateChanged(messageName, data, this.contentWindow);
+ } catch (ex) {
+ console.error(ex);
+ }
+ }
+ }
+
+ get input() {
+ return this._input;
+ }
+
+ set selectedIndex(index) {
+ this.sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
+ }
+
+ get selectedIndex() {
+ // selectedIndex getter must be synchronous because we need the
+ // correct value when the controller is in controller::HandleEnter.
+ // We can't easily just let the parent inform us the new value every
+ // time it changes because not every action that can change the
+ // selectedIndex is trivial to catch (e.g. moving the mouse over the
+ // list).
+ let selectedIndexResult = Services.cpmm.sendSyncMessage(
+ "FormAutoComplete:GetSelectedIndex",
+ {
+ browsingContext: this.browsingContext,
+ }
+ );
+
+ if (
+ selectedIndexResult.length != 1 ||
+ !Number.isInteger(selectedIndexResult[0])
+ ) {
+ throw new Error("Invalid autocomplete selectedIndex");
+ }
+ return selectedIndexResult[0];
+ }
+
+ get popupOpen() {
+ return this._popupOpen;
+ }
+
+ openAutocompletePopup(input, element) {
+ if (this._popupOpen || !input) {
+ return;
+ }
+
+ let rect = lazy.LayoutUtils.getElementBoundingScreenRect(element);
+ let window = element.ownerGlobal;
+ let dir = window.getComputedStyle(element).direction;
+ let results = this.getResultsFromController(input);
+ let formOrigin = lazy.LoginHelper.getLoginOrigin(
+ element.ownerDocument.documentURI
+ );
+ let inputElementIdentifier = lazy.ContentDOMReference.get(element);
+
+ this.sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {
+ results,
+ rect,
+ dir,
+ inputElementIdentifier,
+ formOrigin,
+ });
+
+ this._input = input;
+ }
+
+ closePopup() {
+ // We set this here instead of just waiting for the
+ // PopupClosed message to do it so that we don't end
+ // up in a state where the content thinks that a popup
+ // is open when it isn't (or soon won't be).
+ this._popupOpen = false;
+ this.sendAsyncMessage("FormAutoComplete:ClosePopup", {});
+ }
+
+ invalidate() {
+ if (this._popupOpen) {
+ let results = this.getResultsFromController(this._input);
+ this.sendAsyncMessage("FormAutoComplete:Invalidate", { results });
+ }
+ }
+
+ selectBy(reverse, page) {
+ Services.cpmm.sendSyncMessage("FormAutoComplete:SelectBy", {
+ browsingContext: this.browsingContext,
+ reverse,
+ page,
+ });
+ }
+
+ getResultsFromController(inputField) {
+ let results = [];
+
+ if (!inputField) {
+ return results;
+ }
+
+ let controller = inputField.controller;
+ if (!(controller instanceof Ci.nsIAutoCompleteController)) {
+ return results;
+ }
+
+ for (let i = 0; i < controller.matchCount; ++i) {
+ let result = {};
+ result.value = controller.getValueAt(i);
+ result.label = controller.getLabelAt(i);
+ result.comment = controller.getCommentAt(i);
+ result.style = controller.getStyleAt(i);
+ result.image = controller.getImageAt(i);
+ results.push(result);
+ }
+
+ return results;
+ }
+}
+
+AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIAutoCompletePopup",
+]);