зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1573836, make autocomplete component fission compatible, r=mak,MattN
Differential Revision: https://phabricator.services.mozilla.com/D47093 --HG-- rename : toolkit/modules/AutoCompletePopupContent.jsm => toolkit/actors/AutoCompleteChild.jsm rename : toolkit/components/satchel/AutoCompletePopup.jsm => toolkit/actors/AutoCompleteParent.jsm extra : moz-landing-system : lando
This commit is contained in:
Родитель
5b1e475e24
Коммит
ec8b2c4f13
|
@ -45,6 +45,7 @@ const whitelist = {
|
|||
"resource:///modules/ContentMetaHandler.jsm",
|
||||
"resource:///actors/LinkHandlerChild.jsm",
|
||||
"resource:///actors/SearchTelemetryChild.jsm",
|
||||
"resource://gre/actors/AutoCompleteChild.jsm",
|
||||
"resource://gre/modules/ActorChild.jsm",
|
||||
"resource://gre/modules/ActorManagerChild.jsm",
|
||||
"resource://gre/modules/E10SUtils.jsm",
|
||||
|
|
|
@ -470,7 +470,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
AddonManager: "resource://gre/modules/AddonManager.jsm",
|
||||
AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm",
|
||||
AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
|
||||
AutoCompletePopup: "resource://gre/modules/AutoCompletePopup.jsm",
|
||||
Blocklist: "resource://gre/modules/Blocklist.jsm",
|
||||
BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.jsm",
|
||||
BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm",
|
||||
|
@ -1618,7 +1617,6 @@ BrowserGlue.prototype = {
|
|||
|
||||
this._checkForOldBuildUpdates();
|
||||
|
||||
AutoCompletePopup.init();
|
||||
// Check if Sync is configured
|
||||
if (Services.prefs.prefHasUserValue("services.sync.username")) {
|
||||
WeaveService.init();
|
||||
|
@ -1879,7 +1877,6 @@ BrowserGlue.prototype = {
|
|||
AboutNetErrorHandler.uninit();
|
||||
AboutPrivateBrowsingHandler.uninit();
|
||||
AboutProtectionsHandler.uninit();
|
||||
AutoCompletePopup.uninit();
|
||||
|
||||
Normandy.uninit();
|
||||
RFPHelper.uninit();
|
||||
|
|
|
@ -39,7 +39,10 @@ add_task(async function setup() {
|
|||
// AddonManager shuts down BrowserGlue will then try to uninit which will
|
||||
// cause AutoComplete.jsm to throw an error.
|
||||
// TODO: Fix in https://bugzilla.mozilla.org/show_bug.cgi?id=1543112.
|
||||
PromiseTestUtils.whitelistRejectionsGlobally(/Component returned failure code/);
|
||||
PromiseTestUtils.whitelistRejectionsGlobally(/A request was aborted/);
|
||||
PromiseTestUtils.whitelistRejectionsGlobally(
|
||||
/The operation failed for reasons unrelated/
|
||||
);
|
||||
|
||||
const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization";
|
||||
const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
|
||||
|
|
|
@ -350,24 +350,17 @@ let ProfileAutocomplete = {
|
|||
}
|
||||
},
|
||||
|
||||
_frameMMFromWindow(contentWindow) {
|
||||
return contentWindow.docShell.messageManager;
|
||||
getActorFromWindow(contentWindow) {
|
||||
return contentWindow.getWindowGlobalChild().getActor("AutoComplete");
|
||||
},
|
||||
|
||||
_getSelectedIndex(contentWindow) {
|
||||
let mm = this._frameMMFromWindow(contentWindow);
|
||||
let selectedIndexResult = mm.sendSyncMessage(
|
||||
"FormAutoComplete:GetSelectedIndex",
|
||||
{}
|
||||
);
|
||||
if (
|
||||
selectedIndexResult.length != 1 ||
|
||||
!Number.isInteger(selectedIndexResult[0])
|
||||
) {
|
||||
let actor = this.getActorFromWindow(contentWindow);
|
||||
if (!actor) {
|
||||
throw new Error("Invalid autocomplete selectedIndex");
|
||||
}
|
||||
|
||||
return selectedIndexResult[0];
|
||||
return actor.selectedIndex;
|
||||
},
|
||||
|
||||
_fillFromAutocompleteRow(focusedInput) {
|
||||
|
|
|
@ -23,6 +23,11 @@ ChromeUtils.defineModuleGetter(
|
|||
"formAutofillParent",
|
||||
"resource://formautofill/FormAutofillParent.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutoCompleteParent",
|
||||
"resource://gre/actors/AutoCompleteParent.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
|
@ -50,8 +55,7 @@ function insertStyleSheet(domWindow, url) {
|
|||
}
|
||||
}
|
||||
|
||||
function onMaybeOpenPopup(evt) {
|
||||
let domWindow = evt.target.ownerGlobal;
|
||||
function onMaybeOpenPopup(domWindow) {
|
||||
if (CACHED_STYLESHEETS.has(domWindow)) {
|
||||
// This window already has autofill stylesheets.
|
||||
return;
|
||||
|
@ -164,10 +168,7 @@ this.formautofill = class extends ExtensionAPI {
|
|||
}
|
||||
|
||||
// Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
|
||||
Services.mm.addMessageListener(
|
||||
"FormAutoComplete:MaybeOpenPopup",
|
||||
onMaybeOpenPopup
|
||||
);
|
||||
AutoCompleteParent.addPopupStateListener(onMaybeOpenPopup);
|
||||
|
||||
formAutofillParent.init().catch(Cu.reportError);
|
||||
Services.mm.loadFrameScript(
|
||||
|
@ -193,10 +194,7 @@ this.formautofill = class extends ExtensionAPI {
|
|||
);
|
||||
}
|
||||
|
||||
Services.mm.removeMessageListener(
|
||||
"FormAutoComplete:MaybeOpenPopup",
|
||||
onMaybeOpenPopup
|
||||
);
|
||||
AutoCompleteParent.removePopupStateListener(onMaybeOpenPopup);
|
||||
|
||||
for (let win of Services.wm.getEnumerator("navigator:browser")) {
|
||||
let cachedStyleSheets = CACHED_STYLESHEETS.get(win);
|
||||
|
|
|
@ -31,6 +31,11 @@ ChromeUtils.defineModuleGetter(
|
|||
"FormAutofillUtils",
|
||||
"resource://formautofill/FormAutofillUtils.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutoCompleteChild",
|
||||
"resource://gre/actors/AutoCompleteChild.jsm"
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles content's interactions for the frame.
|
||||
|
@ -41,6 +46,38 @@ var FormAutofillFrameScript = {
|
|||
_hasDOMContentLoadedHandler: false,
|
||||
_hasPendingTask: false,
|
||||
|
||||
popupStateListener(messageName, data, target) {
|
||||
if (!content || !FormAutofill.isAutofillEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = target.document;
|
||||
const { chromeEventHandler } = doc.ownerGlobal.docShell;
|
||||
|
||||
switch (messageName) {
|
||||
case "FormAutoComplete:PopupClosed": {
|
||||
FormAutofillContent.onPopupClosed(data.selectedRowStyle);
|
||||
Services.tm.dispatchToMainThread(() => {
|
||||
chromeEventHandler.removeEventListener(
|
||||
"keydown",
|
||||
FormAutofillContent._onKeyDown,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "FormAutoComplete:PopupOpened": {
|
||||
chromeEventHandler.addEventListener(
|
||||
"keydown",
|
||||
FormAutofillContent._onKeyDown,
|
||||
true
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_doIdentifyAutofillFields() {
|
||||
if (this._hasPendingTask) {
|
||||
return;
|
||||
|
@ -61,26 +98,36 @@ var FormAutofillFrameScript = {
|
|||
init() {
|
||||
addEventListener("focusin", this);
|
||||
addEventListener("DOMFormBeforeSubmit", this);
|
||||
addEventListener("unload", this, { once: true });
|
||||
addMessageListener("FormAutofill:PreviewProfile", this);
|
||||
addMessageListener("FormAutofill:ClearForm", this);
|
||||
addMessageListener("FormAutoComplete:PopupClosed", this);
|
||||
addMessageListener("FormAutoComplete:PopupOpened", this);
|
||||
|
||||
AutoCompleteChild.addPopupStateListener(this.popupStateListener);
|
||||
},
|
||||
|
||||
handleEvent(evt) {
|
||||
if (!evt.isTrusted || !FormAutofill.isAutofillEnabled) {
|
||||
if (!evt.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (evt.type) {
|
||||
case "focusin": {
|
||||
this.onFocusIn(evt);
|
||||
if (FormAutofill.isAutofillEnabled) {
|
||||
this.onFocusIn(evt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "DOMFormBeforeSubmit": {
|
||||
this.onDOMFormBeforeSubmit(evt);
|
||||
if (FormAutofill.isAutofillEnabled) {
|
||||
this.onDOMFormBeforeSubmit(evt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "unload": {
|
||||
AutoCompleteChild.removePopupStateListener(this.popupStateListener);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error("Unexpected event type");
|
||||
}
|
||||
|
@ -135,7 +182,6 @@ var FormAutofillFrameScript = {
|
|||
}
|
||||
|
||||
const doc = content.document;
|
||||
const { chromeEventHandler } = doc.ownerGlobal.docShell;
|
||||
|
||||
switch (message.name) {
|
||||
case "FormAutofill:PreviewProfile": {
|
||||
|
@ -146,26 +192,6 @@ var FormAutofillFrameScript = {
|
|||
FormAutofillContent.clearForm();
|
||||
break;
|
||||
}
|
||||
case "FormAutoComplete:PopupClosed": {
|
||||
FormAutofillContent.onPopupClosed(message.data.selectedRowStyle);
|
||||
Services.tm.dispatchToMainThread(() => {
|
||||
chromeEventHandler.removeEventListener(
|
||||
"keydown",
|
||||
FormAutofillContent._onKeyDown,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "FormAutoComplete:PopupOpened": {
|
||||
chromeEventHandler.addEventListener(
|
||||
"keydown",
|
||||
FormAutofillContent._onKeyDown,
|
||||
true
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,6 +14,25 @@
|
|||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
|
||||
function sendMessageToBrowser(msgName, data) {
|
||||
let { AutoCompleteParent } = ChromeUtils.import(
|
||||
"resource://gre/actors/AutoCompleteParent.jsm"
|
||||
);
|
||||
|
||||
let browser = AutoCompleteParent.getCurrentBrowser();
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (browser.messageManager) {
|
||||
browser.messageManager.sendAsyncMessage(msgName, data);
|
||||
} else {
|
||||
Cu.reportError(
|
||||
`customElements.js: No messageManager for message "${msgName}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MozAutocompleteProfileListitemBase extends MozElements.MozRichlistitem {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -111,10 +130,7 @@
|
|||
this.removeAttribute("selected");
|
||||
}
|
||||
|
||||
let { AutoCompletePopup } = ChromeUtils.import(
|
||||
"resource://gre/modules/AutoCompletePopup.jsm"
|
||||
);
|
||||
AutoCompletePopup.sendMessageToBrowser("FormAutofill:PreviewProfile");
|
||||
sendMessageToBrowser("FormAutofill:PreviewProfile");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
@ -350,10 +366,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
let { AutoCompletePopup } = ChromeUtils.import(
|
||||
"resource://gre/modules/AutoCompletePopup.jsm"
|
||||
);
|
||||
AutoCompletePopup.sendMessageToBrowser("FormAutofill:ClearForm");
|
||||
sendMessageToBrowser("FormAutofill:ClearForm");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1251,13 +1251,13 @@ class SpecialPowersChild extends JSWindowActorChild {
|
|||
);
|
||||
}
|
||||
attachFormFillControllerTo(window) {
|
||||
this.getFormFillController().attachPopupElementToBrowser(
|
||||
window.docShell,
|
||||
this.getFormFillController().attachPopupElementToDocument(
|
||||
window.document,
|
||||
this._getAutoCompletePopup(window)
|
||||
);
|
||||
}
|
||||
detachFormFillControllerFrom(window) {
|
||||
this.getFormFillController().detachFromBrowser(window.docShell);
|
||||
this.getFormFillController().detachFromDocument(window.document);
|
||||
}
|
||||
isBackButtonEnabled(window) {
|
||||
return !this._getTopChromeWindow(window)
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
/* -*- 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(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).
|
||||
return Services.cpmm.sendSyncMessage("FormAutoComplete:GetSelectedIndex", {
|
||||
browsingContext: this.browsingContext,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -4,14 +4,59 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["AutoCompletePopup"];
|
||||
var EXPORTED_SYMBOLS = ["AutoCompleteParent"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// AutoCompleteResultView is an abstraction around a list of results
|
||||
// we got back up from browser-content.js. It implements enough of
|
||||
// nsIAutoCompleteController and nsIAutoCompleteInput to make the
|
||||
// richlistbox popup work.
|
||||
// Stores the browser and actor that has the active popup, used by formfill
|
||||
let currentBrowserWeakRef = null;
|
||||
let currentActor = null;
|
||||
|
||||
let autoCompleteListeners = new Set();
|
||||
|
||||
function compareContext(message) {
|
||||
if (
|
||||
!currentActor ||
|
||||
(currentActor.browsingContext != message.data.browsingContext &&
|
||||
currentActor.browsingContext.top != message.data.browsingContext)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// These are two synchronous messages sent by the child.
|
||||
// The browsingContext within the message data is either the one that has
|
||||
// the active autocomplete popup or the top-level of the one that has
|
||||
// the active autocomplete popup.
|
||||
Services.ppmm.addMessageListener(
|
||||
"FormAutoComplete:GetSelectedIndex",
|
||||
message => {
|
||||
if (compareContext(message)) {
|
||||
let actor = currentActor;
|
||||
if (actor && actor.openedPopup) {
|
||||
return actor.openedPopup.selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
);
|
||||
|
||||
Services.ppmm.addMessageListener("FormAutoComplete:SelectBy", message => {
|
||||
if (compareContext(message)) {
|
||||
let actor = currentActor;
|
||||
if (actor && actor.openedPopup) {
|
||||
actor.openedPopup.selectBy(message.data.reverse, message.data.page);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// AutoCompleteResultView is an abstraction around a list of results.
|
||||
// It implements enough of nsIAutoCompleteController and
|
||||
// nsIAutoCompleteInput to make the richlistbox popup work. Since only
|
||||
// one autocomplete popup should be open at a time, this is a singleton.
|
||||
var AutoCompleteResultView = {
|
||||
// nsISupports
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
|
@ -22,6 +67,9 @@ var AutoCompleteResultView = {
|
|||
// Private variables
|
||||
results: [],
|
||||
|
||||
// The AutoCompleteParent currently showing results or null otherwise.
|
||||
currentActor: null,
|
||||
|
||||
// nsIAutoCompleteController
|
||||
get matchCount() {
|
||||
return this.results.length;
|
||||
|
@ -57,7 +105,9 @@ var AutoCompleteResultView = {
|
|||
},
|
||||
|
||||
handleEnter(aIsPopupSelection) {
|
||||
AutoCompletePopup.handleEnter(aIsPopupSelection);
|
||||
if (this.currentActor) {
|
||||
this.currentActor.handleEnter(aIsPopupSelection);
|
||||
}
|
||||
},
|
||||
|
||||
stopSearch() {},
|
||||
|
@ -74,60 +124,46 @@ var AutoCompleteResultView = {
|
|||
},
|
||||
|
||||
_focus() {
|
||||
AutoCompletePopup.requestFocus();
|
||||
if (this.currentActor) {
|
||||
this.currentActor.requestFocus();
|
||||
}
|
||||
},
|
||||
|
||||
// Internal JS-only API
|
||||
clearResults() {
|
||||
this.currentActor = null;
|
||||
this.results = [];
|
||||
},
|
||||
|
||||
setResults(results) {
|
||||
setResults(actor, results) {
|
||||
this.currentActor = actor;
|
||||
this.results = results;
|
||||
},
|
||||
};
|
||||
|
||||
this.AutoCompletePopup = {
|
||||
MESSAGES: [
|
||||
"FormAutoComplete:SelectBy",
|
||||
"FormAutoComplete:GetSelectedIndex",
|
||||
"FormAutoComplete:SetSelectedIndex",
|
||||
"FormAutoComplete:MaybeOpenPopup",
|
||||
"FormAutoComplete:ClosePopup",
|
||||
"FormAutoComplete:Disconnect",
|
||||
"FormAutoComplete:RemoveEntry",
|
||||
"FormAutoComplete:Invalidate",
|
||||
],
|
||||
|
||||
init() {
|
||||
for (let msg of this.MESSAGES) {
|
||||
Services.mm.addMessageListener(msg, this);
|
||||
class AutoCompleteParent extends JSWindowActorParent {
|
||||
willDestroy() {
|
||||
if (this.openedPopup) {
|
||||
this.openedPopup.closePopup();
|
||||
}
|
||||
Services.obs.addObserver(this, "message-manager-disconnect");
|
||||
},
|
||||
}
|
||||
|
||||
uninit() {
|
||||
for (let msg of this.MESSAGES) {
|
||||
Services.mm.removeMessageListener(msg, this);
|
||||
}
|
||||
Services.obs.removeObserver(this, "message-manager-disconnect");
|
||||
},
|
||||
static getCurrentBrowser() {
|
||||
return currentBrowserWeakRef ? currentBrowserWeakRef.get() : null;
|
||||
}
|
||||
|
||||
observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "message-manager-disconnect": {
|
||||
if (this.openedPopup) {
|
||||
this.openedPopup.closePopup();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
static addPopupStateListener(listener) {
|
||||
autoCompleteListeners.add(listener);
|
||||
}
|
||||
|
||||
static removePopupStateListener(listener) {
|
||||
autoCompleteListeners.delete(listener);
|
||||
}
|
||||
|
||||
handleEvent(evt) {
|
||||
switch (evt.type) {
|
||||
case "popupshowing": {
|
||||
this.sendMessageToBrowser("FormAutoComplete:PopupOpened");
|
||||
this.sendAsyncMessage("FormAutoComplete:PopupOpened", {});
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -137,7 +173,7 @@ this.AutoCompletePopup = {
|
|||
selectedIndex != -1
|
||||
? AutoCompleteResultView.getStyleAt(selectedIndex)
|
||||
: "";
|
||||
this.sendMessageToBrowser("FormAutoComplete:PopupClosed", {
|
||||
this.sendAsyncMessage("FormAutoComplete:PopupClosed", {
|
||||
selectedRowStyle,
|
||||
});
|
||||
AutoCompleteResultView.clearResults();
|
||||
|
@ -146,15 +182,16 @@ this.AutoCompletePopup = {
|
|||
// large list, and then open on a small one.
|
||||
this.openedPopup.adjustHeight();
|
||||
this.openedPopup = null;
|
||||
this.weakBrowser = null;
|
||||
currentBrowserWeakRef = null;
|
||||
currentActor = null;
|
||||
evt.target.removeEventListener("popuphidden", this);
|
||||
evt.target.removeEventListener("popupshowing", this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
showPopupWithResults({ browser, rect, dir, results }) {
|
||||
showPopupWithResults({ rect, dir, results }) {
|
||||
if (!results.length || this.openedPopup) {
|
||||
// We shouldn't ever be showing an empty popup, and if we
|
||||
// already have a popup open, the old one needs to close before
|
||||
|
@ -162,6 +199,7 @@ this.AutoCompletePopup = {
|
|||
return;
|
||||
}
|
||||
|
||||
let browser = this.browsingContext.top.embedderElement;
|
||||
let window = browser.ownerGlobal;
|
||||
// Also check window top in case this is a sidebar.
|
||||
if (
|
||||
|
@ -175,7 +213,8 @@ this.AutoCompletePopup = {
|
|||
|
||||
// Non-empty result styles
|
||||
let resultStyles = new Set(results.map(r => r.style).filter(r => !!r));
|
||||
this.weakBrowser = Cu.getWeakReference(browser);
|
||||
currentBrowserWeakRef = Cu.getWeakReference(browser);
|
||||
currentActor = this;
|
||||
this.openedPopup = browser.autoCompletePopup;
|
||||
// the layout varies according to different result type
|
||||
this.openedPopup.setAttribute("resultstyles", [...resultStyles].join(" "));
|
||||
|
@ -184,7 +223,7 @@ this.AutoCompletePopup = {
|
|||
this.openedPopup.setAttribute("width", Math.max(100, rect.width));
|
||||
this.openedPopup.style.direction = dir;
|
||||
|
||||
AutoCompleteResultView.setResults(results);
|
||||
AutoCompleteResultView.setResults(this, results);
|
||||
this.openedPopup.view = AutoCompleteResultView;
|
||||
this.openedPopup.selectedIndex = -1;
|
||||
|
||||
|
@ -216,7 +255,7 @@ this.AutoCompletePopup = {
|
|||
} else {
|
||||
this.closePopup();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
invalidate(results) {
|
||||
if (!this.openedPopup) {
|
||||
|
@ -226,10 +265,10 @@ this.AutoCompletePopup = {
|
|||
if (!results.length) {
|
||||
this.closePopup();
|
||||
} else {
|
||||
AutoCompleteResultView.setResults(results);
|
||||
AutoCompleteResultView.setResults(this, results);
|
||||
this.openedPopup.invalidate();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
closePopup() {
|
||||
if (this.openedPopup) {
|
||||
|
@ -238,36 +277,22 @@ this.AutoCompletePopup = {
|
|||
// and handled during this call.
|
||||
this.openedPopup.hidePopup();
|
||||
}
|
||||
},
|
||||
|
||||
removeLogin(login) {
|
||||
Services.logins.removeLogin(login);
|
||||
},
|
||||
}
|
||||
|
||||
receiveMessage(message) {
|
||||
if (!message.target.autoCompletePopup) {
|
||||
let browser = this.browsingContext.top.embedderElement;
|
||||
if (!browser || !browser.autoCompletePopup) {
|
||||
// If there is no browser or popup, just make sure that the popup has been closed.
|
||||
if (this.openedPopup) {
|
||||
this.openedPopup.closePopup();
|
||||
}
|
||||
|
||||
// Returning false to pacify ESLint, but this return value is
|
||||
// ignored by the messaging infrastructure.
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (message.name) {
|
||||
case "FormAutoComplete:SelectBy": {
|
||||
if (this.openedPopup) {
|
||||
this.openedPopup.selectBy(message.data.reverse, message.data.page);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAutoComplete:GetSelectedIndex": {
|
||||
if (this.openedPopup) {
|
||||
return this.openedPopup.selectedIndex;
|
||||
}
|
||||
// If the popup was closed, then the selection
|
||||
// has not changed.
|
||||
return -1;
|
||||
}
|
||||
|
||||
case "FormAutoComplete:SetSelectedIndex": {
|
||||
let { index } = message.data;
|
||||
if (this.openedPopup) {
|
||||
|
@ -279,11 +304,12 @@ this.AutoCompletePopup = {
|
|||
case "FormAutoComplete:MaybeOpenPopup": {
|
||||
let { results, rect, dir } = message.data;
|
||||
this.showPopupWithResults({
|
||||
browser: message.target,
|
||||
rect,
|
||||
dir,
|
||||
results,
|
||||
});
|
||||
|
||||
this.notifyListeners();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -310,7 +336,18 @@ this.AutoCompletePopup = {
|
|||
// Returning false to pacify ESLint, but this return value is
|
||||
// ignored by the messaging infrastructure.
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
notifyListeners() {
|
||||
let window = this.browsingContext.top.embedderElement.ownerGlobal;
|
||||
for (let listener of autoCompleteListeners) {
|
||||
try {
|
||||
listener(window);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Despite its name, this handleEnter is only called when the user clicks on
|
||||
|
@ -322,46 +359,25 @@ this.AutoCompletePopup = {
|
|||
*/
|
||||
handleEnter(aIsPopupSelection) {
|
||||
if (this.openedPopup) {
|
||||
this.sendMessageToBrowser("FormAutoComplete:HandleEnter", {
|
||||
this.sendAsyncMessage("FormAutoComplete:HandleEnter", {
|
||||
selectedIndex: this.openedPopup.selectedIndex,
|
||||
isPopupSelection: aIsPopupSelection,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
stopSearch() {}
|
||||
|
||||
/**
|
||||
* If a browser exists that AutoCompletePopup knows about,
|
||||
* sends it a message. Otherwise, this is a no-op.
|
||||
*
|
||||
* @param {string} msgName
|
||||
* The name of the message to send.
|
||||
* @param {object} data
|
||||
* The optional data to send with the message.
|
||||
*/
|
||||
sendMessageToBrowser(msgName, data) {
|
||||
let browser = this.weakBrowser ? this.weakBrowser.get() : null;
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (browser.messageManager) {
|
||||
browser.messageManager.sendAsyncMessage(msgName, data);
|
||||
} else {
|
||||
Cu.reportError(
|
||||
`AutoCompletePopup: No messageManager for message "${msgName}"`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
stopSearch() {},
|
||||
|
||||
/**
|
||||
* Sends a message to the browser requesting that the input
|
||||
* that the AutoCompletePopup is open for be focused.
|
||||
* Sends a message to the browser that is requesting the input
|
||||
* that the open popup should be focused.
|
||||
*/
|
||||
requestFocus() {
|
||||
// Bug 1582722 - See the response in AutoCompleteChild.jsm for why this disabled.
|
||||
/*
|
||||
if (this.openedPopup) {
|
||||
this.sendMessageToBrowser("FormAutoComplete:Focus");
|
||||
this.sendAsyncMessage("FormAutoComplete:Focus");
|
||||
}
|
||||
},
|
||||
};
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@ TESTING_JS_MODULES += [
|
|||
FINAL_TARGET_FILES.actors += [
|
||||
'AudioPlaybackChild.jsm',
|
||||
'AudioPlaybackParent.jsm',
|
||||
'AutoCompleteChild.jsm',
|
||||
'AutoCompleteParent.jsm',
|
||||
'AutoplayChild.jsm',
|
||||
'AutoplayParent.jsm',
|
||||
'BrowserElementChild.jsm',
|
||||
|
|
|
@ -66,6 +66,11 @@ ChromeUtils.defineModuleGetter(
|
|||
"ContentDOMReference",
|
||||
"resource://gre/modules/ContentDOMReference.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutoCompleteChild",
|
||||
"resource://gre/actors/AutoCompleteChild.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
|
@ -262,6 +267,76 @@ const observer = {
|
|||
// Add this observer once for the process.
|
||||
Services.obs.addObserver(observer, "autocomplete-did-enter-text");
|
||||
|
||||
let gAutoCompleteListener = {
|
||||
// Input element on which enter keydown event was fired.
|
||||
keyDownEnterForInput: null,
|
||||
|
||||
added: false,
|
||||
|
||||
init() {
|
||||
if (!this.added) {
|
||||
AutoCompleteChild.addPopupStateListener((...args) => {
|
||||
this.popupStateListener(...args);
|
||||
});
|
||||
this.added = true;
|
||||
}
|
||||
},
|
||||
|
||||
popupStateListener(messageName, data, target) {
|
||||
switch (messageName) {
|
||||
case "FormAutoComplete:PopupOpened": {
|
||||
let { chromeEventHandler } = target.docShell;
|
||||
chromeEventHandler.addEventListener("keydown", this, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAutoComplete:PopupClosed": {
|
||||
this.onPopupClosed(
|
||||
data.selectedRowStyle,
|
||||
target.docShell.messageManager
|
||||
);
|
||||
let { chromeEventHandler } = target.docShell;
|
||||
chromeEventHandler.removeEventListener("keydown", this, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type != "keydown") {
|
||||
return;
|
||||
}
|
||||
|
||||
let focusedElement = LoginManagerContent._formFillService.focusedInput;
|
||||
if (
|
||||
event.keyCode != event.DOM_VK_RETURN ||
|
||||
focusedElement != event.target
|
||||
) {
|
||||
this.keyDownEnterForInput = null;
|
||||
return;
|
||||
}
|
||||
this.keyDownEnterForInput = focusedElement;
|
||||
},
|
||||
|
||||
onPopupClosed(selectedRowStyle, mm) {
|
||||
let focusedElement = LoginManagerContent._formFillService.focusedInput;
|
||||
let eventTarget = this.keyDownEnterForInput;
|
||||
if (
|
||||
!eventTarget ||
|
||||
eventTarget !== focusedElement ||
|
||||
selectedRowStyle != "loginsFooter"
|
||||
) {
|
||||
this.keyDownEnterForInput = null;
|
||||
return;
|
||||
}
|
||||
let hostname = eventTarget.ownerDocument.documentURIObject.host;
|
||||
mm.sendAsyncMessage("PasswordManager:OpenPreferences", {
|
||||
hostname,
|
||||
entryPoint: "autocomplete",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// This object maps to the "child" process (even in the single-process case).
|
||||
this.LoginManagerContent = {
|
||||
__formFillService: null, // FormFillController, for username autocompleting
|
||||
|
@ -314,9 +389,6 @@ this.LoginManagerContent = {
|
|||
// Number of outstanding requests to each manager.
|
||||
_managers: new Map(),
|
||||
|
||||
// Input element on which enter keydown event was fired.
|
||||
_keyDownEnterForInput: null,
|
||||
|
||||
_takeRequest(msg) {
|
||||
let data = msg.data;
|
||||
let request = this._requests.get(data.requestId);
|
||||
|
@ -400,36 +472,6 @@ this.LoginManagerContent = {
|
|||
return false;
|
||||
},
|
||||
|
||||
_onKeyDown(event) {
|
||||
let focusedElement = LoginManagerContent._formFillService.focusedInput;
|
||||
if (
|
||||
event.keyCode != event.DOM_VK_RETURN ||
|
||||
focusedElement != event.target
|
||||
) {
|
||||
this._keyDownEnterForInput = null;
|
||||
return;
|
||||
}
|
||||
LoginManagerContent._keyDownEnterForInput = focusedElement;
|
||||
},
|
||||
|
||||
_onPopupClosed(selectedRowStyle, mm) {
|
||||
let focusedElement = LoginManagerContent._formFillService.focusedInput;
|
||||
let eventTarget = LoginManagerContent._keyDownEnterForInput;
|
||||
if (
|
||||
!eventTarget ||
|
||||
eventTarget !== focusedElement ||
|
||||
selectedRowStyle != "loginsFooter"
|
||||
) {
|
||||
this._keyDownEnterForInput = null;
|
||||
return;
|
||||
}
|
||||
let hostname = eventTarget.ownerDocument.documentURIObject.host;
|
||||
mm.sendAsyncMessage("PasswordManager:OpenPreferences", {
|
||||
hostname,
|
||||
entryPoint: "autocomplete",
|
||||
});
|
||||
},
|
||||
|
||||
receiveMessage(msg, topWindow) {
|
||||
if (msg.name == "PasswordManager:fillForm") {
|
||||
this.fillForm({
|
||||
|
@ -495,23 +537,6 @@ this.LoginManagerContent = {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAutoComplete:PopupOpened": {
|
||||
let { chromeEventHandler } = msg.target.docShell;
|
||||
chromeEventHandler.addEventListener("keydown", this._onKeyDown, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAutoComplete:PopupClosed": {
|
||||
this._onPopupClosed(msg.data.selectedRowStyle, msg.target);
|
||||
let { chromeEventHandler } = msg.target.docShell;
|
||||
chromeEventHandler.removeEventListener(
|
||||
"keydown",
|
||||
this._onKeyDown,
|
||||
true
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -580,8 +605,7 @@ this.LoginManagerContent = {
|
|||
};
|
||||
|
||||
if (LoginHelper.showAutoCompleteFooter) {
|
||||
messageManager.addMessageListener("FormAutoComplete:PopupOpened", this);
|
||||
messageManager.addMessageListener("FormAutoComplete:PopupClosed", this);
|
||||
gAutoCompleteListener.init();
|
||||
}
|
||||
|
||||
return this._sendRequest(
|
||||
|
|
|
@ -17,11 +17,6 @@ const LoginInfo = new Components.Constructor(
|
|||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutoCompletePopup",
|
||||
"resource://gre/modules/AutoCompletePopup.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"DeferredTask",
|
||||
|
@ -186,7 +181,7 @@ this.LoginManagerParent = {
|
|||
|
||||
case "PasswordManager:removeLogin": {
|
||||
let login = LoginHelper.vanillaObjectToLogin(data.login);
|
||||
AutoCompletePopup.removeLogin(login);
|
||||
Services.logins.removeLogin(login);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ LOCAL_INCLUDES += [
|
|||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AutoCompletePopup.jsm',
|
||||
'FormAutoComplete.jsm',
|
||||
'FormHistory.jsm',
|
||||
'FormHistoryStartup.jsm',
|
||||
|
|
|
@ -67,7 +67,7 @@ static nsIFormAutoComplete* GetFormAutoComplete() {
|
|||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(nsFormFillController, mController, mLoginManagerAC,
|
||||
mLoginReputationService, mFocusedPopup, mDocShells,
|
||||
mLoginReputationService, mFocusedPopup,
|
||||
mPopups, mLastListener, mLastFormAutoComplete)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
|
||||
|
@ -75,7 +75,6 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
|
|||
NS_INTERFACE_MAP_ENTRY(nsIFormFillController)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteInput)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
@ -114,13 +113,6 @@ nsFormFillController::~nsFormFillController() {
|
|||
mFocusedInput = nullptr;
|
||||
}
|
||||
RemoveForDocument(nullptr);
|
||||
|
||||
// Remove ourselves as a focus listener from all cached docShells
|
||||
uint32_t count = mDocShells.Length();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(mDocShells[i]);
|
||||
RemoveWindowListeners(window);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -217,18 +209,13 @@ void nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode) {
|
|||
//// nsIFormFillController
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsFormFillController::AttachToBrowser(nsIDocShell* aDocShell,
|
||||
nsIAutoCompletePopup* aPopup) {
|
||||
nsFormFillController::AttachToDocument(Document* aDocument,
|
||||
nsIAutoCompletePopup* aPopup) {
|
||||
MOZ_LOG(sLogger, LogLevel::Debug,
|
||||
("AttachToBrowser for docShell %p with popup %p", aDocShell, aPopup));
|
||||
NS_ENSURE_TRUE(aDocShell && aPopup, NS_ERROR_ILLEGAL_VALUE);
|
||||
("AttachToDocument for document %p with popup %p", aDocument, aPopup));
|
||||
NS_ENSURE_TRUE(aDocument && aPopup, NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
mDocShells.AppendElement(aDocShell);
|
||||
mPopups.AppendElement(aPopup);
|
||||
|
||||
// Listen for focus events on the domWindow of the docShell
|
||||
nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(aDocShell);
|
||||
AddWindowListeners(window);
|
||||
mPopups.Put(aDocument, aPopup);
|
||||
|
||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
if (fm) {
|
||||
|
@ -241,32 +228,22 @@ nsFormFillController::AttachToBrowser(nsIDocShell* aDocShell,
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsFormFillController::AttachPopupElementToBrowser(nsIDocShell* aDocShell,
|
||||
dom::Element* aPopupEl) {
|
||||
nsFormFillController::AttachPopupElementToDocument(Document* aDocument,
|
||||
dom::Element* aPopupEl) {
|
||||
MOZ_LOG(sLogger, LogLevel::Debug,
|
||||
("AttachPopupElementToBrowser for docShell %p with popup %p",
|
||||
aDocShell, aPopupEl));
|
||||
NS_ENSURE_TRUE(aDocShell && aPopupEl, NS_ERROR_ILLEGAL_VALUE);
|
||||
("AttachPopupElementToDocument for document %p with popup %p",
|
||||
aDocument, aPopupEl));
|
||||
NS_ENSURE_TRUE(aDocument && aPopupEl, NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
nsCOMPtr<nsIAutoCompletePopup> popup = aPopupEl->AsAutoCompletePopup();
|
||||
NS_ENSURE_STATE(popup);
|
||||
|
||||
return AttachToBrowser(aDocShell, popup);
|
||||
return AttachToDocument(aDocument, popup);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsFormFillController::DetachFromBrowser(nsIDocShell* aDocShell) {
|
||||
int32_t index = GetIndexOfDocShell(aDocShell);
|
||||
NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE);
|
||||
|
||||
// Stop listening for focus events on the domWindow of the docShell
|
||||
nsCOMPtr<nsPIDOMWindowOuter> window =
|
||||
GetWindowForDocShell(mDocShells.SafeElementAt(index));
|
||||
RemoveWindowListeners(window);
|
||||
|
||||
mDocShells.RemoveElementAt(index);
|
||||
mPopups.RemoveElementAt(index);
|
||||
|
||||
nsFormFillController::DetachFromDocument(Document* aDocument) {
|
||||
mPopups.Remove(aDocument);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -836,7 +813,7 @@ nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult* aResult) {
|
|||
//// nsIDOMEventListener
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsFormFillController::HandleEvent(Event* aEvent) {
|
||||
nsFormFillController::HandleFormEvent(Event* aEvent) {
|
||||
WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
|
||||
NS_ENSURE_STATE(internalEvent);
|
||||
|
||||
|
@ -1239,115 +1216,21 @@ NS_IMETHODIMP nsFormFillController::GetPasswordPopupAutomaticallyOpened(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//// nsFormFillController
|
||||
|
||||
void nsFormFillController::AddWindowListeners(nsPIDOMWindowOuter* aWindow) {
|
||||
MOZ_LOG(sLogger, LogLevel::Debug,
|
||||
("AddWindowListeners for window %p", aWindow));
|
||||
if (!aWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventTarget* target = aWindow->GetChromeEventHandler();
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventListenerManager* elm = target->GetOrCreateListenerManager();
|
||||
if (NS_WARN_IF(!elm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("focus"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("blur"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("mousedown"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("input"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("keydown"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("keypress"),
|
||||
TrustedEventsAtSystemGroupCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("compositionstart"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("compositionend"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->AddEventListenerByType(this, NS_LITERAL_STRING("contextmenu"),
|
||||
TrustedEventsAtCapture());
|
||||
|
||||
// Note that any additional listeners added should ensure that they ignore
|
||||
// untrusted events, which might be sent by content that's up to no good.
|
||||
}
|
||||
|
||||
void nsFormFillController::RemoveWindowListeners(nsPIDOMWindowOuter* aWindow) {
|
||||
MOZ_LOG(sLogger, LogLevel::Debug,
|
||||
("RemoveWindowListeners for window %p", aWindow));
|
||||
if (!aWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopControllingInput();
|
||||
|
||||
RefPtr<Document> doc = aWindow->GetDoc();
|
||||
RemoveForDocument(doc);
|
||||
|
||||
EventTarget* target = aWindow->GetChromeEventHandler();
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventListenerManager* elm = target->GetOrCreateListenerManager();
|
||||
if (NS_WARN_IF(!elm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("focus"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("blur"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("mousedown"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("input"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("keydown"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("keypress"),
|
||||
TrustedEventsAtSystemGroupCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("compositionstart"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("compositionend"),
|
||||
TrustedEventsAtCapture());
|
||||
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("contextmenu"),
|
||||
TrustedEventsAtCapture());
|
||||
}
|
||||
|
||||
void nsFormFillController::StartControllingInput(HTMLInputElement* aInput) {
|
||||
MOZ_LOG(sLogger, LogLevel::Verbose, ("StartControllingInput for %p", aInput));
|
||||
// Make sure we're not still attached to an input
|
||||
StopControllingInput();
|
||||
|
||||
if (!mController) {
|
||||
if (!mController || !aInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the currently focused docShell
|
||||
nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(aInput);
|
||||
int32_t index = GetIndexOfDocShell(docShell);
|
||||
if (index < 0) {
|
||||
nsCOMPtr<nsIAutoCompletePopup> popup = mPopups.Get(aInput->OwnerDoc());
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aInput, "How did we get a docshell index??");
|
||||
|
||||
// Cache the popup for the focused docShell
|
||||
mFocusedPopup = mPopups.SafeElementAt(index);
|
||||
mFocusedPopup = popup;
|
||||
|
||||
aInput->AddMutationObserverUnlessExists(this);
|
||||
mFocusedInput = aInput;
|
||||
|
@ -1410,40 +1293,3 @@ nsIDocShell* nsFormFillController::GetDocShellForInput(
|
|||
|
||||
return win->GetDocShell();
|
||||
}
|
||||
|
||||
nsPIDOMWindowOuter* nsFormFillController::GetWindowForDocShell(
|
||||
nsIDocShell* aDocShell) {
|
||||
nsCOMPtr<nsIContentViewer> contentViewer;
|
||||
aDocShell->GetContentViewer(getter_AddRefs(contentViewer));
|
||||
NS_ENSURE_TRUE(contentViewer, nullptr);
|
||||
|
||||
RefPtr<Document> doc = contentViewer->GetDocument();
|
||||
NS_ENSURE_TRUE(doc, nullptr);
|
||||
|
||||
return doc->GetWindow();
|
||||
}
|
||||
|
||||
int32_t nsFormFillController::GetIndexOfDocShell(nsIDocShell* aDocShell) {
|
||||
if (!aDocShell) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Loop through our cached docShells looking for the given docShell
|
||||
uint32_t count = mDocShells.Length();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
if (mDocShells[i] == aDocShell) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check the parent docShell of this one
|
||||
nsCOMPtr<nsIDocShellTreeItem> treeItem = aDocShell;
|
||||
nsCOMPtr<nsIDocShellTreeItem> parentItem;
|
||||
treeItem->GetInProcessParent(getter_AddRefs(parentItem));
|
||||
if (parentItem) {
|
||||
nsCOMPtr<nsIDocShell> parentShell = do_QueryInterface(parentItem);
|
||||
return GetIndexOfDocShell(parentShell);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "nsIDOMEventListener.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsInterfaceHashtable.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsILoginAutoCompleteSearch.h"
|
||||
#include "nsIMutationObserver.h"
|
||||
|
@ -40,7 +41,6 @@ class HTMLInputElement;
|
|||
class nsFormFillController final : public nsIFormFillController,
|
||||
public nsIAutoCompleteInput,
|
||||
public nsIAutoCompleteSearch,
|
||||
public nsIDOMEventListener,
|
||||
public nsIFormAutoCompleteObserver,
|
||||
public nsIMutationObserver {
|
||||
public:
|
||||
|
@ -49,7 +49,6 @@ class nsFormFillController final : public nsIFormFillController,
|
|||
NS_DECL_NSIAUTOCOMPLETESEARCH
|
||||
NS_DECL_NSIAUTOCOMPLETEINPUT
|
||||
NS_DECL_NSIFORMAUTOCOMPLETEOBSERVER
|
||||
NS_DECL_NSIDOMEVENTLISTENER
|
||||
NS_DECL_NSIMUTATIONOBSERVER
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFormFillController,
|
||||
|
@ -96,8 +95,6 @@ class nsFormFillController final : public nsIFormFillController,
|
|||
|
||||
inline nsIDocShell* GetDocShellForInput(
|
||||
mozilla::dom::HTMLInputElement* aInput);
|
||||
inline nsPIDOMWindowOuter* GetWindowForDocShell(nsIDocShell* aDocShell);
|
||||
inline int32_t GetIndexOfDocShell(nsIDocShell* aDocShell);
|
||||
|
||||
void MaybeRemoveMutationObserver(nsINode* aNode);
|
||||
|
||||
|
@ -119,8 +116,7 @@ class nsFormFillController final : public nsIFormFillController,
|
|||
nsINode* mListNode;
|
||||
nsCOMPtr<nsIAutoCompletePopup> mFocusedPopup;
|
||||
|
||||
nsTArray<nsCOMPtr<nsIDocShell> > mDocShells;
|
||||
nsTArray<nsCOMPtr<nsIAutoCompletePopup> > mPopups;
|
||||
nsInterfaceHashtable<nsRefPtrHashKey<mozilla::dom::Document>, nsIAutoCompletePopup> mPopups;
|
||||
|
||||
// The observer passed to StartSearch. It will be notified when the search is
|
||||
// complete or the data from a datalist changes.
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIDocShell;
|
||||
interface nsIAutoCompletePopup;
|
||||
|
||||
webidl Document;
|
||||
webidl Element;
|
||||
webidl Event;
|
||||
webidl HTMLInputElement;
|
||||
|
||||
/*
|
||||
|
@ -34,22 +35,22 @@ interface nsIFormFillController : nsISupports
|
|||
readonly attribute boolean passwordPopupAutomaticallyOpened;
|
||||
|
||||
/*
|
||||
* Start controlling form fill behavior for the given browser
|
||||
* Start controlling form fill behavior for the given document
|
||||
*
|
||||
* @param docShell - The docShell to attach to
|
||||
* @param document - The document to attach to
|
||||
* @param popup - The popup to show when autocomplete results are available
|
||||
*/
|
||||
[can_run_script]
|
||||
void attachToBrowser(in nsIDocShell docShell, in nsIAutoCompletePopup popup);
|
||||
void attachToDocument(in Document document, in nsIAutoCompletePopup popup);
|
||||
[can_run_script]
|
||||
void attachPopupElementToBrowser(in nsIDocShell docShell, in Element popup);
|
||||
void attachPopupElementToDocument(in Document document, in Element popup);
|
||||
|
||||
/*
|
||||
* Stop controlling form fill behavior for the given browser
|
||||
*
|
||||
* @param docShell - The docShell to detach from
|
||||
* @param document - The document to detach from
|
||||
*/
|
||||
[can_run_script] void detachFromBrowser(in nsIDocShell docShell);
|
||||
[can_run_script] void detachFromDocument(in Document document);
|
||||
|
||||
/*
|
||||
* Mark the specified <input> element as being managed by password manager.
|
||||
|
@ -72,4 +73,6 @@ interface nsIFormFillController : nsISupports
|
|||
* Open the autocomplete popup, if possible.
|
||||
*/
|
||||
[can_run_script] void showPopup();
|
||||
|
||||
[can_run_script] void handleFormEvent(in Event aEvent);
|
||||
};
|
||||
|
|
|
@ -35,24 +35,6 @@ add_task(async function test() {
|
|||
return autoCompletePopup.popupOpen;
|
||||
});
|
||||
|
||||
let listener;
|
||||
let errorLogPromise = new Promise(resolve => {
|
||||
listener = ({ message }) => {
|
||||
const ERROR_MSG =
|
||||
"AutoCompletePopup: No messageManager for " +
|
||||
'message "FormAutoComplete:PopupClosed"';
|
||||
if (message.includes(ERROR_MSG)) {
|
||||
Assert.ok(
|
||||
true,
|
||||
"Got the error message for inexistent messageManager."
|
||||
);
|
||||
Services.console.unregisterListener(listener);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
});
|
||||
Services.console.registerListener(listener);
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
await TestUtils.waitForCondition(() => {
|
||||
|
@ -60,7 +42,5 @@ add_task(async function test() {
|
|||
});
|
||||
|
||||
Assert.ok(!autoCompletePopup.popupOpen, "Ensure the popup is closed.");
|
||||
|
||||
await errorLogPromise;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -115,7 +115,7 @@ async function runTests() {
|
|||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
window.onload = runTests();
|
||||
window.onpageshow = runTests;
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
|
|
@ -7,33 +7,18 @@
|
|||
/* eslint no-unused-vars: ["error", {args: "none"}] */
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
var { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
var { ActorManagerChild } = ChromeUtils.import(
|
||||
"resource://gre/modules/ActorManagerChild.jsm"
|
||||
);
|
||||
|
||||
ActorManagerChild.attach(this);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutoCompletePopup",
|
||||
"resource://gre/modules/AutoCompletePopupContent.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutoScrollController",
|
||||
"resource://gre/modules/AutoScrollController.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"formFill",
|
||||
"@mozilla.org/satchel/form-fill-controller;1",
|
||||
"nsIFormFillController"
|
||||
);
|
||||
|
||||
var global = this;
|
||||
|
||||
var AutoScrollListener = {
|
||||
|
@ -52,48 +37,3 @@ Services.els.addSystemEventListener(
|
|||
AutoScrollListener,
|
||||
true
|
||||
);
|
||||
|
||||
let AutoComplete = {
|
||||
_connected: false,
|
||||
|
||||
init() {
|
||||
addEventListener("unload", this, { once: true });
|
||||
addEventListener("DOMContentLoaded", this, { once: true });
|
||||
// WebExtension browserAction is preloaded and does not receive DCL, wait
|
||||
// on pageshow so we can hookup the formfill controller.
|
||||
addEventListener("pageshow", this, { capture: true, once: true });
|
||||
|
||||
XPCOMUtils.defineLazyProxy(
|
||||
this,
|
||||
"popup",
|
||||
() => new AutoCompletePopup(global),
|
||||
{ QueryInterface: null }
|
||||
);
|
||||
this.init = null;
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "DOMContentLoaded":
|
||||
case "pageshow":
|
||||
// We need to wait for a content viewer to be available
|
||||
// before we can attach our AutoCompletePopup handler,
|
||||
// since nsFormFillController assumes one will exist
|
||||
// when we call attachToBrowser.
|
||||
if (!this._connected) {
|
||||
formFill.attachToBrowser(docShell, this.popup);
|
||||
this._connected = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case "unload":
|
||||
if (this._connected) {
|
||||
formFill.detachFromBrowser(docShell);
|
||||
this._connected = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
AutoComplete.init();
|
||||
|
|
|
@ -68,12 +68,17 @@ nsDoTestsForAutoCompleteWithComposition.prototype = {
|
|||
}
|
||||
test.execute(this._window);
|
||||
|
||||
waitForCondition(() => {
|
||||
return (
|
||||
this._controller.searchStatus >=
|
||||
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH
|
||||
if (test.popup) {
|
||||
waitForCondition(
|
||||
() => this._controller.input.popupOpen,
|
||||
this._checkResult.bind(this)
|
||||
);
|
||||
}, this._checkResult.bind(this));
|
||||
} else {
|
||||
waitForCondition(() => {
|
||||
this._controller.searchStatus >=
|
||||
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
|
||||
}, this._checkResult.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
_checkResult() {
|
||||
|
|
|
@ -116,6 +116,50 @@ let ACTORS = {
|
|||
allFrames: true,
|
||||
},
|
||||
|
||||
AutoComplete: {
|
||||
parent: {
|
||||
moduleURI: "resource://gre/actors/AutoCompleteParent.jsm",
|
||||
messages: [
|
||||
"FormAutoComplete:SelectBy",
|
||||
"FormAutoComplete:SetSelectedIndex",
|
||||
"FormAutoComplete:MaybeOpenPopup",
|
||||
"FormAutoComplete:Invalidate",
|
||||
"FormAutoComplete:ClosePopup",
|
||||
"FormAutoComplete:Disconnect",
|
||||
// These two messages are also used, but are currently synchronous calls
|
||||
// through the per-process message manager.
|
||||
// "FormAutoComplete:GetSelectedIndex",
|
||||
// "FormAutoComplete:SelectBy"
|
||||
],
|
||||
},
|
||||
|
||||
child: {
|
||||
moduleURI: "resource://gre/actors/AutoCompleteChild.jsm",
|
||||
events: {
|
||||
DOMContentLoaded: {},
|
||||
pageshow: { capture: true },
|
||||
pagehide: { capture: true },
|
||||
unload: { capture: true },
|
||||
focus: { capture: true },
|
||||
blur: { capture: true },
|
||||
mousedown: { capture: true },
|
||||
input: { capture: true },
|
||||
keydown: { capture: true },
|
||||
keypress: { capture: true, mozSystemGroup: true },
|
||||
compositionstart: { capture: true },
|
||||
compositionend: { capture: true },
|
||||
contextmenu: { capture: true },
|
||||
},
|
||||
messages: [
|
||||
"FormAutoComplete:HandleEnter",
|
||||
"FormAutoComplete:PopupClosed",
|
||||
"FormAutoComplete:PopupOpened",
|
||||
],
|
||||
},
|
||||
|
||||
allFrames: true,
|
||||
},
|
||||
|
||||
Autoplay: {
|
||||
parent: {
|
||||
moduleURI: "resource://gre/actors/AutoplayParent.jsm",
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
/* -*- 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 = ["AutoCompletePopup"];
|
||||
|
||||
/* eslint no-unused-vars: ["error", {args: "none"}] */
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"BrowserUtils",
|
||||
"resource://gre/modules/BrowserUtils.jsm"
|
||||
);
|
||||
|
||||
const MESSAGES = [
|
||||
"FormAutoComplete:HandleEnter",
|
||||
"FormAutoComplete:PopupClosed",
|
||||
"FormAutoComplete:PopupOpened",
|
||||
"FormAutoComplete:RequestFocus",
|
||||
];
|
||||
|
||||
class AutoCompletePopup {
|
||||
constructor(mm) {
|
||||
this.mm = mm;
|
||||
|
||||
for (let messageName of MESSAGES) {
|
||||
mm.addMessageListener(messageName, this);
|
||||
}
|
||||
|
||||
this._input = null;
|
||||
this._popupOpen = false;
|
||||
}
|
||||
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAutoComplete:PopupOpened": {
|
||||
this._popupOpen = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "FormAutoComplete:RequestFocus": {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get input() {
|
||||
return this._input;
|
||||
}
|
||||
get overrideValue() {
|
||||
return null;
|
||||
}
|
||||
set selectedIndex(index) {
|
||||
this.mm.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).
|
||||
return this.mm.sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
|
||||
}
|
||||
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.mm.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.mm.sendAsyncMessage("FormAutoComplete:ClosePopup", {});
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
if (this._popupOpen) {
|
||||
let results = this.getResultsFromController(this._input);
|
||||
this.mm.sendAsyncMessage("FormAutoComplete:Invalidate", { results });
|
||||
}
|
||||
}
|
||||
|
||||
selectBy(reverse, page) {
|
||||
this._index = this.mm.sendSyncMessage("FormAutoComplete:SelectBy", {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -165,7 +165,6 @@ EXTRA_JS_MODULES += [
|
|||
'ActorManagerParent.jsm',
|
||||
'AppMenuNotifications.jsm',
|
||||
'AsyncPrefs.jsm',
|
||||
'AutoCompletePopupContent.jsm',
|
||||
'AutoScrollController.jsm',
|
||||
'BinarySearch.jsm',
|
||||
'BrowserUtils.jsm',
|
||||
|
|
Загрузка…
Ссылка в новой задаче