Bug 1341569 - Add the form created time in handler and telemetry probe for form filling duration. r=benjamin+7044,MattN
authorsteveck-chung <schung@mozilla.com>
Mon, 11 Sep 2017 10:25:09 +0800
changeset 429477 38e3ac4eee425ead861938e5406466ba585cc48b
parent 429476 5b3b0c446caa3cb7f23c072487d6353561c25d2c
child 429478 c9fe4ec27ca3de145712f848a7ef8deeb2f8870f
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbenjamin
bugs1341569
milestone57.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 1341569 - Add the form created time in handler and telemetry probe for form filling duration. r=benjamin+7044,MattN MozReview-Commit-ID: 6mU606zEtT4
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/FormAutofillParent.jsm
toolkit/components/telemetry/Histograms.json
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -367,20 +367,22 @@ var FormAutofillContent = {
       Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   /**
    * Send the profile to parent for doorhanger and storage saving/updating.
    *
    * @param {Object} profile Submitted form's address/creditcard guid and record.
    * @param {Object} domWin Current content window.
+   * @param {int} timeStartedFillingMS Time of form filling started.
    */
-  _onFormSubmit(profile, domWin) {
+  _onFormSubmit(profile, domWin, timeStartedFillingMS) {
     let mm = this._messageManagerFromWindow(domWin);
-    mm.sendAsyncMessage("FormAutofill:OnFormSubmit", profile);
+    mm.sendAsyncMessage("FormAutofill:OnFormSubmit",
+                        {profile, timeStartedFillingMS});
   },
 
   /**
    * Handle earlyformsubmit event and early return when:
    * 1. In private browsing mode.
    * 2. Could not map any autofill handler by form element.
    * 3. Number of filled fields is less than autofill threshold
    *
@@ -402,17 +404,17 @@ var FormAutofillContent = {
       return true;
     }
 
     let records = handler.createRecords();
     if (!Object.keys(records).length) {
       return true;
     }
 
-    this._onFormSubmit(records, domWin);
+    this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
     return true;
   },
 
   receiveMessage({name, data}) {
     switch (name) {
       case "FormAutofill:enabledStatus": {
         if (data) {
           ProfileAutocomplete.ensureRegistered();
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -116,16 +116,21 @@ FormAutofillHandler.prototype = {
     // When the number of form controls is the same with last collection, it
     // can be recognized as there is no element changed. However, we should
     // improve the function to detect the element changes. e.g. a tel field
     // is changed from type="hidden" to type="tel".
     return this._formFieldCount != this.form.elements.length;
   },
 
   /**
+   * Time in milliseconds since epoch when a user started filling in the form.
+   */
+  timeStartedFillingMS: null,
+
+  /**
    * Set fieldDetails from the form about fields that can be autofilled.
    *
    * @param {boolean} allowDuplicates
    *        true to remain any duplicated field details otherwise to remove the
    *        duplicated ones.
    * @returns {Array} The valid address and credit card details.
    */
   collectFormFields(allowDuplicates = false) {
@@ -148,19 +153,27 @@ FormAutofillHandler.prototype = {
                 "field(s)");
       this.address.fieldDetails = [];
     }
 
     if (!this.creditCard.fieldDetails.some(i => i.fieldName == "cc-number")) {
       log.debug("Ignoring credit card related fields since it's without credit card number field");
       this.creditCard.fieldDetails = [];
     }
+    let validDetails = Array.of(...(this.address.fieldDetails),
+                                ...(this.creditCard.fieldDetails));
+    for (let detail of validDetails) {
+      let input = detail.elementWeakRef.get();
+      if (!input) {
+        continue;
+      }
+      input.addEventListener("input", this);
+    }
 
-    return Array.of(...(this.address.fieldDetails),
-                    ...(this.creditCard.fieldDetails));
+    return validDetails;
   },
 
   getFieldDetailByName(fieldName) {
     return this.fieldDetails.find(detail => detail.fieldName == fieldName);
   },
 
   getFieldDetailsByElement(element) {
     let fieldDetail = this.fieldDetails.find(
@@ -627,9 +640,28 @@ FormAutofillHandler.prototype = {
       Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) {
         Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult);
         resolve(result.data);
       });
 
       Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
     });
   },
+
+  handleEvent(event) {
+    switch (event.type) {
+      case "input":
+        if (!event.isTrusted) {
+          return;
+        }
+
+        for (let detail of this.fieldDetails) {
+          let input = detail.elementWeakRef.get();
+          if (!input) {
+            continue;
+          }
+          input.removeEventListener("input", this);
+        }
+        this.timeStartedFillingMS = Date.now();
+        break;
+    }
+  },
 };
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -344,27 +344,29 @@ FormAutofillParent.prototype = {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
     });
 
     Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
                                         Services.ppmm.initialProcessData.autofillSavedFieldNames);
     this._updateStatus();
   },
 
