1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

[PM-5189] Fixing an issue with how we handle closing the inline menu after a programmtic redirection

This commit is contained in:
Cesar Gonzalez
2024-06-12 06:00:59 -05:00
parent 7673a55784
commit 42080a7377
4 changed files with 52 additions and 11 deletions

View File

@@ -1488,6 +1488,7 @@ describe("OverlayBackground", () => {
describe("triggerDelayedAutofillInlineMenuClosure message handler", () => { describe("triggerDelayedAutofillInlineMenuClosure message handler", () => {
it("skips triggering the delayed closure of the inline menu if a field is currently focused", async () => { it("skips triggering the delayed closure of the inline menu if a field is currently focused", async () => {
jest.useFakeTimers();
sendMockExtensionMessage({ sendMockExtensionMessage({
command: "updateIsFieldCurrentlyFocused", command: "updateIsFieldCurrentlyFocused",
isFieldCurrentlyFocused: true, isFieldCurrentlyFocused: true,
@@ -1499,6 +1500,7 @@ describe("OverlayBackground", () => {
portKey, portKey,
}); });
await flushPromises(); await flushPromises();
jest.advanceTimersByTime(100);
const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; const message = { command: "triggerDelayedAutofillInlineMenuClosure" };
expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith(message); expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith(message);
@@ -1506,16 +1508,42 @@ describe("OverlayBackground", () => {
}); });
it("sends a message to the button and list ports that triggers a delayed closure of the inline menu", async () => { it("sends a message to the button and list ports that triggers a delayed closure of the inline menu", async () => {
jest.useFakeTimers();
sendPortMessage(buttonMessageConnectorSpy, { sendPortMessage(buttonMessageConnectorSpy, {
command: "triggerDelayedAutofillInlineMenuClosure", command: "triggerDelayedAutofillInlineMenuClosure",
portKey, portKey,
}); });
await flushPromises(); await flushPromises();
jest.advanceTimersByTime(100);
const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; const message = { command: "triggerDelayedAutofillInlineMenuClosure" };
expect(buttonPortSpy.postMessage).toHaveBeenCalledWith(message); expect(buttonPortSpy.postMessage).toHaveBeenCalledWith(message);
expect(listPortSpy.postMessage).toHaveBeenCalledWith(message); expect(listPortSpy.postMessage).toHaveBeenCalledWith(message);
}); });
it("triggers a single delayed closure if called again within a 100ms threshold", async () => {
jest.useFakeTimers();
sendPortMessage(buttonMessageConnectorSpy, {
command: "triggerDelayedAutofillInlineMenuClosure",
portKey,
});
await flushPromises();
jest.advanceTimersByTime(50);
sendPortMessage(buttonMessageConnectorSpy, {
command: "triggerDelayedAutofillInlineMenuClosure",
portKey,
});
await flushPromises();
jest.advanceTimersByTime(100);
const message = { command: "triggerDelayedAutofillInlineMenuClosure" };
expect(buttonPortSpy.postMessage).toHaveBeenCalledTimes(2);
expect(buttonPortSpy.postMessage).not.toHaveBeenNthCalledWith(1, message);
expect(buttonPortSpy.postMessage).toHaveBeenNthCalledWith(2, message);
expect(listPortSpy.postMessage).toHaveBeenCalledTimes(2);
expect(listPortSpy.postMessage).not.toHaveBeenNthCalledWith(1, message);
expect(listPortSpy.postMessage).toHaveBeenNthCalledWith(2, message);
});
}); });
describe("autofillInlineMenuBlurred message handler", () => { describe("autofillInlineMenuBlurred message handler", () => {

View File

@@ -60,6 +60,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
private inlineMenuPageTranslations: Record<string, string>; private inlineMenuPageTranslations: Record<string, string>;
private inlineMenuFadeInTimeout: number | NodeJS.Timeout; private inlineMenuFadeInTimeout: number | NodeJS.Timeout;
private updateInlineMenuPositionTimeout: number | NodeJS.Timeout; private updateInlineMenuPositionTimeout: number | NodeJS.Timeout;
private delayedCloseTimeout: number | NodeJS.Timeout;
private focusedFieldData: FocusedFieldData; private focusedFieldData: FocusedFieldData;
private isFieldCurrentlyFocused: boolean = false; private isFieldCurrentlyFocused: boolean = false;
private isFieldCurrentlyFilling: boolean = false; private isFieldCurrentlyFilling: boolean = false;
@@ -99,8 +100,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
deletedCipher: () => this.updateInlineMenuCiphers(), deletedCipher: () => this.updateInlineMenuCiphers(),
}; };
private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = { private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = {
triggerDelayedAutofillInlineMenuClosure: ({ port }) => triggerDelayedAutofillInlineMenuClosure: ({ port }) => this.triggerDelayedInlineMenuClosure(),
this.triggerDelayedInlineMenuClosure(port.sender),
autofillInlineMenuButtonClicked: ({ port }) => this.handleInlineMenuButtonClicked(port), autofillInlineMenuButtonClicked: ({ port }) => this.handleInlineMenuButtonClicked(port),
autofillInlineMenuBlurred: () => this.checkInlineMenuListFocused(), autofillInlineMenuBlurred: () => this.checkInlineMenuListFocused(),
redirectAutofillInlineMenuFocusOut: ({ message, port }) => redirectAutofillInlineMenuFocusOut: ({ message, port }) =>
@@ -372,7 +372,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
} }
} }
this.updateInlineMenuPositionTimeout = setTimeout( this.updateInlineMenuPositionTimeout = globalThis.setTimeout(
() => this.updateInlineMenuPositionAfterSubFrameRebuild(sender), () => this.updateInlineMenuPositionAfterSubFrameRebuild(sender),
650, 650,
); );
@@ -510,17 +510,29 @@ export class OverlayBackground implements OverlayBackgroundInterface {
* Sends a message to the sender tab to trigger a delayed closure of the inline menu. * Sends a message to the sender tab to trigger a delayed closure of the inline menu.
* This is used to ensure that we capture click events on the inline menu in the case * This is used to ensure that we capture click events on the inline menu in the case
* that some on page programmatic method attempts to force focus redirection. * that some on page programmatic method attempts to force focus redirection.
*
* @param sender - The sender of the port message
*/ */
private triggerDelayedInlineMenuClosure(sender: chrome.runtime.MessageSender) { private triggerDelayedInlineMenuClosure() {
if (this.isFieldCurrentlyFocused) { if (this.isFieldCurrentlyFocused) {
return; return;
} }
const message = { command: "triggerDelayedAutofillInlineMenuClosure" }; this.clearDelayedInlineMenuClosure();
this.inlineMenuButtonPort?.postMessage(message);
this.inlineMenuListPort?.postMessage(message); this.delayedCloseTimeout = globalThis.setTimeout(() => {
const message = { command: "triggerDelayedAutofillInlineMenuClosure" };
this.inlineMenuButtonPort?.postMessage(message);
this.inlineMenuListPort?.postMessage(message);
}, 100);
}
/**
* Clears the delayed closure timeout for the inline menu, effectively
* cancelling the event from occurring.
*/
private clearDelayedInlineMenuClosure() {
if (this.delayedCloseTimeout) {
clearTimeout(this.delayedCloseTimeout);
}
} }
/** /**
@@ -772,6 +784,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
return; return;
} }
this.clearDelayedInlineMenuClosure();
await this.openInlineMenu(false, true); await this.openInlineMenu(false, true);
} }

View File

@@ -416,7 +416,7 @@ describe("AutofillInlineMenuIframeService", () => {
"opacity 65ms ease-out 0s", "opacity 65ms ease-out 0s",
); );
jest.advanceTimersByTime(200); jest.advanceTimersByTime(100);
expect(autofillInlineMenuIframeService["iframe"].style.transition).toBe( expect(autofillInlineMenuIframeService["iframe"].style.transition).toBe(
"opacity 125ms ease-out 0s", "opacity 125ms ease-out 0s",
); );

View File

@@ -322,7 +322,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe
this.delayedCloseTimeout = globalThis.setTimeout(() => { this.delayedCloseTimeout = globalThis.setTimeout(() => {
this.updateElementStyles(this.iframe, { transition: this.fadeInOpacityTransition }); this.updateElementStyles(this.iframe, { transition: this.fadeInOpacityTransition });
this.forceCloseInlineMenu(); this.forceCloseInlineMenu();
}, 200); }, 100);
} }
/** /**