ghost-bridge 0.4.0 → 0.5.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/LICENSE +21 -0
- package/README.md +113 -110
- package/dist/cli.js +68 -37
- package/extension/background.js +130 -39
- package/extension/icon-128.png +0 -0
- package/extension/icon-16.png +0 -0
- package/extension/icon-48.png +0 -0
- package/extension/manifest.json +1 -1
- package/extension/offscreen.js +1 -1
- package/extension/popup.html +416 -85
- package/extension/popup.js +128 -53
- package/package.json +24 -1
package/extension/background.js
CHANGED
|
@@ -10,10 +10,10 @@ const CONFIG = {
|
|
|
10
10
|
maxPortRetries: 10,
|
|
11
11
|
token: getMonthlyToken(),
|
|
12
12
|
autoDetach: false,
|
|
13
|
-
maxErrors:
|
|
14
|
-
maxStackFrames:
|
|
13
|
+
maxErrors: 100,
|
|
14
|
+
maxStackFrames: 20,
|
|
15
15
|
maxRequestsTracked: 200,
|
|
16
|
-
maxRequestBodySize:
|
|
16
|
+
maxRequestBodySize: 500000, // 提升至 500KB,容纳较大的 API 请求
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
let attachedTabId = null
|
|
@@ -31,10 +31,10 @@ const pendingRequests = new Map()
|
|
|
31
31
|
function setBadgeState(status) {
|
|
32
32
|
const map = {
|
|
33
33
|
connecting: { text: "…", color: "#999" },
|
|
34
|
-
on: { text: "ON", color: "#
|
|
34
|
+
on: { text: "ON", color: "#00d2ff" },
|
|
35
35
|
off: { text: "OFF", color: "#999" },
|
|
36
36
|
err: { text: "ERR", color: "#ff3b30" },
|
|
37
|
-
att: { text: "ATT", color: "#
|
|
37
|
+
att: { text: "ATT", color: "#a252ff" },
|
|
38
38
|
}
|
|
39
39
|
const cfg = map[status] || map.off
|
|
40
40
|
chrome.action.setBadgeText({ text: cfg.text })
|
|
@@ -111,7 +111,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
|
111
111
|
url: topFrame?.url || detail.url,
|
|
112
112
|
line: topFrame?.lineNumber,
|
|
113
113
|
column: topFrame?.columnNumber,
|
|
114
|
-
text: detail.text,
|
|
114
|
+
text: detail.exception?.description || detail.text,
|
|
115
115
|
scriptId: topFrame?.scriptId,
|
|
116
116
|
stack: compactStack(detail.stackTrace),
|
|
117
117
|
timestamp: Date.now(),
|
|
@@ -139,7 +139,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
if (method === "Runtime.consoleAPICalled") {
|
|
142
|
-
const args = (params.args || []).map((a) => a.value).filter(Boolean)
|
|
142
|
+
const args = (params.args || []).map((a) => a.description || a.value).filter(Boolean)
|
|
143
143
|
pushError({
|
|
144
144
|
type: params.type || "console",
|
|
145
145
|
severity: params.type === "error" ? "error" : params.type === "warning" ? "warn" : "info",
|
|
@@ -253,15 +253,22 @@ chrome.debugger.onDetach.addListener((source, reason) => {
|
|
|
253
253
|
scriptSourceCache = new Map()
|
|
254
254
|
networkRequests = []
|
|
255
255
|
requestMap = new Map()
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
256
|
+
|
|
257
|
+
if (!state.enabled) return
|
|
258
|
+
if (reason === "canceled_by_user") {
|
|
259
|
+
log("调试被用户取消,已关闭")
|
|
260
|
+
state.enabled = false
|
|
261
|
+
state.connected = false
|
|
262
|
+
setBadgeState("off")
|
|
263
|
+
chrome.runtime.sendMessage({ type: 'disconnect' }).catch(() => {})
|
|
264
|
+
} else {
|
|
265
|
+
log(`调试已断开:${reason}`)
|
|
266
|
+
if (state.connected) {
|
|
267
|
+
setBadgeState("on")
|
|
268
|
+
} else {
|
|
269
|
+
setBadgeState("att")
|
|
270
|
+
}
|
|
271
|
+
}
|
|
265
272
|
}
|
|
266
273
|
})
|
|
267
274
|
|
|
@@ -294,11 +301,19 @@ async function ensureAttached() {
|
|
|
294
301
|
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true })
|
|
295
302
|
if (!tab) throw new Error("没有激活的标签页")
|
|
296
303
|
if (attachedTabId !== tab.id) {
|
|
304
|
+
if (attachedTabId) {
|
|
305
|
+
try { await chrome.debugger.detach({ tabId: attachedTabId }) } catch (e) {}
|
|
306
|
+
}
|
|
297
307
|
try {
|
|
298
308
|
await chrome.debugger.attach({ tabId: tab.id }, "1.3")
|
|
299
309
|
setBadgeState("on")
|
|
300
310
|
} catch (e) {
|
|
301
|
-
|
|
311
|
+
attachedTabId = null
|
|
312
|
+
if (state.connected) {
|
|
313
|
+
setBadgeState("on")
|
|
314
|
+
} else {
|
|
315
|
+
setBadgeState("att")
|
|
316
|
+
}
|
|
302
317
|
throw e
|
|
303
318
|
}
|
|
304
319
|
attachedTabId = tab.id
|
|
@@ -312,6 +327,13 @@ async function ensureAttached() {
|
|
|
312
327
|
await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Debugger.enable")
|
|
313
328
|
await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Profiler.enable")
|
|
314
329
|
await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Network.enable").catch(() => {})
|
|
330
|
+
|
|
331
|
+
// Enable auto-attach to sub-targets (iframes, workers) for comprehensive capture
|
|
332
|
+
await chrome.debugger.sendCommand({ tabId: attachedTabId }, "Target.setAutoAttach", {
|
|
333
|
+
autoAttach: true,
|
|
334
|
+
waitForDebuggerOnStart: false,
|
|
335
|
+
flatten: true,
|
|
336
|
+
}).catch(() => {})
|
|
315
337
|
}
|
|
316
338
|
return { tabId: attachedTabId }
|
|
317
339
|
}
|
|
@@ -1052,6 +1074,8 @@ async function handleDispatchAction(params = {}) {
|
|
|
1052
1074
|
try {
|
|
1053
1075
|
const el = document.querySelector('[data-ghost-ref="${ref}"]');
|
|
1054
1076
|
if (!el) return { error: '元素未找到,ref 可能已失效,请重新获取快照' };
|
|
1077
|
+
// 关键修复:确保元素在视口内,否则超出屏幕的坐标无法被 CDP 模拟点击
|
|
1078
|
+
el.scrollIntoView({ block: 'center', inline: 'center' });
|
|
1055
1079
|
const rect = el.getBoundingClientRect();
|
|
1056
1080
|
if (rect.width === 0 && rect.height === 0) return { error: '元素不可见(宽高为 0)' };
|
|
1057
1081
|
return {
|
|
@@ -1257,24 +1281,66 @@ function broadcastStatus() {
|
|
|
1257
1281
|
status = 'disconnected'
|
|
1258
1282
|
} else if (state.connected) {
|
|
1259
1283
|
status = 'connected'
|
|
1260
|
-
} else if (state.scanRound >=
|
|
1284
|
+
} else if (state.scanRound >= 4) {
|
|
1261
1285
|
status = 'not_found'
|
|
1262
1286
|
} else {
|
|
1263
1287
|
status = 'scanning'
|
|
1264
1288
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1289
|
+
|
|
1290
|
+
let tabUrl = ''
|
|
1291
|
+
let tabTitle = ''
|
|
1292
|
+
|
|
1293
|
+
if (attachedTabId) {
|
|
1294
|
+
chrome.tabs.get(attachedTabId).then(t => {
|
|
1295
|
+
tabUrl = t.url
|
|
1296
|
+
tabTitle = t.title
|
|
1297
|
+
doBroadcast()
|
|
1298
|
+
}).catch(() => doBroadcast())
|
|
1299
|
+
} else {
|
|
1300
|
+
doBroadcast()
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function doBroadcast() {
|
|
1304
|
+
const actualErrors = lastErrors.filter(e => e.severity === 'error')
|
|
1305
|
+
chrome.runtime.sendMessage({
|
|
1306
|
+
type: 'statusUpdate',
|
|
1307
|
+
state: {
|
|
1308
|
+
status,
|
|
1309
|
+
enabled: state.enabled,
|
|
1310
|
+
port: state.port,
|
|
1311
|
+
currentPort: state.currentPort,
|
|
1312
|
+
basePort: CONFIG.basePort,
|
|
1313
|
+
scanRound: state.scanRound,
|
|
1314
|
+
errorCount: actualErrors.length,
|
|
1315
|
+
recentErrors: actualErrors.slice(0, 5),
|
|
1316
|
+
tabTitle,
|
|
1317
|
+
tabUrl,
|
|
1318
|
+
}
|
|
1319
|
+
}).catch(() => {}) // popup 可能未打开,忽略错误
|
|
1320
|
+
}
|
|
1276
1321
|
}
|
|
1277
1322
|
|
|
1323
|
+
// 监听被调试页面的导航变化,实时推送到 popup
|
|
1324
|
+
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
1325
|
+
if (tabId === attachedTabId && (changeInfo.title || changeInfo.url)) {
|
|
1326
|
+
if (state.connected) broadcastStatus()
|
|
1327
|
+
}
|
|
1328
|
+
})
|
|
1329
|
+
|
|
1330
|
+
// 监听用户切换标签页(Active Tab 发生变化),让调试器自动跟随到新标签页
|
|
1331
|
+
chrome.tabs.onActivated.addListener(async (activeInfo) => {
|
|
1332
|
+
if (state.enabled && state.connected) {
|
|
1333
|
+
try {
|
|
1334
|
+
// 这里的 ensureAttached() 会自动处理从旧 Tab detach 并 attach 到新 Tab
|
|
1335
|
+
await ensureAttached()
|
|
1336
|
+
broadcastStatus()
|
|
1337
|
+
} catch (e) {
|
|
1338
|
+
log(`自动跟随切换 Tab 失败:${e.message}`)
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
})
|
|
1342
|
+
|
|
1343
|
+
|
|
1278
1344
|
// ========== 消息监听 ==========
|
|
1279
1345
|
|
|
1280
1346
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
@@ -1335,19 +1401,41 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1335
1401
|
status = 'disconnected'
|
|
1336
1402
|
} else if (state.connected) {
|
|
1337
1403
|
status = 'connected'
|
|
1338
|
-
} else if (state.scanRound >=
|
|
1404
|
+
} else if (state.scanRound >= 4) {
|
|
1339
1405
|
status = 'not_found'
|
|
1340
1406
|
} else {
|
|
1341
1407
|
status = 'scanning'
|
|
1342
1408
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1409
|
+
|
|
1410
|
+
let tabUrl = ''
|
|
1411
|
+
let tabTitle = ''
|
|
1412
|
+
if (attachedTabId) {
|
|
1413
|
+
chrome.tabs.get(attachedTabId).then(t => {
|
|
1414
|
+
tabUrl = t.url
|
|
1415
|
+
tabTitle = t.title
|
|
1416
|
+
sendStatusResponse()
|
|
1417
|
+
}).catch(() => {
|
|
1418
|
+
sendStatusResponse()
|
|
1419
|
+
})
|
|
1420
|
+
} else {
|
|
1421
|
+
sendStatusResponse()
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function sendStatusResponse() {
|
|
1425
|
+
const actualErrors = lastErrors.filter(e => e.severity === 'error')
|
|
1426
|
+
sendResponse({
|
|
1427
|
+
status,
|
|
1428
|
+
enabled: state.enabled,
|
|
1429
|
+
port: state.port,
|
|
1430
|
+
currentPort: state.currentPort,
|
|
1431
|
+
basePort: CONFIG.basePort,
|
|
1432
|
+
scanRound: state.scanRound,
|
|
1433
|
+
errorCount: actualErrors.length,
|
|
1434
|
+
recentErrors: actualErrors.slice(0, 5),
|
|
1435
|
+
tabTitle,
|
|
1436
|
+
tabUrl,
|
|
1437
|
+
})
|
|
1438
|
+
}
|
|
1351
1439
|
return true
|
|
1352
1440
|
}
|
|
1353
1441
|
|
|
@@ -1383,8 +1471,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1383
1471
|
setBadgeState('off')
|
|
1384
1472
|
detachAllTargets().catch(() => {})
|
|
1385
1473
|
|
|
1386
|
-
// 通知 offscreen 断开
|
|
1474
|
+
// 通知 offscreen 断开 (WebSocket 清除)
|
|
1387
1475
|
chrome.runtime.sendMessage({ type: 'disconnect' }).catch(() => {})
|
|
1476
|
+
|
|
1477
|
+
// 关键修复:显式销毁 offscreen document 防止内存泄漏
|
|
1478
|
+
closeOffscreenDocument().catch(() => {})
|
|
1388
1479
|
|
|
1389
1480
|
sendResponse({ ok: true })
|
|
1390
1481
|
return true
|
package/extension/icon-128.png
CHANGED
|
Binary file
|
package/extension/icon-16.png
CHANGED
|
Binary file
|
package/extension/icon-48.png
CHANGED
|
Binary file
|
package/extension/manifest.json
CHANGED
package/extension/offscreen.js
CHANGED
|
@@ -171,7 +171,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
171
171
|
return true
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
if (message.type === '
|
|
174
|
+
if (message.type === 'getOffscreenStatus') {
|
|
175
175
|
sendResponse({
|
|
176
176
|
connected: ws && ws.readyState === WebSocket.OPEN,
|
|
177
177
|
})
|