Bug 1364477 - Delay ProfileStorage initialization until focusin. r=lchang,seanlee,steveck
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Tue, 23 May 2017 00:39:00 -0400
changeset 360147 8766c76d6c6ae1f9eb93d0d3e0747091b35ad4eb
parent 360146 c4d9c5751a2f92bbf1eee0153a713cb15f245db5
child 360148 17797be764258e7c8f99f4f1d41c164375cc7656
push id31871
push userryanvm@gmail.com
push dateTue, 23 May 2017 22:02:07 +0000
treeherdermozilla-central@545ffce30eac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslchang, seanlee, steveck
bugs1364477
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1364477 - Delay ProfileStorage initialization until focusin. r=lchang,seanlee,steveck MozReview-Commit-ID: CNlVNOI1mWw
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/test/unit/test_activeStatus.js
browser/extensions/formautofill/test/unit/test_savedFieldNames.js
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -300,22 +300,28 @@ var FormAutofillContent = {
 
   init() {
     FormAutofillUtils.defineLazyLogGetter(this, "FormAutofillContent");
 
     Services.cpmm.addMessageListener("FormAutofill:enabledStatus", this);
     Services.cpmm.addMessageListener("FormAutofill:savedFieldNames", this);
     Services.obs.addObserver(this, "earlyformsubmit");
 
-    if (Services.cpmm.initialProcessData.autofillEnabled) {
+    let autofillEnabled = Services.cpmm.initialProcessData.autofillEnabled;
+    if (autofillEnabled ||
+        // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
+        // autocomplete is registered before the focusin so register it in this case as long as the
+        // pref is true.
+        (autofillEnabled === undefined &&
+         Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled"))) {
       ProfileAutocomplete.ensureRegistered();
     }
 
     this.savedFieldNames =
-      Services.cpmm.initialProcessData.autofillSavedFieldNames || new Set();
+      Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   _onFormSubmit(handler) {
     // TODO: Handle form submit event for profile saving(bug 990219) and metrics(bug 1341569).
   },
 
   notify(formElement) {
     this.log.debug("notified for form early submission");
@@ -395,16 +401,22 @@ var FormAutofillContent = {
 
   getAllFieldNames(element) {
     let formDetails = this.getFormDetails(element);
     return formDetails.map(record => record.fieldName);
   },
 
   identifyAutofillFields(doc) {
     this.log.debug("identifyAutofillFields:", "" + doc.location);
+
+    if (!this.savedFieldNames) {
+      this.log.debug("identifyAutofillFields: savedFieldNames are not known yet");
+      Services.cpmm.sendAsyncMessage("FormAutofill:InitStorage");
+    }
+
     let forms = [];
 
     // Collects root forms from inputs.
     for (let field of doc.getElementsByTagName("input")) {
       // We only consider text-like fields for now until we support radio and
       // checkbox buttons in the future.
       if (!field.mozIsTextField(true)) {
         continue;
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -34,55 +34,63 @@ this.EXPORTED_SYMBOLS = ["FormAutofillPa
 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://gre/modules/Services.jsm");
 
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "profileStorage",
-                                  "resource://formautofill/ProfileStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillPreferences",
                                   "resource://formautofill/FormAutofillPreferences.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 const ENABLED_PREF = "extensions.formautofill.addresses.enabled";
 
 function FormAutofillParent() {
+  // Lazily load the storage JSM to avoid disk I/O until absolutely needed.
+  // Once storage is loaded we need to update saved field names and inform content processes.
+  XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
+    let {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
+    log.debug("Loading profileStorage");
+
+    profileStorage.initialize().then(function onStorageInitialized() {
+      // Update the saved field names to compute the status and update child processes.
+      this._updateSavedFieldNames();
+    }.bind(this));
+
+    return profileStorage;
+  });
 }
 
 FormAutofillParent.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
   /**
    * Cache of the Form Autofill status (considering preferences and storage).
    */
   _active: null,
 
   /**
    * Initializes ProfileStorage and registers the message handler.
    */
   async init() {
     log.debug("init");
-    await profileStorage.initialize();
 
     Services.obs.addObserver(this, "advanced-pane-loaded");
+    Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.addMessageListener("FormAutofill:GetAddresses", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
 
     // Observing the pref and storage changes
     Services.prefs.addObserver(ENABLED_PREF, this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
-
-    // Update the saved field names to compute the status and update child processes.
-    this._updateSavedFieldNames();
   },
 
   observe(subject, topic, data) {
     log.debug("observe:", topic, "with data:", data);
     switch (topic) {
       case "advanced-pane-loaded": {
         let useOldOrganization = Services.prefs.getBoolPref("browser.preferences.useOldOrganization",
                                                             false);
@@ -161,43 +169,48 @@ FormAutofillParent.prototype = {
    * Handles the message coming from FormAutofillContent.
    *
    * @param   {string} message.name The name of the message.
    * @param   {object} message.data The data of the message.
    * @param   {nsIFrameMessageManager} message.target Caller's message manager.
    */
   receiveMessage({name, data, target}) {
     switch (name) {
+      case "FormAutofill:InitStorage": {
+        this.profileStorage.initialize();
+        break;
+      }
       case "FormAutofill:GetAddresses": {
         this._getAddresses(data, target);
         break;
       }
       case "FormAutofill:SaveAddress": {
         if (data.guid) {
-          profileStorage.addresses.update(data.guid, data.address);
+          this.profileStorage.addresses.update(data.guid, data.address);
         } else {
-          profileStorage.addresses.add(data.address);
+          this.profileStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:RemoveAddresses": {
-        data.guids.forEach(guid => profileStorage.addresses.remove(guid));
+        data.guids.forEach(guid => this.profileStorage.addresses.remove(guid));
         break;
       }
     }
   },
 
   /**
    * Uninitializes FormAutofillParent. This is for testing only.
    *
    * @private
    */
   _uninit() {
-    profileStorage._saveImmediately();
+    this.profileStorage._saveImmediately();
 
+    Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.removeMessageListener("FormAutofill:GetAddresses", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
     Services.prefs.removeObserver(ENABLED_PREF, this);
   },
 
   /**
@@ -211,43 +224,43 @@ FormAutofillParent.prototype = {
    *         The input autocomplete property's information.
    * @param  {nsIFrameMessageManager} target
    *         Content's message manager.
    */
   _getAddresses({searchString, info}, target) {
     let addresses = [];
 
     if (info && info.fieldName) {
-      addresses = profileStorage.addresses.getByFilter({searchString, info});
+      addresses = this.profileStorage.addresses.getByFilter({searchString, info});
     } else {
-      addresses = profileStorage.addresses.getAll();
+      addresses = this.profileStorage.addresses.getAll();
     }
 
     target.sendAsyncMessage("FormAutofill:Addresses", addresses);
   },
 
   _updateSavedFieldNames() {
     log.debug("_updateSavedFieldNames");
     if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
       Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
     } else {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
     }
 
-    profileStorage.addresses.getAll().forEach((address) => {
+    this.profileStorage.addresses.getAll().forEach((address) => {
       Object.keys(address).forEach((fieldName) => {
         if (!address[fieldName]) {
           return;
         }
         Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
       });
     });
 
     // Remove the internal guid and metadata fields.
-    profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
+    this.profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
     });
 
     Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
                                         Services.ppmm.initialProcessData.autofillSavedFieldNames);
     this._updateStatus();
   },
 };
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -11,16 +11,24 @@ add_task(async function test_activeStatu
   let formAutofillParent = new FormAutofillParent();
   sinon.spy(formAutofillParent, "_updateStatus");
 
   // Default status is null before initialization
   do_check_eq(formAutofillParent._active, null);
   do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, undefined);
 
   await formAutofillParent.init();
+  // init shouldn't call updateStatus since that requires storage which will
+  // lead to startup time regressions.
+  do_check_eq(formAutofillParent._updateStatus.called, false);
+  do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, undefined);
+
+  // Initialize profile storage
+  await formAutofillParent.profileStorage.initialize();
+  // Upon first initializing profile storage, status should be computed.
   do_check_eq(formAutofillParent._updateStatus.called, true);
   do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, false);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_activeStatus_observe() {
   let formAutofillParent = new FormAutofillParent();
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -7,16 +7,17 @@
 Cu.import("resource://formautofill/FormAutofillParent.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
 add_task(async function test_profileSavedFieldNames_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
 
   await formAutofillParent.init();
+  await formAutofillParent.profileStorage.initialize();
   do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_profileSavedFieldNames_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");