mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 01:33:33 +00:00
[PM-5189] Separating the inline menu UI elements from the base AutofillOverlayContentService and setting up messaging to allow for propagation of those elements
This commit is contained in:
@@ -45,7 +45,8 @@ type OverlayBackgroundExtensionMessage = {
|
||||
sender?: string;
|
||||
details?: AutofillPageDetails;
|
||||
overlayElement?: string;
|
||||
display?: string;
|
||||
forceCloseOverlay?: boolean;
|
||||
isOverlayHidden?: boolean;
|
||||
data?: LockedVaultPendingNotificationsData;
|
||||
} & OverlayAddNewItemMessage;
|
||||
|
||||
@@ -59,6 +60,8 @@ type OverlayPortMessage = {
|
||||
type FocusedFieldData = {
|
||||
focusedFieldStyles: Partial<CSSStyleDeclaration>;
|
||||
focusedFieldRects: Partial<DOMRect>;
|
||||
tabId?: number;
|
||||
frameId?: number;
|
||||
};
|
||||
|
||||
type OverlayCipherData = {
|
||||
@@ -83,13 +86,17 @@ type BackgroundOnMessageHandlerParams = BackgroundMessageParam & BackgroundSende
|
||||
type OverlayBackgroundExtensionMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
openAutofillOverlay: () => void;
|
||||
closeAutofillOverlay: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
autofillOverlayElementClosed: ({ message }: BackgroundMessageParam) => void;
|
||||
autofillOverlayAddNewVaultItem: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
getAutofillOverlayVisibility: () => void;
|
||||
checkAutofillOverlayFocused: () => void;
|
||||
focusAutofillOverlayList: () => void;
|
||||
updateAutofillOverlayPosition: ({ message }: BackgroundMessageParam) => void;
|
||||
updateAutofillOverlayHidden: ({ message }: BackgroundMessageParam) => void;
|
||||
updateAutofillOverlayPosition: ({
|
||||
message,
|
||||
sender,
|
||||
}: BackgroundOnMessageHandlerParams) => Promise<void>;
|
||||
updateAutofillOverlayHidden: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
unlockCompleted: ({ message }: BackgroundMessageParam) => void;
|
||||
|
||||
@@ -783,17 +783,24 @@ describe("OverlayBackground", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("will post a message to the overlay list facilitating an update of the list's position", () => {
|
||||
it("will post a message to the overlay list facilitating an update of the list's position", async () => {
|
||||
const sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||
const focusedFieldData = createFocusedFieldDataMock();
|
||||
sendExtensionRuntimeMessage({ command: "updateFocusedFieldData", focusedFieldData });
|
||||
|
||||
overlayBackground["updateOverlayPosition"]({
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
sendExtensionRuntimeMessage({
|
||||
command: "updateAutofillOverlayPosition",
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
await overlayBackground["updateOverlayPosition"](
|
||||
{
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
},
|
||||
sender,
|
||||
);
|
||||
sendExtensionRuntimeMessage(
|
||||
{
|
||||
command: "updateAutofillOverlayPosition",
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
},
|
||||
sender,
|
||||
);
|
||||
|
||||
expect(listPortSpy.postMessage).toHaveBeenCalledWith({
|
||||
command: "updateIframePosition",
|
||||
|
||||
@@ -56,17 +56,21 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private overlayButtonPort: chrome.runtime.Port;
|
||||
private overlayListPort: chrome.runtime.Port;
|
||||
private focusedFieldData: FocusedFieldData;
|
||||
private isFieldCurrentlyFocused: boolean;
|
||||
private isCurrentlyFilling: boolean;
|
||||
private overlayPageTranslations: Record<string, string>;
|
||||
private readonly iconsServerUrl: string;
|
||||
private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = {
|
||||
openAutofillOverlay: () => this.openOverlay(false),
|
||||
closeAutofillOverlay: ({ message, sender }) => this.closeOverlay(sender, message),
|
||||
autofillOverlayElementClosed: ({ message }) => this.overlayElementClosed(message),
|
||||
autofillOverlayAddNewVaultItem: ({ message, sender }) => this.addNewVaultItem(message, sender),
|
||||
getAutofillOverlayVisibility: () => this.getOverlayVisibility(),
|
||||
checkAutofillOverlayFocused: () => this.checkOverlayFocused(),
|
||||
focusAutofillOverlayList: () => this.focusOverlayList(),
|
||||
updateAutofillOverlayPosition: ({ message }) => this.updateOverlayPosition(message),
|
||||
updateAutofillOverlayHidden: ({ message }) => this.updateOverlayHidden(message),
|
||||
updateAutofillOverlayPosition: ({ message, sender }) =>
|
||||
this.updateOverlayPosition(message, sender),
|
||||
updateAutofillOverlayHidden: ({ message, sender }) => this.updateOverlayHidden(message, sender),
|
||||
updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender),
|
||||
collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender),
|
||||
unlockCompleted: ({ message }) => this.unlockCompleted(message),
|
||||
@@ -75,14 +79,16 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
};
|
||||
private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = {
|
||||
overlayButtonClicked: ({ port }) => this.handleOverlayButtonClicked(port),
|
||||
closeAutofillOverlay: ({ port }) => this.closeOverlay(port),
|
||||
forceCloseAutofillOverlay: ({ port }) => this.closeOverlay(port, true),
|
||||
closeAutofillOverlay: ({ port }) => this.closeOverlay(port.sender),
|
||||
forceCloseAutofillOverlay: ({ port }) =>
|
||||
this.closeOverlay(port.sender, { forceCloseOverlay: true }),
|
||||
overlayPageBlurred: () => this.checkOverlayListFocused(),
|
||||
redirectOverlayFocusOut: ({ message, port }) => this.redirectOverlayFocusOut(message, port),
|
||||
};
|
||||
private readonly overlayListPortMessageHandlers: OverlayListPortMessageHandlers = {
|
||||
checkAutofillOverlayButtonFocused: () => this.checkOverlayButtonFocused(),
|
||||
forceCloseAutofillOverlay: ({ port }) => this.closeOverlay(port, true),
|
||||
forceCloseAutofillOverlay: ({ port }) =>
|
||||
this.closeOverlay(port.sender, { forceCloseOverlay: true }),
|
||||
overlayPageBlurred: () => this.checkOverlayButtonFocused(),
|
||||
unlockVault: ({ port }) => this.unlockVault(port),
|
||||
fillSelectedListItem: ({ message, port }) => this.fillSelectedOverlayListItem(message, port),
|
||||
@@ -216,7 +222,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
};
|
||||
|
||||
if (pageDetails.frameId !== 0 && pageDetails.details.fields.length) {
|
||||
void this.buildSubFrameOffset(pageDetails);
|
||||
void this.buildSubFrameOffsets(pageDetails);
|
||||
}
|
||||
|
||||
const pageDetailsMap = this.pageDetailsForTab[sender.tab.id];
|
||||
@@ -228,7 +234,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
pageDetailsMap.set(sender.frameId, pageDetails);
|
||||
}
|
||||
|
||||
private async buildSubFrameOffset({ tab, frameId, details }: PageDetail) {
|
||||
private async buildSubFrameOffsets({ tab, frameId, details }: PageDetail) {
|
||||
const tabId = tab.id;
|
||||
let subFrameOffsetsForTab = this.subFrameOffsetsForTab[tabId];
|
||||
if (!subFrameOffsetsForTab) {
|
||||
@@ -335,12 +341,42 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* Sends a message to the sender tab to close the autofill overlay.
|
||||
*
|
||||
* @param sender - The sender of the port message
|
||||
* @param forceCloseOverlay - Identifies whether the overlay should be force closed
|
||||
* @param forceCloseOverlay - Identifies whether the overlay should be forced closed
|
||||
* @param overlayElement - The overlay element to close, either the list or button
|
||||
*/
|
||||
private closeOverlay({ sender }: chrome.runtime.Port, forceCloseOverlay = false) {
|
||||
// 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
|
||||
BrowserApi.tabSendMessageData(sender.tab, "closeAutofillOverlay", { forceCloseOverlay });
|
||||
private closeOverlay(
|
||||
sender: chrome.runtime.MessageSender,
|
||||
{
|
||||
forceCloseOverlay,
|
||||
overlayElement,
|
||||
}: { forceCloseOverlay?: boolean; overlayElement?: string } = {},
|
||||
) {
|
||||
if (forceCloseOverlay) {
|
||||
void BrowserApi.tabSendMessage(sender.tab, { command: "closeInlineMenu" }, { frameId: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isFieldCurrentlyFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isCurrentlyFilling) {
|
||||
void BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{
|
||||
command: "closeInlineMenu",
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
},
|
||||
{ frameId: 0 },
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
void BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "closeInlineMenu", overlayElement },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,16 +402,32 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* is based on the focused field's position and dimensions.
|
||||
*
|
||||
* @param overlayElement - The overlay element to update, either the list or button
|
||||
* @param sender - The sender of the extension message
|
||||
*/
|
||||
private updateOverlayPosition({ overlayElement }: { overlayElement?: string }) {
|
||||
private async updateOverlayPosition(
|
||||
{ overlayElement }: { overlayElement?: string },
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
if (!overlayElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
await BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "updateInlineMenuElementsPosition" },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
|
||||
const subFrameOffsetsForTab = this.subFrameOffsetsForTab[sender.tab.id];
|
||||
let subFrameOffsets: SubFrameOffsetData;
|
||||
if (subFrameOffsetsForTab) {
|
||||
subFrameOffsets = subFrameOffsetsForTab.get(sender.frameId);
|
||||
}
|
||||
|
||||
if (overlayElement === AutofillOverlayElement.Button) {
|
||||
this.overlayButtonPort?.postMessage({
|
||||
command: "updateIframePosition",
|
||||
styles: this.getOverlayButtonPosition(),
|
||||
styles: this.getOverlayButtonPosition(subFrameOffsets),
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -383,7 +435,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
|
||||
this.overlayListPort?.postMessage({
|
||||
command: "updateIframePosition",
|
||||
styles: this.getOverlayListPosition(),
|
||||
styles: this.getOverlayListPosition(subFrameOffsets),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -391,11 +443,14 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* Gets the position of the focused field and calculates the position
|
||||
* of the overlay button based on the focused field's position and dimensions.
|
||||
*/
|
||||
private getOverlayButtonPosition() {
|
||||
private getOverlayButtonPosition(subFrameOffsets: SubFrameOffsetData) {
|
||||
if (!this.focusedFieldData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subFrameTopOffset = subFrameOffsets?.top || 0;
|
||||
const subFrameLeftOffset = subFrameOffsets?.left || 0;
|
||||
|
||||
const { top, left, width, height } = this.focusedFieldData.focusedFieldRects;
|
||||
const { paddingRight, paddingLeft } = this.focusedFieldData.focusedFieldStyles;
|
||||
let elementOffset = height * 0.37;
|
||||
@@ -403,15 +458,15 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
elementOffset = height >= 50 ? height * 0.47 : height * 0.42;
|
||||
}
|
||||
|
||||
const elementHeight = height - elementOffset;
|
||||
const elementTopPosition = top + elementOffset / 2;
|
||||
let elementLeftPosition = left + width - height + elementOffset / 2;
|
||||
|
||||
const fieldPaddingRight = parseInt(paddingRight, 10);
|
||||
const fieldPaddingLeft = parseInt(paddingLeft, 10);
|
||||
if (fieldPaddingRight > fieldPaddingLeft) {
|
||||
elementLeftPosition = left + width - height - (fieldPaddingRight - elementOffset + 2);
|
||||
}
|
||||
const elementHeight = height - elementOffset;
|
||||
|
||||
const elementTopPosition = subFrameTopOffset + top + elementOffset / 2;
|
||||
const elementLeftPosition =
|
||||
fieldPaddingRight > fieldPaddingLeft
|
||||
? subFrameLeftOffset + left + width - height - (fieldPaddingRight - elementOffset + 2)
|
||||
: subFrameLeftOffset + left + width - height + elementOffset / 2;
|
||||
|
||||
return {
|
||||
top: `${Math.round(elementTopPosition)}px`,
|
||||
@@ -425,16 +480,19 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* Gets the position of the focused field and calculates the position
|
||||
* of the overlay list based on the focused field's position and dimensions.
|
||||
*/
|
||||
private getOverlayListPosition() {
|
||||
private getOverlayListPosition(subFrameOffsets: SubFrameOffsetData) {
|
||||
if (!this.focusedFieldData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subFrameTopOffset = subFrameOffsets?.top || 0;
|
||||
const subFrameLeftOffset = subFrameOffsets?.left || 0;
|
||||
|
||||
const { top, left, width, height } = this.focusedFieldData.focusedFieldRects;
|
||||
return {
|
||||
width: `${Math.round(width)}px`,
|
||||
top: `${Math.round(top + height)}px`,
|
||||
left: `${Math.round(left)}px`,
|
||||
top: `${Math.round(top + height + subFrameTopOffset)}px`,
|
||||
left: `${Math.round(left + subFrameLeftOffset)}px`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -442,26 +500,34 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* Sets the focused field data to the data passed in the extension message.
|
||||
*
|
||||
* @param focusedFieldData - Contains the rects and styles of the focused field.
|
||||
* @param sender - The sender of the extension message
|
||||
*/
|
||||
private setFocusedFieldData(
|
||||
{ focusedFieldData }: OverlayBackgroundExtensionMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
this.focusedFieldData = focusedFieldData;
|
||||
this.focusedFieldData = { ...focusedFieldData, tabId: sender.tab.id, frameId: sender.frameId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the overlay's visibility based on the display property passed in the extension message.
|
||||
*
|
||||
* @param display - The display property of the overlay, either "block" or "none"
|
||||
* @param sender - The sender of the extension message
|
||||
*/
|
||||
private updateOverlayHidden({ display }: OverlayBackgroundExtensionMessage) {
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
|
||||
private updateOverlayHidden(
|
||||
{ isOverlayHidden }: OverlayBackgroundExtensionMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
) {
|
||||
const display = isOverlayHidden ? "none" : "block";
|
||||
const portMessage = { command: "updateOverlayHidden", styles: { display } };
|
||||
|
||||
void BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "toggleInlineMenuHidden", isInlineMenuHidden: isOverlayHidden },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
|
||||
this.overlayButtonPort?.postMessage(portMessage);
|
||||
this.overlayListPort?.postMessage(portMessage);
|
||||
}
|
||||
@@ -547,7 +613,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private async unlockVault(port: chrome.runtime.Port) {
|
||||
const { sender } = port;
|
||||
|
||||
this.closeOverlay(port);
|
||||
this.closeOverlay(port.sender);
|
||||
const retryMessage: LockedVaultPendingNotificationsData = {
|
||||
commandToRetry: { message: { command: "openAutofillOverlay" }, sender },
|
||||
target: "overlay.background",
|
||||
@@ -761,11 +827,14 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
translations: this.getTranslations(),
|
||||
ciphers: isOverlayListPort ? await this.getOverlayCipherData() : null,
|
||||
});
|
||||
this.updateOverlayPosition({
|
||||
overlayElement: isOverlayListPort
|
||||
? AutofillOverlayElement.List
|
||||
: AutofillOverlayElement.Button,
|
||||
});
|
||||
void this.updateOverlayPosition(
|
||||
{
|
||||
overlayElement: isOverlayListPort
|
||||
? AutofillOverlayElement.List
|
||||
: AutofillOverlayElement.Button,
|
||||
},
|
||||
port.sender,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
|
||||
import { SubFrameOffsetData } from "../../background/abstractions/overlay.background";
|
||||
import AutofillScript from "../../models/autofill-script";
|
||||
|
||||
type AutofillExtensionMessage = {
|
||||
export type AutofillExtensionMessage = {
|
||||
command: string;
|
||||
tab?: chrome.tabs.Tab;
|
||||
sender?: string;
|
||||
@@ -12,6 +12,7 @@ type AutofillExtensionMessage = {
|
||||
subFrameUrl?: string;
|
||||
pageDetailsUrl?: string;
|
||||
ciphers?: any;
|
||||
isInlineMenuHidden?: boolean;
|
||||
data?: {
|
||||
authStatus?: AuthenticationStatus;
|
||||
isFocusingFieldElement?: boolean;
|
||||
@@ -23,15 +24,14 @@ type AutofillExtensionMessage = {
|
||||
};
|
||||
};
|
||||
|
||||
type AutofillExtensionMessageParam = { message: AutofillExtensionMessage };
|
||||
export type AutofillExtensionMessageParam = { message: AutofillExtensionMessage };
|
||||
|
||||
type AutofillExtensionMessageHandlers = {
|
||||
export type AutofillExtensionMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
collectPageDetails: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
collectPageDetailsImmediately: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
fillForm: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
openAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
closeAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
addNewVaultItemFromOverlay: () => void;
|
||||
redirectOverlayFocusOut: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
updateIsOverlayCiphersPopulated: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
@@ -41,9 +41,7 @@ type AutofillExtensionMessageHandlers = {
|
||||
getSubFrameOffsets: ({ message }: AutofillExtensionMessageParam) => Promise<SubFrameOffsetData>;
|
||||
};
|
||||
|
||||
interface AutofillInit {
|
||||
export interface AutofillInit {
|
||||
init(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export { AutofillExtensionMessage, AutofillExtensionMessageHandlers, AutofillInit };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
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";
|
||||
import CollectAutofillContentService from "../services/collect-autofill-content.service";
|
||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
|
||||
class AutofillInit implements AutofillInitInterface {
|
||||
private readonly autofillOverlayContentService: AutofillOverlayContentService | undefined;
|
||||
private readonly inlineMenuElements: InlineMenuElements | undefined;
|
||||
private readonly domElementVisibilityService: DomElementVisibilityService;
|
||||
private readonly collectAutofillContentService: CollectAutofillContentService;
|
||||
private readonly insertAutofillContentService: InsertAutofillContentService;
|
||||
@@ -22,7 +24,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||
collectPageDetailsImmediately: ({ message }) => this.collectPageDetails(message, true),
|
||||
fillForm: ({ message }) => this.fillForm(message),
|
||||
openAutofillOverlay: ({ message }) => this.openAutofillOverlay(message),
|
||||
closeAutofillOverlay: ({ message }) => this.removeAutofillOverlay(message),
|
||||
// closeAutofillOverlay: ({ message }) => this.removeAutofillOverlay(message),
|
||||
addNewVaultItemFromOverlay: () => this.addNewVaultItemFromOverlay(),
|
||||
redirectOverlayFocusOut: ({ message }) => this.redirectOverlayFocusOut(message),
|
||||
updateIsOverlayCiphersPopulated: ({ message }) => this.updateIsOverlayCiphersPopulated(message),
|
||||
@@ -37,9 +39,28 @@ class AutofillInit implements AutofillInitInterface {
|
||||
* CollectAutofillContentService and InsertAutofillContentService classes.
|
||||
*
|
||||
* @param autofillOverlayContentService - The autofill overlay content service, potentially undefined.
|
||||
* @param inlineMenuElements - The inline menu elements, potentially undefined.
|
||||
*/
|
||||
constructor(autofillOverlayContentService?: AutofillOverlayContentService) {
|
||||
constructor(
|
||||
autofillOverlayContentService?: AutofillOverlayContentService,
|
||||
inlineMenuElements?: InlineMenuElements,
|
||||
) {
|
||||
this.autofillOverlayContentService = autofillOverlayContentService;
|
||||
if (this.autofillOverlayContentService) {
|
||||
this.extensionMessageHandlers = Object.assign(
|
||||
this.extensionMessageHandlers,
|
||||
this.autofillOverlayContentService.extensionMessageHandlers,
|
||||
);
|
||||
}
|
||||
|
||||
this.inlineMenuElements = inlineMenuElements;
|
||||
if (this.inlineMenuElements) {
|
||||
this.extensionMessageHandlers = Object.assign(
|
||||
this.extensionMessageHandlers,
|
||||
this.inlineMenuElements.extensionMessageHandlers,
|
||||
);
|
||||
}
|
||||
|
||||
this.domElementVisibilityService = new DomElementVisibilityService();
|
||||
this.collectAutofillContentService = new CollectAutofillContentService(
|
||||
this.domElementVisibilityService,
|
||||
@@ -169,33 +190,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.blurMostRecentOverlayField();
|
||||
this.removeAutofillOverlay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the autofill overlay if the field is not currently focused.
|
||||
* If the autofill is currently filling, only the overlay list will be
|
||||
* removed.
|
||||
*/
|
||||
private removeAutofillOverlay(message?: AutofillExtensionMessage) {
|
||||
if (message?.data?.forceCloseOverlay) {
|
||||
this.autofillOverlayContentService?.removeAutofillOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.autofillOverlayContentService ||
|
||||
this.autofillOverlayContentService.isFieldCurrentlyFocused
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.autofillOverlayContentService.isCurrentlyFilling) {
|
||||
this.autofillOverlayContentService.removeAutofillOverlayList();
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.removeAutofillOverlay();
|
||||
void sendExtensionMessage("closeAutofillOverlay");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,7 +252,6 @@ class AutofillInit implements AutofillInitInterface {
|
||||
const { subFrameUrl } = message;
|
||||
const subFrameUrlWithoutTrailingSlash = subFrameUrl?.replace(/\/$/, "");
|
||||
|
||||
// query iframe based on src attribute
|
||||
const iframeElement = document.querySelector(
|
||||
`iframe[src^="${subFrameUrlWithoutTrailingSlash}"]`,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { InlineMenuElements } from "../overlay/content/inline-menu-elements";
|
||||
import AutofillOverlayContentService from "../services/autofill-overlay-content.service";
|
||||
import { setupAutofillInitDisconnectAction } from "../utils";
|
||||
|
||||
@@ -6,7 +7,14 @@ import AutofillInit from "./autofill-init";
|
||||
(function (windowContext) {
|
||||
if (!windowContext.bitwardenAutofillInit) {
|
||||
const autofillOverlayContentService = new AutofillOverlayContentService();
|
||||
windowContext.bitwardenAutofillInit = new AutofillInit(autofillOverlayContentService);
|
||||
let inlineMenuElements: InlineMenuElements;
|
||||
if (globalThis.parent === globalThis.top) {
|
||||
inlineMenuElements = new InlineMenuElements();
|
||||
}
|
||||
windowContext.bitwardenAutofillInit = new AutofillInit(
|
||||
autofillOverlayContentService,
|
||||
inlineMenuElements,
|
||||
);
|
||||
setupAutofillInitDisconnectAction(windowContext);
|
||||
|
||||
windowContext.bitwardenAutofillInit.init();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { AutofillExtensionMessageParam } from "../../content/abstractions/autofill-init";
|
||||
|
||||
export type InlineMenuExtensionMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
closeInlineMenu: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
updateInlineMenuElementsPosition: () => Promise<[void, void]>;
|
||||
toggleInlineMenuHidden: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
};
|
||||
|
||||
export interface InlineMenuElements {
|
||||
extensionMessageHandlers: InlineMenuExtensionMessageHandlers;
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
import {
|
||||
sendExtensionMessage,
|
||||
generateRandomCustomElementName,
|
||||
setElementStyles,
|
||||
} from "../../utils";
|
||||
import { AutofillOverlayElement } from "../../utils/autofill-overlay.enum";
|
||||
import {
|
||||
InlineMenuExtensionMessageHandlers,
|
||||
InlineMenuElements as InlineMenuElementsInterface,
|
||||
} from "../abstractions/inline-menu-elements";
|
||||
import AutofillOverlayButtonIframe from "../iframe-content/autofill-overlay-button-iframe";
|
||||
import AutofillOverlayListIframe from "../iframe-content/autofill-overlay-list-iframe";
|
||||
|
||||
export class InlineMenuElements implements InlineMenuElementsInterface {
|
||||
private readonly sendExtensionMessage = sendExtensionMessage;
|
||||
private readonly generateRandomCustomElementName = generateRandomCustomElementName;
|
||||
private readonly setElementStyles = setElementStyles;
|
||||
private isFirefoxBrowser =
|
||||
globalThis.navigator.userAgent.indexOf(" Firefox/") !== -1 ||
|
||||
globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
|
||||
private buttonElement: HTMLElement;
|
||||
private listElement: HTMLElement;
|
||||
private isButtonVisible = false;
|
||||
private isListVisible = false;
|
||||
private overlayElementsMutationObserver: MutationObserver;
|
||||
private bodyElementMutationObserver: MutationObserver;
|
||||
private documentElementMutationObserver: MutationObserver;
|
||||
private mutationObserverIterations = 0;
|
||||
private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout;
|
||||
private readonly customElementDefaultStyles: Partial<CSSStyleDeclaration> = {
|
||||
all: "initial",
|
||||
position: "fixed",
|
||||
display: "block",
|
||||
zIndex: "2147483647",
|
||||
};
|
||||
private readonly _extensionMessageHandlers: InlineMenuExtensionMessageHandlers = {
|
||||
closeInlineMenu: ({ message }) => this.removeInlineMenu(),
|
||||
updateInlineMenuElementsPosition: () => this.updateInlineMenuElementsPosition(),
|
||||
toggleInlineMenuHidden: ({ message }) =>
|
||||
this.toggleInlineMenuHidden(message.isInlineMenuHidden),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.setupMutationObserver();
|
||||
}
|
||||
|
||||
get extensionMessageHandlers() {
|
||||
return this._extensionMessageHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message that facilitates hiding the overlay elements.
|
||||
*
|
||||
* @param isHidden - Indicates if the overlay elements should be hidden.
|
||||
*/
|
||||
private toggleInlineMenuHidden(isHidden: boolean) {
|
||||
this.isButtonVisible = !!this.buttonElement && !isHidden;
|
||||
this.isListVisible = !!this.listElement && !isHidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the autofill overlay from the page. This will initially
|
||||
* unobserve the body element to ensure the mutation observer no
|
||||
* longer triggers.
|
||||
*/
|
||||
private removeInlineMenu = () => {
|
||||
this.removeBodyElementObserver();
|
||||
this.removeInlineMenuButton();
|
||||
this.removeInlineMenuList();
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the overlay button from the DOM if it is currently present. Will
|
||||
* also remove the overlay reposition event listeners.
|
||||
*/
|
||||
private removeInlineMenuButton() {
|
||||
if (!this.buttonElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buttonElement.remove();
|
||||
|
||||
this.isButtonVisible = false;
|
||||
|
||||
void this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
overlayElement: AutofillOverlayElement.Button,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the overlay list from the DOM if it is currently present.
|
||||
*/
|
||||
private removeInlineMenuList() {
|
||||
if (!this.listElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listElement.remove();
|
||||
|
||||
this.isListVisible = false;
|
||||
|
||||
void this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position of both the overlay button and overlay list.
|
||||
*/
|
||||
private async updateInlineMenuElementsPosition() {
|
||||
return Promise.all([this.updateButtonPosition(), this.updateListPosition()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position of the overlay button.
|
||||
*/
|
||||
private async updateButtonPosition(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.buttonElement) {
|
||||
this.createButton();
|
||||
this.updateCustomElementDefaultStyles(this.buttonElement);
|
||||
}
|
||||
|
||||
if (!this.isButtonVisible) {
|
||||
this.appendOverlayElementToBody(this.buttonElement);
|
||||
this.isButtonVisible = true;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position of the overlay list.
|
||||
*/
|
||||
private async updateListPosition(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.listElement) {
|
||||
this.createList();
|
||||
this.updateCustomElementDefaultStyles(this.listElement);
|
||||
}
|
||||
|
||||
if (!this.isListVisible) {
|
||||
this.appendOverlayElementToBody(this.listElement);
|
||||
this.isListVisible = true;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the overlay element to the body element. This method will also
|
||||
* observe the body element to ensure that the overlay element is not
|
||||
* interfered with by any DOM changes.
|
||||
*
|
||||
* @param element - The overlay element to append to the body element.
|
||||
*/
|
||||
private appendOverlayElementToBody(element: HTMLElement) {
|
||||
this.observeBodyElement();
|
||||
globalThis.document.body.appendChild(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the autofill overlay button element. Will not attempt
|
||||
* to create the element if it already exists in the DOM.
|
||||
*/
|
||||
private createButton() {
|
||||
if (this.buttonElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isFirefoxBrowser) {
|
||||
this.buttonElement = globalThis.document.createElement("div");
|
||||
new AutofillOverlayButtonIframe(this.buttonElement);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const customElementName = this.generateRandomCustomElementName();
|
||||
globalThis.customElements?.define(
|
||||
customElementName,
|
||||
class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
new AutofillOverlayButtonIframe(this);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.buttonElement = globalThis.document.createElement(customElementName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the autofill overlay list element. Will not attempt
|
||||
* to create the element if it already exists in the DOM.
|
||||
*/
|
||||
private createList() {
|
||||
if (this.listElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isFirefoxBrowser) {
|
||||
this.listElement = globalThis.document.createElement("div");
|
||||
new AutofillOverlayListIframe(this.listElement);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const customElementName = this.generateRandomCustomElementName();
|
||||
globalThis.customElements?.define(
|
||||
customElementName,
|
||||
class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
new AutofillOverlayListIframe(this);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.listElement = globalThis.document.createElement(customElementName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the default styles for the custom element. This method will
|
||||
* remove any styles that are added to the custom element by other methods.
|
||||
*
|
||||
* @param element - The custom element to update the default styles for.
|
||||
*/
|
||||
private updateCustomElementDefaultStyles(element: HTMLElement) {
|
||||
this.unobserveCustomElements();
|
||||
|
||||
setElementStyles(element, this.customElementDefaultStyles, true);
|
||||
|
||||
this.observeCustomElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up mutation observers for the overlay elements, the body element, and the
|
||||
* document element. The mutation observers are used to remove any styles that are
|
||||
* added to the overlay elements by the website. They are also used to ensure that
|
||||
* the overlay elements are always present at the bottom of the body element.
|
||||
*/
|
||||
private setupMutationObserver = () => {
|
||||
this.overlayElementsMutationObserver = new MutationObserver(
|
||||
this.handleOverlayElementMutationObserverUpdate,
|
||||
);
|
||||
|
||||
this.bodyElementMutationObserver = new MutationObserver(
|
||||
this.handleBodyElementMutationObserverUpdate,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up mutation observers to verify that the overlay
|
||||
* elements are not modified by the website.
|
||||
*/
|
||||
private observeCustomElements() {
|
||||
if (this.buttonElement) {
|
||||
this.overlayElementsMutationObserver?.observe(this.buttonElement, {
|
||||
attributes: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.listElement) {
|
||||
this.overlayElementsMutationObserver?.observe(this.listElement, { attributes: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the mutation observers that are used to verify that the overlay
|
||||
* elements are not modified by the website.
|
||||
*/
|
||||
private unobserveCustomElements() {
|
||||
this.overlayElementsMutationObserver?.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a mutation observer for the body element. The mutation observer is used
|
||||
* to ensure that the overlay elements are always present at the bottom of the body
|
||||
* element.
|
||||
*/
|
||||
private observeBodyElement() {
|
||||
this.bodyElementMutationObserver?.observe(globalThis.document.body, { childList: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the mutation observer for the body element.
|
||||
*/
|
||||
private removeBodyElementObserver() {
|
||||
this.bodyElementMutationObserver?.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mutation observer update for the overlay elements. This method will
|
||||
* remove any attributes or styles that might be added to the overlay elements by
|
||||
* a separate process within the website where this script is injected.
|
||||
*
|
||||
* @param mutationRecord - The mutation record that triggered the update.
|
||||
*/
|
||||
private handleOverlayElementMutationObserverUpdate = (mutationRecord: MutationRecord[]) => {
|
||||
if (this.isTriggeringExcessiveMutationObserverIterations()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let recordIndex = 0; recordIndex < mutationRecord.length; recordIndex++) {
|
||||
const record = mutationRecord[recordIndex];
|
||||
if (record.type !== "attributes") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const element = record.target as HTMLElement;
|
||||
if (record.attributeName !== "style") {
|
||||
this.removeModifiedElementAttributes(element);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
element.removeAttribute("style");
|
||||
this.updateCustomElementDefaultStyles(element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all elements from a passed overlay
|
||||
* element except for the style attribute.
|
||||
*
|
||||
* @param element - The element to remove the attributes from.
|
||||
*/
|
||||
private removeModifiedElementAttributes(element: HTMLElement) {
|
||||
const attributes = Array.from(element.attributes);
|
||||
for (let attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) {
|
||||
const attribute = attributes[attributeIndex];
|
||||
if (attribute.name === "style") {
|
||||
continue;
|
||||
}
|
||||
|
||||
element.removeAttribute(attribute.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mutation observer update for the body element. This method will
|
||||
* ensure that the overlay elements are always present at the bottom of the body
|
||||
* element.
|
||||
*/
|
||||
private handleBodyElementMutationObserverUpdate = () => {
|
||||
if (
|
||||
(!this.buttonElement && !this.listElement) ||
|
||||
this.isTriggeringExcessiveMutationObserverIterations()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastChild = globalThis.document.body.lastElementChild;
|
||||
const secondToLastChild = lastChild?.previousElementSibling;
|
||||
const lastChildIsOverlayList = lastChild === this.listElement;
|
||||
const lastChildIsOverlayButton = lastChild === this.buttonElement;
|
||||
const secondToLastChildIsOverlayButton = secondToLastChild === this.buttonElement;
|
||||
|
||||
if (
|
||||
(lastChildIsOverlayList && secondToLastChildIsOverlayButton) ||
|
||||
(lastChildIsOverlayButton && !this.isListVisible)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(lastChildIsOverlayList && !secondToLastChildIsOverlayButton) ||
|
||||
(lastChildIsOverlayButton && this.isListVisible)
|
||||
) {
|
||||
globalThis.document.body.insertBefore(this.buttonElement, this.listElement);
|
||||
return;
|
||||
}
|
||||
|
||||
globalThis.document.body.insertBefore(lastChild, this.buttonElement);
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifies if the mutation observer is triggering excessive iterations.
|
||||
* Will trigger a blur of the most recently focused field and remove the
|
||||
* autofill overlay if any set mutation observer is triggering
|
||||
* excessive iterations.
|
||||
*/
|
||||
private isTriggeringExcessiveMutationObserverIterations() {
|
||||
if (this.mutationObserverIterationsResetTimeout) {
|
||||
clearTimeout(this.mutationObserverIterationsResetTimeout);
|
||||
}
|
||||
|
||||
this.mutationObserverIterations++;
|
||||
this.mutationObserverIterationsResetTimeout = setTimeout(
|
||||
() => (this.mutationObserverIterations = 0),
|
||||
2000,
|
||||
);
|
||||
|
||||
if (this.mutationObserverIterations > 100) {
|
||||
clearTimeout(this.mutationObserverIterationsResetTimeout);
|
||||
this.mutationObserverIterations = 0;
|
||||
void this.sendExtensionMessage("blurMostRecentOverlayField");
|
||||
this.removeInlineMenu();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
destroy() {
|
||||
this.documentElementMutationObserver?.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
||||
}
|
||||
|
||||
this.updateElementStyles(this.iframe, position);
|
||||
setTimeout(() => this.updateElementStyles(this.iframe, { opacity: "1" }), 0);
|
||||
setTimeout(() => this.updateElementStyles(this.iframe, { opacity: "1" }), 75);
|
||||
this.announceAriaAlert();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,32 +3,36 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
|
||||
import AutofillField from "../../models/autofill-field";
|
||||
import { ElementWithOpId, FormFieldElement } from "../../types";
|
||||
|
||||
type OpenAutofillOverlayOptions = {
|
||||
export type OpenAutofillOverlayOptions = {
|
||||
isFocusingFieldElement?: boolean;
|
||||
isOpeningFullOverlay?: boolean;
|
||||
authStatus?: AuthenticationStatus;
|
||||
};
|
||||
|
||||
interface AutofillOverlayContentService {
|
||||
export type AutofillOverlayContentExtensionMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
blurMostRecentOverlayField: () => void;
|
||||
};
|
||||
|
||||
export interface AutofillOverlayContentService {
|
||||
isFieldCurrentlyFocused: boolean;
|
||||
isCurrentlyFilling: boolean;
|
||||
isOverlayCiphersPopulated: boolean;
|
||||
pageDetailsUpdateRequired: boolean;
|
||||
autofillOverlayVisibility: number;
|
||||
extensionMessageHandlers: any;
|
||||
init(): void;
|
||||
setupAutofillOverlayListenerOnField(
|
||||
autofillFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
autofillFieldData: AutofillField,
|
||||
): Promise<void>;
|
||||
openAutofillOverlay(options: OpenAutofillOverlayOptions): void;
|
||||
removeAutofillOverlay(): void;
|
||||
removeAutofillOverlayButton(): void;
|
||||
removeAutofillOverlayList(): void;
|
||||
// removeAutofillOverlay(): void;
|
||||
// removeAutofillOverlayButton(): void;
|
||||
// removeAutofillOverlayList(): void;
|
||||
addNewVaultItem(): void;
|
||||
redirectOverlayFocusOut(direction: "previous" | "next"): void;
|
||||
focusMostRecentOverlayField(): void;
|
||||
blurMostRecentOverlayField(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export { OpenAutofillOverlayOptions, AutofillOverlayContentService };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,18 +7,19 @@ import { EVENTS, AutofillOverlayVisibility } from "@bitwarden/common/autofill/co
|
||||
|
||||
import { FocusedFieldData } from "../background/abstractions/overlay.background";
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import AutofillOverlayButtonIframe from "../overlay/iframe-content/autofill-overlay-button-iframe";
|
||||
import AutofillOverlayListIframe from "../overlay/iframe-content/autofill-overlay-list-iframe";
|
||||
// import AutofillOverlayButtonIframe from "../overlay/iframe-content/autofill-overlay-button-iframe";
|
||||
// import AutofillOverlayListIframe from "../overlay/iframe-content/autofill-overlay-list-iframe";
|
||||
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
|
||||
import {
|
||||
elementIsFillableFormField,
|
||||
generateRandomCustomElementName,
|
||||
// generateRandomCustomElementName,
|
||||
sendExtensionMessage,
|
||||
setElementStyles,
|
||||
// setElementStyles,
|
||||
} from "../utils";
|
||||
import { AutofillOverlayElement, RedirectFocusDirection } from "../utils/autofill-overlay.enum";
|
||||
|
||||
import {
|
||||
AutofillOverlayContentExtensionMessageHandlers,
|
||||
AutofillOverlayContentService as AutofillOverlayContentServiceInterface,
|
||||
OpenAutofillOverlayOptions,
|
||||
} from "./abstractions/autofill-overlay-content.service";
|
||||
@@ -30,10 +31,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
isOverlayCiphersPopulated = false;
|
||||
pageDetailsUpdateRequired = false;
|
||||
autofillOverlayVisibility: number;
|
||||
private isFirefoxBrowser =
|
||||
globalThis.navigator.userAgent.indexOf(" Firefox/") !== -1 ||
|
||||
globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
|
||||
private readonly generateRandomCustomElementName = generateRandomCustomElementName;
|
||||
// private isFirefoxBrowser =
|
||||
// globalThis.navigator.userAgent.indexOf(" Firefox/") !== -1 ||
|
||||
// globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
|
||||
// private readonly generateRandomCustomElementName = generateRandomCustomElementName;
|
||||
private readonly findTabs = tabbable;
|
||||
private readonly sendExtensionMessage = sendExtensionMessage;
|
||||
private formFieldElements: Set<ElementWithOpId<FormFieldElement>> = new Set([]);
|
||||
@@ -43,24 +44,27 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
private focusableElements: FocusableElement[] = [];
|
||||
private isOverlayButtonVisible = false;
|
||||
private isOverlayListVisible = false;
|
||||
private overlayButtonElement: HTMLElement;
|
||||
private overlayListElement: HTMLElement;
|
||||
// private overlayButtonElement: HTMLElement;
|
||||
// private overlayListElement: HTMLElement;
|
||||
private mostRecentlyFocusedField: ElementWithOpId<FormFieldElement>;
|
||||
private focusedFieldData: FocusedFieldData;
|
||||
private userInteractionEventTimeout: number | NodeJS.Timeout;
|
||||
private overlayElementsMutationObserver: MutationObserver;
|
||||
private bodyElementMutationObserver: MutationObserver;
|
||||
private documentElementMutationObserver: MutationObserver;
|
||||
private mutationObserverIterations = 0;
|
||||
private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout;
|
||||
// private overlayElementsMutationObserver: MutationObserver;
|
||||
// private bodyElementMutationObserver: MutationObserver;
|
||||
// private documentElementMutationObserver: MutationObserver;
|
||||
// private mutationObserverIterations = 0;
|
||||
// private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout;
|
||||
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
|
||||
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
||||
private readonly customElementDefaultStyles: Partial<CSSStyleDeclaration> = {
|
||||
all: "initial",
|
||||
position: "fixed",
|
||||
display: "block",
|
||||
zIndex: "2147483647",
|
||||
readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
|
||||
blurMostRecentOverlayField: () => this.blurMostRecentOverlayField,
|
||||
};
|
||||
// private readonly customElementDefaultStyles: Partial<CSSStyleDeclaration> = {
|
||||
// all: "initial",
|
||||
// position: "fixed",
|
||||
// display: "block",
|
||||
// zIndex: "2147483647",
|
||||
// };
|
||||
|
||||
/**
|
||||
* Initializes the autofill overlay content service by setting up the mutation observers.
|
||||
@@ -123,9 +127,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
}
|
||||
|
||||
if (this.pageDetailsUpdateRequired) {
|
||||
// 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
|
||||
this.sendExtensionMessage("bgCollectPageDetails", {
|
||||
void this.sendExtensionMessage("bgCollectPageDetails", {
|
||||
sender: "autofillOverlayContentService",
|
||||
});
|
||||
this.pageDetailsUpdateRequired = false;
|
||||
@@ -164,52 +166,52 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
this.mostRecentlyFocusedField?.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the autofill overlay from the page. This will initially
|
||||
* unobserve the body element to ensure the mutation observer no
|
||||
* longer triggers.
|
||||
*/
|
||||
removeAutofillOverlay = () => {
|
||||
this.removeBodyElementObserver();
|
||||
this.removeAutofillOverlayButton();
|
||||
this.removeAutofillOverlayList();
|
||||
};
|
||||
// /**
|
||||
// * Removes the autofill overlay from the page. This will initially
|
||||
// * unobserve the body element to ensure the mutation observer no
|
||||
// * longer triggers.
|
||||
// */
|
||||
// removeAutofillOverlay = () => {
|
||||
// this.removeBodyElementObserver();
|
||||
// this.removeAutofillOverlayButton();
|
||||
// this.removeAutofillOverlayList();
|
||||
// };
|
||||
|
||||
/**
|
||||
* Removes the overlay button from the DOM if it is currently present. Will
|
||||
* also remove the overlay reposition event listeners.
|
||||
*/
|
||||
removeAutofillOverlayButton() {
|
||||
if (!this.overlayButtonElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.overlayButtonElement.remove();
|
||||
this.isOverlayButtonVisible = false;
|
||||
// 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
|
||||
this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
overlayElement: AutofillOverlayElement.Button,
|
||||
});
|
||||
this.removeOverlayRepositionEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the overlay list from the DOM if it is currently present.
|
||||
*/
|
||||
removeAutofillOverlayList() {
|
||||
if (!this.overlayListElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.overlayListElement.remove();
|
||||
this.isOverlayListVisible = false;
|
||||
// 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
|
||||
this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
}
|
||||
// /**
|
||||
// * Removes the overlay button from the DOM if it is currently present. Will
|
||||
// * also remove the overlay reposition event listeners.
|
||||
// */
|
||||
// removeAutofillOverlayButton() {
|
||||
// if (!this.overlayButtonElement) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// this.overlayButtonElement.remove();
|
||||
// this.isOverlayButtonVisible = false;
|
||||
// // 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
|
||||
// this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
// overlayElement: AutofillOverlayElement.Button,
|
||||
// });
|
||||
// this.removeOverlayRepositionEventListeners();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Removes the overlay list from the DOM if it is currently present.
|
||||
// */
|
||||
// removeAutofillOverlayList() {
|
||||
// if (!this.overlayListElement) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// this.overlayListElement.remove();
|
||||
// this.isOverlayListVisible = false;
|
||||
// // 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
|
||||
// this.sendExtensionMessage("autofillOverlayElementClosed", {
|
||||
// overlayElement: AutofillOverlayElement.List,
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Formats any found user filled fields for a login cipher and sends a message
|
||||
@@ -227,9 +229,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
hostname: globalThis.document.location.hostname,
|
||||
};
|
||||
|
||||
// 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
|
||||
this.sendExtensionMessage("autofillOverlayAddNewVaultItem", { login });
|
||||
void this.sendExtensionMessage("autofillOverlayAddNewVaultItem", { login });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,7 +246,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
|
||||
if (direction === RedirectFocusDirection.Current) {
|
||||
this.focusMostRecentOverlayField();
|
||||
setTimeout(this.removeAutofillOverlay, 100);
|
||||
// setTimeout(this.removeAutofillOverlay, 100);
|
||||
setTimeout(() => void this.sendExtensionMessage("closeAutofillOverlay"), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -340,9 +341,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
*/
|
||||
private handleFormFieldBlurEvent = () => {
|
||||
this.isFieldCurrentlyFocused = false;
|
||||
// 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
|
||||
this.sendExtensionMessage("checkAutofillOverlayFocused");
|
||||
void this.sendExtensionMessage("checkAutofillOverlayFocused");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -356,7 +355,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
private handleFormFieldKeyupEvent = (event: KeyboardEvent) => {
|
||||
const eventCode = event.code;
|
||||
if (eventCode === "Escape") {
|
||||
this.removeAutofillOverlay();
|
||||
// this.removeAutofillOverlay();
|
||||
void this.sendExtensionMessage("closeAutofillOverlay");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -420,7 +420,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
this.storeModifiedFormElement(formFieldElement);
|
||||
|
||||
if (formFieldElement.value && (this.isOverlayCiphersPopulated || !this.isUserAuthed())) {
|
||||
this.removeAutofillOverlayList();
|
||||
// this.removeAutofillOverlayList();
|
||||
void this.sendExtensionMessage("closeAutofillOverlay", {
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -508,7 +511,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
this.autofillOverlayVisibility === AutofillOverlayVisibility.OnButtonClick ||
|
||||
(formElementHasValue && initiallyFocusedField !== this.mostRecentlyFocusedField)
|
||||
) {
|
||||
this.removeAutofillOverlayList();
|
||||
// this.removeAutofillOverlayList();
|
||||
void this.sendExtensionMessage("closeAutofillOverlay", {
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
}
|
||||
|
||||
if (!formElementHasValue || (!this.isOverlayCiphersPopulated && this.isUserAuthed())) {
|
||||
@@ -595,19 +601,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* Updates the position of the overlay button.
|
||||
*/
|
||||
private updateOverlayButtonPosition() {
|
||||
if (!this.overlayButtonElement) {
|
||||
this.createAutofillOverlayButton();
|
||||
this.updateCustomElementDefaultStyles(this.overlayButtonElement);
|
||||
}
|
||||
|
||||
if (!this.isOverlayButtonVisible) {
|
||||
this.appendOverlayElementToBody(this.overlayButtonElement);
|
||||
this.isOverlayButtonVisible = true;
|
||||
this.setOverlayRepositionEventListeners();
|
||||
}
|
||||
// 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
|
||||
this.sendExtensionMessage("updateAutofillOverlayPosition", {
|
||||
void this.sendExtensionMessage("updateAutofillOverlayPosition", {
|
||||
overlayElement: AutofillOverlayElement.Button,
|
||||
});
|
||||
}
|
||||
@@ -616,34 +610,22 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* Updates the position of the overlay list.
|
||||
*/
|
||||
private updateOverlayListPosition() {
|
||||
if (!this.overlayListElement) {
|
||||
this.createAutofillOverlayList();
|
||||
this.updateCustomElementDefaultStyles(this.overlayListElement);
|
||||
}
|
||||
|
||||
if (!this.isOverlayListVisible) {
|
||||
this.appendOverlayElementToBody(this.overlayListElement);
|
||||
this.isOverlayListVisible = true;
|
||||
}
|
||||
|
||||
// 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
|
||||
this.sendExtensionMessage("updateAutofillOverlayPosition", {
|
||||
void this.sendExtensionMessage("updateAutofillOverlayPosition", {
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the overlay element to the body element. This method will also
|
||||
* observe the body element to ensure that the overlay element is not
|
||||
* interfered with by any DOM changes.
|
||||
*
|
||||
* @param element - The overlay element to append to the body element.
|
||||
*/
|
||||
private appendOverlayElementToBody(element: HTMLElement) {
|
||||
this.observeBodyElement();
|
||||
globalThis.document.body.appendChild(element);
|
||||
}
|
||||
// /**
|
||||
// * Appends the overlay element to the body element. This method will also
|
||||
// * observe the body element to ensure that the overlay element is not
|
||||
// * interfered with by any DOM changes.
|
||||
// *
|
||||
// * @param element - The overlay element to append to the body element.
|
||||
// */
|
||||
// private appendOverlayElementToBody(element: HTMLElement) {
|
||||
// this.observeBodyElement();
|
||||
// globalThis.document.body.appendChild(element);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Sends a message that facilitates hiding the overlay elements.
|
||||
@@ -651,11 +633,9 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* @param isHidden - Indicates if the overlay elements should be hidden.
|
||||
*/
|
||||
private toggleOverlayHidden(isHidden: boolean) {
|
||||
const displayValue = isHidden ? "none" : "block";
|
||||
void this.sendExtensionMessage("updateAutofillOverlayHidden", { display: displayValue });
|
||||
|
||||
this.isOverlayButtonVisible = !!this.overlayButtonElement && !isHidden;
|
||||
this.isOverlayListVisible = !!this.overlayListElement && !isHidden;
|
||||
void this.sendExtensionMessage("updateAutofillOverlayHidden", {
|
||||
isOverlayHidden: isHidden,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -676,9 +656,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
focusedFieldRects: { width, height, top, left },
|
||||
};
|
||||
|
||||
// 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
|
||||
this.sendExtensionMessage("updateFocusedFieldData", {
|
||||
void this.sendExtensionMessage("updateFocusedFieldData", {
|
||||
focusedFieldData: this.focusedFieldData,
|
||||
});
|
||||
}
|
||||
@@ -762,77 +740,77 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
return !isLoginCipherField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the autofill overlay button element. Will not attempt
|
||||
* to create the element if it already exists in the DOM.
|
||||
*/
|
||||
private createAutofillOverlayButton() {
|
||||
if (this.overlayButtonElement) {
|
||||
return;
|
||||
}
|
||||
// /**
|
||||
// * Creates the autofill overlay button element. Will not attempt
|
||||
// * to create the element if it already exists in the DOM.
|
||||
// */
|
||||
// private createAutofillOverlayButton() {
|
||||
// if (this.overlayButtonElement) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (this.isFirefoxBrowser) {
|
||||
// this.overlayButtonElement = globalThis.document.createElement("div");
|
||||
// new AutofillOverlayButtonIframe(this.overlayButtonElement);
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const customElementName = this.generateRandomCustomElementName();
|
||||
// globalThis.customElements?.define(
|
||||
// customElementName,
|
||||
// class extends HTMLElement {
|
||||
// constructor() {
|
||||
// super();
|
||||
// new AutofillOverlayButtonIframe(this);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
// this.overlayButtonElement = globalThis.document.createElement(customElementName);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Creates the autofill overlay list element. Will not attempt
|
||||
// * to create the element if it already exists in the DOM.
|
||||
// */
|
||||
// private createAutofillOverlayList() {
|
||||
// if (this.overlayListElement) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (this.isFirefoxBrowser) {
|
||||
// this.overlayListElement = globalThis.document.createElement("div");
|
||||
// new AutofillOverlayListIframe(this.overlayListElement);
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const customElementName = this.generateRandomCustomElementName();
|
||||
// globalThis.customElements?.define(
|
||||
// customElementName,
|
||||
// class extends HTMLElement {
|
||||
// constructor() {
|
||||
// super();
|
||||
// new AutofillOverlayListIframe(this);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
// this.overlayListElement = globalThis.document.createElement(customElementName);
|
||||
// }
|
||||
|
||||
if (this.isFirefoxBrowser) {
|
||||
this.overlayButtonElement = globalThis.document.createElement("div");
|
||||
new AutofillOverlayButtonIframe(this.overlayButtonElement);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const customElementName = this.generateRandomCustomElementName();
|
||||
globalThis.customElements?.define(
|
||||
customElementName,
|
||||
class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
new AutofillOverlayButtonIframe(this);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.overlayButtonElement = globalThis.document.createElement(customElementName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the autofill overlay list element. Will not attempt
|
||||
* to create the element if it already exists in the DOM.
|
||||
*/
|
||||
private createAutofillOverlayList() {
|
||||
if (this.overlayListElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isFirefoxBrowser) {
|
||||
this.overlayListElement = globalThis.document.createElement("div");
|
||||
new AutofillOverlayListIframe(this.overlayListElement);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const customElementName = this.generateRandomCustomElementName();
|
||||
globalThis.customElements?.define(
|
||||
customElementName,
|
||||
class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
new AutofillOverlayListIframe(this);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.overlayListElement = globalThis.document.createElement(customElementName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the default styles for the custom element. This method will
|
||||
* remove any styles that are added to the custom element by other methods.
|
||||
*
|
||||
* @param element - The custom element to update the default styles for.
|
||||
*/
|
||||
private updateCustomElementDefaultStyles(element: HTMLElement) {
|
||||
this.unobserveCustomElements();
|
||||
|
||||
setElementStyles(element, this.customElementDefaultStyles, true);
|
||||
|
||||
this.observeCustomElements();
|
||||
}
|
||||
// /**
|
||||
// * Updates the default styles for the custom element. This method will
|
||||
// * remove any styles that are added to the custom element by other methods.
|
||||
// *
|
||||
// * @param element - The custom element to update the default styles for.
|
||||
// */
|
||||
// private updateCustomElementDefaultStyles(element: HTMLElement) {
|
||||
// this.unobserveCustomElements();
|
||||
//
|
||||
// setElementStyles(element, this.customElementDefaultStyles, true);
|
||||
//
|
||||
// this.observeCustomElements();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Queries the background script for the autofill overlay visibility setting.
|
||||
@@ -890,7 +868,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
private triggerOverlayRepositionUpdates = async () => {
|
||||
if (!this.recentlyFocusedFieldIsCurrentlyFocused()) {
|
||||
this.toggleOverlayHidden(false);
|
||||
this.removeAutofillOverlay();
|
||||
// this.removeAutofillOverlay();
|
||||
void this.sendExtensionMessage("closeAutofillOverlay");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -906,7 +885,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeAutofillOverlay();
|
||||
// this.removeAutofillOverlay();
|
||||
void this.sendExtensionMessage("closeAutofillOverlay");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -927,7 +907,8 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
private setupGlobalEventListeners = () => {
|
||||
globalThis.document.addEventListener(EVENTS.VISIBILITYCHANGE, this.handleVisibilityChangeEvent);
|
||||
globalThis.addEventListener(EVENTS.FOCUSOUT, this.handleFormFieldBlurEvent);
|
||||
this.setupMutationObserver();
|
||||
this.setOverlayRepositionEventListeners();
|
||||
// this.setupMutationObserver();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -940,178 +921,179 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
}
|
||||
|
||||
this.mostRecentlyFocusedField = null;
|
||||
this.removeAutofillOverlay();
|
||||
// this.removeAutofillOverlay();
|
||||
void this.sendExtensionMessage("closeAutofillOverlay");
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up mutation observers for the overlay elements, the body element, and the
|
||||
* document element. The mutation observers are used to remove any styles that are
|
||||
* added to the overlay elements by the website. They are also used to ensure that
|
||||
* the overlay elements are always present at the bottom of the body element.
|
||||
*/
|
||||
private setupMutationObserver = () => {
|
||||
this.overlayElementsMutationObserver = new MutationObserver(
|
||||
this.handleOverlayElementMutationObserverUpdate,
|
||||
);
|
||||
// /**
|
||||
// * Sets up mutation observers for the overlay elements, the body element, and the
|
||||
// * document element. The mutation observers are used to remove any styles that are
|
||||
// * added to the overlay elements by the website. They are also used to ensure that
|
||||
// * the overlay elements are always present at the bottom of the body element.
|
||||
// */
|
||||
// private setupMutationObserver = () => {
|
||||
// this.overlayElementsMutationObserver = new MutationObserver(
|
||||
// this.handleOverlayElementMutationObserverUpdate,
|
||||
// );
|
||||
//
|
||||
// this.bodyElementMutationObserver = new MutationObserver(
|
||||
// this.handleBodyElementMutationObserverUpdate,
|
||||
// );
|
||||
// };
|
||||
|
||||
this.bodyElementMutationObserver = new MutationObserver(
|
||||
this.handleBodyElementMutationObserverUpdate,
|
||||
);
|
||||
};
|
||||
// /**
|
||||
// * Sets up mutation observers to verify that the overlay
|
||||
// * elements are not modified by the website.
|
||||
// */
|
||||
// private observeCustomElements() {
|
||||
// if (this.overlayButtonElement) {
|
||||
// this.overlayElementsMutationObserver?.observe(this.overlayButtonElement, {
|
||||
// attributes: true,
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// if (this.overlayListElement) {
|
||||
// this.overlayElementsMutationObserver?.observe(this.overlayListElement, { attributes: true });
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Sets up mutation observers to verify that the overlay
|
||||
* elements are not modified by the website.
|
||||
*/
|
||||
private observeCustomElements() {
|
||||
if (this.overlayButtonElement) {
|
||||
this.overlayElementsMutationObserver?.observe(this.overlayButtonElement, {
|
||||
attributes: true,
|
||||
});
|
||||
}
|
||||
// /**
|
||||
// * Disconnects the mutation observers that are used to verify that the overlay
|
||||
// * elements are not modified by the website.
|
||||
// */
|
||||
// private unobserveCustomElements() {
|
||||
// this.overlayElementsMutationObserver?.disconnect();
|
||||
// }
|
||||
|
||||
if (this.overlayListElement) {
|
||||
this.overlayElementsMutationObserver?.observe(this.overlayListElement, { attributes: true });
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * Sets up a mutation observer for the body element. The mutation observer is used
|
||||
// * to ensure that the overlay elements are always present at the bottom of the body
|
||||
// * element.
|
||||
// */
|
||||
// private observeBodyElement() {
|
||||
// this.bodyElementMutationObserver?.observe(globalThis.document.body, { childList: true });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Disconnects the mutation observers that are used to verify that the overlay
|
||||
* elements are not modified by the website.
|
||||
*/
|
||||
private unobserveCustomElements() {
|
||||
this.overlayElementsMutationObserver?.disconnect();
|
||||
}
|
||||
// /**
|
||||
// * Disconnects the mutation observer for the body element.
|
||||
// */
|
||||
// private removeBodyElementObserver() {
|
||||
// this.bodyElementMutationObserver?.disconnect();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Sets up a mutation observer for the body element. The mutation observer is used
|
||||
* to ensure that the overlay elements are always present at the bottom of the body
|
||||
* element.
|
||||
*/
|
||||
private observeBodyElement() {
|
||||
this.bodyElementMutationObserver?.observe(globalThis.document.body, { childList: true });
|
||||
}
|
||||
// /**
|
||||
// * Handles the mutation observer update for the overlay elements. This method will
|
||||
// * remove any attributes or styles that might be added to the overlay elements by
|
||||
// * a separate process within the website where this script is injected.
|
||||
// *
|
||||
// * @param mutationRecord - The mutation record that triggered the update.
|
||||
// */
|
||||
// private handleOverlayElementMutationObserverUpdate = (mutationRecord: MutationRecord[]) => {
|
||||
// if (this.isTriggeringExcessiveMutationObserverIterations()) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// for (let recordIndex = 0; recordIndex < mutationRecord.length; recordIndex++) {
|
||||
// const record = mutationRecord[recordIndex];
|
||||
// if (record.type !== "attributes") {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// const element = record.target as HTMLElement;
|
||||
// if (record.attributeName !== "style") {
|
||||
// this.removeModifiedElementAttributes(element);
|
||||
//
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// element.removeAttribute("style");
|
||||
// this.updateCustomElementDefaultStyles(element);
|
||||
// }
|
||||
// };
|
||||
|
||||
/**
|
||||
* Disconnects the mutation observer for the body element.
|
||||
*/
|
||||
private removeBodyElementObserver() {
|
||||
this.bodyElementMutationObserver?.disconnect();
|
||||
}
|
||||
// /**
|
||||
// * Removes all elements from a passed overlay
|
||||
// * element except for the style attribute.
|
||||
// *
|
||||
// * @param element - The element to remove the attributes from.
|
||||
// */
|
||||
// private removeModifiedElementAttributes(element: HTMLElement) {
|
||||
// const attributes = Array.from(element.attributes);
|
||||
// for (let attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) {
|
||||
// const attribute = attributes[attributeIndex];
|
||||
// if (attribute.name === "style") {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// element.removeAttribute(attribute.name);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Handles the mutation observer update for the overlay elements. This method will
|
||||
* remove any attributes or styles that might be added to the overlay elements by
|
||||
* a separate process within the website where this script is injected.
|
||||
*
|
||||
* @param mutationRecord - The mutation record that triggered the update.
|
||||
*/
|
||||
private handleOverlayElementMutationObserverUpdate = (mutationRecord: MutationRecord[]) => {
|
||||
if (this.isTriggeringExcessiveMutationObserverIterations()) {
|
||||
return;
|
||||
}
|
||||
// /**
|
||||
// * Handles the mutation observer update for the body element. This method will
|
||||
// * ensure that the overlay elements are always present at the bottom of the body
|
||||
// * element.
|
||||
// */
|
||||
// private handleBodyElementMutationObserverUpdate = () => {
|
||||
// if (
|
||||
// (!this.overlayButtonElement && !this.overlayListElement) ||
|
||||
// this.isTriggeringExcessiveMutationObserverIterations()
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const lastChild = globalThis.document.body.lastElementChild;
|
||||
// const secondToLastChild = lastChild?.previousElementSibling;
|
||||
// const lastChildIsOverlayList = lastChild === this.overlayListElement;
|
||||
// const lastChildIsOverlayButton = lastChild === this.overlayButtonElement;
|
||||
// const secondToLastChildIsOverlayButton = secondToLastChild === this.overlayButtonElement;
|
||||
//
|
||||
// if (
|
||||
// (lastChildIsOverlayList && secondToLastChildIsOverlayButton) ||
|
||||
// (lastChildIsOverlayButton && !this.isOverlayListVisible)
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (
|
||||
// (lastChildIsOverlayList && !secondToLastChildIsOverlayButton) ||
|
||||
// (lastChildIsOverlayButton && this.isOverlayListVisible)
|
||||
// ) {
|
||||
// globalThis.document.body.insertBefore(this.overlayButtonElement, this.overlayListElement);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// globalThis.document.body.insertBefore(lastChild, this.overlayButtonElement);
|
||||
// };
|
||||
|
||||
for (let recordIndex = 0; recordIndex < mutationRecord.length; recordIndex++) {
|
||||
const record = mutationRecord[recordIndex];
|
||||
if (record.type !== "attributes") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const element = record.target as HTMLElement;
|
||||
if (record.attributeName !== "style") {
|
||||
this.removeModifiedElementAttributes(element);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
element.removeAttribute("style");
|
||||
this.updateCustomElementDefaultStyles(element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all elements from a passed overlay
|
||||
* element except for the style attribute.
|
||||
*
|
||||
* @param element - The element to remove the attributes from.
|
||||
*/
|
||||
private removeModifiedElementAttributes(element: HTMLElement) {
|
||||
const attributes = Array.from(element.attributes);
|
||||
for (let attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) {
|
||||
const attribute = attributes[attributeIndex];
|
||||
if (attribute.name === "style") {
|
||||
continue;
|
||||
}
|
||||
|
||||
element.removeAttribute(attribute.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mutation observer update for the body element. This method will
|
||||
* ensure that the overlay elements are always present at the bottom of the body
|
||||
* element.
|
||||
*/
|
||||
private handleBodyElementMutationObserverUpdate = () => {
|
||||
if (
|
||||
(!this.overlayButtonElement && !this.overlayListElement) ||
|
||||
this.isTriggeringExcessiveMutationObserverIterations()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastChild = globalThis.document.body.lastElementChild;
|
||||
const secondToLastChild = lastChild?.previousElementSibling;
|
||||
const lastChildIsOverlayList = lastChild === this.overlayListElement;
|
||||
const lastChildIsOverlayButton = lastChild === this.overlayButtonElement;
|
||||
const secondToLastChildIsOverlayButton = secondToLastChild === this.overlayButtonElement;
|
||||
|
||||
if (
|
||||
(lastChildIsOverlayList && secondToLastChildIsOverlayButton) ||
|
||||
(lastChildIsOverlayButton && !this.isOverlayListVisible)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(lastChildIsOverlayList && !secondToLastChildIsOverlayButton) ||
|
||||
(lastChildIsOverlayButton && this.isOverlayListVisible)
|
||||
) {
|
||||
globalThis.document.body.insertBefore(this.overlayButtonElement, this.overlayListElement);
|
||||
return;
|
||||
}
|
||||
|
||||
globalThis.document.body.insertBefore(lastChild, this.overlayButtonElement);
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifies if the mutation observer is triggering excessive iterations.
|
||||
* Will trigger a blur of the most recently focused field and remove the
|
||||
* autofill overlay if any set mutation observer is triggering
|
||||
* excessive iterations.
|
||||
*/
|
||||
private isTriggeringExcessiveMutationObserverIterations() {
|
||||
if (this.mutationObserverIterationsResetTimeout) {
|
||||
clearTimeout(this.mutationObserverIterationsResetTimeout);
|
||||
}
|
||||
|
||||
this.mutationObserverIterations++;
|
||||
this.mutationObserverIterationsResetTimeout = setTimeout(
|
||||
() => (this.mutationObserverIterations = 0),
|
||||
2000,
|
||||
);
|
||||
|
||||
if (this.mutationObserverIterations > 100) {
|
||||
clearTimeout(this.mutationObserverIterationsResetTimeout);
|
||||
this.mutationObserverIterations = 0;
|
||||
this.blurMostRecentOverlayField();
|
||||
this.removeAutofillOverlay();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// /**
|
||||
// * Identifies if the mutation observer is triggering excessive iterations.
|
||||
// * Will trigger a blur of the most recently focused field and remove the
|
||||
// * autofill overlay if any set mutation observer is triggering
|
||||
// * excessive iterations.
|
||||
// */
|
||||
// private isTriggeringExcessiveMutationObserverIterations() {
|
||||
// if (this.mutationObserverIterationsResetTimeout) {
|
||||
// clearTimeout(this.mutationObserverIterationsResetTimeout);
|
||||
// }
|
||||
//
|
||||
// this.mutationObserverIterations++;
|
||||
// this.mutationObserverIterationsResetTimeout = setTimeout(
|
||||
// () => (this.mutationObserverIterations = 0),
|
||||
// 2000,
|
||||
// );
|
||||
//
|
||||
// if (this.mutationObserverIterations > 100) {
|
||||
// clearTimeout(this.mutationObserverIterationsResetTimeout);
|
||||
// this.mutationObserverIterations = 0;
|
||||
// this.blurMostRecentOverlayField();
|
||||
// this.removeAutofillOverlay();
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Gets the root node of the passed element and returns the active element within that root node.
|
||||
@@ -1132,7 +1114,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* disconnect the mutation observers and remove all event listeners.
|
||||
*/
|
||||
destroy() {
|
||||
this.documentElementMutationObserver?.disconnect();
|
||||
// this.documentElementMutationObserver?.disconnect();
|
||||
this.clearUserInteractionEventTimeout();
|
||||
this.formFieldElements.forEach((formFieldElement) => {
|
||||
this.removeCachedFormFieldEventListeners(formFieldElement);
|
||||
@@ -1145,7 +1127,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
this.handleVisibilityChangeEvent,
|
||||
);
|
||||
globalThis.removeEventListener(EVENTS.FOCUSOUT, this.handleFormFieldBlurEvent);
|
||||
this.removeAutofillOverlay();
|
||||
// this.removeAutofillOverlay();
|
||||
this.removeOverlayRepositionEventListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user