[PM-5742] Rework Usage of Extension APIs that Cannot be Called with the Background Service Worker (#7667)
* [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
This commit is contained in:
parent
2e11fb2a24
commit
a1745b2dae
|
@ -870,7 +870,7 @@ async function loadNotificationBar() {
|
|||
return;
|
||||
}
|
||||
|
||||
const barPageUrl: string = chrome.extension.getURL(barPage);
|
||||
const barPageUrl: string = chrome.runtime.getURL(barPage);
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.cssText = "height: 42px; width: 100%; border: 0; min-height: initial;";
|
||||
|
|
|
@ -106,6 +106,143 @@ describe("BrowserApi", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("getBackgroundPage", () => {
|
||||
it("returns a null value if the `getBackgroundPage` method is not available", () => {
|
||||
chrome.extension.getBackgroundPage = undefined;
|
||||
|
||||
const result = BrowserApi.getBackgroundPage();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns the background page if the `getBackgroundPage` method is available", () => {
|
||||
chrome.extension.getBackgroundPage = jest.fn().mockReturnValue(window);
|
||||
|
||||
const result = BrowserApi.getBackgroundPage();
|
||||
|
||||
expect(result).toEqual(window);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isBackgroundPage", () => {
|
||||
it("returns false if the passed window is `undefined`", () => {
|
||||
const result = BrowserApi.isBackgroundPage(undefined);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false if the current window is not the background page", () => {
|
||||
chrome.extension.getBackgroundPage = jest.fn().mockReturnValue(null);
|
||||
|
||||
const result = BrowserApi.isBackgroundPage(window);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true if the current window is the background page", () => {
|
||||
chrome.extension.getBackgroundPage = jest.fn().mockReturnValue(window);
|
||||
|
||||
const result = BrowserApi.isBackgroundPage(window);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getExtensionViews", () => {
|
||||
it("returns an empty array if the `getViews` method is not available", () => {
|
||||
chrome.extension.getViews = undefined;
|
||||
|
||||
const result = BrowserApi.getExtensionViews();
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns the extension views if the `getViews` method is available", () => {
|
||||
const views = [window];
|
||||
chrome.extension.getViews = jest.fn().mockReturnValue(views);
|
||||
|
||||
const result = BrowserApi.getExtensionViews();
|
||||
|
||||
expect(result).toEqual(views);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isPopupOpen", () => {
|
||||
it("returns true if the popup is open", async () => {
|
||||
chrome.extension.getViews = jest.fn().mockReturnValue([window]);
|
||||
|
||||
const result = await BrowserApi.isPopupOpen();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false if the popup is not open", async () => {
|
||||
chrome.extension.getViews = jest.fn().mockReturnValue([]);
|
||||
|
||||
const result = await BrowserApi.isPopupOpen();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reloadOpenWindows", () => {
|
||||
const href = window.location.href;
|
||||
const reload = window.location.reload;
|
||||
|
||||
afterEach(() => {
|
||||
window.location.href = href;
|
||||
window.location.reload = reload;
|
||||
});
|
||||
|
||||
it("reloads all open windows", () => {
|
||||
Object.defineProperty(window, "location", {
|
||||
value: { reload: jest.fn(), href: "chrome-extension://id-value/index.html" },
|
||||
writable: true,
|
||||
});
|
||||
const views = [window];
|
||||
chrome.extension.getViews = jest.fn().mockReturnValue(views);
|
||||
|
||||
BrowserApi.reloadOpenWindows();
|
||||
|
||||
expect(window.location.reload).toHaveBeenCalledTimes(views.length);
|
||||
});
|
||||
|
||||
it("skips reloading the background page", () => {
|
||||
Object.defineProperty(window, "location", {
|
||||
value: { reload: jest.fn(), href: "chrome-extension://id-value/background.html" },
|
||||
writable: true,
|
||||
});
|
||||
const views = [window];
|
||||
chrome.extension.getViews = jest.fn().mockReturnValue(views);
|
||||
chrome.extension.getBackgroundPage = jest.fn().mockReturnValue(window);
|
||||
|
||||
BrowserApi.reloadOpenWindows();
|
||||
|
||||
expect(window.location.reload).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("skips reloading the current href if it is exempt", () => {
|
||||
Object.defineProperty(window, "location", {
|
||||
value: { reload: jest.fn(), href: "chrome-extension://id-value/index.html" },
|
||||
writable: true,
|
||||
});
|
||||
const mockWindow = mock<Window>({
|
||||
location: {
|
||||
href: "chrome-extension://id-value/sidebar.html",
|
||||
reload: jest.fn(),
|
||||
},
|
||||
});
|
||||
const views = [window, mockWindow];
|
||||
chrome.extension.getViews = jest.fn().mockReturnValue(views);
|
||||
window.location.href = "chrome-extension://id-value/index.html";
|
||||
|
||||
BrowserApi.reloadOpenWindows(true);
|
||||
|
||||
expect(window.location.reload).toHaveBeenCalledTimes(0);
|
||||
expect(mockWindow.location.reload).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("executeScriptInTab", () => {
|
||||
it("calls to the extension api to execute a script within the give tabId", async () => {
|
||||
const tabId = 1;
|
||||
|
|
|
@ -199,20 +199,54 @@ export class BrowserApi {
|
|||
return chrome.windows.onCreated.addListener(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the background page for the extension. This method is
|
||||
* not valid within manifest v3 background service workers. As
|
||||
* a result, it will return null when called from that context.
|
||||
*/
|
||||
static getBackgroundPage(): any {
|
||||
if (typeof chrome.extension.getBackgroundPage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return chrome.extension.getBackgroundPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a window object and determines if it is
|
||||
* associated with the background page of the extension.
|
||||
*
|
||||
* @param window - The window to check.
|
||||
*/
|
||||
static isBackgroundPage(window: Window & typeof globalThis): boolean {
|
||||
return window === chrome.extension.getBackgroundPage();
|
||||
return typeof window !== "undefined" && window === BrowserApi.getBackgroundPage();
|
||||
}
|
||||
|
||||
static getApplicationVersion(): string {
|
||||
return chrome.runtime.getManifest().version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension views that match the given properties. This method is not
|
||||
* available within background service worker. As a result, it will return an
|
||||
* empty array when called from that context.
|
||||
*
|
||||
* @param fetchProperties - The properties used to filter extension views.
|
||||
*/
|
||||
static getExtensionViews(fetchProperties?: chrome.extension.FetchProperties): Window[] {
|
||||
if (typeof chrome.extension.getViews === "undefined") {
|
||||
return [];
|
||||
}
|
||||
|
||||
return chrome.extension.getViews(fetchProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries all extension views that are of type `popup`
|
||||
* and returns whether any are currently open.
|
||||
*/
|
||||
static async isPopupOpen(): Promise<boolean> {
|
||||
return Promise.resolve(chrome.extension.getViews({ type: "popup" }).length > 0);
|
||||
return Promise.resolve(BrowserApi.getExtensionViews({ type: "popup" }).length > 0);
|
||||
}
|
||||
|
||||
static createNewTab(url: string, active = true): Promise<chrome.tabs.Tab> {
|
||||
|
@ -355,15 +389,19 @@ export class BrowserApi {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads all open extension views, except the background page. Will also
|
||||
* skip reloading the current window location if exemptCurrentHref is true.
|
||||
*
|
||||
* @param exemptCurrentHref - Whether to exempt the current window location from the reload.
|
||||
*/
|
||||
static reloadOpenWindows(exemptCurrentHref = false) {
|
||||
const currentHref = window.location.href;
|
||||
const views = chrome.extension.getViews() as Window[];
|
||||
const currentHref = window?.location.href;
|
||||
const views = BrowserApi.getExtensionViews();
|
||||
views
|
||||
.filter((w) => w.location.href != null && !w.location.href.includes("background.html"))
|
||||
.filter((w) => !exemptCurrentHref || w.location.href !== currentHref)
|
||||
.forEach((w) => {
|
||||
w.location.reload();
|
||||
});
|
||||
.forEach((w) => w.location.reload());
|
||||
}
|
||||
|
||||
static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port {
|
||||
|
|
|
@ -3,9 +3,14 @@ import { DeviceType } from "@bitwarden/common/enums";
|
|||
import BrowserPlatformUtilsService from "./browser-platform-utils.service";
|
||||
|
||||
describe("Browser Utils Service", () => {
|
||||
let browserPlatformUtilsService: BrowserPlatformUtilsService;
|
||||
beforeEach(() => {
|
||||
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
|
||||
browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, window);
|
||||
});
|
||||
|
||||
describe("getBrowser", () => {
|
||||
const originalUserAgent = navigator.userAgent;
|
||||
|
||||
// Reset the userAgent.
|
||||
afterAll(() => {
|
||||
Object.defineProperty(navigator, "userAgent", {
|
||||
|
@ -13,10 +18,8 @@ describe("Browser Utils Service", () => {
|
|||
});
|
||||
});
|
||||
|
||||
let browserPlatformUtilsService: BrowserPlatformUtilsService;
|
||||
beforeEach(() => {
|
||||
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
|
||||
browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, window);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -86,6 +89,45 @@ describe("Browser Utils Service", () => {
|
|||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isViewOpen", () => {
|
||||
beforeEach(() => {
|
||||
globalThis.chrome = {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
extension: {
|
||||
getViews: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it("returns true if the user is on Firefox and the sidebar is open", async () => {
|
||||
chrome.extension.getViews = jest.fn().mockReturnValueOnce([window]);
|
||||
jest
|
||||
.spyOn(browserPlatformUtilsService, "getDevice")
|
||||
.mockReturnValueOnce(DeviceType.FirefoxExtension);
|
||||
|
||||
const result = await browserPlatformUtilsService.isViewOpen();
|
||||
|
||||
expect(result).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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Safari Height Fix", () => {
|
||||
|
|
|
@ -160,12 +160,12 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
|||
}
|
||||
|
||||
// Opera has "sidebar_panel" as a ViewType but doesn't currently work
|
||||
if (this.isFirefox() && chrome.extension.getViews({ type: "sidebar" }).length > 0) {
|
||||
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 = chrome.extension.getViews({ type: "tab" }).length > 0;
|
||||
const tabOpen = BrowserApi.getExtensionViews({ type: "tab" }).length > 0;
|
||||
return tabOpen;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,11 @@ const privacy = {
|
|||
},
|
||||
};
|
||||
|
||||
const extension = {
|
||||
getBackgroundPage: jest.fn(),
|
||||
getViews: jest.fn(),
|
||||
};
|
||||
|
||||
// set chrome
|
||||
global.chrome = {
|
||||
i18n,
|
||||
|
@ -116,4 +121,5 @@ global.chrome = {
|
|||
windows,
|
||||
port,
|
||||
privacy,
|
||||
extension,
|
||||
} as any;
|
||||
|
|
Loading…
Reference in New Issue