[PM-5432] Overlay button iframe presents with a white background on websites that use dark mode (#7415)

* [PM-5432] Overlay button iframe presents with a white background

* [PM-5432] Adding method that allows us to update the overlay button color scheme dynamically

* [PM-5432] Adding jest tests to validate implementation changes
This commit is contained in:
Cesar Gonzalez 2024-01-08 16:15:53 -06:00 committed by GitHub
parent cc9a347482
commit e3f20d81e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 92 additions and 2 deletions

View File

@ -1,6 +1,6 @@
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
type OverlayButtonMessage = { command: string };
type OverlayButtonMessage = { command: string; colorScheme?: string };
type UpdateAuthStatusMessage = OverlayButtonMessage & { authStatus: AuthenticationStatus };
@ -18,10 +18,12 @@ type OverlayButtonWindowMessageHandlers = {
}: {
message: UpdateAuthStatusMessage;
}) => void;
updateOverlayPageColorScheme: ({ message }: { message: OverlayButtonMessage }) => void;
};
export {
UpdateAuthStatusMessage,
OverlayButtonMessage,
InitAutofillOverlayButtonMessage,
OverlayButtonWindowMessageHandlers,
};

View File

@ -7,6 +7,7 @@ type AutofillOverlayIframeExtensionMessage = {
type AutofillOverlayIframeWindowMessageHandlers = {
[key: string]: CallableFunction;
updateAutofillOverlayListHeight: (message: AutofillOverlayIframeExtensionMessage) => void;
getPageColorScheme: () => void;
};
type AutofillOverlayIframeExtensionMessageParam = {

View File

@ -385,6 +385,46 @@ describe("AutofillOverlayIframeService", () => {
expect(autofillOverlayIframeService["iframe"].style.height).toBe("300px");
});
describe("getPageColorScheme window message", () => {
afterEach(() => {
globalThis.document.head.innerHTML = "";
});
it("gets and updates the overlay page color scheme", () => {
const colorSchemeMetaTag = globalThis.document.createElement("meta");
colorSchemeMetaTag.setAttribute("name", "color-scheme");
colorSchemeMetaTag.setAttribute("content", "dark");
globalThis.document.head.append(colorSchemeMetaTag);
globalThis.dispatchEvent(
new MessageEvent("message", {
data: { command: "getPageColorScheme" },
source: autofillOverlayIframeService["iframe"].contentWindow,
origin: "chrome-extension://id",
}),
);
expect(autofillOverlayIframeService["iframe"].contentWindow.postMessage).toBeCalledWith(
{ command: "updateOverlayPageColorScheme", colorScheme: "dark" },
"*",
);
});
it("sends a normal color scheme if the color scheme meta tag is not present", () => {
globalThis.dispatchEvent(
new MessageEvent("message", {
data: { command: "getPageColorScheme" },
source: autofillOverlayIframeService["iframe"].contentWindow,
origin: "chrome-extension://id",
}),
);
expect(autofillOverlayIframeService["iframe"].contentWindow.postMessage).toBeCalledWith(
{ command: "updateOverlayPageColorScheme", colorScheme: "normal" },
"*",
);
});
});
});
});

View File

@ -43,6 +43,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
private readonly windowMessageHandlers: AutofillOverlayIframeWindowMessageHandlers = {
updateAutofillOverlayListHeight: (message) =>
this.updateElementStyles(this.iframe, message.styles),
getPageColorScheme: () => this.updateOverlayPageColorScheme(),
};
private readonly backgroundPortMessageHandlers: BackgroundPortMessageHandlers = {
initAutofillOverlayList: ({ message }) => this.initAutofillOverlayList(message),
@ -238,6 +239,22 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
this.announceAriaAlert();
}
/**
* Gets the page color scheme meta tag and sends a message to the iframe
* to update its color scheme. Will default to "normal" if the meta tag
* does not exist.
*/
private updateOverlayPageColorScheme() {
const colorSchemeValue = globalThis.document
.querySelector("meta[name='color-scheme']")
?.getAttribute("content");
this.iframe.contentWindow?.postMessage(
{ command: "updateOverlayPageColorScheme", colorScheme: colorSchemeValue || "normal" },
"*",
);
}
/**
* Handles messages sent from the iframe. If the message does not have a
* specified handler set, it passes the message to the background script.

View File

@ -64,7 +64,9 @@ describe("AutofillOverlayButton", () => {
postWindowMessage({ command: "checkAutofillOverlayButtonFocused" });
expect(globalThis.parent.postMessage).not.toHaveBeenCalled();
expect(globalThis.parent.postMessage).not.toHaveBeenCalledWith({
command: "closeAutofillOverlay",
});
});
it("posts a message to close the autofill overlay if the element is not focused during the focus check", () => {
@ -88,5 +90,19 @@ describe("AutofillOverlayButton", () => {
expect(autofillOverlayButton["authStatus"]).toBe(AuthenticationStatus.Unlocked);
});
it("updates the page color scheme meta tag", () => {
const colorSchemeMetaTag = globalThis.document.createElement("meta");
colorSchemeMetaTag.setAttribute("name", "color-scheme");
colorSchemeMetaTag.setAttribute("content", "light");
globalThis.document.head.append(colorSchemeMetaTag);
postWindowMessage({
command: "updateOverlayPageColorScheme",
colorScheme: "dark",
});
expect(colorSchemeMetaTag.getAttribute("content")).toBe("dark");
});
});
});

View File

@ -7,6 +7,7 @@ import { buildSvgDomElement } from "../../../utils";
import { logoIcon, logoLockedIcon } from "../../../utils/svg-icons";
import {
InitAutofillOverlayButtonMessage,
OverlayButtonMessage,
OverlayButtonWindowMessageHandlers,
} from "../../abstractions/autofill-overlay-button";
import AutofillOverlayPageElement from "../shared/autofill-overlay-page-element";
@ -21,6 +22,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
checkAutofillOverlayButtonFocused: () => this.checkButtonFocused(),
updateAutofillOverlayButtonAuthStatus: ({ message }) =>
this.updateAuthStatus(message.authStatus),
updateOverlayPageColorScheme: ({ message }) => this.updatePageColorScheme(message),
};
constructor() {
@ -61,6 +63,7 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
this.getTranslation("toggleBitwardenVaultOverlay"),
);
this.buttonElement.addEventListener(EVENTS.CLICK, this.handleButtonElementClick);
this.postMessageToParent({ command: "getPageColorScheme" });
this.updateAuthStatus(authStatus);
@ -84,6 +87,17 @@ class AutofillOverlayButton extends AutofillOverlayPageElement {
this.buttonElement.append(iconElement);
}
/**
* Handles updating the page color scheme meta tag. Ensures that the button
* does not present with a non-transparent background on dark mode pages.
*
* @param colorScheme - The color scheme of the iframe's parent page
*/
private updatePageColorScheme({ colorScheme }: OverlayButtonMessage) {
const colorSchemeMetaTag = globalThis.document.querySelector("meta[name='color-scheme']");
colorSchemeMetaTag?.setAttribute("content", colorScheme);
}
/**
* Handles a click event on the button element. Posts a message to the
* parent window indicating that the button was clicked.