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:
parent
e701ffb73c
commit
47127ed6a7
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user