1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-13 00:51:45 +01:00

[PM-5189] Addressing issues that exist with repositioning and scrolling of frame elements outside of focused frame

This commit is contained in:
Cesar Gonzalez 2024-03-22 09:52:39 -05:00
parent e701ffb73c
commit 47127ed6a7
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
4 changed files with 71 additions and 28 deletions

View File

@ -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 = {

View File

@ -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;

View File

@ -32,6 +32,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
private mostRecentlyFocusedField: ElementWithOpId<FormFieldElement>;
private focusedFieldData: FocusedFieldData;
private userInteractionEventTimeout: number | NodeJS.Timeout;
private recalculateSubFrameOffsetsTimeout: number | NodeJS.Timeout;
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
private eventHandlersMemo: { [key: string]: EventListener } = {};
readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
@ -371,8 +372,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
* @private
*/
private storeModifiedFormElement(formFieldElement: ElementWithOpId<FillableFormFieldElement>) {
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<FormFieldElement>,
) {
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

View File

@ -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<T extends Element>(element: Element, tagName: string): element is T {
return element?.tagName.toLowerCase() === tagName;
return nodeIsElement(element) && element.tagName.toLowerCase() === tagName;
}
/**