mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-06 23:51:28 +01:00
[PM-5189] Reworking extension messages used within autofill init
This commit is contained in:
parent
ef716ee728
commit
e9c351f7f3
@ -326,13 +326,8 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
subFrameOffsetsForTab.set(frameId, null);
|
||||
void BrowserApi.tabSendMessage(
|
||||
tab,
|
||||
{
|
||||
command: "getSubFrameOffsetsThroughWindowMessaging",
|
||||
subFrameId: frameId,
|
||||
},
|
||||
{
|
||||
frameId: frameId,
|
||||
},
|
||||
{ command: "getSubFrameOffsetsFromWindowMessage", subFrameId: frameId },
|
||||
{ frameId: frameId },
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -494,7 +489,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
|
||||
await BrowserApi.tabSendMessage(
|
||||
sender.tab,
|
||||
{ command: "updateInlineMenuElementsPosition", overlayElement },
|
||||
{ command: "appendInlineMenuElementsToDom", overlayElement },
|
||||
{ frameId: 0 },
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
|
||||
import { SubFrameOffsetData } from "../../background/abstractions/overlay.background";
|
||||
import AutofillScript from "../../models/autofill-script";
|
||||
|
||||
export type AutofillExtensionMessage = {
|
||||
@ -19,7 +18,7 @@ export type AutofillExtensionMessage = {
|
||||
authStatus?: AuthenticationStatus;
|
||||
isFocusingFieldElement?: boolean;
|
||||
isOverlayCiphersPopulated?: boolean;
|
||||
direction?: "previous" | "next";
|
||||
direction?: "previous" | "next" | "current";
|
||||
isOpeningFullOverlay?: boolean;
|
||||
forceCloseOverlay?: boolean;
|
||||
autofillOverlayVisibility?: number;
|
||||
@ -33,15 +32,6 @@ export type AutofillExtensionMessageHandlers = {
|
||||
collectPageDetails: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
collectPageDetailsImmediately: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
fillForm: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
openAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
addNewVaultItemFromOverlay: () => void;
|
||||
redirectOverlayFocusOut: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
updateIsOverlayCiphersPopulated: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
bgUnlockPopoutOpened: () => void;
|
||||
bgVaultItemRepromptPopoutOpened: () => void;
|
||||
updateAutofillOverlayVisibility: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
getSubFrameOffsets: ({ message }: AutofillExtensionMessageParam) => Promise<SubFrameOffsetData>;
|
||||
getSubFrameOffsetsThroughWindowMessaging: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
};
|
||||
|
||||
export interface AutofillInit {
|
||||
|
@ -7,7 +7,6 @@ import AutofillPageDetails from "../models/autofill-page-details";
|
||||
import AutofillScript from "../models/autofill-script";
|
||||
import AutofillOverlayContentService from "../services/autofill-overlay-content.service";
|
||||
import { flushPromises, sendMockExtensionMessage } from "../spec/testing-utils";
|
||||
import { RedirectFocusDirection } from "../utils/autofill-overlay.enum";
|
||||
|
||||
import { AutofillExtensionMessage } from "./abstractions/autofill-init";
|
||||
import AutofillInit from "./autofill-init";
|
||||
@ -422,32 +421,6 @@ describe("AutofillInit", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("redirectOverlayFocusOut", () => {
|
||||
const message = {
|
||||
command: "redirectOverlayFocusOut",
|
||||
data: {
|
||||
direction: RedirectFocusDirection.Next,
|
||||
},
|
||||
};
|
||||
|
||||
it("ignores the message to redirect focus if the autofillOverlayContentService does not exist", () => {
|
||||
const newAutofillInit = new AutofillInit(undefined);
|
||||
newAutofillInit.init();
|
||||
|
||||
sendMockExtensionMessage(message);
|
||||
|
||||
expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined);
|
||||
});
|
||||
|
||||
it("redirects the overlay focus", () => {
|
||||
sendMockExtensionMessage(message);
|
||||
|
||||
expect(
|
||||
autofillInit["autofillOverlayContentService"].redirectOverlayFocusOut,
|
||||
).toHaveBeenCalledWith(message.data.direction);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateIsOverlayCiphersPopulated", () => {
|
||||
const message = {
|
||||
command: "updateIsOverlayCiphersPopulated",
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { SubFrameOffsetData } from "../background/abstractions/overlay.background";
|
||||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
import { InlineMenuElements } from "../overlay/abstractions/inline-menu-elements";
|
||||
import { AutofillOverlayContentService } from "../services/abstractions/autofill-overlay-content.service";
|
||||
@ -24,16 +23,6 @@ class AutofillInit implements AutofillInitInterface {
|
||||
collectPageDetails: ({ message }) => this.collectPageDetails(message),
|
||||
collectPageDetailsImmediately: ({ message }) => this.collectPageDetails(message, true),
|
||||
fillForm: ({ message }) => this.fillForm(message),
|
||||
openAutofillOverlay: ({ message }) => this.openAutofillOverlay(message),
|
||||
addNewVaultItemFromOverlay: () => this.addNewVaultItemFromOverlay(),
|
||||
redirectOverlayFocusOut: ({ message }) => this.redirectOverlayFocusOut(message),
|
||||
updateIsOverlayCiphersPopulated: ({ message }) => this.updateIsOverlayCiphersPopulated(message),
|
||||
bgUnlockPopoutOpened: () => this.blurAndRemoveOverlay(),
|
||||
bgVaultItemRepromptPopoutOpened: () => this.blurAndRemoveOverlay(),
|
||||
updateAutofillOverlayVisibility: ({ message }) => this.updateAutofillOverlayVisibility(message),
|
||||
getSubFrameOffsets: ({ message }) => this.getSubFrameOffsets(message),
|
||||
getSubFrameOffsetsThroughWindowMessaging: ({ message }) =>
|
||||
this.getSubFrameOffsetsThroughWindowMessaging(message),
|
||||
};
|
||||
|
||||
/**
|
||||
@ -72,45 +61,6 @@ class AutofillInit implements AutofillInitInterface {
|
||||
this.domElementVisibilityService,
|
||||
this.collectAutofillContentService,
|
||||
);
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
// if (event.source !== window) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (event.data.command === "calculateSubFramePositioning") {
|
||||
const subFrameData = event.data.subFrameData;
|
||||
let subFrameOffsets: SubFrameOffsetData;
|
||||
const iframes = document.querySelectorAll("iframe");
|
||||
for (let i = 0; i < iframes.length; i++) {
|
||||
if (iframes[i].contentWindow === event.source) {
|
||||
const iframeElement = iframes[i];
|
||||
subFrameOffsets = this.calculateSubFrameOffsets(
|
||||
iframeElement,
|
||||
subFrameData.url,
|
||||
subFrameData.frameId,
|
||||
);
|
||||
|
||||
subFrameData.top += subFrameOffsets.top;
|
||||
subFrameData.left += subFrameOffsets.left;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (globalThis.window.self !== globalThis.window.top) {
|
||||
globalThis.parent.postMessage(
|
||||
{ command: "calculateSubFramePositioning", subFrameData },
|
||||
"*",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
void sendExtensionMessage("updateSubFrameData", {
|
||||
subFrameData,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,19 +148,6 @@ class AutofillInit implements AutofillInitInterface {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the autofill overlay.
|
||||
*
|
||||
* @param data - The extension message data.
|
||||
*/
|
||||
private openAutofillOverlay({ data }: AutofillExtensionMessage) {
|
||||
if (!this.autofillOverlayContentService) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.openAutofillOverlay(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blurs the most recent overlay field and removes the overlay. Used
|
||||
* in cases where the background unlock or vault item reprompt popout
|
||||
@ -221,117 +158,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.blurMostRecentOverlayField();
|
||||
void sendExtensionMessage("closeAutofillOverlay");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new vault item from the overlay.
|
||||
*/
|
||||
private addNewVaultItemFromOverlay() {
|
||||
if (!this.autofillOverlayContentService) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.addNewVaultItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the overlay focus out of an overlay iframe.
|
||||
*
|
||||
* @param data - Contains the direction to redirect the focus.
|
||||
*/
|
||||
private redirectOverlayFocusOut({ data }: AutofillExtensionMessage) {
|
||||
if (!this.autofillOverlayContentService) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.redirectOverlayFocusOut(data?.direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates whether the current tab has ciphers that can populate the overlay list
|
||||
*
|
||||
* @param data - Contains the isOverlayCiphersPopulated value
|
||||
*
|
||||
*/
|
||||
private updateIsOverlayCiphersPopulated({ data }: AutofillExtensionMessage) {
|
||||
if (!this.autofillOverlayContentService) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.isOverlayCiphersPopulated = Boolean(
|
||||
data?.isOverlayCiphersPopulated,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the autofill overlay visibility.
|
||||
*
|
||||
* @param data - Contains the autoFillOverlayVisibility value
|
||||
*/
|
||||
private updateAutofillOverlayVisibility({ data }: AutofillExtensionMessage) {
|
||||
if (!this.autofillOverlayContentService || isNaN(data?.autofillOverlayVisibility)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayContentService.autofillOverlayVisibility = data?.autofillOverlayVisibility;
|
||||
}
|
||||
|
||||
private async getSubFrameOffsets(
|
||||
message: AutofillExtensionMessage,
|
||||
): Promise<SubFrameOffsetData | null> {
|
||||
const { subFrameUrl } = message;
|
||||
const subFrameUrlWithoutTrailingSlash = subFrameUrl?.replace(/\/$/, "");
|
||||
|
||||
let iframeElement: HTMLIFrameElement | null = null;
|
||||
const iframeElements = document.querySelectorAll(
|
||||
`iframe[src="${subFrameUrl}"], iframe[src="${subFrameUrlWithoutTrailingSlash}"]`,
|
||||
) as NodeListOf<HTMLIFrameElement>;
|
||||
if (iframeElements.length === 1) {
|
||||
iframeElement = iframeElements[0];
|
||||
}
|
||||
|
||||
if (!iframeElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.calculateSubFrameOffsets(iframeElement, subFrameUrl);
|
||||
}
|
||||
|
||||
private calculateSubFrameOffsets(
|
||||
iframeElement: HTMLIFrameElement,
|
||||
subFrameUrl?: string,
|
||||
frameId?: number,
|
||||
): SubFrameOffsetData {
|
||||
const iframeRect = iframeElement.getBoundingClientRect();
|
||||
const iframeStyles = globalThis.getComputedStyle(iframeElement);
|
||||
const paddingLeft = parseInt(iframeStyles.getPropertyValue("padding-left"));
|
||||
const paddingTop = parseInt(iframeStyles.getPropertyValue("padding-top"));
|
||||
const borderWidthLeft = parseInt(iframeStyles.getPropertyValue("border-left-width"));
|
||||
const borderWidthTop = parseInt(iframeStyles.getPropertyValue("border-top-width"));
|
||||
|
||||
return {
|
||||
url: subFrameUrl,
|
||||
frameId,
|
||||
top: iframeRect.top + paddingTop + borderWidthTop,
|
||||
left: iframeRect.left + paddingLeft + borderWidthLeft,
|
||||
};
|
||||
}
|
||||
|
||||
private getSubFrameOffsetsThroughWindowMessaging(message: any) {
|
||||
globalThis.parent.postMessage(
|
||||
{
|
||||
command: "calculateSubFramePositioning",
|
||||
subFrameData: {
|
||||
url: window.location.href,
|
||||
frameId: message.subFrameId,
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
},
|
||||
"*",
|
||||
);
|
||||
this.autofillOverlayContentService.blurMostRecentOverlayField(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -364,9 +191,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
Promise.resolve(messageResponse).then((response) => sendResponse(response));
|
||||
void Promise.resolve(messageResponse).then((response) => sendResponse(response));
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { AutofillExtensionMessageParam } from "../../content/abstractions/autofi
|
||||
export type InlineMenuExtensionMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
closeInlineMenu: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
updateInlineMenuElementsPosition: ({ message }: AutofillExtensionMessageParam) => Promise<void>;
|
||||
appendInlineMenuElementsToDom: ({ message }: AutofillExtensionMessageParam) => Promise<void>;
|
||||
toggleInlineMenuHidden: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
checkIsInlineMenuButtonVisible: () => boolean;
|
||||
checkIsInlineMenuListVisible: () => boolean;
|
||||
|
@ -36,8 +36,7 @@ export class InlineMenuElements implements InlineMenuElementsInterface {
|
||||
};
|
||||
private readonly _extensionMessageHandlers: InlineMenuExtensionMessageHandlers = {
|
||||
closeInlineMenu: ({ message }) => this.removeInlineMenu(message),
|
||||
updateInlineMenuElementsPosition: ({ message }) =>
|
||||
this.updateInlineMenuElementsPosition(message),
|
||||
appendInlineMenuElementsToDom: ({ message }) => this.appendInlineMenuElements(message),
|
||||
toggleInlineMenuHidden: ({ message }) =>
|
||||
this.toggleInlineMenuHidden(message.isInlineMenuHidden),
|
||||
checkIsInlineMenuButtonVisible: () => this.isButtonVisible,
|
||||
@ -125,25 +124,25 @@ export class InlineMenuElements implements InlineMenuElementsInterface {
|
||||
/**
|
||||
* Updates the position of both the overlay button and overlay list.
|
||||
*/
|
||||
private async updateInlineMenuElementsPosition({ overlayElement }: AutofillExtensionMessage) {
|
||||
private async appendInlineMenuElements({ overlayElement }: AutofillExtensionMessage) {
|
||||
if (overlayElement === AutofillOverlayElement.Button) {
|
||||
return this.updateButtonPosition();
|
||||
return this.appendButtonElement();
|
||||
}
|
||||
|
||||
return this.updateListPosition();
|
||||
return this.appendListElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position of the overlay button.
|
||||
*/
|
||||
private async updateButtonPosition(): Promise<void> {
|
||||
private async appendButtonElement(): Promise<void> {
|
||||
if (!this.buttonElement) {
|
||||
this.createButton();
|
||||
this.updateCustomElementDefaultStyles(this.buttonElement);
|
||||
}
|
||||
|
||||
if (!this.isButtonVisible) {
|
||||
this.appendOverlayElementToBody(this.buttonElement);
|
||||
this.appendInlineMenuElementToBody(this.buttonElement);
|
||||
this.isButtonVisible = true;
|
||||
}
|
||||
}
|
||||
@ -151,14 +150,14 @@ export class InlineMenuElements implements InlineMenuElementsInterface {
|
||||
/**
|
||||
* Updates the position of the overlay list.
|
||||
*/
|
||||
private async updateListPosition(): Promise<void> {
|
||||
private async appendListElement(): Promise<void> {
|
||||
if (!this.listElement) {
|
||||
this.createList();
|
||||
this.updateCustomElementDefaultStyles(this.listElement);
|
||||
}
|
||||
|
||||
if (!this.isListVisible) {
|
||||
this.appendOverlayElementToBody(this.listElement);
|
||||
this.appendInlineMenuElementToBody(this.listElement);
|
||||
this.isListVisible = true;
|
||||
}
|
||||
}
|
||||
@ -170,7 +169,7 @@ export class InlineMenuElements implements InlineMenuElementsInterface {
|
||||
*
|
||||
* @param element - The overlay element to append to the body element.
|
||||
*/
|
||||
private appendOverlayElementToBody(element: HTMLElement) {
|
||||
private appendInlineMenuElementToBody(element: HTMLElement) {
|
||||
this.observeBodyElement();
|
||||
globalThis.document.body.appendChild(element);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
|
||||
import { SubFrameOffsetData } from "../../background/abstractions/overlay.background";
|
||||
import { AutofillExtensionMessageParam } from "../../content/abstractions/autofill-init";
|
||||
import AutofillField from "../../models/autofill-field";
|
||||
import { ElementWithOpId, FormFieldElement } from "../../types";
|
||||
|
||||
@ -11,7 +13,16 @@ export type OpenAutofillOverlayOptions = {
|
||||
|
||||
export type AutofillOverlayContentExtensionMessageHandlers = {
|
||||
[key: string]: CallableFunction;
|
||||
openAutofillOverlay: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
addNewVaultItemFromOverlay: () => void;
|
||||
blurMostRecentOverlayField: () => void;
|
||||
bgUnlockPopoutOpened: () => void;
|
||||
bgVaultItemRepromptPopoutOpened: () => void;
|
||||
redirectOverlayFocusOut: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
updateAutofillOverlayVisibility: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
updateIsOverlayCiphersPopulated: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
getSubFrameOffsets: ({ message }: AutofillExtensionMessageParam) => Promise<SubFrameOffsetData>;
|
||||
getSubFrameOffsetsFromWindowMessage: ({ message }: AutofillExtensionMessageParam) => void;
|
||||
};
|
||||
|
||||
export interface AutofillOverlayContentService {
|
||||
@ -31,8 +42,7 @@ export interface AutofillOverlayContentService {
|
||||
// removeAutofillOverlayButton(): void;
|
||||
// removeAutofillOverlayList(): void;
|
||||
addNewVaultItem(): void;
|
||||
redirectOverlayFocusOut(direction: "previous" | "next"): void;
|
||||
focusMostRecentOverlayField(): void;
|
||||
blurMostRecentOverlayField(): void;
|
||||
blurMostRecentOverlayField(isRemovingOverlay?: boolean): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
@ -5,7 +5,11 @@ import { FocusableElement, tabbable } from "tabbable";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { EVENTS, AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
||||
|
||||
import { FocusedFieldData } from "../background/abstractions/overlay.background";
|
||||
import {
|
||||
FocusedFieldData,
|
||||
SubFrameOffsetData,
|
||||
} from "../background/abstractions/overlay.background";
|
||||
import { AutofillExtensionMessage } from "../content/abstractions/autofill-init";
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
|
||||
import { elementIsFillableFormField, sendExtensionMessage } from "../utils";
|
||||
@ -36,7 +40,17 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
|
||||
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
||||
readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
|
||||
blurMostRecentOverlayField: () => this.blurMostRecentOverlayField,
|
||||
openAutofillOverlay: ({ message }) => this.openAutofillOverlay(message.data),
|
||||
addNewVaultItemFromOverlay: () => this.addNewVaultItem(),
|
||||
blurMostRecentOverlayField: () => this.blurMostRecentOverlayField(),
|
||||
bgUnlockPopoutOpened: () => this.blurMostRecentOverlayField(true),
|
||||
bgVaultItemRepromptPopoutOpened: () => this.blurMostRecentOverlayField(true),
|
||||
redirectOverlayFocusOut: ({ message }) => this.redirectOverlayFocusOut(message),
|
||||
updateAutofillOverlayVisibility: ({ message }) => this.updateAutofillOverlayVisibility(message),
|
||||
updateIsOverlayCiphersPopulated: ({ message }) => this.updateIsOverlayCiphersPopulated(message),
|
||||
getSubFrameOffsets: ({ message }) => this.getSubFrameOffsets(message),
|
||||
getSubFrameOffsetsFromWindowMessage: ({ message }) =>
|
||||
this.getSubFrameOffsetsFromWindowMessage(message),
|
||||
};
|
||||
|
||||
/**
|
||||
@ -135,8 +149,12 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
/**
|
||||
* Removes focus from the most recently focused field element.
|
||||
*/
|
||||
blurMostRecentOverlayField() {
|
||||
blurMostRecentOverlayField(isRemovingOverlay: boolean = false) {
|
||||
this.mostRecentlyFocusedField?.blur();
|
||||
|
||||
if (isRemovingOverlay) {
|
||||
void sendExtensionMessage("closeAutofillOverlay");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,16 +181,19 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* either previous or next in the tab order. If the direction is current, the most
|
||||
* recently focused field will be focused.
|
||||
*
|
||||
* @param direction - The direction to redirect the focus.
|
||||
* @param data - Contains the direction to redirect the focus.
|
||||
*/
|
||||
async redirectOverlayFocusOut(direction: string) {
|
||||
async redirectOverlayFocusOut({ data }: AutofillExtensionMessage) {
|
||||
if (
|
||||
!data?.direction ||
|
||||
!this.mostRecentlyFocusedField ||
|
||||
(await this.sendExtensionMessage("checkIsInlineMenuListVisible")) !== true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { direction } = data;
|
||||
|
||||
if (direction === RedirectFocusDirection.Current) {
|
||||
this.focusMostRecentOverlayField();
|
||||
setTimeout(() => void this.sendExtensionMessage("closeAutofillOverlay"), 100);
|
||||
@ -573,7 +594,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
private async updateMostRecentlyFocusedField(
|
||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
) {
|
||||
if (!elementIsFillableFormField(formFieldElement)) {
|
||||
if (!formFieldElement || !elementIsFillableFormField(formFieldElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -781,6 +802,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* overlay elements.
|
||||
*/
|
||||
private setupGlobalEventListeners = () => {
|
||||
globalThis.addEventListener(EVENTS.MESSAGE, this.handleWindowMessageEvent);
|
||||
globalThis.document.addEventListener(EVENTS.VISIBILITYCHANGE, this.handleVisibilityChangeEvent);
|
||||
globalThis.addEventListener(EVENTS.FOCUSOUT, this.handleFormFieldBlurEvent);
|
||||
this.setOverlayRepositionEventListeners();
|
||||
@ -815,6 +837,112 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
return documentRoot?.activeElement;
|
||||
}
|
||||
|
||||
private async getSubFrameOffsets(
|
||||
message: AutofillExtensionMessage,
|
||||
): Promise<SubFrameOffsetData | null> {
|
||||
const { subFrameUrl } = message;
|
||||
const subFrameUrlWithoutTrailingSlash = subFrameUrl?.replace(/\/$/, "");
|
||||
|
||||
let iframeElement: HTMLIFrameElement | null = null;
|
||||
const iframeElements = document.querySelectorAll(
|
||||
`iframe[src="${subFrameUrl}"], iframe[src="${subFrameUrlWithoutTrailingSlash}"]`,
|
||||
) as NodeListOf<HTMLIFrameElement>;
|
||||
if (iframeElements.length === 1) {
|
||||
iframeElement = iframeElements[0];
|
||||
}
|
||||
|
||||
if (!iframeElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.calculateSubFrameOffsets(iframeElement, subFrameUrl);
|
||||
}
|
||||
|
||||
private calculateSubFrameOffsets(
|
||||
iframeElement: HTMLIFrameElement,
|
||||
subFrameUrl?: string,
|
||||
frameId?: number,
|
||||
): SubFrameOffsetData {
|
||||
const iframeRect = iframeElement.getBoundingClientRect();
|
||||
const iframeStyles = globalThis.getComputedStyle(iframeElement);
|
||||
const paddingLeft = parseInt(iframeStyles.getPropertyValue("padding-left"));
|
||||
const paddingTop = parseInt(iframeStyles.getPropertyValue("padding-top"));
|
||||
const borderWidthLeft = parseInt(iframeStyles.getPropertyValue("border-left-width"));
|
||||
const borderWidthTop = parseInt(iframeStyles.getPropertyValue("border-top-width"));
|
||||
|
||||
return {
|
||||
url: subFrameUrl,
|
||||
frameId,
|
||||
top: iframeRect.top + paddingTop + borderWidthTop,
|
||||
left: iframeRect.left + paddingLeft + borderWidthLeft,
|
||||
};
|
||||
}
|
||||
|
||||
private getSubFrameOffsetsFromWindowMessage(message: any) {
|
||||
globalThis.parent.postMessage(
|
||||
{
|
||||
command: "calculateSubFramePositioning",
|
||||
subFrameData: {
|
||||
url: window.location.href,
|
||||
frameId: message.subFrameId,
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
},
|
||||
"*",
|
||||
);
|
||||
}
|
||||
|
||||
private handleWindowMessageEvent = (event: MessageEvent) => {
|
||||
if (event.data?.command !== "calculateSubFramePositioning") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.calculateSubFramePositioning(event);
|
||||
};
|
||||
|
||||
private calculateSubFramePositioning = (event: MessageEvent) => {
|
||||
const subFrameData = event.data.subFrameData;
|
||||
let subFrameOffsets: SubFrameOffsetData;
|
||||
const iframes = document.querySelectorAll("iframe");
|
||||
for (let i = 0; i < iframes.length; i++) {
|
||||
if (iframes[i].contentWindow === event.source) {
|
||||
const iframeElement = iframes[i];
|
||||
subFrameOffsets = this.calculateSubFrameOffsets(
|
||||
iframeElement,
|
||||
subFrameData.url,
|
||||
subFrameData.frameId,
|
||||
);
|
||||
|
||||
subFrameData.top += subFrameOffsets.top;
|
||||
subFrameData.left += subFrameOffsets.left;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (globalThis.window.self !== globalThis.window.top) {
|
||||
globalThis.parent.postMessage({ command: "calculateSubFramePositioning", subFrameData }, "*");
|
||||
return;
|
||||
}
|
||||
|
||||
void sendExtensionMessage("updateSubFrameData", {
|
||||
subFrameData,
|
||||
});
|
||||
};
|
||||
|
||||
private updateAutofillOverlayVisibility({ data }: AutofillExtensionMessage) {
|
||||
if (isNaN(data?.autofillOverlayVisibility)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autofillOverlayVisibility = data.autofillOverlayVisibility;
|
||||
}
|
||||
|
||||
private updateIsOverlayCiphersPopulated({ data }: AutofillExtensionMessage) {
|
||||
this.isOverlayCiphersPopulated = Boolean(data?.isOverlayCiphersPopulated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the autofill overlay content service. This method will
|
||||
* disconnect the mutation observers and remove all event listeners.
|
||||
|
Loading…
Reference in New Issue
Block a user