1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

[PM-8027] Inlin menu appears within input fields that do not relate to user login

This commit is contained in:
Cesar Gonzalez
2024-05-09 17:27:04 -05:00
parent fb3766b6c1
commit 6b9564db8f
4 changed files with 166 additions and 10 deletions

View File

@@ -15,6 +15,8 @@ type UpdateAutofillDataAttributeParams = {
};
interface CollectAutofillContentService {
autofillFormElements: AutofillFormElements;
autofillFieldElements: AutofillFieldElements;
getPageDetails(): Promise<AutofillPageDetails>;
getAutofillFieldElementByOpid(opid: string): HTMLElement | null;
queryAllTreeWalkerNodes(

View File

@@ -0,0 +1,116 @@
import AutofillField from "../models/autofill-field";
import AutofillPageDetails from "../models/autofill-page-details";
import { AutoFillConstants } from "./autofill-constants";
export class AutofillFieldQualificationService {
autofillPageDetails: AutofillPageDetails;
private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
private excludedAutofillLoginTypesSet = new Set(AutoFillConstants.ExcludedAutofillLoginTypes);
private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(",");
private passwordFieldExcludeListString = AutoFillConstants.PasswordFieldExcludeList.join(",");
setAutofillPageDetails(autofillPageDetails: AutofillPageDetails): void {
this.autofillPageDetails = autofillPageDetails;
}
isLoginUsernameField(field: AutofillField): boolean {
return false;
}
isLoginPasswordField(field: AutofillField): boolean {
if (
field.type !== "password" ||
field.autoComplete === "new-password" ||
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet)
) {
return false;
}
if (this.fieldHasDisqualifyingAttributeValue(field)) {
return false;
}
if (field.type === "password") {
return true;
}
return this.isPseudoPasswordField(field);
}
private isPseudoPasswordField(field: AutofillField): boolean {
if (field.type !== "text") {
return false;
}
const testedValues = [field.htmlID, field.htmlName, field.placeholder];
for (let i = 0; i < testedValues.length; i++) {
if (this.valueIsLikePassword(testedValues[i])) {
return true;
}
}
return false;
}
private valueIsLikePassword(value: string): boolean {
if (value == null) {
return false;
}
// Removes all whitespace, _ and - characters
const cleanedValue = value.toLowerCase().replace(/[\s_-]/g, "");
if (cleanedValue.indexOf("password") < 0) {
return false;
}
return !(this.passwordFieldExcludeListString.indexOf(cleanedValue) > -1);
}
private fieldHasDisqualifyingAttributeValue(field: AutofillField): boolean {
const checkedAttributeValues = [field.htmlID, field.htmlName, field.placeholder];
for (let i = 0; i < checkedAttributeValues.length; i++) {
const checkedAttributeValue = checkedAttributeValues[i];
const cleanedValue = checkedAttributeValue?.toLowerCase().replace(/[\s_-]/g, "");
if (cleanedValue && this.fieldIgnoreListString.indexOf(cleanedValue) > -1) {
return true;
}
}
return false;
}
private isExcludedFieldType(field: AutofillField, excludedTypes: Set<string>): boolean {
if (excludedTypes.has(field.type)) {
return true;
}
return this.isSearchField(field);
}
private isSearchField(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;
}
// Separate camel case words and case them to lower case values
const camelCaseSeparatedFieldAttribute = matchFieldAttributeValues[attrIndex]
.replace(/([a-z])([A-Z])/g, "$1 $2")
.toLowerCase();
// Split the attribute by non-alphabetical characters to get the keywords
const attributeKeywords = camelCaseSeparatedFieldAttribute.split(/[^a-z]/gi);
for (let keywordIndex = 0; keywordIndex < attributeKeywords.length; keywordIndex++) {
if (this.searchFieldNamesSet.has(attributeKeywords[keywordIndex])) {
return true;
}
}
}
return false;
}
}

View File

@@ -4,29 +4,29 @@ import AutofillPageDetails from "../models/autofill-page-details";
import {
ElementWithOpId,
FillableFormFieldElement,
FormFieldElement,
FormElementWithAttribute,
FormFieldElement,
} from "../types";
import {
elementIsDescriptionDetailsElement,
elementIsDescriptionTermElement,
elementIsFillableFormField,
elementIsFormElement,
elementIsInputElement,
elementIsLabelElement,
elementIsSelectElement,
elementIsSpanElement,
nodeIsFormElement,
nodeIsElement,
elementIsInputElement,
elementIsTextAreaElement,
nodeIsElement,
nodeIsFormElement,
} from "../utils";
import { AutofillOverlayContentService } from "./abstractions/autofill-overlay-content.service";
import {
UpdateAutofillDataAttributeParams,
AutofillFieldElements,
AutofillFormElements,
CollectAutofillContentService as CollectAutofillContentServiceInterface,
UpdateAutofillDataAttributeParams,
} from "./abstractions/collect-autofill-content.service";
import { DomElementVisibilityService } from "./abstractions/dom-element-visibility.service";
@@ -35,8 +35,8 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
private readonly autofillOverlayContentService: AutofillOverlayContentService;
private noFieldsFound = false;
private domRecentlyMutated = true;
private autofillFormElements: AutofillFormElements = new Map();
private autofillFieldElements: AutofillFieldElements = new Map();
autofillFormElements: AutofillFormElements = new Map();
autofillFieldElements: AutofillFieldElements = new Map();
private currentLocationHref = "";
private intersectionObserver: IntersectionObserver;
private elementInitializingIntersectionObserver: Set<Element> = new Set();
@@ -454,11 +454,11 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
* @private
*/
private getAutoCompleteAttribute(element: ElementWithOpId<FormFieldElement>): string {
const autoCompleteType =
return (
this.getPropertyOrAttribute(element, "x-autocompletetype") ||
this.getPropertyOrAttribute(element, "autocompletetype") ||
this.getPropertyOrAttribute(element, "autocomplete");
return autoCompleteType !== "off" ? autoCompleteType : null;
this.getPropertyOrAttribute(element, "autocomplete")
);
}
/**

View File

@@ -0,0 +1,38 @@
import { Observable, concatMap } from "rxjs";
import { DerivedStateDependencies } from "../../../types/state";
import { DeriveDefinition } from "../derive-definition";
import { DerivedState } from "../derived-state";
import { DerivedStateProvider } from "../derived-state.provider";
export class InlineDerivedStateProvider implements DerivedStateProvider {
get<TFrom, TTo, TDeps extends DerivedStateDependencies>(
parentState$: Observable<TFrom>,
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
dependencies: TDeps,
): DerivedState<TTo> {
// TODO: Cache?
return new InlineDerivedState(parentState$, deriveDefinition, dependencies);
}
}
class InlineDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies>
implements DerivedState<TTo>
{
constructor(
parentState$: Observable<TFrom>,
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
dependencies: TDeps,
) {
this.state$ = parentState$.pipe(
concatMap(async (value) => await deriveDefinition.derive(value, dependencies)),
);
}
state$: Observable<TTo>;
forceValue(value: TTo): Promise<TTo> {
// No need to force anything, we don't keep a cache
return Promise.resolve(value);
}
}