mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 14:53:33 +00:00
[PM-25025] Additional defense against top-layer content (#16101)
* additional defense against top-layer content * fix error and update tests
This commit is contained in:
@@ -41,6 +41,10 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
autofillInlineMenuContentService as any,
|
||||
"sendExtensionMessage",
|
||||
);
|
||||
jest.spyOn(autofillInlineMenuContentService as any, "getPageIsOpaque");
|
||||
jest
|
||||
.spyOn(autofillInlineMenuContentService as any, "getPageTopLayerInUse")
|
||||
.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -386,30 +390,45 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes the inline menu if the page has content in the top layer", async () => {
|
||||
document.querySelector("html").style.opacity = "1";
|
||||
document.body.style.opacity = "1";
|
||||
|
||||
jest
|
||||
.spyOn(autofillInlineMenuContentService as any, "getPageTopLayerInUse")
|
||||
.mockResolvedValue(true);
|
||||
|
||||
await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]);
|
||||
|
||||
expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(true);
|
||||
expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes the inline menu if the page body is not sufficiently opaque", async () => {
|
||||
document.querySelector("html").style.opacity = "0.9";
|
||||
document.body.style.opacity = "0";
|
||||
autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]);
|
||||
await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]);
|
||||
|
||||
expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(false);
|
||||
expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(false);
|
||||
expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes the inline menu if the page html is not sufficiently opaque", async () => {
|
||||
document.querySelector("html").style.opacity = "0.3";
|
||||
document.body.style.opacity = "0.7";
|
||||
autofillInlineMenuContentService["handlePageMutations"]([mockHTMLMutationRecord]);
|
||||
await autofillInlineMenuContentService["handlePageMutations"]([mockHTMLMutationRecord]);
|
||||
|
||||
expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(false);
|
||||
expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(false);
|
||||
expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not close the inline menu if the page html and body is sufficiently opaque", async () => {
|
||||
document.querySelector("html").style.opacity = "0.9";
|
||||
document.body.style.opacity = "1";
|
||||
autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]);
|
||||
await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(true);
|
||||
expect(autofillInlineMenuContentService["getPageIsOpaque"]).toHaveReturnedWith(true);
|
||||
expect(autofillInlineMenuContentService["closeInlineMenu"]).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
private listElement?: HTMLElement;
|
||||
private htmlMutationObserver: MutationObserver;
|
||||
private bodyMutationObserver: MutationObserver;
|
||||
private pageIsOpaque = true;
|
||||
private inlineMenuElementsMutationObserver: MutationObserver;
|
||||
private containerElementMutationObserver: MutationObserver;
|
||||
private mutationObserverIterations = 0;
|
||||
@@ -52,7 +51,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.checkPageOpacity();
|
||||
this.setupMutationObserver();
|
||||
}
|
||||
|
||||
@@ -405,20 +403,35 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
});
|
||||
};
|
||||
|
||||
private checkPageOpacity = () => {
|
||||
this.pageIsOpaque = this.getPageIsOpaque();
|
||||
private checkPageRisks = async () => {
|
||||
const pageIsOpaque = await this.getPageIsOpaque();
|
||||
const pageTopLayerInUse = await this.getPageTopLayerInUse();
|
||||
|
||||
if (!this.pageIsOpaque) {
|
||||
const risksFound = !pageIsOpaque || pageTopLayerInUse;
|
||||
|
||||
if (risksFound) {
|
||||
this.closeInlineMenu();
|
||||
}
|
||||
|
||||
return risksFound;
|
||||
};
|
||||
|
||||
/*
|
||||
* Checks for known risks at the page level
|
||||
*/
|
||||
private handlePageMutations = async (mutations: MutationRecord[]) => {
|
||||
if (mutations.some(({ type }) => type === "attributes")) {
|
||||
await this.checkPageRisks();
|
||||
}
|
||||
};
|
||||
|
||||
private handlePageMutations = (mutations: MutationRecord[]) => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === "attributes") {
|
||||
this.checkPageOpacity();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if the page top layer has content (will obscure/overlap the inline menu)
|
||||
*/
|
||||
private getPageTopLayerInUse = () => {
|
||||
const pageHasOpenPopover = !!globalThis.document.querySelector(":popover-open");
|
||||
|
||||
return pageHasOpenPopover;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -427,7 +440,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
* of parents below the body. Assumes the target element will be a direct child of the page
|
||||
* `body` (enforced elsewhere).
|
||||
*/
|
||||
private getPageIsOpaque() {
|
||||
private getPageIsOpaque = () => {
|
||||
// These are computed style values, so we don't need to worry about non-float values
|
||||
// for `opacity`, here
|
||||
const htmlOpacity = globalThis.window.getComputedStyle(
|
||||
@@ -441,17 +454,16 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
const opacityThreshold = 0.6;
|
||||
|
||||
return parseFloat(htmlOpacity) > opacityThreshold && parseFloat(bodyOpacity) > opacityThreshold;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes the mutation of the element that contains the inline menu. Will trigger when an
|
||||
* idle moment in the execution of the main thread is detected.
|
||||
*/
|
||||
private processContainerElementMutation = async (containerElement: HTMLElement) => {
|
||||
// If the computed opacity of the body and parent is not sufficiently opaque, tear
|
||||
// down and prevent building the inline menu experience.
|
||||
this.checkPageOpacity();
|
||||
if (!this.pageIsOpaque) {
|
||||
// If the page contains risks, tear down and prevent building the inline menu experience.
|
||||
const pageRisksFound = await this.checkPageRisks();
|
||||
if (pageRisksFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user