[PM-8027] Working through logic heuristics that will help us determine login form fields
This commit is contained in:
parent
d1b6b40b5d
commit
7786e6c567
|
@ -1,5 +1,6 @@
|
|||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
import { AutofillOverlayContentService } from "../services/abstractions/autofill-overlay-content.service";
|
||||
import { AutofillFieldQualificationService } from "../services/autofill-field-qualification.service";
|
||||
import CollectAutofillContentService from "../services/collect-autofill-content.service";
|
||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||
import InsertAutofillContentService from "../services/insert-autofill-content.service";
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
} from "./abstractions/autofill-init";
|
||||
|
||||
class AutofillInit implements AutofillInitInterface {
|
||||
private readonly autofillFieldQualificationService: AutofillFieldQualificationService;
|
||||
private readonly autofillOverlayContentService: AutofillOverlayContentService | undefined;
|
||||
private readonly domElementVisibilityService: DomElementVisibilityService;
|
||||
private readonly collectAutofillContentService: CollectAutofillContentService;
|
||||
|
@ -38,6 +40,7 @@ class AutofillInit implements AutofillInitInterface {
|
|||
* @param autofillOverlayContentService - The autofill overlay content service, potentially undefined.
|
||||
*/
|
||||
constructor(autofillOverlayContentService?: AutofillOverlayContentService) {
|
||||
this.autofillFieldQualificationService = new AutofillFieldQualificationService();
|
||||
this.autofillOverlayContentService = autofillOverlayContentService;
|
||||
this.domElementVisibilityService = new DomElementVisibilityService();
|
||||
this.collectAutofillContentService = new CollectAutofillContentService(
|
||||
|
@ -98,6 +101,17 @@ class AutofillInit implements AutofillInitInterface {
|
|||
): Promise<AutofillPageDetails | void> {
|
||||
const pageDetails: AutofillPageDetails =
|
||||
await this.collectAutofillContentService.getPageDetails();
|
||||
|
||||
// console.log(pageDetails);
|
||||
// pageDetails.fields.forEach((field) => {
|
||||
// const isLoginField = this.autofillFieldQualificationService.isFieldForLoginForm(
|
||||
// field,
|
||||
// pageDetails,
|
||||
// );
|
||||
//
|
||||
// console.log(isLoginField, field);
|
||||
// });
|
||||
|
||||
if (sendDetailsInResponse) {
|
||||
return pageDetails;
|
||||
}
|
||||
|
|
|
@ -10,13 +10,163 @@ export class AutofillFieldQualificationService {
|
|||
private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(",");
|
||||
private passwordFieldExcludeListString = AutoFillConstants.PasswordFieldExcludeList.join(",");
|
||||
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
|
||||
private invalidAutocompleteValuesSet = new Set(["off", "false"]);
|
||||
|
||||
isFieldForLoginForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean {
|
||||
// Check if the field
|
||||
return false;
|
||||
// TODO: Determine whether it makes sense to even incorporate this.
|
||||
if (!field.viewable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isExistingPasswordField = this.isExistingPasswordField(field);
|
||||
if (isExistingPasswordField) {
|
||||
return this.isPasswordFieldForLoginForm(field, pageDetails);
|
||||
}
|
||||
|
||||
const isUsernameField = this.isUsernameField(field);
|
||||
if (!isUsernameField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.isUsernameFieldForLoginForm(field, pageDetails);
|
||||
}
|
||||
|
||||
isUsernameField(field: AutofillField): boolean {
|
||||
private isPasswordFieldForLoginForm(
|
||||
field: AutofillField,
|
||||
pageDetails: AutofillPageDetails,
|
||||
): boolean {
|
||||
// Check if the autocomplete attribute is set to "current-password", if so treat this as a password field
|
||||
if (field.autoCompleteType === "current-password") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the field has a form parent
|
||||
const parentForm = pageDetails.forms[field.form];
|
||||
const usernameFieldsInPageDetails = pageDetails.fields.filter(this.isUsernameField);
|
||||
const passwordFieldsInPageDetails = pageDetails.fields.filter(this.isExistingPasswordField);
|
||||
// If no form parent is found, check if a username field exists and no other password fields are found in the page details, if so treat this as a password field
|
||||
if (
|
||||
!parentForm &&
|
||||
usernameFieldsInPageDetails.length === 1 &&
|
||||
passwordFieldsInPageDetails.length === 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no form parent is found and the autocomplete attribute is set to "off" or "false", this is not a password field
|
||||
if (!parentForm && this.invalidAutocompleteValuesSet.has(field.autoCompleteType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the field has a form parent and if the form has username field and no other password fields exist, if so treat this as a password field
|
||||
if (
|
||||
parentForm &&
|
||||
usernameFieldsInPageDetails.length === 1 &&
|
||||
passwordFieldsInPageDetails.length === 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the field has a form parent and there are multiple visible password fields in the form, this is not a username field
|
||||
const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter(
|
||||
(field) => field.viewable,
|
||||
);
|
||||
if (parentForm && visiblePasswordFieldsInPageDetails.length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the field has a form parent a no username field exists and the field has an autocomplete attribute set to "off" or "false", this is not a password field
|
||||
if (
|
||||
parentForm &&
|
||||
usernameFieldsInPageDetails.length === 0 &&
|
||||
this.invalidAutocompleteValuesSet.has(field.autoCompleteType)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private isUsernameFieldForLoginForm(
|
||||
field: AutofillField,
|
||||
pageDetails: AutofillPageDetails,
|
||||
): boolean {
|
||||
// console.log(field);
|
||||
|
||||
// Check if the autocomplete attribute is set to "username", if so treat this as a username field
|
||||
if (field.autoCompleteType === "username") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the field has a form parent
|
||||
const parentForm = pageDetails.forms[field.form];
|
||||
const passwordFieldsInPageDetails = pageDetails.fields.filter(this.isExistingPasswordField);
|
||||
// console.log(passwordFieldsInPageDetails);
|
||||
|
||||
// If no form parent is found, check if a single password field is found in the page details, if so treat this as a username field
|
||||
if (!parentForm && passwordFieldsInPageDetails.length === 1) {
|
||||
// TODO: We should consider checking the distance between the username and password fields in the DOM to determine if they are close enough to be considered a pair
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no form parent is found and the autocomplete attribute is set to "off" or "false", this is not a username field
|
||||
if (!parentForm && this.invalidAutocompleteValuesSet.has(field.autoCompleteType)) {
|
||||
// console.log("invalid autocomplete value");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the field has a form parent and if the form has a single password field, if so treat this as a username field
|
||||
if (
|
||||
parentForm &&
|
||||
passwordFieldsInPageDetails.length === 1 &&
|
||||
parentForm === pageDetails.forms[passwordFieldsInPageDetails[0].form] &&
|
||||
field.elementNumber < passwordFieldsInPageDetails[0].elementNumber
|
||||
) {
|
||||
// console.log("shared form");
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the field has a form parent and the form has a single password that is before the username, this is not a username field
|
||||
if (
|
||||
parentForm &&
|
||||
passwordFieldsInPageDetails.length === 1 &&
|
||||
(parentForm !== pageDetails.forms[passwordFieldsInPageDetails[0].form] ||
|
||||
field.elementNumber >= passwordFieldsInPageDetails[0].elementNumber)
|
||||
) {
|
||||
// console.log("username field is below password field");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the field has a form parent and there are multiple visible password fields in the form, this is not a username field
|
||||
const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter(
|
||||
(field) => field.viewable,
|
||||
);
|
||||
if (parentForm && visiblePasswordFieldsInPageDetails.length > 1) {
|
||||
// console.log("multiple password fields");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the field has a form parent and the form has no password fields and has an autocomplete attribute set to "off" or "false", this is not a username field
|
||||
if (
|
||||
parentForm &&
|
||||
passwordFieldsInPageDetails.length === 0 &&
|
||||
this.invalidAutocompleteValuesSet.has(field.autoCompleteType)
|
||||
) {
|
||||
// console.log("no password fields");
|
||||
return false;
|
||||
}
|
||||
|
||||
const otherFieldsInForm = pageDetails.fields.filter((f) => f.form === field.form);
|
||||
// If the parent form has no password fields and the form has multiple fields, this is not a username field
|
||||
if (parentForm && passwordFieldsInPageDetails.length === 0 && otherFieldsInForm.length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// console.log("no previous conditions met");
|
||||
return true;
|
||||
}
|
||||
|
||||
isUsernameField = (field: AutofillField): boolean => {
|
||||
if (
|
||||
!this.usernameFieldTypes.has(field.type) ||
|
||||
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet)
|
||||
|
@ -25,17 +175,17 @@ export class AutofillFieldQualificationService {
|
|||
}
|
||||
|
||||
return this.keywordsFoundInFieldData(field, AutoFillConstants.UsernameFieldNames);
|
||||
}
|
||||
};
|
||||
|
||||
isExistingPasswordField(field: AutofillField): boolean {
|
||||
if (field.autoComplete === "new-password") {
|
||||
isExistingPasswordField = (field: AutofillField): boolean => {
|
||||
if (field.autoCompleteType === "new-password") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.isPasswordField(field);
|
||||
}
|
||||
};
|
||||
|
||||
isPasswordField(field: AutofillField): boolean {
|
||||
isPasswordField = (field: AutofillField): boolean => {
|
||||
const isInputPasswordType = field.type === "password";
|
||||
if (
|
||||
!isInputPasswordType ||
|
||||
|
@ -46,7 +196,7 @@ export class AutofillFieldQualificationService {
|
|||
}
|
||||
|
||||
return isInputPasswordType || this.isLikePasswordField(field);
|
||||
}
|
||||
};
|
||||
|
||||
private isLikePasswordField(field: AutofillField): boolean {
|
||||
if (field.type !== "text") {
|
||||
|
|
Loading…
Reference in New Issue