-  _onAddressSubmit(address, target) {
+  _onAddressSubmit(address, target, timeStartedFillingMS) {
     if (address.guid) {
       // Avoid updating the fields that users don't modify.
       let originalAddress = this.profileStorage.addresses.get(address.guid);
       for (let field in address.record) {
         if (address.untouchedFields.includes(field) && originalAddress[field]) {
           address.record[field] = originalAddress[field];
         }
       }
 
       if (!this.profileStorage.addresses.mergeIfPossible(address.guid, address.record)) {
+        this._recordFormFillingTime("address", "autofill-update", timeStartedFillingMS);
+
         FormAutofillDoorhanger.show(target, "update").then((state) => {
           let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
           switch (state) {
             case "create":
               if (!changedGUIDs.length) {
                 changedGUIDs.push(this.profileStorage.addresses.add(address.record));
               }
               break;
@@ -378,25 +380,27 @@ FormAutofillParent.prototype = {
               break;
           }
           changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
         });
         // Address should be updated
         Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill_update", 1);
         return;
       }
+      this._recordFormFillingTime("address", "autofill", timeStartedFillingMS);
       this.profileStorage.addresses.notifyUsed(address.guid);
       // Address is merged successfully
       Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill", 1);
     } else {
       let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
       if (!changedGUIDs.length) {
         changedGUIDs.push(this.profileStorage.addresses.add(address.record));
       }
       changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
+      this._recordFormFillingTime("address", "manual", timeStartedFillingMS);
 
       // Show first time use doorhanger
       if (Services.prefs.getBoolPref("extensions.formautofill.firstTimeUse")) {
         Services.prefs.setBoolPref("extensions.formautofill.firstTimeUse", false);
         FormAutofillDoorhanger.show(target, "firstTimeUse").then((state) => {
           if (state !== "open-pref") {
             return;
           }
@@ -437,18 +441,33 @@ FormAutofillParent.prototype = {
       return;
     }
 
     await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record);
     this.profileStorage.creditCards.add(creditCard.record);
   },
 
   _onFormSubmit(data, target) {
-    let {address, creditCard} = data;
+    let {profile: {address, creditCard}, timeStartedFillingMS} = data;
 
     if (address) {
-      this._onAddressSubmit(address, target);
+      this._onAddressSubmit(address, target, timeStartedFillingMS);
     }
     if (creditCard) {
       this._onCreditCardSubmit(creditCard, target);
     }
   },
+  /**
+   * Set the probes for the filling time with specific filling type and form type.
+   *
+   * @private
+   * @param  {string} formType
+   *         3 type of form (address/creditcard/address-creditcard).
+   * @param  {string} fillingType
+   *         3 filling type (manual/autofill/autofill-update).
+   * @param  {int} startedFillingMS
+   *         Time that form started to filling in ms.
+   */
+  _recordFormFillingTime(formType, fillingType, startedFillingMS) {
+    let histogram = Services.telemetry.getKeyedHistogramById("FORM_FILLING_REQUIRED_TIME_MS");
+    histogram.add(`${formType}-${fillingType}`, Date.now() - startedFillingMS);
+  },
 };
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -13630,10 +13630,21 @@
     "alert_emails": ["ajones@mozilla.com", "bwu@mozilla.com", "tkuo@mozilla.com"],
     "expires_in_version": "60",
     "releaseChannelCollection": "opt-out",
     "kind": "exponential",
     "high": 64000,
     "n_buckets": 100,
     "bug_numbers": [1393399],
     "description": "Time in milliseconds to recover a video decoder from a HW video decoder crash, calculated since MFR gets a NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER error. The data is sent when video decoding is recovered from a GPU crash and the first frame is decoded."
+  },
+  "FORM_FILLING_REQUIRED_TIME_MS": {
+    "record_in_processes": ["main"],
+    "alert_emails": ["autofill@lists.mozilla.org", "jcheng@mozilla.com", "chsiang@mozilla.com"],
+    "expires_in_version": "60",
+    "kind": "exponential",
+    "high": 300000,
+    "n_buckets": 22,
+    "keyed": true,
+    "bug_numbers": [1341569],
+    "description": "Milliseconds between starting to fill an autofill-eligible form field and submitting the form, keyed by the combination of form type and filling type."
   }
 }