From 47127ed6a7329111496c797225aae26a45f3d242 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Fri, 22 Mar 2024 09:52:39 -0500 Subject: [PATCH] [PM-5189] Addressing issues that exist with repositioning and scrolling of frame elements outside of focused frame --- .../abstractions/overlay.background.ts | 1 + .../autofill/background/overlay.background.ts | 66 +++++++++++++------ .../autofill-overlay-content.service.ts | 28 ++++++-- apps/browser/src/autofill/utils/index.ts | 4 +- 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index f5d8257e635..7e111d5c3c6 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -112,6 +112,7 @@ type OverlayBackgroundExtensionMessageHandlers = { checkIsInlineMenuButtonVisible: ({ sender }: BackgroundSenderParam) => void; checkIsInlineMenuListVisible: ({ sender }: BackgroundSenderParam) => void; updateSubFrameData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; + rebuildSubFrameOffsets: ({ sender }: BackgroundSenderParam) => void; }; type PortMessageParam = { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 653acd526d8..2e8a475f6c8 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -22,27 +22,27 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window"; import { BrowserApi } from "../../platform/browser/browser-api"; import { - openViewVaultItemPopout, openAddEditVaultItemPopout, + openViewVaultItemPopout, } from "../../vault/popup/utils/vault-popout-window"; -import { AutofillService, PageDetail } from "../services/abstractions/autofill.service"; +import { AutofillService } from "../services/abstractions/autofill.service"; import { AutofillOverlayElement, AutofillOverlayPort } from "../utils/autofill-overlay.enum"; import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background"; import { FocusedFieldData, + OverlayAddNewItemMessage, + OverlayBackground as OverlayBackgroundInterface, + OverlayBackgroundExtensionMessage, OverlayBackgroundExtensionMessageHandlers, OverlayButtonPortMessageHandlers, OverlayCipherData, OverlayListPortMessageHandlers, - OverlayBackground as OverlayBackgroundInterface, - OverlayBackgroundExtensionMessage, - OverlayAddNewItemMessage, OverlayPortMessage, - WebsiteIconData, PageDetailsForTab, - SubFrameOffsetsForTab, SubFrameOffsetData, + SubFrameOffsetsForTab, + WebsiteIconData, } from "./abstractions/overlay.background"; class OverlayBackground implements OverlayBackgroundInterface { @@ -85,6 +85,7 @@ class OverlayBackground implements OverlayBackgroundInterface { checkIsInlineMenuButtonVisible: ({ sender }) => this.checkIsInlineMenuButtonVisible(sender), checkIsInlineMenuListVisible: ({ sender }) => this.checkIsInlineMenuListVisible(sender), updateSubFrameData: ({ message, sender }) => this.updateSubFrameData(message, sender), + rebuildSubFrameOffsets: ({ sender }) => this.rebuildSubFrameOffsets(sender), }; private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = { overlayButtonClicked: ({ port }) => this.handleOverlayButtonClicked(port), @@ -120,19 +121,11 @@ class OverlayBackground implements OverlayBackgroundInterface { ) {} private async checkIsInlineMenuButtonVisible(sender: chrome.runtime.MessageSender) { - const value = await BrowserApi.tabSendMessage( + return await BrowserApi.tabSendMessage( sender.tab, { command: "checkIsInlineMenuButtonVisible" }, { frameId: 0 }, ); - return value; - } - - updateSubFrameData(message: any, sender: chrome.runtime.MessageSender) { - const subFrameOffsetsForTab = this.subFrameOffsetsForTab[sender.tab.id]; - if (subFrameOffsetsForTab) { - subFrameOffsetsForTab.set(message.subFrameData.frameId, message.subFrameData); - } } private async checkIsInlineMenuListVisible(sender: chrome.runtime.MessageSender) { @@ -143,6 +136,13 @@ class OverlayBackground implements OverlayBackgroundInterface { ); } + updateSubFrameData(message: any, sender: chrome.runtime.MessageSender) { + const subFrameOffsetsForTab = this.subFrameOffsetsForTab[sender.tab.id]; + if (subFrameOffsetsForTab) { + subFrameOffsetsForTab.set(message.subFrameData.frameId, message.subFrameData); + } + } + /** * Removes cached page details for a tab * based on the passed tabId. @@ -255,7 +255,7 @@ class OverlayBackground implements OverlayBackgroundInterface { }; if (pageDetails.frameId !== 0 && pageDetails.details.fields.length) { - void this.buildSubFrameOffsets(pageDetails); + void this.buildSubFrameOffsets(pageDetails.tab, pageDetails.frameId, pageDetails.details.url); } const pageDetailsMap = this.pageDetailsForTab[sender.tab.id]; @@ -267,7 +267,24 @@ class OverlayBackground implements OverlayBackgroundInterface { pageDetailsMap.set(sender.frameId, pageDetails); } - private async buildSubFrameOffsets({ tab, frameId, details }: PageDetail) { + private async rebuildSubFrameOffsets(sender: chrome.runtime.MessageSender) { + if (sender.frameId === this.focusedFieldData.frameId) { + return; + } + + const subFrameOffsetsForTab = this.subFrameOffsetsForTab[sender.tab.id]; + if (!subFrameOffsetsForTab) { + return; + } + + subFrameOffsetsForTab.forEach((subFrameData) => { + const { url, frameId } = subFrameData; + subFrameOffsetsForTab.delete(frameId); + void this.buildSubFrameOffsets(sender.tab, frameId, url); + }); + } + + private async buildSubFrameOffsets(tab: chrome.tabs.Tab, frameId: number, url: string) { const tabId = tab.id; let subFrameOffsetsForTab = this.subFrameOffsetsForTab[tabId]; if (!subFrameOffsetsForTab) { @@ -279,7 +296,7 @@ class OverlayBackground implements OverlayBackgroundInterface { return; } - const subFrameData = { url: details.url, top: 0, left: 0 }; + const subFrameData = { url, top: 0, left: 0 }; let frameDetails = await BrowserApi.getFrameDetails({ tabId, frameId }); while (frameDetails.parentFrameId !== -1) { @@ -872,6 +889,7 @@ class OverlayBackground implements OverlayBackgroundInterface { } port.onMessage.addListener(this.handleOverlayElementPortMessage); + port.onDisconnect.addListener(this.handlePortOnDisconnect); port.postMessage({ command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`, authStatus: await this.getAuthStatus(), @@ -917,6 +935,16 @@ class OverlayBackground implements OverlayBackgroundInterface { handler({ message, port }); }; + + private handlePortOnDisconnect = (port: chrome.runtime.Port) => { + if (port.name === AutofillOverlayPort.List) { + this.overlayListPort = null; + } + + if (port.name === AutofillOverlayPort.Button) { + this.overlayButtonPort = null; + } + }; } export default OverlayBackground; 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 8bbdf09636b..c191e0c10a0 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -32,6 +32,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte private mostRecentlyFocusedField: ElementWithOpId; private focusedFieldData: FocusedFieldData; private userInteractionEventTimeout: number | NodeJS.Timeout; + private recalculateSubFrameOffsetsTimeout: number | NodeJS.Timeout; private autofillFieldKeywordsMap: WeakMap = new WeakMap(); private eventHandlersMemo: { [key: string]: EventListener } = {}; readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = { @@ -371,8 +372,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte * @private */ private storeModifiedFormElement(formFieldElement: ElementWithOpId) { - if (formFieldElement === this.mostRecentlyFocusedField) { - this.mostRecentlyFocusedField = formFieldElement; + if (formFieldElement !== this.mostRecentlyFocusedField) { + void this.updateMostRecentlyFocusedField(formFieldElement); } if (formFieldElement.type === "password") { @@ -570,6 +571,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte private async updateMostRecentlyFocusedField( formFieldElement: ElementWithOpId, ) { + if (!elementIsFillableFormField(formFieldElement)) { + return; + } + this.mostRecentlyFocusedField = formFieldElement; const { paddingRight, paddingLeft } = globalThis.getComputedStyle(formFieldElement); const { width, height, top, left } = @@ -700,6 +705,12 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte * repositioning of the overlay. */ private handleOverlayRepositionEvent = async () => { + this.clearRecalculateSubFrameOffsetsTimeout(); + this.recalculateSubFrameOffsetsTimeout = setTimeout( + () => void this.sendExtensionMessage("rebuildSubFrameOffsets"), + 750, + ); + if ( (await this.sendExtensionMessage("checkIsInlineMenuButtonVisible")) !== true && (await this.sendExtensionMessage("checkIsInlineMenuListVisible")) !== true @@ -709,10 +720,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte this.toggleOverlayHidden(true); this.clearUserInteractionEventTimeout(); - this.userInteractionEventTimeout = setTimeout( - this.triggerOverlayRepositionUpdates, - 750, - ) as unknown as number; + this.userInteractionEventTimeout = setTimeout(this.triggerOverlayRepositionUpdates, 750); }; /** @@ -730,7 +738,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte await this.updateMostRecentlyFocusedField(this.mostRecentlyFocusedField); this.updateOverlayElementsPosition(); - this.toggleOverlayHidden(false); + setTimeout(() => this.toggleOverlayHidden(false), 50); this.clearUserInteractionEventTimeout(); if ( @@ -755,6 +763,12 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte } } + private clearRecalculateSubFrameOffsetsTimeout() { + if (this.recalculateSubFrameOffsetsTimeout) { + clearTimeout(this.recalculateSubFrameOffsetsTimeout); + } + } + /** * Sets up global event listeners and the mutation * observer to facilitate required changes to the diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 2069ba40e76..99fae9a0289 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -161,7 +161,7 @@ function setupAutofillInitDisconnectAction(windowContext: Window) { function elementIsFillableFormField( formFieldElement: FormFieldElement, ): formFieldElement is FillableFormFieldElement { - return formFieldElement?.tagName.toLowerCase() !== "span"; + return !elementIsSpanElement(formFieldElement); } /** @@ -171,7 +171,7 @@ function elementIsFillableFormField( * @param tagName - The tag name to check against. */ function elementIsInstanceOf(element: Element, tagName: string): element is T { - return element?.tagName.toLowerCase() === tagName; + return nodeIsElement(element) && element.tagName.toLowerCase() === tagName; } /**