mirror of
https://github.com/bitwarden/browser
synced 2026-02-03 10:13:31 +00:00
create inline menu exclusively on page targeted elements
This commit is contained in:
@@ -36,7 +36,11 @@ export interface AutofillOverlayContentService {
|
||||
setupOverlayListeners(
|
||||
autofillFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
autofillFieldData: AutofillField,
|
||||
pageDetails: AutofillPageDetails,
|
||||
pageDetails?: AutofillPageDetails,
|
||||
): Promise<void>;
|
||||
setupOverlayListenersOnQualifiedField(
|
||||
autofillFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
autofillFieldData: AutofillField,
|
||||
): Promise<void>;
|
||||
blurMostRecentlyFocusedField(isClosingInlineMenu?: boolean): void;
|
||||
getOwnedInlineMenuTagNames(): string[];
|
||||
|
||||
@@ -18,6 +18,7 @@ interface CollectAutofillContentService {
|
||||
autofillFormElements: AutofillFormElements;
|
||||
getPageDetails(): Promise<AutofillPageDetails>;
|
||||
getAutofillFieldElementByOpid(opid: string): HTMLElement | null;
|
||||
getTargetedFields(): {[key: string]: Element} | null;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -197,8 +197,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
async setupOverlayListeners(
|
||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
autofillFieldData: AutofillField,
|
||||
pageDetails: AutofillPageDetails,
|
||||
pageDetails?: AutofillPageDetails,
|
||||
) {
|
||||
// This should get targeted fields only
|
||||
if (formFieldElement && !pageDetails) {
|
||||
this.formFieldElements.set(formFieldElement, autofillFieldData);
|
||||
await this.setupFormFieldElementEventListeners(formFieldElement);
|
||||
await this.triggerFormFieldFocusedAction(formFieldElement);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
currentlyInSandboxedIframe() ||
|
||||
this.formFieldElements.has(formFieldElement) ||
|
||||
@@ -1265,7 +1274,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
* @param formFieldElement - The form field element to set up the inline menu on.
|
||||
* @param autofillFieldData - Autofill field data captured from the form field element.
|
||||
*/
|
||||
private async setupOverlayListenersOnQualifiedField(
|
||||
async setupOverlayListenersOnQualifiedField(
|
||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
autofillFieldData: AutofillField,
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import { AutofillFieldQualifierType } from "../enums/autofill-field.enums";
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import AutofillForm from "../models/autofill-form";
|
||||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
@@ -35,6 +38,8 @@ import {
|
||||
import { DomElementVisibilityService } from "./abstractions/dom-element-visibility.service";
|
||||
import { DomQueryService } from "./abstractions/dom-query.service";
|
||||
|
||||
type TargetedFields = { [type in AutofillFieldQualifierType]?: Element };
|
||||
|
||||
export class CollectAutofillContentService implements CollectAutofillContentServiceInterface {
|
||||
private readonly sendExtensionMessage = sendExtensionMessage;
|
||||
private readonly getAttributeBoolean = getAttributeBoolean;
|
||||
@@ -44,6 +49,10 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
|
||||
private _autofillFormElements: AutofillFormElements = new Map();
|
||||
private autofillFieldElements: AutofillFieldElements = new Map();
|
||||
private currentLocationHref = "";
|
||||
/**
|
||||
* A value of `null` indicates the configuration has not been initialized, whereas an empty object indicates no set rules.
|
||||
*/
|
||||
private readonly pageTargetingRules: null | { [type in AutofillFieldQualifierType]?: string } = {};
|
||||
private intersectionObserver: IntersectionObserver;
|
||||
private elementInitializingIntersectionObserver: Set<Element> = new Set();
|
||||
private mutationObserver: MutationObserver;
|
||||
@@ -89,6 +98,33 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
|
||||
// Set up listeners on top-layer candidates that predate Mutation Observer setup
|
||||
this.setupInitialTopLayerListeners();
|
||||
|
||||
const targetedFields = this.getTargetedFields();
|
||||
const targetedFieldNames = Object.keys(targetedFields) as AutofillFieldQualifierType[];
|
||||
if (targetedFieldNames.length) {
|
||||
for (const fieldName of targetedFieldNames) {
|
||||
const fieldNode = targetedFields[fieldName] as ElementWithOpId<FormFieldElement>;
|
||||
|
||||
void this.autofillOverlayContentService.setupOverlayListenersOnQualifiedField(
|
||||
fieldNode,
|
||||
{
|
||||
// @TODO needs an opid and added to the list in order for autofill to work
|
||||
opid: fieldNode.opid || `targeted_field_${fieldName}`,
|
||||
elementNumber: 0, // Not relevant when using targeting rules
|
||||
viewable: true,
|
||||
htmlID: fieldNode.getAttribute('id'),
|
||||
htmlName: fieldNode.getAttribute('name'),
|
||||
htmlClass: fieldNode.getAttribute('class'),
|
||||
tabindex: fieldNode.getAttribute('tabindex'),
|
||||
title: fieldNode.getAttribute('title'),
|
||||
inlineMenuFillType: CipherType.Login,
|
||||
fieldQualifier: fieldName,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return {} as AutofillPageDetails;
|
||||
}
|
||||
|
||||
if (!this.mutationObserver) {
|
||||
this.setupMutationObserver();
|
||||
}
|
||||
@@ -124,11 +160,40 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
|
||||
|
||||
this.domRecentlyMutated = false;
|
||||
const pageDetails = this.getFormattedPageDetails(autofillFormsData, autofillFieldsData);
|
||||
|
||||
this.setupOverlayListeners(pageDetails);
|
||||
|
||||
return pageDetails;
|
||||
}
|
||||
|
||||
getTargetedFields(): TargetedFields {
|
||||
if (this.pageTargetingRules) {
|
||||
const definedTargetingRuleFields = Object.keys(
|
||||
this.pageTargetingRules,
|
||||
) as AutofillFieldQualifierType[];
|
||||
|
||||
// Note - potential bottleneck at async lookup (alternatively, promise map)
|
||||
const foundTargetedFields = definedTargetingRuleFields.reduce((foundFields, fieldName) => {
|
||||
const targetingRule = this.pageTargetingRules[fieldName];
|
||||
const fieldMatches = this.domQueryService.queryDeepSelector(
|
||||
globalThis.document,
|
||||
targetingRule,
|
||||
);
|
||||
|
||||
return fieldMatches.length
|
||||
? {
|
||||
...foundFields,
|
||||
[fieldName]: fieldMatches[0],
|
||||
}
|
||||
: foundFields;
|
||||
}, {});
|
||||
|
||||
return foundTargetedFields;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an AutofillField element by its opid, will only return the first
|
||||
* element if there are multiple elements with the same opid. If no
|
||||
@@ -1248,6 +1313,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
|
||||
cancelIdleCallbackPolyfill(this.updateAfterMutationIdleCallback);
|
||||
}
|
||||
|
||||
// If page targeting rules are present, call `getFieldByQuery` for each field and bypass
|
||||
this.updateAfterMutationIdleCallback = requestIdleCallbackPolyfill(
|
||||
this.getPageDetails.bind(this),
|
||||
{ timeout: this.updateAfterMutationTimeout },
|
||||
@@ -1451,6 +1517,8 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
|
||||
* @param pageDetails - The page details to use for the inline menu listeners
|
||||
*/
|
||||
private setupOverlayListeners(pageDetails: AutofillPageDetails) {
|
||||
// if there are targeted rules for the page, get the nodes with those rules, and if successful, pass those to `autofillOverlayContentService.setupOverlayListeners`
|
||||
|
||||
if (this.autofillOverlayContentService) {
|
||||
this.autofillFieldElements.forEach((autofillField, formFieldElement) => {
|
||||
this.setupOverlayOnField(formFieldElement, autofillField, pageDetails);
|
||||
|
||||
Reference in New Issue
Block a user