From e9c351f7f319bc104afd35e53096ab53d88857f3 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Sun, 7 Apr 2024 17:11:27 -0500 Subject: [PATCH] [PM-5189] Reworking extension messages used within autofill init --- .../autofill/background/overlay.background.ts | 11 +- .../content/abstractions/autofill-init.ts | 12 +- .../autofill/content/autofill-init.spec.ts | 27 --- .../src/autofill/content/autofill-init.ts | 179 +----------------- .../abstractions/inline-menu-elements.ts | 2 +- .../overlay/content/inline-menu-elements.ts | 19 +- .../autofill-overlay-content.service.ts | 14 +- .../autofill-overlay-content.service.ts | 140 +++++++++++++- 8 files changed, 162 insertions(+), 242 deletions(-) diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 65aa340b99f..a0eef362067 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -326,13 +326,8 @@ class OverlayBackground implements OverlayBackgroundInterface { subFrameOffsetsForTab.set(frameId, null); void BrowserApi.tabSendMessage( tab, - { - command: "getSubFrameOffsetsThroughWindowMessaging", - subFrameId: frameId, - }, - { - frameId: frameId, - }, + { command: "getSubFrameOffsetsFromWindowMessage", subFrameId: frameId }, + { frameId: frameId }, ); return; } @@ -494,7 +489,7 @@ class OverlayBackground implements OverlayBackgroundInterface { await BrowserApi.tabSendMessage( sender.tab, - { command: "updateInlineMenuElementsPosition", overlayElement }, + { command: "appendInlineMenuElementsToDom", overlayElement }, { frameId: 0 }, ); diff --git a/apps/browser/src/autofill/content/abstractions/autofill-init.ts b/apps/browser/src/autofill/content/abstractions/autofill-init.ts index 59e44bfed0c..0ab2c342eb1 100644 --- a/apps/browser/src/autofill/content/abstractions/autofill-init.ts +++ b/apps/browser/src/autofill/content/abstractions/autofill-init.ts @@ -1,6 +1,5 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { SubFrameOffsetData } from "../../background/abstractions/overlay.background"; import AutofillScript from "../../models/autofill-script"; export type AutofillExtensionMessage = { @@ -19,7 +18,7 @@ export type AutofillExtensionMessage = { authStatus?: AuthenticationStatus; isFocusingFieldElement?: boolean; isOverlayCiphersPopulated?: boolean; - direction?: "previous" | "next"; + direction?: "previous" | "next" | "current"; isOpeningFullOverlay?: boolean; forceCloseOverlay?: boolean; autofillOverlayVisibility?: number; @@ -33,15 +32,6 @@ export type AutofillExtensionMessageHandlers = { collectPageDetails: ({ message }: AutofillExtensionMessageParam) => void; collectPageDetailsImmediately: ({ message }: AutofillExtensionMessageParam) => void; fillForm: ({ message }: AutofillExtensionMessageParam) => void; - openAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void; - addNewVaultItemFromOverlay: () => void; - redirectOverlayFocusOut: ({ message }: AutofillExtensionMessageParam) => void; - updateIsOverlayCiphersPopulated: ({ message }: AutofillExtensionMessageParam) => void; - bgUnlockPopoutOpened: () => void; - bgVaultItemRepromptPopoutOpened: () => void; - updateAutofillOverlayVisibility: ({ message }: AutofillExtensionMessageParam) => void; - getSubFrameOffsets: ({ message }: AutofillExtensionMessageParam) => Promise; - getSubFrameOffsetsThroughWindowMessaging: ({ message }: AutofillExtensionMessageParam) => void; }; export interface AutofillInit { diff --git a/apps/browser/src/autofill/content/autofill-init.spec.ts b/apps/browser/src/autofill/content/autofill-init.spec.ts index fc2edfb9b39..36d143d71fe 100644 --- a/apps/browser/src/autofill/content/autofill-init.spec.ts +++ b/apps/browser/src/autofill/content/autofill-init.spec.ts @@ -7,7 +7,6 @@ import AutofillPageDetails from "../models/autofill-page-details"; import AutofillScript from "../models/autofill-script"; import AutofillOverlayContentService from "../services/autofill-overlay-content.service"; import { flushPromises, sendMockExtensionMessage } from "../spec/testing-utils"; -import { RedirectFocusDirection } from "../utils/autofill-overlay.enum"; import { AutofillExtensionMessage } from "./abstractions/autofill-init"; import AutofillInit from "./autofill-init"; @@ -422,32 +421,6 @@ describe("AutofillInit", () => { }); }); - describe("redirectOverlayFocusOut", () => { - const message = { - command: "redirectOverlayFocusOut", - data: { - direction: RedirectFocusDirection.Next, - }, - }; - - it("ignores the message to redirect focus if the autofillOverlayContentService does not exist", () => { - const newAutofillInit = new AutofillInit(undefined); - newAutofillInit.init(); - - sendMockExtensionMessage(message); - - expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); - }); - - it("redirects the overlay focus", () => { - sendMockExtensionMessage(message); - - expect( - autofillInit["autofillOverlayContentService"].redirectOverlayFocusOut, - ).toHaveBeenCalledWith(message.data.direction); - }); - }); - describe("updateIsOverlayCiphersPopulated", () => { const message = { command: "updateIsOverlayCiphersPopulated", diff --git a/apps/browser/src/autofill/content/autofill-init.ts b/apps/browser/src/autofill/content/autofill-init.ts index eee933cc762..a7f5afe5eaa 100644 --- a/apps/browser/src/autofill/content/autofill-init.ts +++ b/apps/browser/src/autofill/content/autofill-init.ts @@ -1,4 +1,3 @@ -import { SubFrameOffsetData } from "../background/abstractions/overlay.background"; import AutofillPageDetails from "../models/autofill-page-details"; import { InlineMenuElements } from "../overlay/abstractions/inline-menu-elements"; import { AutofillOverlayContentService } from "../services/abstractions/autofill-overlay-content.service"; @@ -24,16 +23,6 @@ class AutofillInit implements AutofillInitInterface { collectPageDetails: ({ message }) => this.collectPageDetails(message), collectPageDetailsImmediately: ({ message }) => this.collectPageDetails(message, true), fillForm: ({ message }) => this.fillForm(message), - openAutofillOverlay: ({ message }) => this.openAutofillOverlay(message), - addNewVaultItemFromOverlay: () => this.addNewVaultItemFromOverlay(), - redirectOverlayFocusOut: ({ message }) => this.redirectOverlayFocusOut(message), - updateIsOverlayCiphersPopulated: ({ message }) => this.updateIsOverlayCiphersPopulated(message), - bgUnlockPopoutOpened: () => this.blurAndRemoveOverlay(), - bgVaultItemRepromptPopoutOpened: () => this.blurAndRemoveOverlay(), - updateAutofillOverlayVisibility: ({ message }) => this.updateAutofillOverlayVisibility(message), - getSubFrameOffsets: ({ message }) => this.getSubFrameOffsets(message), - getSubFrameOffsetsThroughWindowMessaging: ({ message }) => - this.getSubFrameOffsetsThroughWindowMessaging(message), }; /** @@ -72,45 +61,6 @@ class AutofillInit implements AutofillInitInterface { this.domElementVisibilityService, this.collectAutofillContentService, ); - - window.addEventListener("message", (event) => { - // if (event.source !== window) { - // return; - // } - - if (event.data.command === "calculateSubFramePositioning") { - const subFrameData = event.data.subFrameData; - let subFrameOffsets: SubFrameOffsetData; - const iframes = document.querySelectorAll("iframe"); - for (let i = 0; i < iframes.length; i++) { - if (iframes[i].contentWindow === event.source) { - const iframeElement = iframes[i]; - subFrameOffsets = this.calculateSubFrameOffsets( - iframeElement, - subFrameData.url, - subFrameData.frameId, - ); - - subFrameData.top += subFrameOffsets.top; - subFrameData.left += subFrameOffsets.left; - - break; - } - } - - if (globalThis.window.self !== globalThis.window.top) { - globalThis.parent.postMessage( - { command: "calculateSubFramePositioning", subFrameData }, - "*", - ); - return; - } - - void sendExtensionMessage("updateSubFrameData", { - subFrameData, - }); - } - }); } /** @@ -198,19 +148,6 @@ class AutofillInit implements AutofillInitInterface { ); } - /** - * Opens the autofill overlay. - * - * @param data - The extension message data. - */ - private openAutofillOverlay({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.openAutofillOverlay(data); - } - /** * Blurs the most recent overlay field and removes the overlay. Used * in cases where the background unlock or vault item reprompt popout @@ -221,117 +158,7 @@ class AutofillInit implements AutofillInitInterface { return; } - this.autofillOverlayContentService.blurMostRecentOverlayField(); - void sendExtensionMessage("closeAutofillOverlay"); - } - - /** - * Adds a new vault item from the overlay. - */ - private addNewVaultItemFromOverlay() { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.addNewVaultItem(); - } - - /** - * Redirects the overlay focus out of an overlay iframe. - * - * @param data - Contains the direction to redirect the focus. - */ - private redirectOverlayFocusOut({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.redirectOverlayFocusOut(data?.direction); - } - - /** - * Updates whether the current tab has ciphers that can populate the overlay list - * - * @param data - Contains the isOverlayCiphersPopulated value - * - */ - private updateIsOverlayCiphersPopulated({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService) { - return; - } - - this.autofillOverlayContentService.isOverlayCiphersPopulated = Boolean( - data?.isOverlayCiphersPopulated, - ); - } - - /** - * Updates the autofill overlay visibility. - * - * @param data - Contains the autoFillOverlayVisibility value - */ - private updateAutofillOverlayVisibility({ data }: AutofillExtensionMessage) { - if (!this.autofillOverlayContentService || isNaN(data?.autofillOverlayVisibility)) { - return; - } - - this.autofillOverlayContentService.autofillOverlayVisibility = data?.autofillOverlayVisibility; - } - - private async getSubFrameOffsets( - message: AutofillExtensionMessage, - ): Promise { - const { subFrameUrl } = message; - const subFrameUrlWithoutTrailingSlash = subFrameUrl?.replace(/\/$/, ""); - - let iframeElement: HTMLIFrameElement | null = null; - const iframeElements = document.querySelectorAll( - `iframe[src="${subFrameUrl}"], iframe[src="${subFrameUrlWithoutTrailingSlash}"]`, - ) as NodeListOf; - if (iframeElements.length === 1) { - iframeElement = iframeElements[0]; - } - - if (!iframeElement) { - return null; - } - - return this.calculateSubFrameOffsets(iframeElement, subFrameUrl); - } - - private calculateSubFrameOffsets( - iframeElement: HTMLIFrameElement, - subFrameUrl?: string, - frameId?: number, - ): SubFrameOffsetData { - const iframeRect = iframeElement.getBoundingClientRect(); - const iframeStyles = globalThis.getComputedStyle(iframeElement); - const paddingLeft = parseInt(iframeStyles.getPropertyValue("padding-left")); - const paddingTop = parseInt(iframeStyles.getPropertyValue("padding-top")); - const borderWidthLeft = parseInt(iframeStyles.getPropertyValue("border-left-width")); - const borderWidthTop = parseInt(iframeStyles.getPropertyValue("border-top-width")); - - return { - url: subFrameUrl, - frameId, - top: iframeRect.top + paddingTop + borderWidthTop, - left: iframeRect.left + paddingLeft + borderWidthLeft, - }; - } - - private getSubFrameOffsetsThroughWindowMessaging(message: any) { - globalThis.parent.postMessage( - { - command: "calculateSubFramePositioning", - subFrameData: { - url: window.location.href, - frameId: message.subFrameId, - left: 0, - top: 0, - }, - }, - "*", - ); + this.autofillOverlayContentService.blurMostRecentOverlayField(true); } /** @@ -364,9 +191,7 @@ class AutofillInit implements AutofillInitInterface { return; } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Promise.resolve(messageResponse).then((response) => sendResponse(response)); + void Promise.resolve(messageResponse).then((response) => sendResponse(response)); return true; }; diff --git a/apps/browser/src/autofill/overlay/abstractions/inline-menu-elements.ts b/apps/browser/src/autofill/overlay/abstractions/inline-menu-elements.ts index 92b29267518..6b7163a3c74 100644 --- a/apps/browser/src/autofill/overlay/abstractions/inline-menu-elements.ts +++ b/apps/browser/src/autofill/overlay/abstractions/inline-menu-elements.ts @@ -3,7 +3,7 @@ import { AutofillExtensionMessageParam } from "../../content/abstractions/autofi export type InlineMenuExtensionMessageHandlers = { [key: string]: CallableFunction; closeInlineMenu: ({ message }: AutofillExtensionMessageParam) => void; - updateInlineMenuElementsPosition: ({ message }: AutofillExtensionMessageParam) => Promise; + appendInlineMenuElementsToDom: ({ message }: AutofillExtensionMessageParam) => Promise; toggleInlineMenuHidden: ({ message }: AutofillExtensionMessageParam) => void; checkIsInlineMenuButtonVisible: () => boolean; checkIsInlineMenuListVisible: () => boolean; diff --git a/apps/browser/src/autofill/overlay/content/inline-menu-elements.ts b/apps/browser/src/autofill/overlay/content/inline-menu-elements.ts index 9826c1cd83d..9b0cbf4e9ab 100644 --- a/apps/browser/src/autofill/overlay/content/inline-menu-elements.ts +++ b/apps/browser/src/autofill/overlay/content/inline-menu-elements.ts @@ -36,8 +36,7 @@ export class InlineMenuElements implements InlineMenuElementsInterface { }; private readonly _extensionMessageHandlers: InlineMenuExtensionMessageHandlers = { closeInlineMenu: ({ message }) => this.removeInlineMenu(message), - updateInlineMenuElementsPosition: ({ message }) => - this.updateInlineMenuElementsPosition(message), + appendInlineMenuElementsToDom: ({ message }) => this.appendInlineMenuElements(message), toggleInlineMenuHidden: ({ message }) => this.toggleInlineMenuHidden(message.isInlineMenuHidden), checkIsInlineMenuButtonVisible: () => this.isButtonVisible, @@ -125,25 +124,25 @@ export class InlineMenuElements implements InlineMenuElementsInterface { /** * Updates the position of both the overlay button and overlay list. */ - private async updateInlineMenuElementsPosition({ overlayElement }: AutofillExtensionMessage) { + private async appendInlineMenuElements({ overlayElement }: AutofillExtensionMessage) { if (overlayElement === AutofillOverlayElement.Button) { - return this.updateButtonPosition(); + return this.appendButtonElement(); } - return this.updateListPosition(); + return this.appendListElement(); } /** * Updates the position of the overlay button. */ - private async updateButtonPosition(): Promise { + private async appendButtonElement(): Promise { if (!this.buttonElement) { this.createButton(); this.updateCustomElementDefaultStyles(this.buttonElement); } if (!this.isButtonVisible) { - this.appendOverlayElementToBody(this.buttonElement); + this.appendInlineMenuElementToBody(this.buttonElement); this.isButtonVisible = true; } } @@ -151,14 +150,14 @@ export class InlineMenuElements implements InlineMenuElementsInterface { /** * Updates the position of the overlay list. */ - private async updateListPosition(): Promise { + private async appendListElement(): Promise { if (!this.listElement) { this.createList(); this.updateCustomElementDefaultStyles(this.listElement); } if (!this.isListVisible) { - this.appendOverlayElementToBody(this.listElement); + this.appendInlineMenuElementToBody(this.listElement); this.isListVisible = true; } } @@ -170,7 +169,7 @@ export class InlineMenuElements implements InlineMenuElementsInterface { * * @param element - The overlay element to append to the body element. */ - private appendOverlayElementToBody(element: HTMLElement) { + private appendInlineMenuElementToBody(element: HTMLElement) { this.observeBodyElement(); globalThis.document.body.appendChild(element); } diff --git a/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts index addeef186b3..21bd964ee42 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts @@ -1,5 +1,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { SubFrameOffsetData } from "../../background/abstractions/overlay.background"; +import { AutofillExtensionMessageParam } from "../../content/abstractions/autofill-init"; import AutofillField from "../../models/autofill-field"; import { ElementWithOpId, FormFieldElement } from "../../types"; @@ -11,7 +13,16 @@ export type OpenAutofillOverlayOptions = { export type AutofillOverlayContentExtensionMessageHandlers = { [key: string]: CallableFunction; + openAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void; + addNewVaultItemFromOverlay: () => void; blurMostRecentOverlayField: () => void; + bgUnlockPopoutOpened: () => void; + bgVaultItemRepromptPopoutOpened: () => void; + redirectOverlayFocusOut: ({ message }: AutofillExtensionMessageParam) => void; + updateAutofillOverlayVisibility: ({ message }: AutofillExtensionMessageParam) => void; + updateIsOverlayCiphersPopulated: ({ message }: AutofillExtensionMessageParam) => void; + getSubFrameOffsets: ({ message }: AutofillExtensionMessageParam) => Promise; + getSubFrameOffsetsFromWindowMessage: ({ message }: AutofillExtensionMessageParam) => void; }; export interface AutofillOverlayContentService { @@ -31,8 +42,7 @@ export interface AutofillOverlayContentService { // removeAutofillOverlayButton(): void; // removeAutofillOverlayList(): void; addNewVaultItem(): void; - redirectOverlayFocusOut(direction: "previous" | "next"): void; focusMostRecentOverlayField(): void; - blurMostRecentOverlayField(): void; + blurMostRecentOverlayField(isRemovingOverlay?: boolean): void; destroy(): void; } diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index 8a63aef5b38..cd7f3af6192 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -5,7 +5,11 @@ import { FocusableElement, tabbable } from "tabbable"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { EVENTS, AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; -import { FocusedFieldData } from "../background/abstractions/overlay.background"; +import { + FocusedFieldData, + SubFrameOffsetData, +} from "../background/abstractions/overlay.background"; +import { AutofillExtensionMessage } from "../content/abstractions/autofill-init"; import AutofillField from "../models/autofill-field"; import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types"; import { elementIsFillableFormField, sendExtensionMessage } from "../utils"; @@ -36,7 +40,17 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte private autofillFieldKeywordsMap: WeakMap = new WeakMap(); private eventHandlersMemo: { [key: string]: EventListener } = {}; readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = { - blurMostRecentOverlayField: () => this.blurMostRecentOverlayField, + openAutofillOverlay: ({ message }) => this.openAutofillOverlay(message.data), + addNewVaultItemFromOverlay: () => this.addNewVaultItem(), + blurMostRecentOverlayField: () => this.blurMostRecentOverlayField(), + bgUnlockPopoutOpened: () => this.blurMostRecentOverlayField(true), + bgVaultItemRepromptPopoutOpened: () => this.blurMostRecentOverlayField(true), + redirectOverlayFocusOut: ({ message }) => this.redirectOverlayFocusOut(message), + updateAutofillOverlayVisibility: ({ message }) => this.updateAutofillOverlayVisibility(message), + updateIsOverlayCiphersPopulated: ({ message }) => this.updateIsOverlayCiphersPopulated(message), + getSubFrameOffsets: ({ message }) => this.getSubFrameOffsets(message), + getSubFrameOffsetsFromWindowMessage: ({ message }) => + this.getSubFrameOffsetsFromWindowMessage(message), }; /** @@ -135,8 +149,12 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte /** * Removes focus from the most recently focused field element. */ - blurMostRecentOverlayField() { + blurMostRecentOverlayField(isRemovingOverlay: boolean = false) { this.mostRecentlyFocusedField?.blur(); + + if (isRemovingOverlay) { + void sendExtensionMessage("closeAutofillOverlay"); + } } /** @@ -163,16 +181,19 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte * either previous or next in the tab order. If the direction is current, the most * recently focused field will be focused. * - * @param direction - The direction to redirect the focus. + * @param data - Contains the direction to redirect the focus. */ - async redirectOverlayFocusOut(direction: string) { + async redirectOverlayFocusOut({ data }: AutofillExtensionMessage) { if ( + !data?.direction || !this.mostRecentlyFocusedField || (await this.sendExtensionMessage("checkIsInlineMenuListVisible")) !== true ) { return; } + const { direction } = data; + if (direction === RedirectFocusDirection.Current) { this.focusMostRecentOverlayField(); setTimeout(() => void this.sendExtensionMessage("closeAutofillOverlay"), 100); @@ -573,7 +594,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte private async updateMostRecentlyFocusedField( formFieldElement: ElementWithOpId, ) { - if (!elementIsFillableFormField(formFieldElement)) { + if (!formFieldElement || !elementIsFillableFormField(formFieldElement)) { return; } @@ -781,6 +802,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte * overlay elements. */ private setupGlobalEventListeners = () => { + globalThis.addEventListener(EVENTS.MESSAGE, this.handleWindowMessageEvent); globalThis.document.addEventListener(EVENTS.VISIBILITYCHANGE, this.handleVisibilityChangeEvent); globalThis.addEventListener(EVENTS.FOCUSOUT, this.handleFormFieldBlurEvent); this.setOverlayRepositionEventListeners(); @@ -815,6 +837,112 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte return documentRoot?.activeElement; } + private async getSubFrameOffsets( + message: AutofillExtensionMessage, + ): Promise { + const { subFrameUrl } = message; + const subFrameUrlWithoutTrailingSlash = subFrameUrl?.replace(/\/$/, ""); + + let iframeElement: HTMLIFrameElement | null = null; + const iframeElements = document.querySelectorAll( + `iframe[src="${subFrameUrl}"], iframe[src="${subFrameUrlWithoutTrailingSlash}"]`, + ) as NodeListOf; + if (iframeElements.length === 1) { + iframeElement = iframeElements[0]; + } + + if (!iframeElement) { + return null; + } + + return this.calculateSubFrameOffsets(iframeElement, subFrameUrl); + } + + private calculateSubFrameOffsets( + iframeElement: HTMLIFrameElement, + subFrameUrl?: string, + frameId?: number, + ): SubFrameOffsetData { + const iframeRect = iframeElement.getBoundingClientRect(); + const iframeStyles = globalThis.getComputedStyle(iframeElement); + const paddingLeft = parseInt(iframeStyles.getPropertyValue("padding-left")); + const paddingTop = parseInt(iframeStyles.getPropertyValue("padding-top")); + const borderWidthLeft = parseInt(iframeStyles.getPropertyValue("border-left-width")); + const borderWidthTop = parseInt(iframeStyles.getPropertyValue("border-top-width")); + + return { + url: subFrameUrl, + frameId, + top: iframeRect.top + paddingTop + borderWidthTop, + left: iframeRect.left + paddingLeft + borderWidthLeft, + }; + } + + private getSubFrameOffsetsFromWindowMessage(message: any) { + globalThis.parent.postMessage( + { + command: "calculateSubFramePositioning", + subFrameData: { + url: window.location.href, + frameId: message.subFrameId, + left: 0, + top: 0, + }, + }, + "*", + ); + } + + private handleWindowMessageEvent = (event: MessageEvent) => { + if (event.data?.command !== "calculateSubFramePositioning") { + return; + } + + this.calculateSubFramePositioning(event); + }; + + private calculateSubFramePositioning = (event: MessageEvent) => { + const subFrameData = event.data.subFrameData; + let subFrameOffsets: SubFrameOffsetData; + const iframes = document.querySelectorAll("iframe"); + for (let i = 0; i < iframes.length; i++) { + if (iframes[i].contentWindow === event.source) { + const iframeElement = iframes[i]; + subFrameOffsets = this.calculateSubFrameOffsets( + iframeElement, + subFrameData.url, + subFrameData.frameId, + ); + + subFrameData.top += subFrameOffsets.top; + subFrameData.left += subFrameOffsets.left; + + break; + } + } + + if (globalThis.window.self !== globalThis.window.top) { + globalThis.parent.postMessage({ command: "calculateSubFramePositioning", subFrameData }, "*"); + return; + } + + void sendExtensionMessage("updateSubFrameData", { + subFrameData, + }); + }; + + private updateAutofillOverlayVisibility({ data }: AutofillExtensionMessage) { + if (isNaN(data?.autofillOverlayVisibility)) { + return; + } + + this.autofillOverlayVisibility = data.autofillOverlayVisibility; + } + + private updateIsOverlayCiphersPopulated({ data }: AutofillExtensionMessage) { + this.isOverlayCiphersPopulated = Boolean(data?.isOverlayCiphersPopulated); + } + /** * Destroys the autofill overlay content service. This method will * disconnect the mutation observers and remove all event listeners.