Bug 1392975 - [Form Autofill] Avoid registering multiple listeners of DOMContentLoaded. r=seanlee a=gchang FIREFOX_RELEASE_56_BASE
authorLuke Chang <lchang@mozilla.com>
Thu, 14 Sep 2017 10:13:05 +0200
changeset 424103 12809a8ce21a7b10c7a4e54baa9ae742c176de87
parent 424102 1c08a2f4d2fc14e2a88915a2bd8f033f04c058ab
child 424104 d519209bdd0a4dd4c4a74f0a9097851d949eb25f
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseanlee, gchang
bugs1392975
milestone56.0
Bug 1392975 - [Form Autofill] Avoid registering multiple listeners of DOMContentLoaded. r=seanlee a=gchang MozReview-Commit-ID: Eo3KSBoaotr --- .../formautofill/FormAutofillContent.jsm | 27 ++++------ .../extensions/formautofill/FormAutofillUtils.jsm | 11 +++- .../content/FormAutofillFrameScript.js | 61 +++++++++++++--------- 3 files changed, 56 insertions(+), 43 deletions(-)
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/content/FormAutofillFrameScript.js
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -478,21 +478,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;
     }
@@ -502,27 +497,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 ||
@@ -543,16 +528,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
@@ -11,21 +11,24 @@ const {classes: Cc, interfaces: Ci, util
 const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
 
 // TODO: We only support US in MVP. We are going to support more countries in
 //       bug 1370193.
 const ALTERNATIVE_COUNTRY_NAMES = {
   "US": ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"],
 };
 
+const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.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; },
 
   _fieldNameInfo: {
     "name": "name",
     "given-name": "name",
     "additional-name": "name",
     "family-name": "name",
     "organization": "organization",
     "street-address": "address",
@@ -137,22 +140,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"]
@@ -421,8 +425,11 @@ XPCOMUtils.defineLazyGetter(this.FormAut
 });
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 XPCOMUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function() {
   return Services.strings.createBundle("chrome://formautofill/locale/formautofill.properties");
 });
+
+XPCOMUtils.defineLazyPreferenceGetter(this.FormAutofillUtils,
+                                      "isAutofillAddressesEnabled", ENABLED_AUTOFILL_ADDRESSES_PREF);
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -7,80 +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 (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
+    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 (!Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled")) {
+    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});
       }