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:
Neil Deakin 2019-10-09 19:02:57 +00:00
Родитель 5b1e475e24
Коммит ec8b2c4f13
24 изменённых файлов: 644 добавлений и 656 удалений

Просмотреть файл

@ -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',