Files
tubestation/toolkit/components/formautofill/FormAutofillTelemetryUtils.jsm

278 lines
8.3 KiB
JavaScript

/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["CreditCardTelemetry"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { FormAutofillUtils } = ChromeUtils.import(
"resource://autofill/FormAutofillUtils.jsm"
);
const { FIELD_STATES } = FormAutofillUtils;
const CreditCardTelemetry = {
// Mapping of field name used in formautofill code to the field name
// used in the telemetry.
CC_FORM_V2_SUPPORTED_FIELDS: {
"cc-name": "cc_name",
"cc-number": "cc_number",
"cc-type": "cc_type",
"cc-exp": "cc_exp",
"cc-exp-month": "cc_exp_month",
"cc-exp-year": "cc_exp_year",
},
/**
* Utility function to get an `extra` object of `cc_form_v2` event telemetry
* with a default value that applies to all keys.
*
* @param {string} value The default value
* @returns {Object} The extra object
*/
_ccFormV2InitExtra(value) {
let extra = {};
for (const field of Object.values(this.CC_FORM_V2_SUPPORTED_FIELDS)) {
extra[field] = value;
}
return extra;
},
/**
* Utility function to set the value of the specified fieldName of `cc_form_v2`
* extra object.
*
* @param {Object} extra The `extra` object to be set
* @param {string} key Field name, all supported field names are listed in
* `CC_FORM_V2_SUPPORTED_FIELDS`
* @param {string} value
*/
_ccFormV2SetExtra(extra, key, value) {
extra[this.CC_FORM_V2_SUPPORTED_FIELDS[key]] = value;
},
/**
* Utility function to record both `cc_form` and `cc_form_v2` events
*
* @param {string} method The method name.
* @param {string} flowId Flow id.
* @param {Object} ccFormExtra The extra object passed to `cc_form` telemetry
* @param {Object} ccFormV2Extra The extra object passed to `cc_form_v2` telemetry
*/
_recordCCFormEvent(method, flowId, ccFormExtra, ccFormV2Extra) {
Services.telemetry.recordEvent(
"creditcard",
method,
"cc_form",
flowId,
ccFormExtra
);
Services.telemetry.recordEvent(
"creditcard",
method,
"cc_form_v2",
flowId,
ccFormV2Extra
);
},
/**
* Called when a form is recognized as a credit card form.
*
* @param {string} flowId Flow id.
* @param {Array<Object>} fieldDetails List of current field details
*/
recordFormDetected(flowId, fieldDetails) {
// Record which fields could be identified
let ccFormV2Extra = this._ccFormV2InitExtra("false");
let identified = new Set();
fieldDetails.forEach(detail => {
identified.add(detail.fieldName);
this._ccFormV2SetExtra(ccFormV2Extra, detail.fieldName, "true");
});
let ccFormExtra = {
cc_name_found: identified.has("cc-name") ? "true" : "false",
cc_number_found: identified.has("cc-number") ? "true" : "false",
cc_exp_found:
identified.has("cc-exp") ||
(identified.has("cc-exp-month") && identified.has("cc-exp-year"))
? "true"
: "false",
};
this._recordCCFormEvent("detected", flowId, ccFormExtra, ccFormV2Extra);
Services.telemetry.scalarAdd(
"formautofill.creditCards.detected_sections_count",
1
);
},
/**
* Called when the credit card autofill popup is shown.
*
* @param {string} flowId Flow id.
* @param {string} fieldName Field that triggers the event.
*/
recordPopupShown(flowId, fieldName) {
if (!flowId) {
return;
}
let ccFormExtra = null;
let ccFormV2Extra = { field_name: fieldName };
this._recordCCFormEvent("popup_shown", flowId, ccFormExtra, ccFormV2Extra);
},
/**
* Called when a credit card form is autofilled.
*
* @param {string} flowId Flow id.
* @param {Array<Object>} fieldDetails List of current field details
* @param {Object} profile The profile to be autofilled
*/
recordFormFilled(flowId, fieldDetails, profile) {
// Calculate values for telemetry
let ccFormExtra = {
cc_name: "unavailable",
cc_number: "unavailable",
cc_exp: "unavailable",
};
let ccFormV2Extra = this._ccFormV2InitExtra("unavailable");
for (let fieldDetail of fieldDetails) {
let element = fieldDetail.elementWeakRef.get();
let state = profile[fieldDetail.fieldName] ? "filled" : "not_filled";
if (
fieldDetail.state == FIELD_STATES.NORMAL &&
(HTMLSelectElement.isInstance(element) ||
(HTMLInputElement.isInstance(element) && element.value.length))
) {
state = "user_filled";
}
this._ccFormV2SetExtra(ccFormV2Extra, fieldDetail.fieldName, state);
switch (fieldDetail.fieldName) {
case "cc-name":
ccFormExtra.cc_name = state;
break;
case "cc-number":
ccFormExtra.cc_number = state;
break;
case "cc-exp":
case "cc-exp-month":
case "cc-exp-year":
ccFormExtra.cc_exp = state;
break;
}
}
this._recordCCFormEvent("filled", flowId, ccFormExtra, ccFormV2Extra);
},
/**
* Called when a credit card field is filled and then modifed by
* the user.
*
* @param {string} flowId Flow id.
* @param {string} fieldName Field that triggers the clear form event.
*/
recordFilledModified(flowId, fieldName) {
let ccFormExtra = { field_name: fieldName };
let ccFormV2Extra = { field_name: fieldName };
this._recordCCFormEvent(
"filled_modified",
flowId,
ccFormExtra,
ccFormV2Extra
);
},
/**
* Called when a credit card form is submitted
*
* @param {Objecy} records Credit card and address records filled in the form.
* @param {Array<HTMLElements>} elements Elements in the form
*/
recordFormSubmitted(records, elements) {
records.creditCard.forEach(record => {
let ccFormExtra = {
// Fields which have been filled manually.
fields_not_auto: "0",
// Fields which have been autofilled.
fields_auto: "0",
// Fields which have been autofilled and then modified.
fields_modified: "0",
};
let ccFormV2Extra = this._ccFormV2InitExtra("unavailable");
if (record.guid !== null) {
// If the `guid` is not null, it means we're editing an existing record.
// In that case, all fields in the record are autofilled, and fields in
// `untouchedFields` are unmodified.
let totalCount = elements.length;
let autofilledCount = Object.keys(record.record).length;
let unmodifiedCount = record.untouchedFields.length;
for (let fieldName of Object.keys(record.record)) {
if (record.untouchedFields?.includes(fieldName)) {
this._ccFormV2SetExtra(ccFormV2Extra, fieldName, "autofilled");
} else {
this._ccFormV2SetExtra(ccFormV2Extra, fieldName, "user filled");
}
}
ccFormExtra.fields_not_auto = (totalCount - autofilledCount).toString();
ccFormExtra.fields_auto = autofilledCount.toString();
ccFormExtra.fields_modified = (
autofilledCount - unmodifiedCount
).toString();
} else {
// If the `guid` is null, we're filling a new form.
// In that case, all not-null fields are manually filled.
ccFormExtra.fields_not_auto = Array.from(elements)
.filter(element => !!element.value?.trim().length)
.length.toString();
}
this._recordCCFormEvent(
"submitted",
record.flowId,
ccFormExtra,
ccFormV2Extra
);
});
if (records.creditCard.length) {
Services.telemetry.scalarAdd(
"formautofill.creditCards.submitted_sections_count",
records.creditCard.length
);
}
},
/**
* Called when a credit card form is cleared.
*
* @param {string} flowId Flow id.
* @param {string} fieldName Field that triggers the clear form event.
*/
recordFormCleared(flowId, fieldName) {
// Note that when a form is cleared, we also record `filled_modified` events
// for all the fields that have been cleared.
Services.telemetry.recordEvent(
"creditcard",
"cleared",
"cc_form_v2",
flowId,
{ field_name: fieldName }
);
},
};