anvilsign in

collin/browser-terminal-extension

1// Background worker.
2//
3// Its one real job is the element picker's keyboard shortcut. `activeTab` — the
4// permission that lets us touch a page without asking for <all_urls> — is only
5// granted on specific user gestures: a toolbar-action click, a context menu
6// item, or a keyboard command. A click inside the side panel is not one of
7// them, so the in-panel button can fail while the shortcut always works. Both
8// paths exist; this is the reliable one.
9
10// One of the two always exists — this file only ever runs as an extension
11// background script — but neither is declared unconditionally, so say which.
12const api = /** @type {TbExtensionApi} */ (globalThis.browser ?? globalThis.chrome);
13
14// Chrome's service worker starts empty; Firefox loads picker.js alongside this
15// file via background.scripts, so there it is already defined.
16if (typeof tbPickElement === "undefined" && typeof importScripts === "function") {
17 importScripts("picker.js", "lib/shot.js");
18}
19
20// Chrome only: make the toolbar button open the side panel.
21if (api.sidePanel?.setPanelBehavior) {
22 api.sidePanel
23 .setPanelBehavior({ openPanelOnActionClick: true })
24 .catch((e) => console.error(e));
25}
26
27const SKIP = /^(chrome|about|edge|moz-extension|chrome-extension|view-source|devtools):/;
28
29async function runPicker() {
30 const [tab] = await api.tabs.query({ active: true, currentWindow: true });
31 if (!tab || SKIP.test(tab.url ?? "")) {
32 console.warn("termbridge: cannot pick on this page", tab?.url);
33 return;
34 }
35
36 let value = null;
37 try {
38 const results = await api.scripting.executeScript({
39 target: { tabId: tab.id },
40 func: tbPickElement,
41 });
42 value = results?.[0]?.result ?? null;
43 } catch (e) {
44 console.error("termbridge: pick failed", e);
45 return;
46 }
47 if (!value) return;
48
49 // Screenshot here rather than in the sidebar: this path's page access comes
50 // from the activeTab grant the keyboard command just minted, and that grant
51 // belongs to the worker.
52 const shot = await Shot.copyPickedShot(api, tab.id, value);
53 if (shot !== true) console.warn("termbridge: no screenshot", shot);
54
55 // The sidebar may not be open. Try to hand it over directly, and fall back to
56 // storage so the pick isn't lost — the sidebar drains it when it next opens.
57 try {
58 await api.runtime.sendMessage({ type: "picked", value, shot: shot === true });
59 } catch {
60 await api.storage.local.set({ pendingPick: value });
61 }
62}
63
64api.commands?.onCommand.addListener((command) => {
65 if (command === "pick-element") runPicker();
66});