-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathbackground.js
147 lines (127 loc) · 4.74 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/** @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 },
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.
func: () => {
const source = chrome.runtime.getURL('/courier.js');
if (document.querySelector(`script[src="${source}"]`)) return;
// attach script manually instead of declaring through `files`
// because `detail` in the dispatched custom events is `null`
const script = document.createElement('script');
script.setAttribute('src', source);
document.head.appendChild(script);
// // TODO: reenable profiler
// if (message.type === 'bridge::ext/profiler' && message.payload) {
// // start profiler
// }
chrome.runtime.onMessage.addListener((message, sender) => {
if (sender.id !== chrome.runtime.id) return; // unexpected sender
window.postMessage(message); // relay to content script (courier.js)
// switch (message.type) {
// case 'startProfiler':
// window.sessionStorage.SvelteDevToolsProfilerEnabled = 'true';
// break;
// case 'stopProfiler':
// case 'bridge::ext/clear':
// delete window.sessionStorage.SvelteDevToolsProfilerEnabled;
// break;
// }
});
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) });
}
}