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:
@@ -15,6 +15,8 @@ type UpdateAutofillDataAttributeParams = {
|
||||
};
|
||||
|
||||
interface CollectAutofillContentService {
|
||||
autofillFormElements: AutofillFormElements;
|
||||
autofillFieldElements: AutofillFieldElements;
|
||||
getPageDetails(): Promise<AutofillPageDetails>;
|
||||
getAutofillFieldElementByOpid(opid: string): HTMLElement | null;
|
||||
queryAllTreeWalkerNodes(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user