mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 03:03:43 +00:00
[PM-5189] Addressing issues that exist with repositioning and scrolling of frame elements outside of focused frame
This commit is contained in:
@@ -112,6 +112,7 @@ type OverlayBackgroundExtensionMessageHandlers = {
|
|||||||
checkIsInlineMenuButtonVisible: ({ sender }: BackgroundSenderParam) => void;
|
checkIsInlineMenuButtonVisible: ({ sender }: BackgroundSenderParam) => void;
|
||||||
checkIsInlineMenuListVisible: ({ sender }: BackgroundSenderParam) => void;
|
checkIsInlineMenuListVisible: ({ sender }: BackgroundSenderParam) => void;
|
||||||
updateSubFrameData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
updateSubFrameData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||||
|
rebuildSubFrameOffsets: ({ sender }: BackgroundSenderParam) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PortMessageParam = {
|
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 { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
|
||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
import {
|
import {
|
||||||
openViewVaultItemPopout,
|
|
||||||
openAddEditVaultItemPopout,
|
openAddEditVaultItemPopout,
|
||||||
|
openViewVaultItemPopout,
|
||||||
} from "../../vault/popup/utils/vault-popout-window";
|
} 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 { AutofillOverlayElement, AutofillOverlayPort } from "../utils/autofill-overlay.enum";
|
||||||
|
|
||||||
import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background";
|
import { LockedVaultPendingNotificationsData } from "./abstractions/notification.background";
|
||||||
import {
|
import {
|
||||||
FocusedFieldData,
|
FocusedFieldData,
|
||||||
|
OverlayAddNewItemMessage,
|
||||||
|
OverlayBackground as OverlayBackgroundInterface,
|
||||||
|
OverlayBackgroundExtensionMessage,
|
||||||
OverlayBackgroundExtensionMessageHandlers,
|
OverlayBackgroundExtensionMessageHandlers,
|
||||||
OverlayButtonPortMessageHandlers,
|
OverlayButtonPortMessageHandlers,
|
||||||
OverlayCipherData,
|
OverlayCipherData,
|
||||||
OverlayListPortMessageHandlers,
|
OverlayListPortMessageHandlers,
|
||||||
OverlayBackground as OverlayBackgroundInterface,
|
|
||||||
OverlayBackgroundExtensionMessage,
|
|
||||||
OverlayAddNewItemMessage,
|
|
||||||
OverlayPortMessage,
|
OverlayPortMessage,
|
||||||
WebsiteIconData,
|
|
||||||
PageDetailsForTab,
|
PageDetailsForTab,
|
||||||
SubFrameOffsetsForTab,
|
|
||||||
SubFrameOffsetData,
|
SubFrameOffsetData,
|
||||||
|
SubFrameOffsetsForTab,
|
||||||
|
WebsiteIconData,
|
||||||
} from "./abstractions/overlay.background";
|
} from "./abstractions/overlay.background";
|
||||||
|
|
||||||
class OverlayBackground implements OverlayBackgroundInterface {
|
class OverlayBackground implements OverlayBackgroundInterface {
|
||||||
@@ -85,6 +85,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
checkIsInlineMenuButtonVisible: ({ sender }) => this.checkIsInlineMenuButtonVisible(sender),
|
checkIsInlineMenuButtonVisible: ({ sender }) => this.checkIsInlineMenuButtonVisible(sender),
|
||||||
checkIsInlineMenuListVisible: ({ sender }) => this.checkIsInlineMenuListVisible(sender),
|
checkIsInlineMenuListVisible: ({ sender }) => this.checkIsInlineMenuListVisible(sender),
|
||||||
updateSubFrameData: ({ message, sender }) => this.updateSubFrameData(message, sender),
|
updateSubFrameData: ({ message, sender }) => this.updateSubFrameData(message, sender),
|
||||||
|
rebuildSubFrameOffsets: ({ sender }) => this.rebuildSubFrameOffsets(sender),
|
||||||
};
|
};
|
||||||
private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = {
|
private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = {
|
||||||
overlayButtonClicked: ({ port }) => this.handleOverlayButtonClicked(port),
|
overlayButtonClicked: ({ port }) => this.handleOverlayButtonClicked(port),
|
||||||
@@ -120,19 +121,11 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async checkIsInlineMenuButtonVisible(sender: chrome.runtime.MessageSender) {
|
private async checkIsInlineMenuButtonVisible(sender: chrome.runtime.MessageSender) {
|
||||||
const value = await BrowserApi.tabSendMessage(
|
return await BrowserApi.tabSendMessage(
|
||||||
sender.tab,
|
sender.tab,
|
||||||
{ command: "checkIsInlineMenuButtonVisible" },
|
{ command: "checkIsInlineMenuButtonVisible" },
|
||||||
{ frameId: 0 },
|
{ 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) {
|
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
|
* Removes cached page details for a tab
|
||||||
* based on the passed tabId.
|
* based on the passed tabId.
|
||||||
@@ -255,7 +255,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (pageDetails.frameId !== 0 && pageDetails.details.fields.length) {
|
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];
|
const pageDetailsMap = this.pageDetailsForTab[sender.tab.id];
|
||||||
@@ -267,7 +267,24 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
pageDetailsMap.set(sender.frameId, pageDetails);
|
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;
|
const tabId = tab.id;
|
||||||
let subFrameOffsetsForTab = this.subFrameOffsetsForTab[tabId];
|
let subFrameOffsetsForTab = this.subFrameOffsetsForTab[tabId];
|
||||||
if (!subFrameOffsetsForTab) {
|
if (!subFrameOffsetsForTab) {
|
||||||
@@ -279,7 +296,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subFrameData = { url: details.url, top: 0, left: 0 };
|
const subFrameData = { url, top: 0, left: 0 };
|
||||||
let frameDetails = await BrowserApi.getFrameDetails({ tabId, frameId });
|
let frameDetails = await BrowserApi.getFrameDetails({ tabId, frameId });
|
||||||
|
|
||||||
while (frameDetails.parentFrameId !== -1) {
|
while (frameDetails.parentFrameId !== -1) {
|
||||||
@@ -872,6 +889,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
port.onMessage.addListener(this.handleOverlayElementPortMessage);
|
port.onMessage.addListener(this.handleOverlayElementPortMessage);
|
||||||
|
port.onDisconnect.addListener(this.handlePortOnDisconnect);
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`,
|
command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`,
|
||||||
authStatus: await this.getAuthStatus(),
|
authStatus: await this.getAuthStatus(),
|
||||||
@@ -917,6 +935,16 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
|
|
||||||
handler({ message, port });
|
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;
|
export default OverlayBackground;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
private mostRecentlyFocusedField: ElementWithOpId<FormFieldElement>;
|
private mostRecentlyFocusedField: ElementWithOpId<FormFieldElement>;
|
||||||
private focusedFieldData: FocusedFieldData;
|
private focusedFieldData: FocusedFieldData;
|
||||||
private userInteractionEventTimeout: number | NodeJS.Timeout;
|
private userInteractionEventTimeout: number | NodeJS.Timeout;
|
||||||
|
private recalculateSubFrameOffsetsTimeout: number | NodeJS.Timeout;
|
||||||
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
|
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
|
||||||
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
||||||
readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
|
readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
|
||||||
@@ -371,8 +372,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private storeModifiedFormElement(formFieldElement: ElementWithOpId<FillableFormFieldElement>) {
|
private storeModifiedFormElement(formFieldElement: ElementWithOpId<FillableFormFieldElement>) {
|
||||||
if (formFieldElement === this.mostRecentlyFocusedField) {
|
if (formFieldElement !== this.mostRecentlyFocusedField) {
|
||||||
this.mostRecentlyFocusedField = formFieldElement;
|
void this.updateMostRecentlyFocusedField(formFieldElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formFieldElement.type === "password") {
|
if (formFieldElement.type === "password") {
|
||||||
@@ -570,6 +571,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
private async updateMostRecentlyFocusedField(
|
private async updateMostRecentlyFocusedField(
|
||||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||||
) {
|
) {
|
||||||
|
if (!elementIsFillableFormField(formFieldElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.mostRecentlyFocusedField = formFieldElement;
|
this.mostRecentlyFocusedField = formFieldElement;
|
||||||
const { paddingRight, paddingLeft } = globalThis.getComputedStyle(formFieldElement);
|
const { paddingRight, paddingLeft } = globalThis.getComputedStyle(formFieldElement);
|
||||||
const { width, height, top, left } =
|
const { width, height, top, left } =
|
||||||
@@ -700,6 +705,12 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
* repositioning of the overlay.
|
* repositioning of the overlay.
|
||||||
*/
|
*/
|
||||||
private handleOverlayRepositionEvent = async () => {
|
private handleOverlayRepositionEvent = async () => {
|
||||||
|
this.clearRecalculateSubFrameOffsetsTimeout();
|
||||||
|
this.recalculateSubFrameOffsetsTimeout = setTimeout(
|
||||||
|
() => void this.sendExtensionMessage("rebuildSubFrameOffsets"),
|
||||||
|
750,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(await this.sendExtensionMessage("checkIsInlineMenuButtonVisible")) !== true &&
|
(await this.sendExtensionMessage("checkIsInlineMenuButtonVisible")) !== true &&
|
||||||
(await this.sendExtensionMessage("checkIsInlineMenuListVisible")) !== true
|
(await this.sendExtensionMessage("checkIsInlineMenuListVisible")) !== true
|
||||||
@@ -709,10 +720,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
|
|
||||||
this.toggleOverlayHidden(true);
|
this.toggleOverlayHidden(true);
|
||||||
this.clearUserInteractionEventTimeout();
|
this.clearUserInteractionEventTimeout();
|
||||||
this.userInteractionEventTimeout = setTimeout(
|
this.userInteractionEventTimeout = setTimeout(this.triggerOverlayRepositionUpdates, 750);
|
||||||
this.triggerOverlayRepositionUpdates,
|
|
||||||
750,
|
|
||||||
) as unknown as number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -730,7 +738,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
|
|
||||||
await this.updateMostRecentlyFocusedField(this.mostRecentlyFocusedField);
|
await this.updateMostRecentlyFocusedField(this.mostRecentlyFocusedField);
|
||||||
this.updateOverlayElementsPosition();
|
this.updateOverlayElementsPosition();
|
||||||
this.toggleOverlayHidden(false);
|
setTimeout(() => this.toggleOverlayHidden(false), 50);
|
||||||
this.clearUserInteractionEventTimeout();
|
this.clearUserInteractionEventTimeout();
|
||||||
|
|
||||||
if (
|
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
|
* Sets up global event listeners and the mutation
|
||||||
* observer to facilitate required changes to the
|
* observer to facilitate required changes to the
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ function setupAutofillInitDisconnectAction(windowContext: Window) {
|
|||||||
function elementIsFillableFormField(
|
function elementIsFillableFormField(
|
||||||
formFieldElement: FormFieldElement,
|
formFieldElement: FormFieldElement,
|
||||||
): formFieldElement is FillableFormFieldElement {
|
): 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.
|
* @param tagName - The tag name to check against.
|
||||||
*/
|
*/
|
||||||
function elementIsInstanceOf<T extends Element>(element: Element, tagName: string): element is T {
|
function elementIsInstanceOf<T extends Element>(element: Element, tagName: string): element is T {
|
||||||
return element?.tagName.toLowerCase() === tagName;
|
return nodeIsElement(element) && element.tagName.toLowerCase() === tagName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user