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