@monotykamary/localterm-server 2.77.2 → 2.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdp/cdp-client.d.ts +17 -4
- package/dist/cdp/cdp-client.d.ts.map +1 -1
- package/dist/cdp/cdp-client.js +57 -5
- package/dist/cdp/cdp-client.js.map +1 -1
- package/dist/cdp/cdp-connection.d.ts +3 -0
- package/dist/cdp/cdp-connection.d.ts.map +1 -1
- package/dist/cdp/cdp-connection.js +52 -7
- package/dist/cdp/cdp-connection.js.map +1 -1
- package/dist/cdp/cdp-socket.d.ts +27 -0
- package/dist/cdp/cdp-socket.d.ts.map +1 -0
- package/dist/cdp/cdp-socket.js +45 -0
- package/dist/cdp/cdp-socket.js.map +1 -0
- package/dist/cdp/extension-hub.d.ts +7 -0
- package/dist/cdp/extension-hub.d.ts.map +1 -0
- package/dist/cdp/extension-hub.js +59 -0
- package/dist/cdp/extension-hub.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -5
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +12 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +3 -0
- package/dist/schemas.js.map +1 -1
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +17 -3
- package/dist/security.js.map +1 -1
- package/dist/utils/is-loopback-remote-address.d.ts +3 -0
- package/dist/utils/is-loopback-remote-address.d.ts.map +1 -0
- package/dist/utils/is-loopback-remote-address.js +6 -0
- package/dist/utils/is-loopback-remote-address.js.map +1 -0
- package/dist/utils/resolve-term-backspace-sequence.d.ts +1 -1
- package/dist/utils/resolve-term-backspace-sequence.d.ts.map +1 -1
- package/dist/utils/resolve-term-backspace-sequence.js +1 -1
- package/dist/utils/resolve-term-backspace-sequence.js.map +1 -1
- package/extension/manifest.json +14 -0
- package/extension/sw.js +931 -0
- package/package.json +12 -11
- package/LICENSE +0 -21
package/extension/sw.js
ADDED
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MV3 CDP relay. The daemon speaks the same JSON-RPC as Chrome's browser
|
|
3
|
+
* WebSocket; this worker emulates browser-level Target.* / Browser window
|
|
4
|
+
* bounds with chrome.tabs / chrome.windows / chrome.debugger, exposes extra
|
|
5
|
+
* Chrome.* tab/window/group commands, and pass-throughs every other domain
|
|
6
|
+
* via sendCommand. OOPIF/worker targets come from chrome.debugger.getTargets();
|
|
7
|
+
* Browser.grantPermissions maps through chrome.contentSettings.
|
|
8
|
+
*
|
|
9
|
+
* Connects to ws://127.0.0.1:<port>/extension (default 3417, same as
|
|
10
|
+
* localterm DEFAULT_PORT). Last-writer-wins on the daemon side.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PORT = 3417;
|
|
14
|
+
const PROTOCOL = "1.3";
|
|
15
|
+
const RECONNECT_MIN_MS = 400;
|
|
16
|
+
const RECONNECT_MAX_MS = 5_000;
|
|
17
|
+
|
|
18
|
+
/** @type {WebSocket | null} */
|
|
19
|
+
let ws = null;
|
|
20
|
+
let reconnectMs = RECONNECT_MIN_MS;
|
|
21
|
+
let reconnectTimer = null;
|
|
22
|
+
let autoAttach = false;
|
|
23
|
+
let discoverTargets = false;
|
|
24
|
+
|
|
25
|
+
/** sessionId -> { debuggee, targetId, tabId? } */
|
|
26
|
+
const sessions = new Map();
|
|
27
|
+
/** tabId -> sessionId */
|
|
28
|
+
const tabToSession = new Map();
|
|
29
|
+
/** debugger target id -> sessionId */
|
|
30
|
+
const debuggerToSession = new Map();
|
|
31
|
+
|
|
32
|
+
chrome.runtime.onInstalled.addListener(() => connect());
|
|
33
|
+
chrome.runtime.onStartup.addListener(() => connect());
|
|
34
|
+
chrome.action.onClicked.addListener(() => {
|
|
35
|
+
disconnect();
|
|
36
|
+
reconnectMs = RECONNECT_MIN_MS;
|
|
37
|
+
connect();
|
|
38
|
+
});
|
|
39
|
+
chrome.alarms.create("bh-keepalive", { periodInMinutes: 0.5 });
|
|
40
|
+
chrome.alarms.onAlarm.addListener((a) => {
|
|
41
|
+
if (a.name !== "bh-keepalive") return;
|
|
42
|
+
if (!ws || ws.readyState === WebSocket.CLOSING || ws.readyState === WebSocket.CLOSED) connect();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
46
|
+
const sessionId = sessionIdForDebuggee(source);
|
|
47
|
+
if (!sessionId) return;
|
|
48
|
+
emit({ method, params, sessionId });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
chrome.debugger.onDetach.addListener((source, reason) => {
|
|
52
|
+
const sess = sessionForDebuggee(source);
|
|
53
|
+
if (!sess) return;
|
|
54
|
+
forgetSession(sess.sessionId);
|
|
55
|
+
emit({
|
|
56
|
+
method: "Inspector.detached",
|
|
57
|
+
params: { reason: reason || "target_closed" },
|
|
58
|
+
sessionId: sess.sessionId,
|
|
59
|
+
});
|
|
60
|
+
emit({
|
|
61
|
+
method: "Target.detachedFromTarget",
|
|
62
|
+
params: { sessionId: sess.sessionId, targetId: sess.targetId },
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
chrome.tabs.onCreated.addListener(async (tab) => {
|
|
67
|
+
if (tab.id == null) return;
|
|
68
|
+
const info = targetInfoFromTab(tab);
|
|
69
|
+
if (discoverTargets) emit({ method: "Target.targetCreated", params: { targetInfo: info } });
|
|
70
|
+
if (!autoAttach) return;
|
|
71
|
+
try {
|
|
72
|
+
const sessionId = await attachTab(tab.id, String(tab.id));
|
|
73
|
+
emit({
|
|
74
|
+
method: "Target.attachedToTarget",
|
|
75
|
+
params: { sessionId, waitingForDebugger: false, targetInfo: info },
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
/* chrome:// and other restricted pages */
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
83
|
+
const sessionId = tabToSession.get(tabId);
|
|
84
|
+
forgetTab(tabId);
|
|
85
|
+
if (discoverTargets)
|
|
86
|
+
emit({ method: "Target.targetDestroyed", params: { targetId: String(tabId) } });
|
|
87
|
+
if (sessionId)
|
|
88
|
+
emit({ method: "Target.detachedFromTarget", params: { sessionId, targetId: String(tabId) } });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
chrome.tabs.onUpdated.addListener(async (_tabId, _change, tab) => {
|
|
92
|
+
if (tab.id == null) return;
|
|
93
|
+
if (discoverTargets)
|
|
94
|
+
emit({ method: "Target.targetInfoChanged", params: { targetInfo: targetInfoFromTab(tab) } });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
chrome.tabs.onMoved.addListener(async (tabId) => {
|
|
98
|
+
if (!discoverTargets) return;
|
|
99
|
+
try {
|
|
100
|
+
const tab = await chrome.tabs.get(tabId);
|
|
101
|
+
emit({ method: "Target.targetInfoChanged", params: { targetInfo: targetInfoFromTab(tab) } });
|
|
102
|
+
} catch {
|
|
103
|
+
/* gone */
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
chrome.tabs.onAttached.addListener(async (tabId) => {
|
|
108
|
+
if (!discoverTargets) return;
|
|
109
|
+
try {
|
|
110
|
+
const tab = await chrome.tabs.get(tabId);
|
|
111
|
+
emit({ method: "Target.targetInfoChanged", params: { targetInfo: targetInfoFromTab(tab) } });
|
|
112
|
+
} catch {
|
|
113
|
+
/* gone */
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
chrome.tabs.onActivated.addListener(async (active) => {
|
|
118
|
+
if (!discoverTargets) return;
|
|
119
|
+
try {
|
|
120
|
+
const tab = await chrome.tabs.get(active.tabId);
|
|
121
|
+
emit({ method: "Target.targetInfoChanged", params: { targetInfo: targetInfoFromTab(tab) } });
|
|
122
|
+
} catch {
|
|
123
|
+
/* gone */
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (chrome.tabGroups) {
|
|
128
|
+
chrome.tabGroups.onCreated.addListener((group) => {
|
|
129
|
+
emit({ method: "Chrome.tabGroupCreated", params: { group: serializeGroup(group) } });
|
|
130
|
+
});
|
|
131
|
+
chrome.tabGroups.onUpdated.addListener((group) => {
|
|
132
|
+
emit({ method: "Chrome.tabGroupUpdated", params: { group: serializeGroup(group) } });
|
|
133
|
+
});
|
|
134
|
+
chrome.tabGroups.onRemoved.addListener((group) => {
|
|
135
|
+
emit({ method: "Chrome.tabGroupRemoved", params: { groupId: group.id } });
|
|
136
|
+
});
|
|
137
|
+
chrome.tabGroups.onMoved.addListener((group) => {
|
|
138
|
+
emit({
|
|
139
|
+
method: "Chrome.tabGroupMoved",
|
|
140
|
+
params: { groupId: group.id, windowId: group.windowId },
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
connect();
|
|
146
|
+
|
|
147
|
+
function port() {
|
|
148
|
+
return DEFAULT_PORT;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function hubUrl() {
|
|
152
|
+
return "ws://127.0.0.1:" + port() + "/extension";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function connect() {
|
|
156
|
+
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
|
|
157
|
+
disconnect();
|
|
158
|
+
let socket;
|
|
159
|
+
try {
|
|
160
|
+
socket = new WebSocket(hubUrl());
|
|
161
|
+
} catch {
|
|
162
|
+
scheduleReconnect();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
ws = socket;
|
|
166
|
+
socket.addEventListener("open", () => {
|
|
167
|
+
if (ws !== socket) return;
|
|
168
|
+
reconnectMs = RECONNECT_MIN_MS;
|
|
169
|
+
setBadge(true);
|
|
170
|
+
});
|
|
171
|
+
socket.addEventListener("message", (ev) => {
|
|
172
|
+
if (typeof ev.data === "string") onMessage(ev.data);
|
|
173
|
+
});
|
|
174
|
+
socket.addEventListener("close", () => {
|
|
175
|
+
if (ws === socket) ws = null;
|
|
176
|
+
setBadge(false);
|
|
177
|
+
scheduleReconnect();
|
|
178
|
+
});
|
|
179
|
+
socket.addEventListener("error", () => {
|
|
180
|
+
try {
|
|
181
|
+
socket.close();
|
|
182
|
+
} catch {
|
|
183
|
+
/* ignore */
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function disconnect() {
|
|
189
|
+
if (reconnectTimer) {
|
|
190
|
+
clearTimeout(reconnectTimer);
|
|
191
|
+
reconnectTimer = null;
|
|
192
|
+
}
|
|
193
|
+
const prev = ws;
|
|
194
|
+
ws = null;
|
|
195
|
+
if (prev) {
|
|
196
|
+
try {
|
|
197
|
+
prev.close();
|
|
198
|
+
} catch {
|
|
199
|
+
/* ignore */
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function scheduleReconnect() {
|
|
205
|
+
if (reconnectTimer) return;
|
|
206
|
+
const wait = reconnectMs;
|
|
207
|
+
reconnectMs = Math.min(reconnectMs * 1.5, RECONNECT_MAX_MS);
|
|
208
|
+
reconnectTimer = setTimeout(() => {
|
|
209
|
+
reconnectTimer = null;
|
|
210
|
+
connect();
|
|
211
|
+
}, wait);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function setBadge(connected) {
|
|
215
|
+
try {
|
|
216
|
+
chrome.action.setBadgeText({ text: connected ? "on" : "" });
|
|
217
|
+
chrome.action.setBadgeBackgroundColor({ color: connected ? "#0a0" : "#666" });
|
|
218
|
+
} catch {
|
|
219
|
+
/* tests / no action */
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function emit(obj) {
|
|
224
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
225
|
+
try {
|
|
226
|
+
ws.send(JSON.stringify(obj));
|
|
227
|
+
} catch {
|
|
228
|
+
/* ignore */
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function onMessage(raw) {
|
|
233
|
+
let msg;
|
|
234
|
+
try {
|
|
235
|
+
msg = JSON.parse(raw);
|
|
236
|
+
} catch {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (typeof msg.id !== "number" || typeof msg.method !== "string") return;
|
|
240
|
+
try {
|
|
241
|
+
const result = await dispatch(msg.method, msg.params ?? {}, msg.sessionId);
|
|
242
|
+
emit({ id: msg.id, result: result ?? {} });
|
|
243
|
+
} catch (e) {
|
|
244
|
+
const message = e && e.message ? e.message : String(e);
|
|
245
|
+
const code = /session with given id not found/i.test(message) ? -32001 : -32000;
|
|
246
|
+
emit({ id: msg.id, error: { code, message } });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function dispatch(method, params, sessionId) {
|
|
251
|
+
switch (method) {
|
|
252
|
+
case "Target.getTargets":
|
|
253
|
+
return getTargets();
|
|
254
|
+
case "Target.createTarget":
|
|
255
|
+
return createTarget(params);
|
|
256
|
+
case "Target.attachToTarget":
|
|
257
|
+
return attachToTarget(params);
|
|
258
|
+
case "Target.closeTarget":
|
|
259
|
+
return closeTarget(params);
|
|
260
|
+
case "Target.activateTarget":
|
|
261
|
+
return activateTarget(params);
|
|
262
|
+
case "Target.detachFromTarget":
|
|
263
|
+
return detachFromTarget(params);
|
|
264
|
+
case "Target.getTargetInfo":
|
|
265
|
+
return getTargetInfo(params);
|
|
266
|
+
case "Target.setDiscoverTargets":
|
|
267
|
+
discoverTargets = Boolean(params.discover);
|
|
268
|
+
return {};
|
|
269
|
+
case "Target.setAutoAttach":
|
|
270
|
+
return setAutoAttach(params);
|
|
271
|
+
case "Browser.getVersion":
|
|
272
|
+
return {
|
|
273
|
+
protocolVersion: PROTOCOL,
|
|
274
|
+
product: "Chrome/extension",
|
|
275
|
+
revision: "",
|
|
276
|
+
userAgent: navigator.userAgent,
|
|
277
|
+
jsVersion: "",
|
|
278
|
+
};
|
|
279
|
+
case "Browser.getWindowForTarget":
|
|
280
|
+
return getWindowForTarget(params);
|
|
281
|
+
case "Browser.getWindowBounds":
|
|
282
|
+
return getWindowBounds(params);
|
|
283
|
+
case "Browser.setWindowBounds":
|
|
284
|
+
return setWindowBounds(params);
|
|
285
|
+
case "Browser.grantPermissions":
|
|
286
|
+
return grantPermissions(params);
|
|
287
|
+
case "Browser.resetPermissions":
|
|
288
|
+
return resetPermissions();
|
|
289
|
+
case "Browser.setPermission":
|
|
290
|
+
return setPermission(params);
|
|
291
|
+
case "Chrome.updateTab":
|
|
292
|
+
return updateTab(params);
|
|
293
|
+
case "Chrome.moveTabs":
|
|
294
|
+
return moveTabs(params);
|
|
295
|
+
case "Chrome.discardTab":
|
|
296
|
+
return discardTab(params);
|
|
297
|
+
case "Chrome.reloadTab":
|
|
298
|
+
return reloadTab(params);
|
|
299
|
+
case "Chrome.duplicateTab":
|
|
300
|
+
return duplicateTab(params);
|
|
301
|
+
case "Chrome.highlight":
|
|
302
|
+
return highlightTabs(params);
|
|
303
|
+
case "Chrome.group":
|
|
304
|
+
return groupTabs(params);
|
|
305
|
+
case "Chrome.ungroup":
|
|
306
|
+
return ungroupTabs(params);
|
|
307
|
+
case "Chrome.getTabGroups":
|
|
308
|
+
return getTabGroups(params);
|
|
309
|
+
case "Chrome.updateTabGroup":
|
|
310
|
+
return updateTabGroup(params);
|
|
311
|
+
case "Chrome.moveTabGroup":
|
|
312
|
+
return moveTabGroup(params);
|
|
313
|
+
case "Chrome.getWindows":
|
|
314
|
+
return getWindows(params);
|
|
315
|
+
case "Chrome.createWindow":
|
|
316
|
+
return createWindow(params);
|
|
317
|
+
case "Chrome.updateWindow":
|
|
318
|
+
return updateWindow(params);
|
|
319
|
+
case "Chrome.removeWindow":
|
|
320
|
+
return removeWindow(params);
|
|
321
|
+
default:
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
method.startsWith("Target.") ||
|
|
326
|
+
method.startsWith("Browser.") ||
|
|
327
|
+
method.startsWith("Chrome.")
|
|
328
|
+
) {
|
|
329
|
+
throw new Error(method + " is not available over the extension transport");
|
|
330
|
+
}
|
|
331
|
+
if (!sessionId) throw new Error("No sessionId for " + method);
|
|
332
|
+
const sess = sessions.get(sessionId);
|
|
333
|
+
if (!sess) throw new Error("Session with given id not found");
|
|
334
|
+
return await sendCommand(sess.debuggee, method, params);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function sendCommand(debuggee, method, params) {
|
|
338
|
+
const result = await chrome.debugger.sendCommand(debuggee, method, params ?? {});
|
|
339
|
+
return result ?? {};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function sessionIdForDebuggee(source) {
|
|
343
|
+
if (source.tabId != null && tabToSession.has(source.tabId)) return tabToSession.get(source.tabId);
|
|
344
|
+
if (source.targetId) return debuggerToSession.get(source.targetId);
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function sessionForDebuggee(source) {
|
|
349
|
+
const sessionId = sessionIdForDebuggee(source);
|
|
350
|
+
if (!sessionId) return undefined;
|
|
351
|
+
const sess = sessions.get(sessionId);
|
|
352
|
+
return sess ? { sessionId, ...sess } : undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function canAttachUrl(url) {
|
|
356
|
+
return (
|
|
357
|
+
!url.startsWith("chrome://") &&
|
|
358
|
+
!url.startsWith("devtools://") &&
|
|
359
|
+
!url.startsWith("chrome-extension://")
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function getTargets() {
|
|
364
|
+
const tabs = await chrome.tabs.query({});
|
|
365
|
+
const pages = tabs.filter((t) => t.id != null).map(targetInfoFromTab);
|
|
366
|
+
const tabIds = new Set(tabs.map((t) => t.id).filter((id) => id != null));
|
|
367
|
+
let extras = [];
|
|
368
|
+
try {
|
|
369
|
+
const dbg = await chrome.debugger.getTargets();
|
|
370
|
+
extras = dbg
|
|
371
|
+
.filter((t) => !(t.tabId != null && tabIds.has(t.tabId) && (t.type === "page" || !t.type)))
|
|
372
|
+
.map(targetInfoFromDebugger);
|
|
373
|
+
} catch {
|
|
374
|
+
/* debugger.getTargets unavailable */
|
|
375
|
+
}
|
|
376
|
+
return { targetInfos: pages.concat(extras) };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function targetInfoFromTab(tab) {
|
|
380
|
+
const tabId = tab.id;
|
|
381
|
+
const muted = Boolean(tab.mutedInfo && tab.mutedInfo.muted);
|
|
382
|
+
return {
|
|
383
|
+
targetId: String(tabId),
|
|
384
|
+
type: "page",
|
|
385
|
+
title: tab.title || "",
|
|
386
|
+
url: tab.url || "",
|
|
387
|
+
attached: tabToSession.has(tabId),
|
|
388
|
+
canAccessOpener: tab.openerTabId != null,
|
|
389
|
+
openerId: tab.openerTabId != null ? String(tab.openerTabId) : undefined,
|
|
390
|
+
windowId: tab.windowId,
|
|
391
|
+
index: tab.index,
|
|
392
|
+
groupId: tab.groupId,
|
|
393
|
+
pinned: Boolean(tab.pinned),
|
|
394
|
+
muted,
|
|
395
|
+
discarded: Boolean(tab.discarded),
|
|
396
|
+
audible: Boolean(tab.audible),
|
|
397
|
+
active: Boolean(tab.active),
|
|
398
|
+
status: tab.status || "",
|
|
399
|
+
incognito: Boolean(tab.incognito),
|
|
400
|
+
autoDiscardable: tab.autoDiscardable !== false,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function cdpTypeFromDebugger(type) {
|
|
405
|
+
if (type === "iframe") return "iframe";
|
|
406
|
+
if (type === "worker") return "worker";
|
|
407
|
+
if (type === "shared_worker") return "shared_worker";
|
|
408
|
+
if (type === "service_worker") return "service_worker";
|
|
409
|
+
if (type === "background_page") return "worker";
|
|
410
|
+
if (type === "page") return "page";
|
|
411
|
+
return type || "other";
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function targetInfoFromDebugger(t) {
|
|
415
|
+
const targetId = String(t.id);
|
|
416
|
+
return {
|
|
417
|
+
targetId,
|
|
418
|
+
type: cdpTypeFromDebugger(t.type),
|
|
419
|
+
title: t.title || "",
|
|
420
|
+
url: t.url || "",
|
|
421
|
+
attached: Boolean(t.attached) || debuggerToSession.has(targetId),
|
|
422
|
+
canAccessOpener: false,
|
|
423
|
+
tabId: t.tabId,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function serializeGroup(group) {
|
|
428
|
+
return {
|
|
429
|
+
groupId: group.id,
|
|
430
|
+
windowId: group.windowId,
|
|
431
|
+
title: group.title || "",
|
|
432
|
+
color: group.color,
|
|
433
|
+
collapsed: Boolean(group.collapsed),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function serializeWindow(win) {
|
|
438
|
+
return {
|
|
439
|
+
windowId: win.id,
|
|
440
|
+
focused: Boolean(win.focused),
|
|
441
|
+
incognito: Boolean(win.incognito),
|
|
442
|
+
type: win.type,
|
|
443
|
+
state: win.state,
|
|
444
|
+
left: win.left,
|
|
445
|
+
top: win.top,
|
|
446
|
+
width: win.width,
|
|
447
|
+
height: win.height,
|
|
448
|
+
tabIds: (win.tabs || []).filter((t) => t.id != null).map((t) => String(t.id)),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function boundsFromWindow(win) {
|
|
453
|
+
return {
|
|
454
|
+
left: win.left,
|
|
455
|
+
top: win.top,
|
|
456
|
+
width: win.width,
|
|
457
|
+
height: win.height,
|
|
458
|
+
windowState: win.state || "normal",
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function tabIdFromTarget(targetId) {
|
|
463
|
+
const tabId = Number(targetId);
|
|
464
|
+
if (!Number.isFinite(tabId)) throw new Error("unknown targetId " + targetId);
|
|
465
|
+
return tabId;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function tabIdsFromTargets(tabIds) {
|
|
469
|
+
if (!Array.isArray(tabIds) || tabIds.length === 0) throw new Error("tabIds required");
|
|
470
|
+
return tabIds.map(tabIdFromTarget);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function createTarget(params) {
|
|
474
|
+
const url = typeof params.url === "string" && params.url ? params.url : "about:blank";
|
|
475
|
+
const background = Boolean(params.background);
|
|
476
|
+
if (params.newWindow) {
|
|
477
|
+
const win = await chrome.windows.create({ url, focused: !background });
|
|
478
|
+
const tab = win.tabs && win.tabs[0];
|
|
479
|
+
if (!tab || tab.id == null) throw new Error("Target.createTarget: window opened with no tab");
|
|
480
|
+
return { targetId: String(tab.id) };
|
|
481
|
+
}
|
|
482
|
+
const tab = await chrome.tabs.create({ url, active: !background });
|
|
483
|
+
if (tab.id == null) throw new Error("Target.createTarget: tab has no id");
|
|
484
|
+
return { targetId: String(tab.id) };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function attachToTarget(params) {
|
|
488
|
+
const targetId = String(params.targetId ?? "");
|
|
489
|
+
if (!targetId) throw new Error("Target.attachToTarget requires targetId");
|
|
490
|
+
if (/^\d+$/.test(targetId)) {
|
|
491
|
+
const sessionId = await attachTab(Number(targetId), targetId);
|
|
492
|
+
return { sessionId };
|
|
493
|
+
}
|
|
494
|
+
const sessionId = await attachDebuggerTarget(targetId);
|
|
495
|
+
return { sessionId };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function attachTab(tabId, targetId) {
|
|
499
|
+
const existing = tabToSession.get(tabId);
|
|
500
|
+
if (existing) return existing;
|
|
501
|
+
let tab;
|
|
502
|
+
try {
|
|
503
|
+
tab = await chrome.tabs.get(tabId);
|
|
504
|
+
} catch {
|
|
505
|
+
throw new Error("No target with given id found");
|
|
506
|
+
}
|
|
507
|
+
const url = tab.url || "";
|
|
508
|
+
if (!canAttachUrl(url)) {
|
|
509
|
+
throw new Error("Cannot attach to " + url + " over the extension transport");
|
|
510
|
+
}
|
|
511
|
+
const debuggee = { tabId };
|
|
512
|
+
await chrome.debugger.attach(debuggee, PROTOCOL);
|
|
513
|
+
return rememberSession(debuggee, targetId || String(tabId), tabId);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function attachDebuggerTarget(targetId) {
|
|
517
|
+
const existing = debuggerToSession.get(targetId);
|
|
518
|
+
if (existing) return existing;
|
|
519
|
+
let info;
|
|
520
|
+
try {
|
|
521
|
+
const dbg = await chrome.debugger.getTargets();
|
|
522
|
+
info = dbg.find((t) => String(t.id) === targetId);
|
|
523
|
+
} catch {
|
|
524
|
+
info = undefined;
|
|
525
|
+
}
|
|
526
|
+
if (!info) throw new Error("No target with given id found");
|
|
527
|
+
if (!canAttachUrl(info.url || "")) {
|
|
528
|
+
throw new Error("Cannot attach to " + (info.url || targetId) + " over the extension transport");
|
|
529
|
+
}
|
|
530
|
+
const debuggee = { targetId };
|
|
531
|
+
await chrome.debugger.attach(debuggee, PROTOCOL);
|
|
532
|
+
return rememberSession(debuggee, targetId, info.tabId);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function rememberSession(debuggee, targetId, tabId) {
|
|
536
|
+
const sessionId = crypto.randomUUID();
|
|
537
|
+
sessions.set(sessionId, { sessionId, debuggee, targetId, tabId });
|
|
538
|
+
if (tabId != null) tabToSession.set(tabId, sessionId);
|
|
539
|
+
if (debuggee.targetId) debuggerToSession.set(String(debuggee.targetId), sessionId);
|
|
540
|
+
return sessionId;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function setAutoAttach(params) {
|
|
544
|
+
autoAttach = Boolean(params.autoAttach);
|
|
545
|
+
if (!autoAttach) return {};
|
|
546
|
+
const { targetInfos } = await getTargets();
|
|
547
|
+
for (const info of targetInfos) {
|
|
548
|
+
if (!canAttachUrl(info.url || "")) continue;
|
|
549
|
+
try {
|
|
550
|
+
const attached = await attachToTarget({ targetId: info.targetId });
|
|
551
|
+
emit({
|
|
552
|
+
method: "Target.attachedToTarget",
|
|
553
|
+
params: { sessionId: attached.sessionId, waitingForDebugger: false, targetInfo: info },
|
|
554
|
+
});
|
|
555
|
+
} catch {
|
|
556
|
+
/* restricted page or already attached elsewhere */
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return {};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function closeTarget(params) {
|
|
563
|
+
const targetId = String(params.targetId ?? "");
|
|
564
|
+
if (/^\d+$/.test(targetId)) {
|
|
565
|
+
const tabId = Number(targetId);
|
|
566
|
+
try {
|
|
567
|
+
await chrome.debugger.detach({ tabId });
|
|
568
|
+
} catch {
|
|
569
|
+
/* not attached */
|
|
570
|
+
}
|
|
571
|
+
forgetTab(tabId);
|
|
572
|
+
try {
|
|
573
|
+
await chrome.tabs.remove(tabId);
|
|
574
|
+
} catch {
|
|
575
|
+
/* already gone */
|
|
576
|
+
}
|
|
577
|
+
return { success: true };
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
await chrome.debugger.detach({ targetId });
|
|
581
|
+
} catch {
|
|
582
|
+
/* not attached */
|
|
583
|
+
}
|
|
584
|
+
const sessionId = debuggerToSession.get(targetId);
|
|
585
|
+
if (sessionId) forgetSession(sessionId);
|
|
586
|
+
return { success: true };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function activateTarget(params) {
|
|
590
|
+
const tabId = tabIdFromTarget(params.targetId);
|
|
591
|
+
const tab = await chrome.tabs.update(tabId, { active: true });
|
|
592
|
+
if (tab.windowId != null) {
|
|
593
|
+
try {
|
|
594
|
+
await chrome.windows.update(tab.windowId, { focused: true });
|
|
595
|
+
} catch {
|
|
596
|
+
/* ignore */
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return {};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async function detachFromTarget(params) {
|
|
603
|
+
let sess;
|
|
604
|
+
if (params.sessionId) {
|
|
605
|
+
sess = sessions.get(params.sessionId);
|
|
606
|
+
} else if (params.targetId != null) {
|
|
607
|
+
const targetId = String(params.targetId);
|
|
608
|
+
const sessionId = /^\d+$/.test(targetId)
|
|
609
|
+
? tabToSession.get(Number(targetId))
|
|
610
|
+
: debuggerToSession.get(targetId);
|
|
611
|
+
sess = sessionId ? sessions.get(sessionId) : undefined;
|
|
612
|
+
if (!sess && /^\d+$/.test(targetId)) {
|
|
613
|
+
try {
|
|
614
|
+
await chrome.debugger.detach({ tabId: Number(targetId) });
|
|
615
|
+
} catch {
|
|
616
|
+
/* ignore */
|
|
617
|
+
}
|
|
618
|
+
forgetTab(Number(targetId));
|
|
619
|
+
return {};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (!sess) return {};
|
|
623
|
+
try {
|
|
624
|
+
await chrome.debugger.detach(sess.debuggee);
|
|
625
|
+
} catch {
|
|
626
|
+
/* ignore */
|
|
627
|
+
}
|
|
628
|
+
if (sess.sessionId) forgetSession(sess.sessionId);
|
|
629
|
+
return {};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async function getTargetInfo(params) {
|
|
633
|
+
const targetId = String(params.targetId ?? "");
|
|
634
|
+
if (/^\d+$/.test(targetId)) {
|
|
635
|
+
const tab = await chrome.tabs.get(Number(targetId));
|
|
636
|
+
return { targetInfo: targetInfoFromTab(tab) };
|
|
637
|
+
}
|
|
638
|
+
const dbg = await chrome.debugger.getTargets();
|
|
639
|
+
const info = dbg.find((t) => String(t.id) === targetId);
|
|
640
|
+
if (!info) throw new Error("No target with given id found");
|
|
641
|
+
return { targetInfo: targetInfoFromDebugger(info) };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async function getWindowForTarget(params) {
|
|
645
|
+
const targetId = String(params.targetId ?? "");
|
|
646
|
+
let tabId;
|
|
647
|
+
if (/^\d+$/.test(targetId)) {
|
|
648
|
+
tabId = Number(targetId);
|
|
649
|
+
} else {
|
|
650
|
+
const dbg = await chrome.debugger.getTargets();
|
|
651
|
+
const info = dbg.find((t) => String(t.id) === targetId);
|
|
652
|
+
tabId = info && info.tabId;
|
|
653
|
+
}
|
|
654
|
+
if (tabId == null) throw new Error("Browser.getWindowForTarget: target has no tab");
|
|
655
|
+
const tab = await chrome.tabs.get(tabId);
|
|
656
|
+
const win = await chrome.windows.get(tab.windowId);
|
|
657
|
+
return { windowId: win.id, bounds: boundsFromWindow(win) };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async function getWindowBounds(params) {
|
|
661
|
+
const windowId = Number(params.windowId);
|
|
662
|
+
if (!Number.isFinite(windowId)) throw new Error("Browser.getWindowBounds requires windowId");
|
|
663
|
+
const win = await chrome.windows.get(windowId);
|
|
664
|
+
return { bounds: boundsFromWindow(win) };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async function setWindowBounds(params) {
|
|
668
|
+
const windowId = Number(params.windowId);
|
|
669
|
+
if (!Number.isFinite(windowId)) throw new Error("Browser.setWindowBounds requires windowId");
|
|
670
|
+
const bounds = params.bounds || {};
|
|
671
|
+
const update = {};
|
|
672
|
+
const state = bounds.windowState;
|
|
673
|
+
if (state && state !== "normal") {
|
|
674
|
+
update.state = state;
|
|
675
|
+
} else {
|
|
676
|
+
if (typeof bounds.left === "number") update.left = bounds.left;
|
|
677
|
+
if (typeof bounds.top === "number") update.top = bounds.top;
|
|
678
|
+
if (typeof bounds.width === "number") update.width = bounds.width;
|
|
679
|
+
if (typeof bounds.height === "number") update.height = bounds.height;
|
|
680
|
+
if (state === "normal") update.state = "normal";
|
|
681
|
+
}
|
|
682
|
+
await chrome.windows.update(windowId, update);
|
|
683
|
+
return {};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async function updateTab(params) {
|
|
687
|
+
const tabId = tabIdFromTarget(params.targetId);
|
|
688
|
+
const patch = {};
|
|
689
|
+
if (typeof params.pinned === "boolean") patch.pinned = params.pinned;
|
|
690
|
+
if (typeof params.muted === "boolean") patch.muted = params.muted;
|
|
691
|
+
if (typeof params.active === "boolean") patch.active = params.active;
|
|
692
|
+
if (typeof params.autoDiscardable === "boolean") patch.autoDiscardable = params.autoDiscardable;
|
|
693
|
+
if (typeof params.url === "string") patch.url = params.url;
|
|
694
|
+
const tab = await chrome.tabs.update(tabId, patch);
|
|
695
|
+
return { targetInfo: targetInfoFromTab(tab) };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async function moveTabs(params) {
|
|
699
|
+
const ids = tabIdsFromTargets(
|
|
700
|
+
params.tabIds || (params.targetId != null ? [params.targetId] : []),
|
|
701
|
+
);
|
|
702
|
+
const move = { index: Number(params.index) };
|
|
703
|
+
if (!Number.isFinite(move.index)) throw new Error("Chrome.moveTabs requires index");
|
|
704
|
+
if (params.windowId != null) move.windowId = Number(params.windowId);
|
|
705
|
+
const moved = await chrome.tabs.move(ids, move);
|
|
706
|
+
const tabs = Array.isArray(moved) ? moved : [moved];
|
|
707
|
+
return { targetInfos: tabs.filter((t) => t.id != null).map(targetInfoFromTab) };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function discardTab(params) {
|
|
711
|
+
const tabId = tabIdFromTarget(params.targetId);
|
|
712
|
+
const tab = await chrome.tabs.discard(tabId);
|
|
713
|
+
return { targetInfo: tab && tab.id != null ? targetInfoFromTab(tab) : undefined };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function reloadTab(params) {
|
|
717
|
+
const tabId = tabIdFromTarget(params.targetId);
|
|
718
|
+
await chrome.tabs.reload(tabId, { bypassCache: Boolean(params.bypassCache) });
|
|
719
|
+
return {};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async function duplicateTab(params) {
|
|
723
|
+
const tabId = tabIdFromTarget(params.targetId);
|
|
724
|
+
const tab = await chrome.tabs.duplicate(tabId);
|
|
725
|
+
if (!tab || tab.id == null) throw new Error("Chrome.duplicateTab produced no tab");
|
|
726
|
+
return { targetId: String(tab.id), targetInfo: targetInfoFromTab(tab) };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async function highlightTabs(params) {
|
|
730
|
+
const ids = tabIdsFromTargets(params.tabIds);
|
|
731
|
+
const tabs = await Promise.all(ids.map((id) => chrome.tabs.get(id)));
|
|
732
|
+
const windowId = params.windowId != null ? Number(params.windowId) : tabs[0].windowId;
|
|
733
|
+
const windowTabs = await chrome.tabs.query({ windowId });
|
|
734
|
+
const indexById = new Map(windowTabs.filter((t) => t.id != null).map((t) => [t.id, t.index]));
|
|
735
|
+
const tabsIndexes = ids.map((id) => indexById.get(id)).filter((n) => n != null);
|
|
736
|
+
await chrome.tabs.highlight({ windowId, tabs: tabsIndexes });
|
|
737
|
+
return {};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async function groupTabs(params) {
|
|
741
|
+
if (!chrome.tabs.group) throw new Error("Chrome.group requires the tabGroups permission");
|
|
742
|
+
const ids = tabIdsFromTargets(params.tabIds);
|
|
743
|
+
const opts = { tabIds: ids };
|
|
744
|
+
if (params.groupId != null && params.groupId !== -1) opts.groupId = Number(params.groupId);
|
|
745
|
+
else if (params.createProperties) opts.createProperties = params.createProperties;
|
|
746
|
+
const groupId = await chrome.tabs.group(opts);
|
|
747
|
+
return { groupId };
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function ungroupTabs(params) {
|
|
751
|
+
if (!chrome.tabs.ungroup) throw new Error("Chrome.ungroup requires the tabGroups permission");
|
|
752
|
+
const ids = tabIdsFromTargets(params.tabIds);
|
|
753
|
+
await chrome.tabs.ungroup(ids);
|
|
754
|
+
return {};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
async function getTabGroups(params) {
|
|
758
|
+
if (!chrome.tabGroups) throw new Error("Chrome.getTabGroups requires the tabGroups permission");
|
|
759
|
+
const query = {};
|
|
760
|
+
if (params.windowId != null) query.windowId = Number(params.windowId);
|
|
761
|
+
const groups = await chrome.tabGroups.query(query);
|
|
762
|
+
return { groups: groups.map(serializeGroup) };
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async function updateTabGroup(params) {
|
|
766
|
+
if (!chrome.tabGroups) throw new Error("Chrome.updateTabGroup requires the tabGroups permission");
|
|
767
|
+
const groupId = Number(params.groupId);
|
|
768
|
+
if (!Number.isFinite(groupId)) throw new Error("Chrome.updateTabGroup requires groupId");
|
|
769
|
+
const patch = {};
|
|
770
|
+
if (typeof params.title === "string") patch.title = params.title;
|
|
771
|
+
if (typeof params.color === "string") patch.color = params.color;
|
|
772
|
+
if (typeof params.collapsed === "boolean") patch.collapsed = params.collapsed;
|
|
773
|
+
const group = await chrome.tabGroups.update(groupId, patch);
|
|
774
|
+
return { group: serializeGroup(group) };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async function moveTabGroup(params) {
|
|
778
|
+
if (!chrome.tabGroups) throw new Error("Chrome.moveTabGroup requires the tabGroups permission");
|
|
779
|
+
const groupId = Number(params.groupId);
|
|
780
|
+
if (!Number.isFinite(groupId)) throw new Error("Chrome.moveTabGroup requires groupId");
|
|
781
|
+
const move = { index: Number(params.index) };
|
|
782
|
+
if (!Number.isFinite(move.index)) throw new Error("Chrome.moveTabGroup requires index");
|
|
783
|
+
if (params.windowId != null) move.windowId = Number(params.windowId);
|
|
784
|
+
const group = await chrome.tabGroups.move(groupId, move);
|
|
785
|
+
return { group: serializeGroup(group) };
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
async function getWindows(params) {
|
|
789
|
+
const populate = params.populate !== false;
|
|
790
|
+
const wins = await chrome.windows.getAll({ populate, windowTypes: params.windowTypes });
|
|
791
|
+
return { windows: wins.map(serializeWindow) };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async function createWindow(params) {
|
|
795
|
+
const create = {};
|
|
796
|
+
if (typeof params.url === "string") create.url = params.url;
|
|
797
|
+
else if (Array.isArray(params.url)) create.url = params.url;
|
|
798
|
+
if (typeof params.focused === "boolean") create.focused = params.focused;
|
|
799
|
+
if (typeof params.incognito === "boolean") create.incognito = params.incognito;
|
|
800
|
+
if (typeof params.type === "string") create.type = params.type;
|
|
801
|
+
if (typeof params.state === "string") create.state = params.state;
|
|
802
|
+
if (typeof params.left === "number") create.left = params.left;
|
|
803
|
+
if (typeof params.top === "number") create.top = params.top;
|
|
804
|
+
if (typeof params.width === "number") create.width = params.width;
|
|
805
|
+
if (typeof params.height === "number") create.height = params.height;
|
|
806
|
+
if (params.tabId != null) create.tabId = tabIdFromTarget(params.tabId);
|
|
807
|
+
const win = await chrome.windows.create(create);
|
|
808
|
+
return { window: serializeWindow(win) };
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async function updateWindow(params) {
|
|
812
|
+
const windowId = Number(params.windowId);
|
|
813
|
+
if (!Number.isFinite(windowId)) throw new Error("Chrome.updateWindow requires windowId");
|
|
814
|
+
const patch = {};
|
|
815
|
+
if (typeof params.focused === "boolean") patch.focused = params.focused;
|
|
816
|
+
if (typeof params.drawAttention === "boolean") patch.drawAttention = params.drawAttention;
|
|
817
|
+
if (typeof params.state === "string") patch.state = params.state;
|
|
818
|
+
if (typeof params.left === "number") patch.left = params.left;
|
|
819
|
+
if (typeof params.top === "number") patch.top = params.top;
|
|
820
|
+
if (typeof params.width === "number") patch.width = params.width;
|
|
821
|
+
if (typeof params.height === "number") patch.height = params.height;
|
|
822
|
+
const win = await chrome.windows.update(windowId, patch);
|
|
823
|
+
return { window: serializeWindow(win) };
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async function removeWindow(params) {
|
|
827
|
+
const windowId = Number(params.windowId);
|
|
828
|
+
if (!Number.isFinite(windowId)) throw new Error("Chrome.removeWindow requires windowId");
|
|
829
|
+
await chrome.windows.remove(windowId);
|
|
830
|
+
return {};
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function forgetTab(tabId) {
|
|
834
|
+
const sessionId = tabToSession.get(tabId);
|
|
835
|
+
if (sessionId) forgetSession(sessionId);
|
|
836
|
+
else tabToSession.delete(tabId);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function forgetSession(sessionId) {
|
|
840
|
+
const sess = sessions.get(sessionId);
|
|
841
|
+
sessions.delete(sessionId);
|
|
842
|
+
if (!sess) return;
|
|
843
|
+
if (sess.tabId != null && tabToSession.get(sess.tabId) === sessionId) {
|
|
844
|
+
tabToSession.delete(sess.tabId);
|
|
845
|
+
}
|
|
846
|
+
if (
|
|
847
|
+
sess.debuggee &&
|
|
848
|
+
sess.debuggee.targetId &&
|
|
849
|
+
debuggerToSession.get(sess.debuggee.targetId) === sessionId
|
|
850
|
+
) {
|
|
851
|
+
debuggerToSession.delete(sess.debuggee.targetId);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const CONTENT_SETTING_BY_PERMISSION = {
|
|
856
|
+
geolocation: "location",
|
|
857
|
+
notifications: "notifications",
|
|
858
|
+
audioCapture: "microphone",
|
|
859
|
+
videoCapture: "camera",
|
|
860
|
+
automaticDownloads: "automaticDownloads",
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
function originPattern(origin) {
|
|
864
|
+
if (!origin || typeof origin !== "string") throw new Error("origin required");
|
|
865
|
+
return origin.endsWith("*") ? origin : origin.replace(/\/?$/, "/") + "*";
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function contentSettingName(permission) {
|
|
869
|
+
const name = typeof permission === "string" ? permission : permission && permission.name;
|
|
870
|
+
return CONTENT_SETTING_BY_PERMISSION[name];
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function settingFromCdp(setting) {
|
|
874
|
+
if (setting === "granted" || setting === "allow") return "allow";
|
|
875
|
+
if (setting === "denied" || setting === "block") return "block";
|
|
876
|
+
if (setting === "prompt" || setting === "ask") return "ask";
|
|
877
|
+
throw new Error("unknown permission setting " + setting);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async function grantPermissions(params) {
|
|
881
|
+
if (!chrome.contentSettings) {
|
|
882
|
+
throw new Error("Browser.grantPermissions requires the contentSettings permission");
|
|
883
|
+
}
|
|
884
|
+
const types = params.permissionTypes || [];
|
|
885
|
+
const pattern = originPattern(params.origin);
|
|
886
|
+
const unknown = types.filter((type) => {
|
|
887
|
+
const cs = contentSettingName(type);
|
|
888
|
+
return !cs || !chrome.contentSettings[cs];
|
|
889
|
+
});
|
|
890
|
+
if (unknown.length) {
|
|
891
|
+
throw new Error("unsupported permission types over the extension: " + unknown.join(", "));
|
|
892
|
+
}
|
|
893
|
+
for (const type of types) {
|
|
894
|
+
const cs = contentSettingName(type);
|
|
895
|
+
await chrome.contentSettings[cs].set({ primaryPattern: pattern, setting: "allow" });
|
|
896
|
+
}
|
|
897
|
+
return {};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async function setPermission(params) {
|
|
901
|
+
if (!chrome.contentSettings) {
|
|
902
|
+
throw new Error("Browser.setPermission requires the contentSettings permission");
|
|
903
|
+
}
|
|
904
|
+
const cs = contentSettingName(params.permission);
|
|
905
|
+
if (!cs || !chrome.contentSettings[cs]) {
|
|
906
|
+
throw new Error(
|
|
907
|
+
"unsupported permission over the extension: " + JSON.stringify(params.permission),
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
const pattern = originPattern(params.origin);
|
|
911
|
+
await chrome.contentSettings[cs].set({
|
|
912
|
+
primaryPattern: pattern,
|
|
913
|
+
setting: settingFromCdp(params.setting),
|
|
914
|
+
});
|
|
915
|
+
return {};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
async function resetPermissions() {
|
|
919
|
+
if (!chrome.contentSettings) {
|
|
920
|
+
throw new Error("Browser.resetPermissions requires the contentSettings permission");
|
|
921
|
+
}
|
|
922
|
+
for (const cs of Object.values(CONTENT_SETTING_BY_PERMISSION)) {
|
|
923
|
+
if (!chrome.contentSettings[cs]) continue;
|
|
924
|
+
try {
|
|
925
|
+
await chrome.contentSettings[cs].clear({});
|
|
926
|
+
} catch {
|
|
927
|
+
/* ignore */
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return {};
|
|
931
|
+
}
|