mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
Bug 1801039 - P1. Share common code for Address and Credit Card r=sgalich
Differential Revision: https://phabricator.services.mozilla.com/D164351
This commit is contained in:
parent
c4e930dbcd
commit
73e1a5d878
@ -12,6 +12,11 @@ ChromeUtils.defineModuleGetter(
|
||||
"formAutofillStorage",
|
||||
"resource://autofill/FormAutofillStorage.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutofillTelemetry",
|
||||
"resource://autofill/AutofillTelemetry.jsm"
|
||||
);
|
||||
|
||||
class AutofillEditDialog {
|
||||
constructor(subStorageName, elements, record) {
|
||||
@ -148,11 +153,21 @@ class AutofillEditDialog {
|
||||
|
||||
// An interface to be inherited.
|
||||
localizeDocument() {}
|
||||
|
||||
recordFormSubmit() {
|
||||
let method = this._record?.guid ? "edit" : "add";
|
||||
AutofillTelemetry.recordManageEvent(this.telemetryType, method);
|
||||
}
|
||||
}
|
||||
|
||||
class EditAddressDialog extends AutofillEditDialog {
|
||||
telemetryType = AutofillTelemetry.ADDRESS;
|
||||
|
||||
constructor(elements, record) {
|
||||
super("addresses", elements, record);
|
||||
if (record) {
|
||||
AutofillTelemetry.recordManageEvent(this.telemetryType, "show_entry");
|
||||
}
|
||||
}
|
||||
|
||||
localizeDocument() {
|
||||
@ -169,11 +184,15 @@ class EditAddressDialog extends AutofillEditDialog {
|
||||
this._elements.fieldContainer.buildFormObject(),
|
||||
this._record ? this._record.guid : null
|
||||
);
|
||||
this.recordFormSubmit();
|
||||
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
class EditCreditCardDialog extends AutofillEditDialog {
|
||||
telemetryType = AutofillTelemetry.CREDIT_CARD;
|
||||
|
||||
constructor(elements, record) {
|
||||
elements.fieldContainer._elements.billingAddress.disabled = true;
|
||||
super("creditCards", elements, record);
|
||||
@ -182,7 +201,7 @@ class EditCreditCardDialog extends AutofillEditDialog {
|
||||
this._onCCNumberFieldBlur.bind(this)
|
||||
);
|
||||
if (record) {
|
||||
Services.telemetry.recordEvent("creditcard", "show_entry", "manage");
|
||||
AutofillTelemetry.recordManageEvent(this.telemetryType, "show_entry");
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,11 +231,7 @@ class EditCreditCardDialog extends AutofillEditDialog {
|
||||
this._record ? this._record.guid : null
|
||||
);
|
||||
|
||||
if (this._record?.guid) {
|
||||
Services.telemetry.recordEvent("creditcard", "edit", "manage");
|
||||
} else {
|
||||
Services.telemetry.recordEvent("creditcard", "add", "manage");
|
||||
}
|
||||
this.recordFormSubmit();
|
||||
|
||||
window.close();
|
||||
} catch (ex) {
|
||||
|
@ -19,6 +19,9 @@ const { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
const { FormAutofill } = ChromeUtils.import(
|
||||
"resource://autofill/FormAutofill.jsm"
|
||||
);
|
||||
const { AutofillTelemetry } = ChromeUtils.import(
|
||||
"resource://autofill/AutofillTelemetry.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
|
||||
@ -192,6 +195,10 @@ class ManageRecords {
|
||||
Services.obs.addObserver(this, "formautofill-storage-changed");
|
||||
// For testing only: notify record(s) has been removed
|
||||
this._elements.records.dispatchEvent(new CustomEvent("RecordsRemoved"));
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
AutofillTelemetry.recordManageEvent(this.telemetryType, "delete");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,12 +322,15 @@ class ManageRecords {
|
||||
}
|
||||
|
||||
class ManageAddresses extends ManageRecords {
|
||||
telemetryType = AutofillTelemetry.ADDRESS;
|
||||
|
||||
constructor(elements) {
|
||||
super("addresses", elements);
|
||||
elements.add.setAttribute(
|
||||
"search-l10n-ids",
|
||||
FormAutofillUtils.EDIT_ADDRESS_L10N_IDS.join(",")
|
||||
);
|
||||
AutofillTelemetry.recordManageEvent(this.telemetryType, "show");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -343,6 +353,8 @@ class ManageAddresses extends ManageRecords {
|
||||
}
|
||||
|
||||
class ManageCreditCards extends ManageRecords {
|
||||
telemetryType = AutofillTelemetry.CREDIT_CARD;
|
||||
|
||||
constructor(elements) {
|
||||
super("creditCards", elements);
|
||||
elements.add.setAttribute(
|
||||
@ -350,9 +362,8 @@ class ManageCreditCards extends ManageRecords {
|
||||
FormAutofillUtils.EDIT_CREDITCARD_L10N_IDS.join(",")
|
||||
);
|
||||
|
||||
Services.telemetry.recordEvent("creditcard", "show", "manage");
|
||||
|
||||
this._isDecrypted = false;
|
||||
AutofillTelemetry.recordManageEvent(this.telemetryType, "show");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -451,13 +462,6 @@ class ManageCreditCards extends ManageRecords {
|
||||
}
|
||||
}
|
||||
|
||||
async removeRecords(options) {
|
||||
await super.removeRecords(options);
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
Services.telemetry.recordEvent("creditcard", "delete", "manage");
|
||||
}
|
||||
}
|
||||
|
||||
updateButtonsStates(selectedCount) {
|
||||
super.updateButtonsStates(selectedCount);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
const { CreditCardTelemetry } = ChromeUtils.import(
|
||||
"resource://autofill/FormAutofillTelemetryUtils.jsm"
|
||||
"resource://autofill/Autofilltelemetry.jsm"
|
||||
);
|
||||
|
||||
const CC_NUM_USES_HISTOGRAM = "CREDITCARD_NUM_USES";
|
||||
|
@ -789,6 +789,10 @@ TESTCASES.forEach(testcase => {
|
||||
.creditCard) {
|
||||
delete ccRecord.flowId;
|
||||
}
|
||||
for (let addrRecord of FormAutofillContent._onFormSubmit.args[0][0]
|
||||
.address) {
|
||||
delete addrRecord.flowId;
|
||||
}
|
||||
|
||||
Assert.deepEqual(
|
||||
FormAutofillContent._onFormSubmit.args[0][0],
|
||||
|
473
toolkit/components/formautofill/AutofillTelemetry.jsm
Normal file
473
toolkit/components/formautofill/AutofillTelemetry.jsm
Normal file
@ -0,0 +1,473 @@
|
||||
/* 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 = ["AutofillTelemetry"];
|
||||
|
||||
const { FormAutofillUtils } = ChromeUtils.import(
|
||||
"resource://autofill/FormAutofillUtils.jsm"
|
||||
);
|
||||
const { FormAutofillCreditCardSection } = ChromeUtils.import(
|
||||
"resource://autofill/FormAutofillHandler.jsm"
|
||||
);
|
||||
|
||||
const { FIELD_STATES } = FormAutofillUtils;
|
||||
|
||||
class AutofillTelemetryBase {
|
||||
SUPPORTED_FIELDS = {};
|
||||
|
||||
EVENT_CATEGORY = null;
|
||||
EVENT_OBJECT_FORM_INTERACTION = null;
|
||||
|
||||
SCALAR_DETECTED_SECTION_COUNT = null;
|
||||
SCALAR_SUBMITTED_SECTION_COUNT = null;
|
||||
SCALAR_AUTOFILL_PROFILE_COUNT = null;
|
||||
|
||||
HISTOGRAM_NUM_USES = null;
|
||||
HISTOGRAM_PROFILE_NUM_USES = null;
|
||||
HISTOGRAM_PROFILE_NUM_USES_KEY = null;
|
||||
|
||||
#initFormEventExtra(value) {
|
||||
let extra = {};
|
||||
for (const field of Object.values(this.SUPPORTED_FIELDS)) {
|
||||
extra[field] = value;
|
||||
}
|
||||
return extra;
|
||||
}
|
||||
|
||||
#setFormEventExtra(extra, key, value) {
|
||||
extra[this.SUPPORTED_FIELDS[key]] = value;
|
||||
}
|
||||
|
||||
recordFormDetected(section) {
|
||||
let extra = this.#initFormEventExtra("false");
|
||||
let identified = new Set();
|
||||
section.fieldDetails.forEach(detail => {
|
||||
identified.add(detail.fieldName);
|
||||
|
||||
if (detail._reason == "autocomplete") {
|
||||
this.#setFormEventExtra(extra, detail.fieldName, "true");
|
||||
} else {
|
||||
// confidence exists only when a field is identified by fathom.
|
||||
let confidence =
|
||||
detail.confidence > 0 ? Math.floor(100 * detail.confidence) / 100 : 0;
|
||||
this.#setFormEventExtra(extra, detail.fieldName, confidence.toString());
|
||||
}
|
||||
});
|
||||
|
||||
this.recordFormEvent("detected", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordPopupShown(section, fieldName) {
|
||||
const extra = { field_name: fieldName };
|
||||
this.recordFormEvent("popup_shown", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordFormFilled(section, profile) {
|
||||
// Calculate values for telemetry
|
||||
let extra = this.#initFormEventExtra("unavailable");
|
||||
|
||||
for (let fieldDetail of section.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.#setFormEventExtra(extra, fieldDetail.fieldName, state);
|
||||
}
|
||||
|
||||
this.recordFormEvent("filled", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordFilledModified(section, fieldName) {
|
||||
const extra = { field_name: fieldName };
|
||||
this.recordFormEvent("filled_modified", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordFormSubmitted(section, record, form) {
|
||||
let extra = this.#initFormEventExtra("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.
|
||||
for (let fieldName of Object.keys(record.record)) {
|
||||
if (record.untouchedFields?.includes(fieldName)) {
|
||||
this.#setFormEventExtra(extra, fieldName, "autofilled");
|
||||
} else {
|
||||
this.#setFormEventExtra(extra, fieldName, "user_filled");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Object.keys(record.record).forEach(fieldName =>
|
||||
this.#setFormEventExtra(extra, fieldName, "user_filled")
|
||||
);
|
||||
}
|
||||
|
||||
this.recordFormEvent("submitted", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordFormCleared(section, fieldName) {
|
||||
const extra = { field_name: fieldName };
|
||||
|
||||
// Note that when a form is cleared, we also record `filled_modified` events
|
||||
// for all the fields that have been cleared.
|
||||
this.recordFormEvent("cleared", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordFormEvent(method, flowId, extra) {
|
||||
Services.telemetry.recordEvent(
|
||||
this.EVENT_CATEGORY,
|
||||
method,
|
||||
this.EVENT_OBJECT_FORM_INTERACTION,
|
||||
flowId,
|
||||
extra
|
||||
);
|
||||
}
|
||||
|
||||
recordFormInteractionEvent(
|
||||
method,
|
||||
section,
|
||||
{ fieldName, profile, record, form } = {}
|
||||
) {
|
||||
if (!this.EVENT_OBJECT_FORM_INTERACTION) {
|
||||
return undefined;
|
||||
}
|
||||
switch (method) {
|
||||
case "detected":
|
||||
return this.recordFormDetected(section);
|
||||
case "popup_shown":
|
||||
return this.recordPopupShown(section, fieldName);
|
||||
case "filled":
|
||||
return this.recordFormFilled(section, profile);
|
||||
case "filled_modified":
|
||||
return this.recordFilledModified(section, fieldName);
|
||||
case "submitted":
|
||||
return this.recordFormSubmitted(section, record, form);
|
||||
case "cleared":
|
||||
return this.recordFormCleared(section, fieldName);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
recordDoorhangerEvent(method, record) {
|
||||
Services.telemetry.recordEvent(
|
||||
this.EVENT_CATEGORY,
|
||||
method,
|
||||
record.guid ? "update_doorhanger" : "capture_doorhanger",
|
||||
record.flowId
|
||||
);
|
||||
}
|
||||
|
||||
recordManageEvent(method, object) {
|
||||
Services.telemetry.recordEvent(this.EVENT_CATEGORY, method, object);
|
||||
}
|
||||
|
||||
recordAutofillProfileCount(count) {
|
||||
if (!this.SCALAR_AUTOFILL_PROFILE_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.telemetry.scalarSet(this.SCALAR_AUTOFILL_PROFILE_COUNT, count);
|
||||
}
|
||||
|
||||
recordDetectedSectionCount() {
|
||||
if (!this.SCALAR_DETECTED_SECTION_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.telemetry.scalarAdd(this.SCALAR_DETECTED_SECTION_COUNT, 1);
|
||||
}
|
||||
|
||||
recordSubmittedSectionCount(count) {
|
||||
if (!this.SCALAR_SUBMITTED_SECTION_COUNT || !count) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.telemetry.scalarAdd(this.SCALAR_SUBMITTED_SECTION_COUNT, count);
|
||||
}
|
||||
|
||||
recordNumberOfUse(records) {
|
||||
let histogram = Services.telemetry.getKeyedHistogramById(
|
||||
this.HISTOGRAM_PROFILE_NUM_USES
|
||||
);
|
||||
histogram.clear();
|
||||
|
||||
for (let record of records) {
|
||||
histogram.add(this.HISTOGRAM_PROFILE_NUM_USES_KEY, record.timesUsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AddressTelemetry extends AutofillTelemetryBase {
|
||||
EVENT_CATEGORY = "address";
|
||||
}
|
||||
|
||||
class CreditCardTelemetry extends AutofillTelemetryBase {
|
||||
EVENT_CATEGORY = "creditcard";
|
||||
EVENT_OBJECT_FORM_INTERACTION = "cc_form_v2";
|
||||
|
||||
SCALAR_DETECTED_SECTION_COUNT =
|
||||
"formautofill.creditCards.detected_sections_count";
|
||||
SCALAR_SUBMITTED_SECTION_COUNT =
|
||||
"formautofill.creditCards.submitted_sections_count";
|
||||
SCALAR_AUTOFILL_PROFILE_COUNT =
|
||||
"formautofill.creditCards.autofill_profiles_count";
|
||||
|
||||
HISTOGRAM_NUM_USES = "CREDITCARD_NUM_USES";
|
||||
HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES";
|
||||
HISTOGRAM_PROFILE_NUM_USES_KEY = "credit_card";
|
||||
|
||||
// Mapping of field name used in formautofill code to the field name
|
||||
// used in the telemetry.
|
||||
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",
|
||||
};
|
||||
|
||||
recordLegacyFormEvent(method, flowId, extra = null) {
|
||||
Services.telemetry.recordEvent(
|
||||
this.EVENT_CATEGORY,
|
||||
method,
|
||||
"cc_form",
|
||||
flowId,
|
||||
extra
|
||||
);
|
||||
}
|
||||
|
||||
recordFormDetected(section) {
|
||||
super.recordFormDetected(section);
|
||||
|
||||
let identified = new Set();
|
||||
section.fieldDetails.forEach(detail => {
|
||||
identified.add(detail.fieldName);
|
||||
});
|
||||
let extra = {
|
||||
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.recordLegacyFormEvent("detected", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordPopupShown(section, fieldName) {
|
||||
super.recordPopupShown(section, fieldName);
|
||||
|
||||
this.recordLegacyFormEvent("popup_shown", section.flowId);
|
||||
}
|
||||
|
||||
recordFormFilled(section, profile) {
|
||||
super.recordFormFilled(section, profile);
|
||||
// Calculate values for telemetry
|
||||
let extra = {
|
||||
cc_name: "unavailable",
|
||||
cc_number: "unavailable",
|
||||
cc_exp: "unavailable",
|
||||
};
|
||||
|
||||
for (let fieldDetail of section.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";
|
||||
}
|
||||
switch (fieldDetail.fieldName) {
|
||||
case "cc-name":
|
||||
extra.cc_name = state;
|
||||
break;
|
||||
case "cc-number":
|
||||
extra.cc_number = state;
|
||||
break;
|
||||
case "cc-exp":
|
||||
case "cc-exp-month":
|
||||
case "cc-exp-year":
|
||||
extra.cc_exp = state;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.recordLegacyFormEvent("filled", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordFilledModified(section, fieldName) {
|
||||
super.recordFilledModified(section, fieldName);
|
||||
|
||||
let extra = { field_name: fieldName };
|
||||
this.recordLegacyFormEvent("filled_modified", section.flowId, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a credit card form is submitted
|
||||
*
|
||||
* @param {object} section Section that produces this record
|
||||
* @param {object} record Credit card record filled in the form.
|
||||
* @param {Array<HTMLForm>} form Form that contains the section
|
||||
*/
|
||||
recordFormSubmitted(section, record, form) {
|
||||
super.recordFormSubmitted(section, record, form);
|
||||
|
||||
// For legacy cc_form event telemetry
|
||||
let extra = {
|
||||
fields_not_auto: "0",
|
||||
fields_auto: "0",
|
||||
fields_modified: "0",
|
||||
};
|
||||
|
||||
if (record.guid !== null) {
|
||||
let totalCount = form.elements.length;
|
||||
let autofilledCount = Object.keys(record.record).length;
|
||||
let unmodifiedCount = record.untouchedFields.length;
|
||||
|
||||
extra.fields_not_auto = (totalCount - autofilledCount).toString();
|
||||
extra.fields_auto = autofilledCount.toString();
|
||||
extra.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.
|
||||
extra.fields_not_auto = Array.from(form.elements)
|
||||
.filter(element => !!element.value?.trim().length)
|
||||
.length.toString();
|
||||
}
|
||||
|
||||
this.recordLegacyFormEvent("submitted", section.flowId, extra);
|
||||
}
|
||||
|
||||
recordNumberOfUse(records) {
|
||||
super.recordNumberOfUse(records);
|
||||
|
||||
if (!this.HISTOGRAM_NUM_USES) {
|
||||
return;
|
||||
}
|
||||
|
||||
let histogram = Services.telemetry.getHistogramById(
|
||||
this.HISTOGRAM_NUM_USES
|
||||
);
|
||||
histogram.clear();
|
||||
|
||||
for (let record of records) {
|
||||
histogram.add(record.timesUsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AutofillTelemetry {
|
||||
static #creditCardTelemetry = new CreditCardTelemetry();
|
||||
static #addressTelemetry = new AddressTelemetry();
|
||||
|
||||
// const for `type` parameter used in the utility functions
|
||||
static ADDRESS = "address";
|
||||
static CREDIT_CARD = "creditcard";
|
||||
|
||||
static #getTelemetryBySection(section) {
|
||||
return section instanceof FormAutofillCreditCardSection
|
||||
? this.#creditCardTelemetry
|
||||
: this.#addressTelemetry;
|
||||
}
|
||||
|
||||
static #getTelemetryByType(type) {
|
||||
return type == AutofillTelemetry.CREDIT_CARD
|
||||
? this.#creditCardTelemetry
|
||||
: this.#addressTelemetry;
|
||||
}
|
||||
/**
|
||||
* Utility functions for `doorhanger` event (defined in Events.yaml)
|
||||
*
|
||||
* Category: address or creditcard
|
||||
* Event name: doorhanger
|
||||
*/
|
||||
|
||||
static recordDoorhangerShown(type, record) {
|
||||
const telemetry = this.#getTelemetryByType(type);
|
||||
telemetry.recordDoorhangerEvent("show", record);
|
||||
}
|
||||
|
||||
static recordDoorhangerClicked(type, method, record) {
|
||||
const telemetry = this.#getTelemetryByType(type);
|
||||
|
||||
// We don't have `create` method in telemetry, we treat `create` as `save`
|
||||
switch (method) {
|
||||
case "create":
|
||||
method = "save";
|
||||
break;
|
||||
case "open-pref":
|
||||
method = "pref";
|
||||
break;
|
||||
}
|
||||
|
||||
telemetry.recordDoorhangerEvent(method, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions for form event (defined in Events.yaml)
|
||||
*
|
||||
* Category: address or creditcard
|
||||
* Event name: cc_form, cc_form_v2, or address_form
|
||||
*/
|
||||
|
||||
static recordFormInteractionEvent(
|
||||
method,
|
||||
section,
|
||||
{ fieldName, profile, record, form } = {}
|
||||
) {
|
||||
const telemetry = this.#getTelemetryBySection(section);
|
||||
telemetry.recordFormInteractionEvent(method, section, {
|
||||
fieldName,
|
||||
profile,
|
||||
record,
|
||||
form,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions for submitted section count scalar (defined in Scalars.yaml)
|
||||
*
|
||||
* Category: formautofill.creditCards or formautofill.addresses
|
||||
* Scalar name: submitted_sections_count
|
||||
*/
|
||||
static recordDetectedSectionCount(section) {
|
||||
const telemetry = this.#getTelemetryBySection(section);
|
||||
telemetry.recordDetectedSectionCount();
|
||||
}
|
||||
|
||||
static recordSubmittedSectionCount(type, count) {
|
||||
const telemetry = this.#getTelemetryByType(type);
|
||||
telemetry.recordSubmittedSectionCount(count);
|
||||
}
|
||||
|
||||
static recordManageEvent(type, method) {
|
||||
const telemetry = this.#getTelemetryByType(type);
|
||||
telemetry.recordManageEvent(method, "manage");
|
||||
}
|
||||
|
||||
static recordAutofillProfileCount(type, count) {
|
||||
const telemetry = this.#getTelemetryByType(type);
|
||||
telemetry.recordAutofillProfileCount(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions for address/credit card number of use
|
||||
*/
|
||||
static recordNumberOfUse(type, records) {
|
||||
const telemetry = this.#getTelemetryByType(type);
|
||||
telemetry.recordNumberOfUse(records);
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
AddressResult: "resource://autofill/ProfileAutoCompleteResult.jsm",
|
||||
ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
|
||||
CreditCardResult: "resource://autofill/ProfileAutoCompleteResult.jsm",
|
||||
CreditCardTelemetry: "resource://autofill/FormAutofillTelemetryUtils.jsm",
|
||||
AutofillTelemetry: "resource://autofill/AutofillTelemetry.jsm",
|
||||
FormAutofill: "resource://autofill/FormAutofill.jsm",
|
||||
FormAutofillHandler: "resource://autofill/FormAutofillHandler.jsm",
|
||||
FormAutofillUtils: "resource://autofill/FormAutofillUtils.jsm",
|
||||
@ -549,10 +549,26 @@ var FormAutofillContent = {
|
||||
return;
|
||||
}
|
||||
|
||||
lazy.CreditCardTelemetry.recordFormSubmitted(
|
||||
records,
|
||||
handler.form.elements
|
||||
);
|
||||
[records.address, records.creditCard].forEach((rs, idx) => {
|
||||
lazy.AutofillTelemetry.recordSubmittedSectionCount(
|
||||
idx == 0
|
||||
? lazy.AutofillTelemetry.ADDRESS
|
||||
: lazy.AutofillTelemetry.CREDIT_CARD,
|
||||
rs?.length
|
||||
);
|
||||
|
||||
rs?.forEach(r => {
|
||||
lazy.AutofillTelemetry.recordFormInteractionEvent(
|
||||
"submitted",
|
||||
r.section,
|
||||
{
|
||||
record: r,
|
||||
form: handler.form,
|
||||
}
|
||||
);
|
||||
delete r.section;
|
||||
});
|
||||
});
|
||||
|
||||
this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
|
||||
},
|
||||
@ -762,9 +778,10 @@ var FormAutofillContent = {
|
||||
|
||||
let fieldName = FormAutofillContent.activeFieldDetail?.fieldName;
|
||||
if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) {
|
||||
lazy.CreditCardTelemetry.recordFormCleared(
|
||||
this.activeSection?.flowId,
|
||||
fieldName
|
||||
lazy.AutofillTelemetry.recordFormInteractionEvent(
|
||||
"cleared",
|
||||
this.activeSection,
|
||||
{ fieldName }
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -841,9 +858,10 @@ var FormAutofillContent = {
|
||||
|
||||
let fieldName = FormAutofillContent.activeFieldDetail?.fieldName;
|
||||
if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) {
|
||||
lazy.CreditCardTelemetry.recordPopupShown(
|
||||
this.activeSection?.flowId,
|
||||
fieldName
|
||||
lazy.AutofillTelemetry.recordFormInteractionEvent(
|
||||
"popup_shown",
|
||||
this.activeSection,
|
||||
{ fieldName }
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["FormAutofillHandler"];
|
||||
var EXPORTED_SYMBOLS = ["FormAutofillHandler", "FormAutofillCreditCardSection"];
|
||||
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
@ -32,7 +32,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
CreditCardTelemetry: "resource://autofill/FormAutofillTelemetryUtils.jsm",
|
||||
AutofillTelemetry: "resource://autofill/AutofillTelemetry.jsm",
|
||||
FormAutofillHeuristics: "resource://autofill/FormAutofillHeuristics.jsm",
|
||||
});
|
||||
|
||||
@ -87,6 +87,16 @@ class FormAutofillSection {
|
||||
allFieldNames: null,
|
||||
matchingSelectOption: null,
|
||||
};
|
||||
|
||||
// Identifier used to correlate events relating to the same form
|
||||
this.flowId = Services.uuid.generateUUID().toString();
|
||||
lazy.log.debug(
|
||||
"Creating new credit card section with flowId =",
|
||||
this.flowId
|
||||
);
|
||||
|
||||
lazy.AutofillTelemetry.recordDetectedSectionCount(this);
|
||||
lazy.AutofillTelemetry.recordFormInteractionEvent("detected", this);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -407,6 +417,11 @@ class FormAutofillSection {
|
||||
}
|
||||
}
|
||||
focusedInput.focus({ preventScroll: true });
|
||||
|
||||
lazy.AutofillTelemetry.recordFormInteractionEvent("filled", this, {
|
||||
profile,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -620,11 +635,14 @@ class FormAutofillSection {
|
||||
guid: this.filledRecordGUID,
|
||||
record: {},
|
||||
untouchedFields: [],
|
||||
section: this,
|
||||
};
|
||||
if (this.flowId) {
|
||||
data.flowId = this.flowId;
|
||||
}
|
||||
let condensedDetails = this.fieldDetails;
|
||||
|
||||
// TODO: This is credit card specific code...
|
||||
this._condenseMultipleCCNumberFields(condensedDetails);
|
||||
|
||||
condensedDetails.forEach(detail => {
|
||||
@ -684,9 +702,12 @@ class FormAutofillSection {
|
||||
this._changeFieldState(targetFieldDetail, FIELD_STATES.NORMAL);
|
||||
|
||||
if (isCreditCardField) {
|
||||
lazy.CreditCardTelemetry.recordFilledModified(
|
||||
this.flowId,
|
||||
targetFieldDetail.fieldName
|
||||
lazy.AutofillTelemetry.recordFormInteractionEvent(
|
||||
"filled_modified",
|
||||
this,
|
||||
{
|
||||
fieldName: targetFieldDetail.fieldName,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -984,19 +1005,10 @@ class FormAutofillCreditCardSection extends FormAutofillSection {
|
||||
|
||||
this.handler = handler;
|
||||
|
||||
// Identifier used to correlate events relating to the same form
|
||||
this.flowId = Services.uuid.generateUUID().toString();
|
||||
lazy.log.debug(
|
||||
"Creating new credit card section with flowId =",
|
||||
this.flowId
|
||||
);
|
||||
|
||||
if (!this.isValidSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lazy.CreditCardTelemetry.recordFormDetected(this.flowId, fieldDetails);
|
||||
|
||||
// Check whether the section is in an <iframe>; and, if so,
|
||||
// watch for the <iframe> to pagehide.
|
||||
if (handler.window.location != handler.window.parent?.location) {
|
||||
@ -1394,11 +1406,6 @@ class FormAutofillCreditCardSection extends FormAutofillSection {
|
||||
return false;
|
||||
}
|
||||
|
||||
lazy.CreditCardTelemetry.recordFormFilled(
|
||||
this.flowId,
|
||||
this.fieldDetails,
|
||||
profile
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -477,6 +477,9 @@ class FormAutofillParent extends JSWindowActorParent {
|
||||
|
||||
async _onAddressSubmit(address, browser, timeStartedFillingMS) {
|
||||
let showDoorhanger = null;
|
||||
|
||||
// Bug 1808176 - We should always ecord used count in this function regardless
|
||||
// whether capture is enabled or not.
|
||||
if (!FormAutofill.isAutofillAddressesCaptureEnabled) {
|
||||
return showDoorhanger;
|
||||
}
|
||||
@ -508,9 +511,11 @@ class FormAutofillParent extends JSWindowActorParent {
|
||||
const description = FormAutofillUtils.getAddressLabel(address.record);
|
||||
const state = await lazy.FormAutofillPrompter.promptToSaveAddress(
|
||||
browser,
|
||||
"updateAddress",
|
||||
address,
|
||||
description
|
||||
);
|
||||
|
||||
// Bug 1808176 : We should sync how we run the following code with Credit Card
|
||||
let changedGUIDs = await lazy.gFormAutofillStorage.addresses.mergeToStorage(
|
||||
address.record,
|
||||
true
|
||||
@ -582,7 +587,7 @@ class FormAutofillParent extends JSWindowActorParent {
|
||||
const description = FormAutofillUtils.getAddressLabel(address.record);
|
||||
const state = await lazy.FormAutofillPrompter.promptToSaveAddress(
|
||||
browser,
|
||||
"firstTimeUse",
|
||||
address,
|
||||
description
|
||||
);
|
||||
if (state !== "open-pref") {
|
||||
|
@ -147,6 +147,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
AutofillTelemetry: "resource://autofill/AutofillTelemetry.jsm",
|
||||
FormAutofillNameUtils: "resource://autofill/FormAutofillNameUtils.jsm",
|
||||
FormAutofillUtils: "resource://autofill/FormAutofillUtils.jsm",
|
||||
PhoneNumber: "resource://autofill/phonenumberutils/PhoneNumber.jsm",
|
||||
@ -280,6 +281,8 @@ class AutofillRecords {
|
||||
this._schemaVersion = schemaVersion;
|
||||
|
||||
this._initialize();
|
||||
|
||||
Services.obs.addObserver(this, "formautofill-storage-changed");
|
||||
}
|
||||
|
||||
_initialize() {
|
||||
@ -295,6 +298,23 @@ class AutofillRecords {
|
||||
});
|
||||
}
|
||||
|
||||
observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "formautofill-storage-changed":
|
||||
let collectionName = subject.wrappedJSObject.collectionName;
|
||||
if (collectionName != this._collectionName) {
|
||||
return;
|
||||
}
|
||||
const telemetryType =
|
||||
subject.wrappedJSObject.collectionName == "creditCards"
|
||||
? lazy.AutofillTelemetry.CREDIT_CARD
|
||||
: lazy.AutofillTelemetry.ADDRESS;
|
||||
const count = this._data.filter(entry => !entry.deleted).length;
|
||||
lazy.AutofillTelemetry.recordAutofillProfileCount(telemetryType, count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schema version number.
|
||||
*
|
||||
@ -534,6 +554,7 @@ class AutofillRecords {
|
||||
* Indicates which record to be notified.
|
||||
*/
|
||||
notifyUsed(guid) {
|
||||
dump("notifyUsed:" + guid + "\n");
|
||||
this.log.debug("notifyUsed:", guid);
|
||||
|
||||
let recordFound = this._findByGUID(guid);
|
||||
@ -559,7 +580,14 @@ class AutofillRecords {
|
||||
);
|
||||
}
|
||||
|
||||
updateUseCountTelemetry() {}
|
||||
updateUseCountTelemetry() {
|
||||
const telemetryType =
|
||||
this._collectionName == "creditCards"
|
||||
? lazy.AutofillTelemetry.CREDIT_CARD
|
||||
: lazy.AutofillTelemetry.ADDRESS;
|
||||
let records = this._data.filter(r => !r.deleted);
|
||||
lazy.AutofillTelemetry.recordNumberOfUse(telemetryType, records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified record. No error occurs if the record isn't found.
|
||||
@ -1677,19 +1705,6 @@ class CreditCardsBase extends AutofillRecords {
|
||||
VALID_CREDIT_CARD_COMPUTED_FIELDS,
|
||||
CREDIT_CARD_SCHEMA_VERSION
|
||||
);
|
||||
Services.obs.addObserver(this, "formautofill-storage-changed");
|
||||
}
|
||||
|
||||
observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "formautofill-storage-changed":
|
||||
let count = this._data.filter(entry => !entry.deleted).length;
|
||||
Services.telemetry.scalarSet(
|
||||
"formautofill.creditCards.autofill_profiles_count",
|
||||
count
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async computeFields(creditCard) {
|
||||
@ -1948,17 +1963,6 @@ class CreditCardsBase extends AutofillRecords {
|
||||
async mergeIfPossible(guid, creditCard) {
|
||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
updateUseCountTelemetry() {
|
||||
let histogram = Services.telemetry.getHistogramById("CREDITCARD_NUM_USES");
|
||||
histogram.clear();
|
||||
|
||||
let records = this._data.filter(r => !r.deleted);
|
||||
|
||||
for (let record of records) {
|
||||
histogram.add(record.timesUsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FormAutofillStorageBase {
|
||||
|
@ -1,292 +0,0 @@
|
||||
/* 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 { 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);
|
||||
|
||||
if (detail._reason == "autocomplete") {
|
||||
this._ccFormV2SetExtra(ccFormV2Extra, detail.fieldName, "true");
|
||||
} else {
|
||||
// confidence exists only when a field is identified by fathom.
|
||||
let confidence =
|
||||
detail.confidence > 0 ? Math.floor(100 * detail.confidence) / 100 : 0;
|
||||
this._ccFormV2SetExtra(
|
||||
ccFormV2Extra,
|
||||
detail.fieldName,
|
||||
confidence.toString()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
Object.keys(record.record).forEach(fieldName =>
|
||||
this._ccFormV2SetExtra(ccFormV2Extra, fieldName, "user_filled")
|
||||
);
|
||||
}
|
||||
|
||||
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 }
|
||||
);
|
||||
},
|
||||
};
|
@ -14,6 +14,9 @@ var EXPORTED_SYMBOLS = ["FormAutofillPrompter"];
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
const { AutofillTelemetry } = ChromeUtils.import(
|
||||
"resource://autofill/AutofillTelemetry.jsm"
|
||||
);
|
||||
const { FormAutofill } = ChromeUtils.import(
|
||||
"resource://autofill/FormAutofill.jsm"
|
||||
);
|
||||
@ -377,8 +380,15 @@ let FormAutofillPrompter = {
|
||||
}
|
||||
},
|
||||
|
||||
async promptToSaveAddress(browser, type, description) {
|
||||
return this._showCCorAddressCaptureDoorhanger(browser, type, description);
|
||||
async promptToSaveAddress(browser, address, description) {
|
||||
const state = this._showCCorAddressCaptureDoorhanger(
|
||||
browser,
|
||||
address,
|
||||
address.guid ? "updateAddress" : "firstTimeUse",
|
||||
description
|
||||
);
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
async promptToSaveCreditCard(browser, creditCard, storage) {
|
||||
@ -389,30 +399,16 @@ let FormAutofillPrompter = {
|
||||
let type = lazy.CreditCard.getType(number);
|
||||
let maskedNumber = lazy.CreditCard.getMaskedNumber(number);
|
||||
let description = `${maskedNumber}, ${name}`;
|
||||
const telemetryObject = creditCard.guid
|
||||
? "update_doorhanger"
|
||||
: "capture_doorhanger";
|
||||
Services.telemetry.recordEvent(
|
||||
"creditcard",
|
||||
"show",
|
||||
telemetryObject,
|
||||
creditCard.flowId
|
||||
);
|
||||
|
||||
const state = await FormAutofillPrompter._showCCorAddressCaptureDoorhanger(
|
||||
browser,
|
||||
creditCard,
|
||||
creditCard.guid ? "updateCreditCard" : "addCreditCard",
|
||||
description,
|
||||
type
|
||||
);
|
||||
|
||||
if (state == "cancel") {
|
||||
Services.telemetry.recordEvent(
|
||||
"creditcard",
|
||||
"cancel",
|
||||
telemetryObject,
|
||||
creditCard.flowId
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -421,12 +417,6 @@ let FormAutofillPrompter = {
|
||||
"extensions.formautofill.creditCards.enabled",
|
||||
false
|
||||
);
|
||||
Services.telemetry.recordEvent(
|
||||
"creditcard",
|
||||
"disable",
|
||||
telemetryObject,
|
||||
creditCard.flowId
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -438,12 +428,6 @@ let FormAutofillPrompter = {
|
||||
let changedGUIDs = [];
|
||||
if (creditCard.guid) {
|
||||
if (state == "update") {
|
||||
Services.telemetry.recordEvent(
|
||||
"creditcard",
|
||||
"update",
|
||||
telemetryObject,
|
||||
creditCard.flowId
|
||||
);
|
||||
await storage.creditCards.update(
|
||||
creditCard.guid,
|
||||
creditCard.record,
|
||||
@ -451,12 +435,6 @@ let FormAutofillPrompter = {
|
||||
);
|
||||
changedGUIDs.push(creditCard.guid);
|
||||
} else if ("create") {
|
||||
Services.telemetry.recordEvent(
|
||||
"creditcard",
|
||||
"save",
|
||||
telemetryObject,
|
||||
creditCard.flowId
|
||||
);
|
||||
changedGUIDs.push(await storage.creditCards.add(creditCard.record));
|
||||
}
|
||||
} else {
|
||||
@ -464,12 +442,6 @@ let FormAutofillPrompter = {
|
||||
...(await storage.creditCards.mergeToStorage(creditCard.record))
|
||||
);
|
||||
if (!changedGUIDs.length) {
|
||||
Services.telemetry.recordEvent(
|
||||
"creditcard",
|
||||
"save",
|
||||
telemetryObject,
|
||||
creditCard.flowId
|
||||
);
|
||||
changedGUIDs.push(await storage.creditCards.add(creditCard.record));
|
||||
}
|
||||
}
|
||||
@ -483,18 +455,26 @@ let FormAutofillPrompter = {
|
||||
/**
|
||||
* Show different types of doorhanger by leveraging PopupNotifications.
|
||||
*
|
||||
* @param {XULElement} browser
|
||||
* Target browser element for showing doorhanger.
|
||||
* @param {string} type
|
||||
* The type of the doorhanger. There will have first time use/update/credit card.
|
||||
* @param {string} description
|
||||
* The message that provides more information on doorhanger.
|
||||
* @param {string} network
|
||||
* The network type for credit card doorhangers.
|
||||
* @returns {Promise}
|
||||
Resolved with action type when action callback is triggered.
|
||||
* @param {XULElement} browser Target browser element for showing doorhanger.
|
||||
* @param {object} record The record being saved
|
||||
* @param {string} type The type of the doorhanger. There will have first time use/update/credit card.
|
||||
* @param {string} description The message that provides more information on doorhanger.
|
||||
* @param {string} network The network type for credit card doorhangers.
|
||||
* @returns {Promise} Resolved with action type when action callback is triggered.
|
||||
*/
|
||||
async _showCCorAddressCaptureDoorhanger(browser, type, description, network) {
|
||||
async _showCCorAddressCaptureDoorhanger(
|
||||
browser,
|
||||
record,
|
||||
type,
|
||||
description,
|
||||
network
|
||||
) {
|
||||
const telemetryType = ["updateCreditCard", "addCreditCard"].includes(type)
|
||||
? AutofillTelemetry.CREDIT_CARD
|
||||
: AutofillTelemetry.ADDRESS;
|
||||
|
||||
AutofillTelemetry.recordDoorhangerShown(telemetryType, record);
|
||||
|
||||
lazy.log.debug("show doorhanger with type:", type);
|
||||
return new Promise(resolve => {
|
||||
let {
|
||||
@ -564,6 +544,9 @@ let FormAutofillPrompter = {
|
||||
...this._createActions(mainAction, secondaryActions, resolve),
|
||||
options
|
||||
);
|
||||
}).then(state => {
|
||||
AutofillTelemetry.recordDoorhangerClicked(telemetryType, state, record);
|
||||
return state;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -718,8 +718,8 @@
|
||||
"toolkit/components/formautofill/FormAutofillStorageBase.jsm",
|
||||
"resource://autofill/FormAutofillSync.jsm":
|
||||
"toolkit/components/formautofill/FormAutofillSync.jsm",
|
||||
"resource://autofill/FormAutofillTelemetryUtils.jsm":
|
||||
"toolkit/components/formautofill/FormAutofillTelemetryUtils.jsm",
|
||||
"resource://autofill/Autofilltelemetry.jsm":
|
||||
"toolkit/components/formautofill/Autofilltelemetry.jsm",
|
||||
"resource://autofill/FormAutofillUtils.jsm":
|
||||
"toolkit/components/formautofill/FormAutofillUtils.jsm",
|
||||
"resource://autofill/ProfileAutoCompleteResult.jsm":
|
||||
|
Loading…
Reference in New Issue
Block a user