Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ export const AddressMetaDataExtension = {
},
"data/JP": {
alpha_3_code: "JPN",
fmt: "〒%Z%n%S%C%n%A%n%O%n%N",
require: "ACSZ",
},
"data/JE": {
alpha_3_code: "JEY",
Expand Down Expand Up @@ -474,6 +476,7 @@ export const AddressMetaDataExtension = {
},
"data/NL": {
alpha_3_code: "NLD",
address_reversed: true,
},
"data/NC": {
alpha_3_code: "NCL",
Expand Down
36 changes: 36 additions & 0 deletions firefox-ios/Client/Assets/CC_Script/AddressParser.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,40 @@ export class AddressParser {
static mergeWhitespace(s) {
return s?.replace(/\s{2,}/g, " ");
}

// This is quite fragile, but handles a number of basic cases. This is intended
// to split the street number suffix from the street number. For example:
// 35B will be split into number=35,suffix=B
// Returns an array with [housenumber, suffix]
static parseHouseSuffix(address, structuredAddress) {
let streetNumber = structuredAddress?.street_number;
if (!streetNumber) {
return null;
}

let numberIndex = address.indexOf(streetNumber);
if (numberIndex < 0) {
return [streetNumber];
}

let match = streetNumber.match(/^(\d+)(\w?)/);
if (!match) {
return [streetNumber];
}

let suffix;
let result = [match[1]];
// If the house number is after the street, include the rest of the address
// as part of the suffix, otherwise just include the suffix on the number.
if (numberIndex > structuredAddress?.street_name.length) {
suffix = address.substring(numberIndex + match[1].length).trim();
} else {
suffix = match[2];
}
if (suffix) {
result.push(suffix.replace("\n", " "));
}

return result;
}
}
20 changes: 20 additions & 0 deletions firefox-ios/Client/Assets/CC_Script/AddressRecord.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FormAutofillNameUtils } from "resource://gre/modules/shared/FormAutofil
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
import { PhoneNumber } from "resource://gre/modules/shared/PhoneNumber.sys.mjs";
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { AddressParser } from "resource://gre/modules/shared/AddressParser.sys.mjs";

/**
* The AddressRecord class serves to handle and normalize internal address records.
Expand All @@ -32,6 +33,7 @@ export class AddressRecord {
static computeFields(address) {
this.#computeNameFields(address);
this.#computeAddressLineFields(address);
this.#computeStreetAndHouseNumberFields(address);
this.#computeCountryFields(address);
this.#computeTelFields(address);
}
Expand Down Expand Up @@ -66,6 +68,24 @@ export class AddressRecord {
}
}

static #computeStreetAndHouseNumberFields(address) {
if (!("address-housenumber" in address) && "street-address" in address) {
let streetAddress = address["street-address"];
let parsedAddress = AddressParser.parseStreetAddress(streetAddress);
if (parsedAddress) {
address["address-housenumber"] = parsedAddress.street_number;

let splitNumber = AddressParser.parseHouseSuffix(
streetAddress,
parsedAddress
);
if (splitNumber?.length >= 2) {
address["address-extra-housesuffix"] = splitNumber[1];
}
}
}
}

static #computeCountryFields(address) {
// Compute country name
if (!("country-name" in address)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ export const AutofillFormFactory = {
}
return lazy.FormLikeFactory.createFromField(aField, { ignoreForm });
},

createFromDocumentRoot(aDocRoot) {
return lazy.FormLikeFactory.createFromDocumentRoot(aDocRoot);
},
};
146 changes: 29 additions & 117 deletions firefox-ios/Client/Assets/CC_Script/AutofillTelemetry.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ class AutofillTelemetryBase {
EVENT_CATEGORY = null;
EVENT_OBJECT_FORM_INTERACTION = 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)) {
Expand All @@ -32,33 +28,15 @@ class AutofillTelemetryBase {
extra[this.SUPPORTED_FIELDS[key]] = value;
}

/**
* Building the extra keys object that is included in the Legacy Telemetry event `cc_form_v2`
* or `address_form` event and the Glean event `cc_form`, and `address_form`.
* It indicates the detected credit card or address fields and which method (autocomplete property, regular expression heuristics or fathom) identified them.
*
* @param {Array<object>} fieldDetails fieldDetails to extract which fields were identified and how
* @param {string} undetected Default value when a field is not detected: 'undetected' (Glean) and 'false' in (Legacy)
* @param {string} autocomplete Value when a field is identified with autocomplete property: 'autocomplete' (Glean), 'true' (Legacy)
* @param {string} regexp Value when a field is identified with regex expression heuristics: 'regexp' (Glean), '0' (Legacy)
* @param {boolean} includeMultiPart Include multi part data or not
* @returns {object} Extra keys to include in the form event
*/
#buildFormDetectedEventExtra(
fieldDetails,
undetected,
autocomplete,
regexp,
includeMultiPart
) {
let extra = this.#initFormEventExtra(undetected);
recordFormDetected(flowId, fieldDetails) {
let extra = this.#initFormEventExtra("false");

let identified = new Set();
fieldDetails.forEach(detail => {
identified.add(detail.fieldName);

if (detail.reason == "autocomplete") {
this.#setFormEventExtra(extra, detail.fieldName, autocomplete);
this.#setFormEventExtra(extra, detail.fieldName, "true");
} else {
// confidence exists only when a field is identified by fathom.
let confidence =
Expand All @@ -67,46 +45,12 @@ class AutofillTelemetryBase {
this.#setFormEventExtra(
extra,
detail.fieldName,
confidence ? confidence.toString() : regexp
confidence ? confidence.toString() : "0"
);
}

if (
detail.fieldName === "cc-number" &&
this.SUPPORTED_FIELDS[detail.fieldName] &&
includeMultiPart
) {
extra.cc_number_multi_parts = detail.part ?? 1;
}
});
return extra;
}

recordFormDetected(flowId, fieldDetails) {
this.recordFormEvent(
"detected",
flowId,
this.#buildFormDetectedEventExtra(
fieldDetails,
"false",
"true",
"0",
false
)
);

this.recordGleanFormEvent(
"formDetected",
flowId,
this.#buildFormDetectedEventExtra(
fieldDetails,
"undetected",
"autocomplete",
"regexp",
true
)
);

