Skip to content

Commit 3079445

Browse files
authored
Merge branch 'main' into dbajpeyi/fix/ios-prompt-cc-once-and-icons
2 parents ac794ab + 3d32056 commit 3079445

12 files changed

+262
-3
lines changed

dist/autofill-debug.js

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/autofill.js

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Form/Form.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,10 @@ class Form {
393393
return this.device.settings.featureToggles.unknown_username_categorization && this.isLogin && this.ambiguousInputs?.length === 1;
394394
}
395395

396+
canCategorizePasswordVariant() {
397+
return this.device.settings.featureToggles.password_variant_categorization;
398+
}
399+
396400
/**
397401
* Takes an ambiguous input and tries to get a target type that the input should be categorized to.
398402
* @param {HTMLInputElement} ambiguousInput
@@ -450,6 +454,33 @@ class Form {
450454
}
451455
}
452456

457+
/**
458+
* Recategorizes the new/current password field variant
459+
*/
460+
recategorizeInputVariantIfNeeded() {
461+
let newPasswordFields = 0;
462+
let currentPasswordFields = 0;
463+
let firstNewPasswordField = null;
464+
465+
for (const credentialElement of this.inputs.credentials) {
466+
const variant = getInputVariant(credentialElement);
467+
if (variant === 'new') {
468+
newPasswordFields++;
469+
if (!firstNewPasswordField) firstNewPasswordField = credentialElement;
470+
}
471+
if (variant === 'current') currentPasswordFields++;
472+
473+
// Short circuit if the field counts wouldn't match the requirements
474+
if (newPasswordFields > 3 || currentPasswordFields > 0) return;
475+
}
476+
477+
// If a form has 3 new-password fields, but no current, the first is likely a current
478+
if (newPasswordFields === 3 && currentPasswordFields === 0) {
479+
if (shouldLog()) console.log('Recategorizing password variant to "current"', firstNewPasswordField);
480+
firstNewPasswordField.setAttribute(ATTR_INPUT_TYPE, 'credentials.password.current');
481+
}
482+
}
483+
453484
categorizeInputs() {
454485
const selector = this.matching.cssSelector('formInputsSelector');
455486
// If there's no form container and it's just a lonely input field (this.form is an input field)
@@ -475,6 +506,8 @@ class Form {
475506

476507
if (this.canCategorizeAmbiguousInput()) this.recategorizeInputToTargetType();
477508

509+
if (this.canCategorizePasswordVariant()) this.recategorizeInputVariantIfNeeded();
510+
478511
// If the form has only one input and it's unknown, discard the form
479512
if (this.inputs.all.size === 1 && this.inputs.unknown.size === 1) {
480513
this.destroy();

src/Form/Form.test.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ describe('Form re-categorizes inputs', () => {
430430
const deviceInterface = InterfacePrototype.default();
431431
deviceInterface.settings.setFeatureToggles({
432432
unknown_username_categorization: true,
433+
password_variant_categorization: true,
433434
});
434435
describe('Should recategorize', () => {
435436
test('when form has unknown input and has username data available', () => {
@@ -735,3 +736,114 @@ describe('site specific fixes', () => {
735736
});
736737
});
737738
});
739+
740+
describe('Password variant recategorization', () => {
741+
test('recategorizes first password field to current-password when there are 3 new-password fields', () => {
742+
attachAndReturnGenericForm(`
743+
<form>
744+
<input id="old-password" type="password" value="oldPassword" />
745+
<input id="new-password" type="password" value="newPassword" autocomplete="new-password" />
746+
<input id="confirm-password" type="password" value="confirmPassword" autocomplete="new-password" />
747+
<button type="submit">Change Password</button>
748+
</form>`);
749+
750+
// Create a device interface with password_variant_categorization enabled
751+
const deviceInterface = InterfacePrototype.default();
752+
deviceInterface.settings.setFeatureToggles({
753+
password_variant_categorization: true,
754+
});
755+
756+
createScanner(deviceInterface).findEligibleInputs(document);
757+
758+
// Query password inputs by their IDs
759+
const oldPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('old-password'));
760+
const newPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('new-password'));
761+
const confirmPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('confirm-password'));
762+
763+
// The first password field should be recategorized to current-password
764+
expect(oldPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.current');
765+
// The other password fields should remain as new-password
766+
expect(newPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
767+
expect(confirmPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
768+
});
769+
770+
test('does not recategorize when there are less than 3 new-password fields', () => {
771+
attachAndReturnGenericForm(`
772+
<form>
773+
<input id="new-password" type="password" value="newPassword" autocomplete="new-password" />
774+
<input id="confirm-password" type="password" value="confirmPassword" autocomplete="new-password" />
775+
<button type="submit">Create Account</button>
776+
</form>`);
777+
778+
const deviceInterface = InterfacePrototype.default();
779+
deviceInterface.settings.setFeatureToggles({
780+
password_variant_categorization: true,
781+
});
782+
783+
createScanner(deviceInterface).findEligibleInputs(document);
784+
785+
// Query password inputs by their IDs
786+
const newPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('new-password'));
787+
const confirmPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('confirm-password'));
788+
789+
// Both password fields should remain as new-password
790+
expect(newPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
791+
expect(confirmPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
792+
});
793+
794+
test('does not recategorize when there is already a current-password field', () => {
795+
attachAndReturnGenericForm(`
796+
<form>
797+
<input id="current-password" type="password" value="oldPassword" autocomplete="current-password" />
798+
<input id="new-password" type="password" value="newPassword" autocomplete="new-password" />
799+
<input id="confirm-password" type="password" value="confirmPassword" autocomplete="new-password" />
800+
<button type="submit">Change Password</button>
801+
</form>`);
802+
803+
const deviceInterface = InterfacePrototype.default();
804+
deviceInterface.settings.setFeatureToggles({
805+
password_variant_categorization: true,
806+
});
807+
808+
createScanner(deviceInterface).findEligibleInputs(document);
809+
810+
// Query password inputs by their IDs
811+
const currentPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('current-password'));
812+
const newPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('new-password'));
813+
const confirmPasswordInput = /** @type {HTMLInputElement} */ (document.getElementById('confirm-password'));
814+
815+
// The first password should remain as current-password
816+
expect(currentPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.current');
817+
// The other password fields should remain as new-password
818+
expect(newPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
819+
expect(confirmPasswordInput.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
820+
});
821+
822+
test('does not recategorize when feature toggle is disabled', () => {
823+
attachAndReturnGenericForm(`
824+
<form>
825+
<input id="password1" type="password" value="oldPassword" autocomplete="new-password" />
826+
<input id="password2" type="password" value="newPassword" autocomplete="new-password" />
827+
<input id="password3" type="password" value="confirmPassword" autocomplete="new-password" />
828+
<button type="submit">Change Password</button>
829+
</form>`);
830+
831+
// Create a device interface with password_variant_categorization disabled
832+
const deviceInterface = InterfacePrototype.default();
833+
deviceInterface.settings.setFeatureToggles({
834+
password_variant_categorization: false,
835+
});
836+
837+
createScanner(deviceInterface).findEligibleInputs(document);
838+
839+
// Query password inputs by their IDs
840+
const password1 = /** @type {HTMLInputElement} */ (document.getElementById('password1'));
841+
const password2 = /** @type {HTMLInputElement} */ (document.getElementById('password2'));
842+
const password3 = /** @type {HTMLInputElement} */ (document.getElementById('password3'));
843+
844+
// All password fields should remain as new-password
845+
expect(password1.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
846+
expect(password2.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
847+
expect(password3.getAttribute(constants.ATTR_INPUT_TYPE)).toBe('credentials.password.new');
848+
});
849+
});

src/Form/input-classifiers.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ describe.each(testCases)('Test $html fields', (testCase) => {
214214
deviceInterface.settings.setAvailableInputTypes(availableInputTypes);
215215
deviceInterface.settings.setFeatureToggles({
216216
unknown_username_categorization: true,
217+
password_variant_categorization: true,
217218
});
218219
const scanner = createScanner(deviceInterface);
219220
scanner.findEligibleInputs(document);

src/Settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ export class Settings {
399399
inputType_creditCards: false,
400400
inlineIcon_credentials: false,
401401
unknown_username_categorization: false,
402+
password_variant_categorization: false,
402403
partial_form_saves: false,
403404
},
404405
/** @type {AvailableInputTypes} */

src/Settings.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ describe('Settings', () => {
148148
"inputType_identities": false,
149149
"partial_form_saves": false,
150150
"password_generation": false,
151+
"password_variant_categorization": false,
151152
"unknown_username_categorization": false,
152153
}
153154
`);

src/deviceApiCalls/__generated__/validators-ts.ts

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/deviceApiCalls/__generated__/validators.zod.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/deviceApiCalls/schemas/autofill-settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@
4040
},
4141
"unknown_username_categorization": {
4242
"type": "boolean",
43-
"description": "If true, we will attempt categorizaing username, based on the rest of the input fields in the form"
43+
"description": "If true, we will attempt re-categorizing username, based on the rest of the input fields in the form"
44+
},
45+
"password_variant_categorization": {
46+
"type": "boolean",
47+
"description": "If true, we will attempt re-categorizing the password variant, based on other fields in the form"
4448
},
4549
"partial_form_saves": {
4650
"type": "boolean",

test-forms/index.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,5 +562,6 @@
562562
{ "html": "sams_club_credit_card.html", "expectedFailures": ["expirationMonth", "expirationYear"] },
563563
{ "html": "peacocktv_payment-form.html" },
564564
{ "html": "victoriassecret_payment-form.html" },
565-
{ "html": "uhaul_payment-form.html" }
565+
{ "html": "uhaul_payment-form.html" },
566+
{ "html": "sielte_change-password.html" }
566567
]

0 commit comments

Comments
 (0)