[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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const barPageUrl: string = chrome.extension.getURL(barPage);
|
const barPageUrl: string = chrome.runtime.getURL(barPage);
|
||||||
|
|
||||||
const iframe = document.createElement("iframe");
|
const iframe = document.createElement("iframe");
|
||||||
iframe.style.cssText = "height: 42px; width: 100%; border: 0; min-height: initial;";
|
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", () => {
|
describe("executeScriptInTab", () => {
|
||||||
it("calls to the extension api to execute a script within the give tabId", async () => {
|
it("calls to the extension api to execute a script within the give tabId", async () => {
|
||||||
const tabId = 1;
|
const tabId = 1;
|
||||||
|
|
|
@ -199,20 +199,54 @@ export class BrowserApi {
|
||||||
return chrome.windows.onCreated.addListener(callback);
|
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 {
|
static getBackgroundPage(): any {
|
||||||
|
if (typeof chrome.extension.getBackgroundPage === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return chrome.extension.getBackgroundPage();
|
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 {
|
static isBackgroundPage(window: Window & typeof globalThis): boolean {
|
||||||
return window === chrome.extension.getBackgroundPage();
|
return typeof window !== "undefined" && window === BrowserApi.getBackgroundPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getApplicationVersion(): string {
|
static getApplicationVersion(): string {
|
||||||
return chrome.runtime.getManifest().version;
|
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> {
|
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> {
|
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) {
|
static reloadOpenWindows(exemptCurrentHref = false) {
|
||||||
const currentHref = window.location.href;
|
const currentHref = window?.location.href;
|
||||||
const views = chrome.extension.getViews() as Window[];
|
const views = BrowserApi.getExtensionViews();
|
||||||
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)
|
||||||
.forEach((w) => {
|
.forEach((w) => w.location.reload());
|
||||||
w.location.reload();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port {
|
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";
|
import BrowserPlatformUtilsService from "./browser-platform-utils.service";
|
||||||
|
|
||||||
describe("Browser 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", () => {
|
describe("getBrowser", () => {
|
||||||
const originalUserAgent = navigator.userAgent;
|
const originalUserAgent = navigator.userAgent;
|
||||||
|
|
||||||
// Reset the userAgent.
|
// Reset the userAgent.
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
Object.defineProperty(navigator, "userAgent", {
|
Object.defineProperty(navigator, "userAgent", {
|
||||||
|
@ -13,10 +18,8 @@ describe("Browser Utils Service", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let browserPlatformUtilsService: BrowserPlatformUtilsService;
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
|
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
|
||||||
browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, window);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -86,6 +89,45 @@ describe("Browser Utils Service", () => {
|
||||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension);
|
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", () => {
|
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
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opera sidebar has type of "tab" (will stick around for a while after closing sidebar)
|
// 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;
|
return tabOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,11 @@ const privacy = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const extension = {
|
||||||
|
getBackgroundPage: jest.fn(),
|
||||||
|
getViews: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
// set chrome
|
// set chrome
|
||||||
global.chrome = {
|
global.chrome = {
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -116,4 +121,5 @@ global.chrome = {
|
||||||
windows,
|
windows,
|
||||||
port,
|
port,
|
||||||
privacy,
|
privacy,
|
||||||
|
extension,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
Loading…
Reference in New Issue