gecko-dev/toolkit/actors/AutoCompleteChild.jsm
Neil Deakin 75efe08ebc Bug 1595154, replace the frame script FormAutofillFrameScript.js with an actor and fix up setTimeout calls in places that were relying on Timer.jsm being loaded in that frame script, r=MattN
Differential Revision: https://phabricator.services.mozilla.com/D52721

--HG--
rename : browser/extensions/formautofill/content/FormAutofillFrameScript.js => browser/extensions/formautofill/FormAutofillChild.jsm
extra : moz-landing-system : lando
2019-12-11 13:37:51 +00:00

273 lines
6.9 KiB
JavaScript

/* -*- 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/. */
var EXPORTED_SYMBOLS = ["AutoCompleteChild"];
/* eslint no-unused-vars: ["error", {args: "none"}] */
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"formFill",
"@mozilla.org/satchel/form-fill-controller;1",
"nsIFormFillController"
);
let autoCompleteListeners = new Set();
class AutoCompletePopup {
constructor(actor) {
this.actor = actor;
}
get input() {
return this.actor.input;
}
get overrideValue() {
return null;
}
set selectedIndex(index) {
return (this.actor.selectedIndex = index);
}
get selectedIndex() {
return this.actor.selectedIndex;
}
get popupOpen() {
return this.actor.popupOpen;
}
openAutocompletePopup(input, element) {
return this.actor.openAutocompletePopup(input, element);
}
closePopup() {
this.actor.closePopup();
}
invalidate() {
this.actor.invalidate();
}
selectBy(reverse, page) {
this.actor.selectBy(reverse, page);
}
}
AutoCompletePopup.prototype.QueryInterface = ChromeUtils.generateQI([
Ci.nsIAutoCompletePopup,
]);
class AutoCompleteChild extends JSWindowActorChild {
constructor() {
super();
this._input = null;
this._popupOpen = false;
this._attached = false;
}
static addPopupStateListener(listener) {
autoCompleteListeners.add(listener);
}
static removePopupStateListener(listener) {
autoCompleteListeners.delete(listener);
}
willDestroy() {
if (this._attached) {
formFill.detachFromDocument(this.document);
}
}
handleEvent(event) {
switch (event.type) {
case "DOMContentLoaded":
case "pageshow":
case "focus":
if (!this._attached && this.document.location != "about:blank") {
this._attached = true;
formFill.attachToDocument(this.document, new AutoCompletePopup(this));
}
if (event.type == "focus") {
formFill.handleFormEvent(event);
}
break;
case "pagehide":
case "unload":
if (this._attached) {
formFill.detachFromDocument(this.document);
this._attached = false;
}
// fall through
default:
formFill.handleFormEvent(event);
break;
}
}
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) {
Cu.reportError(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 = BrowserUtils.getElementBoundingScreenRect(element);
let window = element.ownerGlobal;
let dir = window.getComputedStyle(element).direction;
let results = this.getResultsFromController(input);
this.sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {
results,
rect,
dir,
});
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;
}
}