-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathbackground.js
131 lines (114 loc) · 4.22 KB
/
background.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/** @type {Map<number, chrome.runtime.Port>} */
const ports = new Map();
chrome.runtime.onConnect.addListener((port) => {
if (port.sender?.url !== chrome.runtime.getURL('/index.html')) {
console.error(`Unexpected connection from ${port.sender?.url || '<unknown>'}`);
return port.disconnect();
}
// messages are from the devtools page and not content script (courier.js)
port.onMessage.addListener((message, sender) => {
switch (message.type) {
case 'bypass::ext/init': {
ports.set(message.tabId, sender);
if (!chrome.tabs.onUpdated.hasListener(courier)) {
chrome.tabs.onUpdated.addListener(courier);
}
break;
}
case 'bypass::ext/page->refresh': {
chrome.tabs.reload(message.tabId, { bypassCache: true });
break;
}
default: // relay messages from devtools to tab
chrome.tabs.sendMessage(message.tabId, message);
}
});
port.onDisconnect.addListener((disconnected) => {
ports.delete(+disconnected.name);
if (ports.size === 0) {
chrome.tabs.onUpdated.removeListener(courier);
}
});
});
// relay messages from `chrome.scripting` to devtools page
chrome.runtime.onMessage.addListener((message, sender) => {
if (sender.id !== chrome.runtime.id) return; // unexpected sender
if (message.type === 'bypass::ext/icon:set') {
const selected = message.payload ? 'default' : 'disabled';
const icons = [16, 24, 48, 96, 128].map((s) => [s, `icons/${selected}-${s}.png`]);
return chrome.action.setIcon({ path: Object.fromEntries(icons) });
}
const port = sender.tab?.id && ports.get(sender.tab.id);
if (port) return port.postMessage(message);
});
/** @type {Parameters<chrome.tabs.TabUpdatedEvent['addListener']>[0]} */
function courier(tabId, changed) {
if (!ports.has(tabId) || changed.status !== 'loading') return;
chrome.scripting.executeScript({
target: { tabId },
// ensures we're listening to the events before they're dispatched
injectImmediately: true,
// no lexical context, `func` is serialized and deserialized.
// a limbo world where both `chrome` and `window` are defined
// with many unexpected and out of the ordinary behaviors, do
// minimal work here and delegate to `courier.js` in the page.
// only a subset of APIs are available in this `chrome` limbo
// - chrome.csi->f()
// - chrome.dom.{openOrClosedShadowRoot->f()}
// - chrome.extension.{ViewType, inIncognitoContext}
// - chrome.i18n
// - chrome.runtime
func: () => {
chrome.runtime.onMessage.addListener((message, sender) => {
if (sender.id !== chrome.runtime.id) return; // unexpected sender
window.postMessage(message); // relay to content script (courier.js)
});
window.addEventListener('message', ({ source, data }) => {
// only accept messages from our application or script
if (source === window && data?.source === 'svelte-devtools') {
chrome.runtime.sendMessage(data);
}
});
window.addEventListener('unload', () => {
chrome.runtime.sendMessage({ type: 'bridge::ext/clear' });
});
},
});
}
chrome.tabs.onActivated.addListener(({ tabId }) => sensor(tabId));
chrome.tabs.onUpdated.addListener(
(tabId, changed) => changed.status === 'complete' && sensor(tabId),
);
/** @param {number} tabId */
async function sensor(tabId) {
try {
// add SvelteDevTools event listener
await chrome.scripting.executeScript({
target: { tabId },
func: () => {
document.addEventListener('SvelteDevTools', ({ detail }) => {
chrome.runtime.sendMessage(detail);
});
},
});
// capture data to send to listener
await chrome.scripting.executeScript({
target: { tabId },
world: 'MAIN',
func: () => {
// @ts-ignore - injected if the website is using svelte
const [major] = [...(window.__svelte?.v ?? [])];
document.dispatchEvent(
new CustomEvent('SvelteDevTools', {
detail: { type: 'bypass::ext/icon:set', payload: major },
}),
);
},
});
} catch {
// for internal URLs like `chrome://` or `edge://` and extension gallery
// https://chromium.googlesource.com/chromium/src/+/ee77a52baa1f8a98d15f9749996f90e9d3200f2d/chrome/common/extensions/chrome_extensions_client.cc#131
const icons = [16, 24, 48, 96, 128].map((s) => [s, `icons/disabled-${s}.png`]);
chrome.action.setIcon({ path: Object.fromEntries(icons) });
}
}