diff --git a/apps/browser/src/autofill/services/autofill-constants.ts b/apps/browser/src/autofill/services/autofill-constants.ts index 55c3cced726..7467d5d4ba7 100644 --- a/apps/browser/src/autofill/services/autofill-constants.ts +++ b/apps/browser/src/autofill/services/autofill-constants.ts @@ -50,6 +50,15 @@ export class AutoFillConstants { static readonly SearchFieldNames: string[] = ["search", "query", "find", "go"]; + static readonly NewEmailFieldKeywords: string[] = [ + "new-email", + "newemail", + "new email", + "neue e-mail", + ]; + + static readonly NewsletterFormNames: string[] = ["newsletter"]; + static readonly FieldIgnoreList: string[] = ["captcha", "findanything", "forgot"]; static readonly PasswordFieldExcludeList: string[] = [ diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts index 9b16a0cfbdd..b12017484eb 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts @@ -58,6 +58,8 @@ export class InlineMenuFieldQualificationService "neue e-mail", "pwdcheck", ]; + private newEmailFieldKeywords = new Set(AutoFillConstants.NewEmailFieldKeywords); + private newsletterFormKeywords = new Set(AutoFillConstants.NewsletterFormNames); private updatePasswordFieldKeywords = [ "update password", "change password", @@ -152,6 +154,61 @@ export class InlineMenuFieldQualificationService private totpFieldAutocompleteValue = "one-time-code"; private premiumEnabled = false; + /** + * Validates the provided field to indicate if the field is a new email field used for account creation/registration. + * + * @param field - The field to validate + */ + private isExplicitIdentityEmailField(field: AutofillField): boolean { + const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder]; + for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) { + if (!matchFieldAttributeValues[attrIndex]) { + continue; + } + + for (let keywordIndex = 0; keywordIndex < matchFieldAttributeValues.length; keywordIndex++) { + if (this.newEmailFieldKeywords.has(matchFieldAttributeValues[attrIndex])) { + return true; + } + } + } + + return false; + } + + /** + * Validates the provided form to indicate if the form is related to newsletter registration. + * + * @param parentForm - The form to validate + */ + private isNewsletterForm(parentForm: any): boolean { + if (!parentForm) { + return false; + } + + const matchFieldAttributeValues = [ + parentForm.type, + parentForm.htmlName, + parentForm.htmlID, + parentForm.placeholder, + ]; + + for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) { + const attrValue = matchFieldAttributeValues[attrIndex]; + if (!attrValue || typeof attrValue !== "string") { + continue; + } + const attrValueLower = attrValue.toLowerCase(); + for (const keyword of this.newsletterFormKeywords) { + if (attrValueLower.includes(keyword.toLowerCase())) { + return true; + } + } + } + + return false; + } + constructor() { void Promise.all([ sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag"), @@ -300,7 +357,11 @@ export class InlineMenuFieldQualificationService return false; } - return this.fieldContainsAutocompleteValues(field, this.identityAutocompleteValues); + return ( + // Recognize explicit identity email fields (like id="new-email") + this.isFieldForIdentityEmail(field) || + this.fieldContainsAutocompleteValues(field, this.identityAutocompleteValues) + ); } /** @@ -397,6 +458,12 @@ export class InlineMenuFieldQualificationService ): boolean { // If the provided field is set with an autocomplete of "username", we should assume that // the page developer intends for this field to be interpreted as a username field. + + // Exclude non-login email field from being treated as a login username field + if (this.isExplicitIdentityEmailField(field)) { + return false; + } + if (this.fieldContainsAutocompleteValues(field, this.loginUsernameAutocompleteValues)) { const newPasswordFieldsInPageDetails = pageDetails.fields.filter( (field) => field.viewable && this.isNewPasswordField(field), @@ -415,6 +482,10 @@ export class InlineMenuFieldQualificationService const parentForm = pageDetails.forms[field.form]; const passwordFieldsInPageDetails = pageDetails.fields.filter(this.isCurrentPasswordField); + if (this.isNewsletterForm(parentForm)) { + return false; + } + // If the field is not structured within a form, we need to identify if the field is used in conjunction // with a password field. If that's the case, then we should assume that it is a form field element. if (!parentForm) { @@ -822,9 +893,14 @@ export class InlineMenuFieldQualificationService * @param field - The field to validate */ isFieldForIdentityEmail = (field: AutofillField): boolean => { + if (this.isExplicitIdentityEmailField(field)) { + return true; + } + if ( this.fieldContainsAutocompleteValues(field, this.emailAutocompleteValue) || - field.type === "email" + field.type === "email" || + field.htmlName === "email" ) { return true; }