Skip to content

Commit e98552d

Browse files
committed
fix(env): enhance isBackground check and add tests
- Updated `isBackground` to use safer runtime checks and browser API fallback. - Added a comprehensive test suite (`env.test.ts`) to ensure reliability of background context detection.
1 parent e9acb41 commit e98552d

2 files changed

Lines changed: 151 additions & 3 deletions

File tree

‎src/env.test.ts‎

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {afterEach, beforeEach, describe, expect, jest, test} from "@jest/globals";
2+
import {isBackground} from "./env";
3+
4+
describe("isBackground", () => {
5+
let originalBrowserDescriptor: PropertyDescriptor | undefined;
6+
let originalChromeDescriptor: PropertyDescriptor | undefined;
7+
let originalWindowDescriptor: PropertyDescriptor | undefined;
8+
let originalLocationDescriptor: PropertyDescriptor | undefined;
9+
10+
beforeEach(() => {
11+
originalBrowserDescriptor = Object.getOwnPropertyDescriptor(globalThis, "browser");
12+
originalChromeDescriptor = Object.getOwnPropertyDescriptor(globalThis, "chrome");
13+
originalWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
14+
originalLocationDescriptor = Object.getOwnPropertyDescriptor(globalThis, "location");
15+
16+
delete (globalThis as any).browser;
17+
delete (globalThis as any).chrome;
18+
delete (globalThis as any).window;
19+
delete (globalThis as any).location;
20+
});
21+
22+
afterEach(() => {
23+
restoreGlobalProperty("browser", originalBrowserDescriptor);
24+
restoreGlobalProperty("chrome", originalChromeDescriptor);
25+
restoreGlobalProperty("window", originalWindowDescriptor);
26+
restoreGlobalProperty("location", originalLocationDescriptor);
27+
jest.resetAllMocks();
28+
});
29+
30+
const setGlobalProperty = (name: string, value: unknown): void => {
31+
Object.defineProperty(globalThis, name, {
32+
configurable: true,
33+
value,
34+
writable: true,
35+
});
36+
};
37+
38+
const restoreGlobalProperty = (name: string, descriptor: PropertyDescriptor | undefined): void => {
39+
if (descriptor) {
40+
Object.defineProperty(globalThis, name, descriptor);
41+
42+
return;
43+
}
44+
45+
delete (globalThis as any)[name];
46+
};
47+
48+
const setRuntime = (runtime: object | undefined): void => {
49+
setGlobalProperty("chrome", runtime ? {runtime} : {});
50+
};
51+
52+
const setWindow = (pathname: string): void => {
53+
setGlobalProperty("window", {});
54+
setGlobalProperty("location", {pathname});
55+
};
56+
57+
test("returns false when the browser API is unavailable", () => {
58+
expect(isBackground()).toBe(false);
59+
});
60+
61+
test("returns false when runtime is unavailable", () => {
62+
setRuntime(undefined);
63+
64+
expect(isBackground()).toBe(false);
65+
});
66+
67+
test("returns false when runtime.id is unavailable", () => {
68+
setRuntime({getManifest: jest.fn()});
69+
70+
expect(isBackground()).toBe(false);
71+
});
72+
73+
test("returns false without throwing when runtime.getManifest is unavailable", () => {
74+
setRuntime({id: "extension-id", getManifest: undefined});
75+
76+
expect(() => isBackground()).not.toThrow();
77+
expect(isBackground()).toBe(false);
78+
});
79+
80+
test("returns false when runtime.getManifest is not a function", () => {
81+
setRuntime({id: "extension-id", getManifest: "manifest"});
82+
83+
expect(isBackground()).toBe(false);
84+
});
85+
86+
test("identifies an MV3 service worker as background", () => {
87+
setRuntime({
88+
getManifest: jest.fn(() => ({
89+
background: {service_worker: "service-worker.js"},
90+
manifest_version: 3,
91+
})),
92+
id: "extension-id",
93+
});
94+
95+
expect(isBackground()).toBe(true);
96+
});
97+
98+
test("does not identify an MV3 extension document as background", () => {
99+
setRuntime({
100+
getManifest: jest.fn(() => ({
101+
background: {service_worker: "service-worker.js"},
102+
manifest_version: 3,
103+
})),
104+
id: "extension-id",
105+
});
106+
setWindow("/popup.html");
107+
108+
expect(isBackground()).toBe(false);
109+
});
110+
111+
test("identifies an MV2 generated background page as background", () => {
112+
setRuntime({
113+
getManifest: jest.fn(() => ({
114+
background: {scripts: ["background.js"]},
115+
manifest_version: 2,
116+
})),
117+
id: "extension-id",
118+
});
119+
setWindow("/_generated_background_page.html");
120+
121+
expect(isBackground()).toBe(true);
122+
});
123+
124+
test("does not identify a regular extension page as background", () => {
125+
setRuntime({
126+
getManifest: jest.fn(() => ({
127+
background: {scripts: ["background.js"]},
128+
manifest_version: 2,
129+
})),
130+
id: "extension-id",
131+
});
132+
setWindow("/popup.html");
133+
134+
expect(isBackground()).toBe(false);
135+
});
136+
});

‎src/env.ts‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
import {getId, getManifest} from "./runtime";
1+
import {browser} from "./browser";
22

33
export const isBackground = (): boolean => {
4-
if (!getId()) {
4+
let runtime: typeof chrome.runtime | undefined;
5+
6+
try {
7+
runtime = browser()?.runtime;
8+
} catch {
9+
return false;
10+
}
11+
12+
if (!runtime?.id) {
13+
return false;
14+
}
15+
16+
if (typeof runtime.getManifest !== "function") {
517
return false;
618
}
719

8-
const manifest = getManifest();
20+
const manifest = runtime.getManifest();
921

1022
if (!manifest.background) {
1123
return false;

0 commit comments

Comments
 (0)