Bug 1392975 - [Form Autofill] Avoid registering multiple listeners of DOMContentLoaded. r=seanlee
MozReview-Commit-ID: Eo3KSBoaotr
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -475,21 +475,16 @@ var FormAutofillContent = {
identifyAutofillFields(element) {
this.log.debug("identifyAutofillFields:", "" + element.ownerDocument.location);
if (!this.savedFieldNames) {
this.log.debug("identifyAutofillFields: savedFieldNames are not known yet");
Services.cpmm.sendAsyncMessage("FormAutofill:InitStorage");
}
- if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
- this.log.debug("Not an eligible field.");
- return;
- }
-
let formHandler = this.getFormHandler(element);
if (!formHandler) {
let formLike = FormLikeFactory.createFromField(element);
formHandler = new FormAutofillHandler(formLike);
} else if (!formHandler.isFormChangedSinceLastCollection) {
this.log.debug("No control is removed or inserted since last collection.");
return;
}
@@ -499,27 +494,17 @@ var FormAutofillContent = {
this._formsDetails.set(formHandler.form.rootElement, formHandler);
this.log.debug("Adding form handler to _formsDetails:", formHandler);
validDetails.forEach(detail =>
this._markAsAutofillField(detail.elementWeakRef.get())
);
},
- _markAsAutofillField(field) {
- // Since Form Autofill popup is only for input element, any non-Input
- // element should be excluded here.
- if (!field || !(field instanceof Ci.nsIDOMHTMLInputElement)) {
- return;
- }
-
- formFillController.markAsAutofillField(field);
- },
-
- _previewProfile(doc) {
+ previewProfile(doc) {
let docWin = doc.ownerGlobal;
let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin);
let lastAutoCompleteResult = ProfileAutocomplete.getProfileAutoCompleteResult();
let focusedInput = formFillController.focusedInput;
let mm = this._messageManagerFromWindow(docWin);
if (selectedIndex === -1 ||
!focusedInput ||
@@ -540,16 +525,26 @@ var FormAutofillContent = {
focusedCategory,
categories,
});
ProfileAutocomplete._previewSelectedProfile(selectedIndex);
}
},
+ _markAsAutofillField(field) {
+ // Since Form Autofill popup is only for input element, any non-Input
+ // element should be excluded here.
+ if (!field || !(field instanceof Ci.nsIDOMHTMLInputElement)) {
+ return;
+ }
+
+ formFillController.markAsAutofillField(field);
+ },
+
_messageManagerFromWindow(win) {
return win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
},
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -21,16 +21,17 @@ const CREDITCARDS_COLLECTION_NAME = "cre
const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
this.FormAutofillUtils = {
get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
+ get isAutofillEnabled() { return this.isAutofillAddressesEnabled || this.isAutofillCreditCardsEnabled; },
ADDRESSES_COLLECTION_NAME,
CREDITCARDS_COLLECTION_NAME,
ENABLED_AUTOFILL_ADDRESSES_PREF,
ENABLED_AUTOFILL_CREDITCARDS_PREF,
_fieldNameInfo: {
"name": "name",
@@ -136,22 +137,23 @@ this.FormAutofillUtils = {
},
ALLOWED_TYPES: ["text", "email", "tel", "number"],
isFieldEligibleForAutofill(element) {
if (element.autocomplete == "off") {
return false;
}
- if (element instanceof Ci.nsIDOMHTMLInputElement) {
+ let tagName = element.tagName;
+ if (tagName == "INPUT") {
// `element.type` can be recognized as `text`, if it's missing or invalid.
if (!this.ALLOWED_TYPES.includes(element.type)) {
return false;
}
- } else if (!(element instanceof Ci.nsIDOMHTMLSelectElement)) {
+ } else if (tagName != "SELECT") {
return false;
}
return true;
},
loadDataFromScript(url, sandbox = {}) {
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -7,82 +7,91 @@
*/
"use strict";
/* eslint-env mozilla/frame-script */
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://formautofill/FormAutofillContent.jsm");
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
/**
* Handles content's interactions for the frame.
*
* NOTE: Declares it by "var" to make it accessible in unit tests.
*/
var FormAutofillFrameScript = {
+ _nextHandleElement: null,
+ _alreadyDOMContentLoaded: false,
+ _hasDOMContentLoadedHandler: false,
+ _hasPendingTask: false,
+
+ _doIdentifyAutofillFields() {
+ if (this._hasPendingTask) {
+ return;
+ }
+ this._hasPendingTask = true;
+
+ setTimeout(() => {
+ FormAutofillContent.identifyAutofillFields(this._nextHandleElement);
+ this._hasPendingTask = false;
+ this._nextHandleElement = null;
+ });
+ },
+
init() {
addEventListener("focusin", this);
addMessageListener("FormAutofill:PreviewProfile", this);
addMessageListener("FormAutoComplete:PopupClosed", this);
addMessageListener("FormAutoComplete:PopupOpened", this);
},
handleEvent(evt) {
- if (!evt.isTrusted) {
- return;
- }
-
- if (!FormAutofillUtils.isAutofillAddressesEnabled &&
- !FormAutofillUtils.isAutofillCreditCardsEnable) {
+ if (!evt.isTrusted || !FormAutofillUtils.isAutofillEnabled) {
return;
}
- switch (evt.type) {
- case "focusin": {
- let element = evt.target;
- let doc = element.ownerDocument;
-
- if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
- return;
- }
+ let element = evt.target;
+ if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
+ return;
+ }
+ this._nextHandleElement = element;
- let doIdentifyAutofillFields =
- () => setTimeout(() => FormAutofillContent.identifyAutofillFields(element));
+ if (!this._alreadyDOMContentLoaded) {
+ let doc = element.ownerDocument;
+ if (doc.readyState === "loading") {
+ if (!this._hasDOMContentLoadedHandler) {
+ this._hasDOMContentLoadedHandler = true;
+ doc.addEventListener("DOMContentLoaded", () => this._doIdentifyAutofillFields(), {once: true});
+ }
+ return;
+ }
+ this._alreadyDOMContentLoaded = true;
+ }
- if (doc.readyState === "loading") {
- doc.addEventListener("DOMContentLoaded", doIdentifyAutofillFields, {once: true});
- } else {
- doIdentifyAutofillFields();
- }
- break;
- }
- }
+ this._doIdentifyAutofillFields();
},
receiveMessage(message) {
- if (!FormAutofillUtils.isAutofillAddressesEnabled &&
- !FormAutofillUtils.isAutofillCreditCardsEnable) {
+ if (!FormAutofillUtils.isAutofillEnabled) {
return;
}
const doc = content.document;
const {chromeEventHandler} = doc.ownerGlobal.getInterface(Ci.nsIDocShell);
switch (message.name) {
case "FormAutofill:PreviewProfile": {
- FormAutofillContent._previewProfile(doc);
+ FormAutofillContent.previewProfile(doc);
break;
}
case "FormAutoComplete:PopupClosed": {
- FormAutofillContent._previewProfile(doc);
+ FormAutofillContent.previewProfile(doc);
chromeEventHandler.removeEventListener("keydown", FormAutofillContent._onKeyDown,
{capturing: true});
break;
}
case "FormAutoComplete:PopupOpened": {
chromeEventHandler.addEventListener("keydown", FormAutofillContent._onKeyDown,
{capturing: true});
}