this.recordFormEvent("detected", flowId, extra);
try {
this.recordIframeLayoutDetection(flowId, fieldDetails);
} catch {}
Expand All @@ -115,7 +59,6 @@ class AutofillTelemetryBase {
recordPopupShown(flowId, fieldDetails) {
const extra = { field_name: fieldDetails[0].fieldName };
this.recordFormEvent("popup_shown", flowId, extra);
this.recordGleanFormEvent("formPopupShown", flowId, extra);
}

setUpFormFilledExtra(fieldDetails, data) {
Expand Down Expand Up @@ -149,7 +92,6 @@ class AutofillTelemetryBase {
recordFormFilled(flowId, fieldDetails, data) {
const extra = this.setUpFormFilledExtra(fieldDetails, data);
this.recordFormEvent("filled", flowId, extra);
this.recordGleanFormEvent("formFilled", flowId, extra);
}

recordFormFilledOnFieldsUpdate(flowId, fieldDetails, data) {
Expand All @@ -160,7 +102,6 @@ class AutofillTelemetryBase {
recordFilledModified(flowId, fieldDetails) {
const extra = { field_name: fieldDetails[0].fieldName };
this.recordFormEvent("filled_modified", flowId, extra);
this.recordGleanFormEvent("formFilledModified", flowId, extra);
}

recordFormSubmitted(flowId, fieldDetails, data) {
Expand All @@ -184,7 +125,6 @@ class AutofillTelemetryBase {
}

this.recordFormEvent("submitted", flowId, extra);
this.recordGleanFormEvent("formSubmitted", flowId, extra);
}

recordFormCleared(flowId, fieldDetails) {
Expand All @@ -193,17 +133,12 @@ class AutofillTelemetryBase {
// Note that when a form is cleared, we also record `filled_modified` events
// for all the fields that have been cleared.
this.recordFormEvent("cleared", flowId, extra);
this.recordGleanFormEvent("formCleared", flowId, extra);
}

recordFormEvent(_method, _flowId, _extra) {
throw new Error("Not implemented.");
}

recordGleanFormEvent(_eventName, _flowId, _extra) {
throw new Error("Not implemented.");
}

recordFormInteractionEvent(method, flowId, fieldDetails, data) {
if (!this.EVENT_OBJECT_FORM_INTERACTION) {
return undefined;
Expand Down Expand Up @@ -244,17 +179,6 @@ class AutofillTelemetryBase {
throw new Error("Not implemented.");
}

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);
}
}

recordIframeLayoutDetection(flowId, fieldDetails) {
const fieldsInMainFrame = [];
const fieldsInIframe = [];
Expand Down Expand Up @@ -299,9 +223,6 @@ export class AddressTelemetry extends AutofillTelemetryBase {
EVENT_OBJECT_FORM_INTERACTION = "AddressForm";
EVENT_OBJECT_FORM_INTERACTION_EXT = "AddressFormExt";

HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES";
HISTOGRAM_PROFILE_NUM_USES_KEY = "address";

// Fields that are recorded in `address_form` and `address_form_ext` telemetry
SUPPORTED_FIELDS = {
"street-address": "street_address",
Expand Down Expand Up @@ -344,10 +265,6 @@ export class AddressTelemetry extends AutofillTelemetryBase {
"tel",
];

recordGleanFormEvent(_eventName, _flowId, _extra) {
// To be implemented when migrating the legacy event address.address_form to Glean
}

recordFormEvent(method, flowId, extra) {
let extExtra = {};
if (["detected", "filled", "submitted"].includes(method)) {
Expand Down Expand Up @@ -381,10 +298,6 @@ class CreditCardTelemetry extends AutofillTelemetryBase {
EVENT_CATEGORY = "creditcard";
EVENT_OBJECT_FORM_INTERACTION = "CcFormV2";

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 = {
Expand All @@ -395,12 +308,6 @@ class CreditCardTelemetry extends AutofillTelemetryBase {
"cc-exp-month": "cc_exp_month",
"cc-exp-year": "cc_exp_year",
};

recordGleanFormEvent(eventName, flowId, extra) {
extra.flow_id = flowId;
Glean.formautofillCreditcards[eventName].record(extra);
}

recordFormEvent(method, flowId, aExtra) {
// Don't modify the passed-in aExtra as it's reused.
const extra = Object.assign({ value: flowId }, aExtra);
Expand All @@ -410,20 +317,33 @@ class CreditCardTelemetry extends AutofillTelemetryBase {
);
}

recordNumberOfUse(records) {
super.recordNumberOfUse(records);
recordFormDetected(flowId, fieldDetails) {
super.recordFormDetected(flowId, fieldDetails);
this.recordCcNumberFieldsCount(fieldDetails);
}

if (!this.HISTOGRAM_NUM_USES) {
return;
}
/**
* Collect the amount of consecutive cc number fields to help decide
* whether to support filling other field counts besides 1 and 4 fields
*/
recordCcNumberFieldsCount(fieldDetails) {
const recordCount = count => {
const label = "cc_number_fields_" + (count > 4 ? "other" : count);
Glean.creditcard.detectedCcNumberFieldsCount[label].add(1);
};

let histogram = Services.telemetry.getHistogramById(
this.HISTOGRAM_NUM_USES
);
histogram.clear();
let consecutiveCcNumberCount = 0;
for (const { fieldName, reason } of fieldDetails) {
if (fieldName == "cc-number" && reason == "autocomplete") {
consecutiveCcNumberCount++;
} else if (consecutiveCcNumberCount) {
recordCount(consecutiveCcNumberCount);
consecutiveCcNumberCount = 0;
}
}

for (let record of records) {
histogram.add(record.timesUsed);
if (consecutiveCcNumberCount) {
recordCount(consecutiveCcNumberCount);
}
}

Expand Down Expand Up @@ -504,14 +424,6 @@ export class AutofillTelemetry {
telemetry.recordAutofillProfileCount(count);
}

/**
* Utility functions for address/credit card number of use
*/
static recordNumberOfUse(type, records) {
const telemetry = this.#getTelemetryByType(type);
telemetry.recordNumberOfUse(records);
}

static recordFormSubmissionHeuristicCount(label) {
Glean.formautofill.formSubmissionHeuristic[label].add(1);
}
Expand Down
Loading