mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 02:03: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 {
|
interface CollectAutofillContentService {
|
||||||
|
autofillFormElements: AutofillFormElements;
|
||||||
|
autofillFieldElements: AutofillFieldElements;
|
||||||
getPageDetails(): Promise<AutofillPageDetails>;
|
getPageDetails(): Promise<AutofillPageDetails>;
|
||||||
getAutofillFieldElementByOpid(opid: string): HTMLElement | null;
|
getAutofillFieldElementByOpid(opid: string): HTMLElement | null;
|
||||||
queryAllTreeWalkerNodes(
|
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 {
|
import {
|
||||||
ElementWithOpId,
|
ElementWithOpId,
|
||||||
FillableFormFieldElement,
|
FillableFormFieldElement,
|
||||||
FormFieldElement,
|
|
||||||
FormElementWithAttribute,
|
FormElementWithAttribute,
|
||||||
|
FormFieldElement,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
elementIsDescriptionDetailsElement,
|
elementIsDescriptionDetailsElement,
|
||||||
elementIsDescriptionTermElement,
|
elementIsDescriptionTermElement,
|
||||||
elementIsFillableFormField,
|
elementIsFillableFormField,
|
||||||
elementIsFormElement,
|
elementIsFormElement,
|
||||||
|
elementIsInputElement,
|
||||||
elementIsLabelElement,
|
elementIsLabelElement,
|
||||||
elementIsSelectElement,
|
elementIsSelectElement,
|
||||||
elementIsSpanElement,
|
elementIsSpanElement,
|
||||||
nodeIsFormElement,
|
|
||||||
nodeIsElement,
|
|
||||||
elementIsInputElement,
|
|
||||||
elementIsTextAreaElement,
|
elementIsTextAreaElement,
|
||||||
|
nodeIsElement,
|
||||||
|
nodeIsFormElement,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import { AutofillOverlayContentService } from "./abstractions/autofill-overlay-content.service";
|
import { AutofillOverlayContentService } from "./abstractions/autofill-overlay-content.service";
|
||||||
import {
|
import {
|
||||||
UpdateAutofillDataAttributeParams,
|
|
||||||
AutofillFieldElements,
|
AutofillFieldElements,
|
||||||
AutofillFormElements,
|
AutofillFormElements,
|
||||||
CollectAutofillContentService as CollectAutofillContentServiceInterface,
|
CollectAutofillContentService as CollectAutofillContentServiceInterface,
|
||||||
|
UpdateAutofillDataAttributeParams,
|
||||||
} from "./abstractions/collect-autofill-content.service";
|
} from "./abstractions/collect-autofill-content.service";
|
||||||
import { DomElementVisibilityService } from "./abstractions/dom-element-visibility.service";
|
import { DomElementVisibilityService } from "./abstractions/dom-element-visibility.service";
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
|||||||
private readonly autofillOverlayContentService: AutofillOverlayContentService;
|
private readonly autofillOverlayContentService: AutofillOverlayContentService;
|
||||||
private noFieldsFound = false;
|
private noFieldsFound = false;
|
||||||
private domRecentlyMutated = true;
|
private domRecentlyMutated = true;
|
||||||
private autofillFormElements: AutofillFormElements = new Map();
|
autofillFormElements: AutofillFormElements = new Map();
|
||||||
private autofillFieldElements: AutofillFieldElements = new Map();
|
autofillFieldElements: AutofillFieldElements = new Map();
|
||||||
private currentLocationHref = "";
|
private currentLocationHref = "";
|
||||||
private intersectionObserver: IntersectionObserver;
|
private intersectionObserver: IntersectionObserver;
|
||||||
private elementInitializingIntersectionObserver: Set<Element> = new Set();
|
private elementInitializingIntersectionObserver: Set<Element> = new Set();
|
||||||
@@ -454,11 +454,11 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private getAutoCompleteAttribute(element: ElementWithOpId<FormFieldElement>): string {
|
private getAutoCompleteAttribute(element: ElementWithOpId<FormFieldElement>): string {
|
||||||
const autoCompleteType =
|
return (
|
||||||
this.getPropertyOrAttribute(element, "x-autocompletetype") ||
|
this.getPropertyOrAttribute(element, "x-autocompletetype") ||
|
||||||
this.getPropertyOrAttribute(element, "autocompletetype") ||
|
this.getPropertyOrAttribute(element, "autocompletetype") ||
|
||||||
this.getPropertyOrAttribute(element, "autocomplete");
|
this.getPropertyOrAttribute(element, "autocomplete")
|
||||||
return autoCompleteType !== "off" ? autoCompleteType : null;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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