Depends on D149016 Differential Revision: https://phabricator.services.mozilla.com/D149017
278 lines
8.3 KiB
JavaScript
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 }
|
|
);
|
|
},
|
|
};
|