Skip to content

Commit f467db9

Browse files
Refactor - auto update credential provider script
1 parent 58d564a commit f467db9

10 files changed

+120
-255
lines changed

firefox-ios/Client/Assets/CC_Script/AutofillFormFactory.sys.mjs

+4
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ export const AutofillFormFactory = {
3939
}
4040
return lazy.FormLikeFactory.createFromField(aField, { ignoreForm });
4141
},
42+
43+
createFromDocumentRoot(aDocRoot) {
44+
return lazy.FormLikeFactory.createFromDocumentRoot(aDocRoot);
45+
},
4246
};

firefox-ios/Client/Assets/CC_Script/AutofillTelemetry.sys.mjs

-47
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ class AutofillTelemetryBase {
1212
EVENT_CATEGORY = null;
1313
EVENT_OBJECT_FORM_INTERACTION = null;
1414

15-
HISTOGRAM_NUM_USES = null;
16-
HISTOGRAM_PROFILE_NUM_USES = null;
17-
HISTOGRAM_PROFILE_NUM_USES_KEY = null;
18-
1915
#initFormEventExtra(value) {
2016
let extra = {};
2117
for (const field of Object.values(this.SUPPORTED_FIELDS)) {
@@ -183,17 +179,6 @@ class AutofillTelemetryBase {
183179
throw new Error("Not implemented.");
184180
}
185181

186-
recordNumberOfUse(records) {
187-
let histogram = Services.telemetry.getKeyedHistogramById(
188-
this.HISTOGRAM_PROFILE_NUM_USES
189-
);
190-
histogram.clear();
191-
192-
for (let record of records) {
193-
histogram.add(this.HISTOGRAM_PROFILE_NUM_USES_KEY, record.timesUsed);
194-
}
195-
}
196-
197182
recordIframeLayoutDetection(flowId, fieldDetails) {
198183
const fieldsInMainFrame = [];
199184
const fieldsInIframe = [];
@@ -238,9 +223,6 @@ export class AddressTelemetry extends AutofillTelemetryBase {
238223
EVENT_OBJECT_FORM_INTERACTION = "AddressForm";
239224
EVENT_OBJECT_FORM_INTERACTION_EXT = "AddressFormExt";
240225

241-
HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES";
242-
HISTOGRAM_PROFILE_NUM_USES_KEY = "address";
243-
244226
// Fields that are recorded in `address_form` and `address_form_ext` telemetry
245227
SUPPORTED_FIELDS = {
246228
"street-address": "street_address",
@@ -316,10 +298,6 @@ class CreditCardTelemetry extends AutofillTelemetryBase {
316298
EVENT_CATEGORY = "creditcard";
317299
EVENT_OBJECT_FORM_INTERACTION = "CcFormV2";
318300

319-
HISTOGRAM_NUM_USES = "CREDITCARD_NUM_USES";
320-
HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES";
321-
HISTOGRAM_PROFILE_NUM_USES_KEY = "credit_card";
322-
323301
// Mapping of field name used in formautofill code to the field name
324302
// used in the telemetry.
325303
SUPPORTED_FIELDS = {
@@ -369,23 +347,6 @@ class CreditCardTelemetry extends AutofillTelemetryBase {
369347
}
370348
}
371349

372-
recordNumberOfUse(records) {
373-
super.recordNumberOfUse(records);
374-
375-
if (!this.HISTOGRAM_NUM_USES) {
376-
return;
377-
}
378-
379-
let histogram = Services.telemetry.getHistogramById(
380-
this.HISTOGRAM_NUM_USES
381-
);
382-
histogram.clear();
383-
384-
for (let record of records) {
385-
histogram.add(record.timesUsed);
386-
}
387-
}
388-
389350
recordAutofillProfileCount(count) {
390351
Glean.formautofillCreditcards.autofillProfilesCount.set(count);
391352
}
@@ -463,14 +424,6 @@ export class AutofillTelemetry {
463424
telemetry.recordAutofillProfileCount(count);
464425
}
465426

466-
/**
467-
* Utility functions for address/credit card number of use
468-
*/
469-
static recordNumberOfUse(type, records) {
470-
const telemetry = this.#getTelemetryByType(type);
471-
telemetry.recordNumberOfUse(records);
472-
}
473-
474427
static recordFormSubmissionHeuristicCount(label) {
475428
Glean.formautofill.formSubmissionHeuristic[label].add(1);
476429
}

firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs

+48-18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const { FIELD_STATES } = FormAutofillUtils;
2424
export const FORM_CHANGE_REASON = {
2525
NODES_ADDED: "nodes-added",
2626
NODES_REMOVED: "nodes-removed",
27+
SELECT_OPTIONS_CHANGED: "select-options-changed",
2728
ELEMENT_INVISIBLE: "visible-element-became-invisible",
2829
ELEMENT_VISIBLE: "invisible-element-became-visible",
2930
};
@@ -268,6 +269,11 @@ export class FormAutofillHandler {
268269
return false;
269270
}
270271

272+
updateFormByElement(element) {
273+
const formLike = lazy.AutofillFormFactory.createFromField(element);
274+
this._updateForm(formLike);
275+
}
276+
271277
/**
272278
* Update the form with a new FormLike, and the related fields should be
273279
* updated or clear to ensure the data consistency.
@@ -344,7 +350,7 @@ export class FormAutofillHandler {
344350
}
345351

346352
/**
347-
* Resetting the state element's fieldDetail after it was removed from the form
353+
* Resetting the filled state after an element was removed from the form
348354
* Todo: We'll need to update this.filledResult in FormAutofillParent (Bug 1948077).
349355
*
350356
* @param {HTMLElement} element that was removed
@@ -353,8 +359,7 @@ export class FormAutofillHandler {
353359
if (this.getFilledStateByElement(element) != FIELD_STATES.AUTO_FILLED) {
354360
return;
355361
}
356-
const fieldDetail = this.getFieldDetailByElement(element);
357-
this.#filledStateByElement.delete(fieldDetail);
362+
this.#filledStateByElement.delete(element);
358363
}
359364

360365
/**
@@ -493,6 +498,14 @@ export class FormAutofillHandler {
493498
} else if (HTMLSelectElement.isInstance(element)) {
494499
const option = this.matchSelectOptions(fieldDetail, profile);
495500
if (!option) {
501+
if (
502+
this.getFilledStateByElement(element) == FIELD_STATES.AUTO_FILLED
503+
) {
504+
// The select element was previously autofilled, but there
505+
// is no matching option under the current set of options anymore.
506+
// Changing the state will also remove the highlighting from the element
507+
this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
508+
}
496509
continue;
497510
}
498511

@@ -684,18 +697,21 @@ export class FormAutofillHandler {
684697
const mutationObserver = new this.window.MutationObserver(
685698
(mutations, _) => {
686699
const collectMutatedNodes = mutations => {
687-
let removedNodes = [];
688-
let addedNodes = [];
700+
let removedNodes = new Set();
701+
let addedNodes = new Set();
702+
let changedSelectElements = new Set();
689703
mutations.forEach(mutation => {
690704
if (mutation.type == "childList") {
691-
if (mutation.addedNodes.length) {
692-
addedNodes.push(...mutation.addedNodes);
705+
if (HTMLSelectElement.isInstance(mutation.target)) {
706+
changedSelectElements.add(mutation.target);
707+
} else if (mutation.addedNodes.length) {
708+
addedNodes.add(...mutation.addedNodes);
693709
} else if (mutation.removedNodes.length) {
694-
removedNodes.push(...mutation.removedNodes);
710+
removedNodes.add(...mutation.removedNodes);
695711
}
696712
}
697713
});
698-
return [addedNodes, removedNodes];
714+
return [addedNodes, removedNodes, changedSelectElements];
699715
};
700716

701717
const collectAllSubtreeElements = node => {
@@ -715,22 +731,35 @@ export class FormAutofillHandler {
715731
);
716732
};
717733

718-
let [addedNodes, removedNodes] = collectMutatedNodes(mutations);
719-
let relevantAddedElements = getCCAndAddressElements(addedNodes);
720-
// We only care about removed elements that might change the
721-
// currently detected fieldDetails
722-
let relevantRemovedElements = getCCAndAddressElements(
723-
removedNodes
724-
).filter(
734+
const [addedNodes, removedNodes, changedSelectElements] =
735+
collectMutatedNodes(mutations);
736+
let relevantAddedElements = getCCAndAddressElements([...addedNodes]);
737+
// We only care about removed elements and changed select options
738+
// from the current set of detected fieldDetails
739+
let relevantRemovedElements = getCCAndAddressElements([
740+
...removedNodes,
741+
]).filter(
742+
element =>
743+
this.#fieldDetails && !!this.getFieldDetailByElement(element)
744+
);
745+
let relevantChangedSelectElements = [...changedSelectElements].filter(
725746
element =>
726747
this.#fieldDetails && !!this.getFieldDetailByElement(element)
727748
);
728749

729-
if (!relevantRemovedElements.length && !relevantAddedElements.length) {
750+
if (
751+
!relevantRemovedElements.length &&
752+
!relevantAddedElements.length &&
753+
!relevantChangedSelectElements.length
754+
) {
730755
return;
731756
}
732757

733758
let changes = {};
759+
if (relevantChangedSelectElements.length) {
760+
changes[FORM_CHANGE_REASON.SELECT_OPTIONS_CHANGED] =
761+
relevantChangedSelectElements;
762+
}
734763
if (relevantRemovedElements.length) {
735764
changes[FORM_CHANGE_REASON.NODES_REMOVED] = relevantRemovedElements;
736765
}
@@ -875,7 +904,8 @@ export class FormAutofillHandler {
875904
const value = profile[fieldName];
876905

877906
let option = cache[value]?.deref();
878-
if (!option) {
907+
908+
if (!option || !option.isConnected) {
879909
option = FormAutofillUtils.findSelectOption(element, profile, fieldName);
880910

881911
if (option) {

firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs

+22-17
Original file line numberDiff line numberDiff line change
@@ -333,27 +333,32 @@ export class FormAutofillSection {
333333
return data;
334334
}
335335

336+
shouldAutofillField(fieldDetail) {
337+
// We don't save security code, but if somehow the profile has securty code,
338+
// make sure we don't autofill it.
339+
if (fieldDetail.fieldName == "cc-csc") {
340+
return false;
341+
}
342+
343+
// When both visible and invisible elements exist, we only autofill the
344+
// visible element.
345+
if (!fieldDetail.isVisible) {
346+
return !this.fieldDetails.some(
347+
field => field.fieldName == fieldDetail.fieldName && field.isVisible
348+
);
349+
}
350+
351+
return true;
352+
}
353+
336354
/**
337355
* Heuristics to determine which fields to autofill when a section contains
338356
* multiple fields of the same type.
339357
*/
340358
getAutofillFields() {
341-
return this.fieldDetails.filter(fieldDetail => {
342-
// We don't save security code, but if somehow the profile has securty code,
343-
// make sure we don't autofill it.
344-
if (fieldDetail.fieldName == "cc-csc") {
345-
return false;
346-
}
347-
348-
// When both visible and invisible elements exist, we only autofill the
349-
// visible element.
350-
if (!fieldDetail.isVisible) {
351-
return !this.fieldDetails.some(
352-
field => field.fieldName == fieldDetail.fieldName && field.isVisible
353-
);
354-
}
355-
return true;
356-
});
359+
return this.fieldDetails.filter(fieldDetail =>
360+
this.shouldAutofillField(fieldDetail)
361+
);
357362
}
358363

359364
/*
@@ -645,7 +650,6 @@ export class FormAutofillCreditCardSection extends FormAutofillSection {
645650
result = decrypted ? "success" : "fail_user_canceled";
646651
} catch (ex) {
647652
result = "fail_error";
648-
throw ex;
649653
} finally {
650654
Glean.formautofill.promptShownOsReauth.record({
651655
trigger: "autofill",
@@ -677,6 +681,7 @@ export class FormAutofillCreditCardSection extends FormAutofillSection {
677681
} catch (e) {
678682
errorResult = e.result;
679683
if (e.result != Cr.NS_ERROR_ABORT) {
684+
this.log.warn(`Decryption failed with result: ${e.result}`);
680685
throw e;
681686
}
682687
this.log.warn("User canceled encryption login");

firefox-ios/Client/Assets/CC_Script/FormAutofillUtils.sys.mjs

+23-12
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,9 @@ FormAutofillUtils = {
456456
* @returns {boolean} true if the element can be autofilled
457457
*/
458458
isFieldAutofillable(element) {
459-
return element && !element.readOnly && !element.disabled;
459+
return (
460+
element && !element.readOnly && !element.disabled && element.isConnected
461+
);
460462
},
461463

462464
/**
@@ -684,16 +686,26 @@ FormAutofillUtils = {
684686
*/
685687
buildRegionMapIfAvailable(subKeys, subIsoids, subNames, subLnames) {
686688
// Not all regions have sub_keys. e.g. DE
687-
if (
688-
!subKeys ||
689-
!subKeys.length ||
690-
(!subNames && !subLnames) ||
691-
(subNames && subKeys.length != subNames.length) ||
692-
(subLnames && subKeys.length != subLnames.length)
693-
) {
689+
if (!subKeys?.length) {
694690
return null;
695691
}
696692

693+
let names;
694+
if (!subNames && !subLnames) {
695+
// Use the keys if sub_names does not exist
696+
names = [...subKeys];
697+
} else {
698+
if (
699+
(subNames && subKeys.length != subNames.length) ||
700+
(subLnames && subKeys.length != subLnames.length)
701+
) {
702+
return null;
703+
}
704+
705+
// Apply sub_lnames if sub_names does not exist
706+
names = subNames || subLnames;
707+
}
708+
697709
// Overwrite subKeys with subIsoids, when available
698710
if (subIsoids && subIsoids.length && subIsoids.length == subKeys.length) {
699711
for (let i = 0; i < subIsoids.length; i++) {
@@ -703,8 +715,6 @@ FormAutofillUtils = {
703715
}
704716
}
705717

706-
// Apply sub_lnames if sub_names does not exist
707-
let names = subNames || subLnames;
708718
return new Map(subKeys.map((key, index) => [key, names[index]]));
709719
},
710720

@@ -831,7 +841,7 @@ FormAutofillUtils = {
831841
continue;
832842
}
833843
// Apply sub_lnames if sub_names does not exist
834-
subNames = subNames || subLnames;
844+
subNames = subNames || subLnames || subKeys;
835845

836846
let speculatedSubIndexes = [];
837847
for (const val of values) {
@@ -919,7 +929,8 @@ FormAutofillUtils = {
919929
continue;
920930
}
921931
// Apply sub_lnames if sub_names does not exist
922-
let names = dataset.sub_names || dataset.sub_lnames;
932+
let names =
933+
dataset.sub_names || dataset.sub_lnames || dataset.sub_keys;
923934
let isoids = dataset.sub_isoids;
924935

925936
// Go through options one by one to find a match.

0 commit comments

Comments
 (0)