1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-02 18:17:46 +01:00

[PM-5881] Adjust usage of the chrome.extension.getViews API to ensure expected behavior in mv3 is not modified (#7842)

* [PM-5742] Rework Usage of Extension APIs that Cannot be Called with the Background Service Worker

* [PM-5742] Implementing jest tests for the updated BrowserApi methods

* [PM-5742] Implementing jest tests to validate logic within added API calls

* [PM-5742] Implementing jest tests to validate logic within added API calls

* [PM-5742] Fixing broken Jest tests

* [PM-5742] Fixing linter error

* [PM-5881] Adjust usage of the `chrome.extension.getViews` API to ensure expected behavior in manifest v3

* [PM-5881] Reworking how we handle early returns from `reloadOpenWindows`

* [PM-5881] Implementing jest test to validate changes within BrowserApi.reloadOpenWindows
This commit is contained in:
Cesar Gonzalez 2024-02-23 13:01:45 -06:00 committed by GitHub
parent 34a8d9af86
commit 968355d820
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 59 additions and 48 deletions

View File

@ -219,6 +219,18 @@ describe("BrowserApi", () => {
window.location.reload = reload; window.location.reload = reload;
}); });
it("skips reloading any windows if no views can be found", () => {
Object.defineProperty(window, "location", {
value: { reload: jest.fn(), href: "chrome-extension://id-value/background.html" },
writable: true,
});
chrome.extension.getViews = jest.fn().mockReturnValue([]);
BrowserApi.reloadOpenWindows();
expect(window.location.reload).not.toHaveBeenCalled();
});
it("reloads all open windows", () => { it("reloads all open windows", () => {
Object.defineProperty(window, "location", { Object.defineProperty(window, "location", {
value: { reload: jest.fn(), href: "chrome-extension://id-value/index.html" }, value: { reload: jest.fn(), href: "chrome-extension://id-value/index.html" },

View File

@ -404,8 +404,12 @@ export class BrowserApi {
* @param exemptCurrentHref - Whether to exempt the current window location from the reload. * @param exemptCurrentHref - Whether to exempt the current window location from the reload.
*/ */
static reloadOpenWindows(exemptCurrentHref = false) { static reloadOpenWindows(exemptCurrentHref = false) {
const currentHref = window?.location.href;
const views = BrowserApi.getExtensionViews(); const views = BrowserApi.getExtensionViews();
if (!views.length) {
return;
}
const currentHref = window.location.href;
views views
.filter((w) => w.location.href != null && !w.location.href.includes("background.html")) .filter((w) => w.location.href != null && !w.location.href.includes("background.html"))
.filter((w) => !exemptCurrentHref || w.location.href !== currentHref) .filter((w) => !exemptCurrentHref || w.location.href !== currentHref)

View File

@ -1,5 +1,7 @@
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
import { BrowserApi } from "../browser/browser-api";
import BrowserPlatformUtilsService from "./browser-platform-utils.service"; import BrowserPlatformUtilsService from "./browser-platform-utils.service";
describe("Browser Utils Service", () => { describe("Browser Utils Service", () => {
@ -91,41 +93,24 @@ describe("Browser Utils Service", () => {
}); });
describe("isViewOpen", () => { describe("isViewOpen", () => {
beforeEach(() => { it("returns false if a heartbeat response is not received", async () => {
globalThis.chrome = { BrowserApi.sendMessageWithResponse = jest.fn().mockResolvedValueOnce(undefined);
// eslint-disable-next-line
// @ts-ignore const isViewOpen = await browserPlatformUtilsService.isViewOpen();
extension: {
getViews: jest.fn(), expect(isViewOpen).toBe(false);
},
};
}); });
it("returns true if the user is on Firefox and the sidebar is open", async () => { it("returns true if a heartbeat response is received", async () => {
chrome.extension.getViews = jest.fn().mockReturnValueOnce([window]); BrowserApi.sendMessageWithResponse = jest
jest .fn()
.spyOn(browserPlatformUtilsService, "getDevice") .mockImplementationOnce((subscriber) =>
.mockReturnValueOnce(DeviceType.FirefoxExtension); Promise.resolve((subscriber === "checkVaultPopupHeartbeat") as any),
);
const result = await browserPlatformUtilsService.isViewOpen(); const isViewOpen = await browserPlatformUtilsService.isViewOpen();
expect(result).toBe(true); expect(isViewOpen).toBe(true);
});
it("returns true if a extension view is open as a tab", async () => {
chrome.extension.getViews = jest.fn().mockReturnValueOnce([window]);
const result = await browserPlatformUtilsService.isViewOpen();
expect(result).toBe(true);
});
it("returns false if no extension view is open", async () => {
chrome.extension.getViews = jest.fn().mockReturnValue([]);
const result = await browserPlatformUtilsService.isViewOpen();
expect(result).toBe(false);
}); });
}); });
}); });

View File

@ -150,23 +150,13 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
return false; return false;
} }
/**
* Identifies if the vault popup is currently open. This is done by sending a
* message to the popup and waiting for a response. If a response is received,
* the view is open.
*/
async isViewOpen(): Promise<boolean> { async isViewOpen(): Promise<boolean> {
if (await BrowserApi.isPopupOpen()) { return Boolean(await BrowserApi.sendMessageWithResponse("checkVaultPopupHeartbeat"));
return true;
}
if (this.isSafari()) {
return false;
}
// Opera has "sidebar_panel" as a ViewType but doesn't currently work
if (this.isFirefox() && BrowserApi.getExtensionViews({ type: "sidebar" }).length > 0) {
return true;
}
// Opera sidebar has type of "tab" (will stick around for a while after closing sidebar)
const tabOpen = BrowserApi.getExtensionViews({ type: "tab" }).length > 0;
return tabOpen;
} }
lockTimeout(): number { lockTimeout(): number {

View File

@ -6,6 +6,7 @@ import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/services/config/config.service";
import { BrowserApi } from "../../platform/browser/browser-api";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
@ -52,6 +53,25 @@ export class InitService {
} }
this.configService.init(); this.configService.init();
this.setupVaultPopupHeartbeat();
}; };
} }
/**
* Sets up a runtime message listener to indicate to the background
* script that the extension popup is open in some manner.
*/
private setupVaultPopupHeartbeat() {
const respondToHeartbeat = (
message: { command: string },
_sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void,
) => {
if (message?.command === "checkVaultPopupHeartbeat") {
sendResponse(true);
}
};
BrowserApi.messageListener("vaultPopupHeartbeat", respondToHeartbeat);
}
} }