cdp-tunnel 2.10.12 → 2.10.14

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.
@@ -1,4 +1,13 @@
1
1
  var ForwardHandler = (function() {
2
+ // 合成输入事件(keyboard/mouse)需要页面 visibility=visible 才能投递到 DOM。
3
+ // cdp-tunnel 的隔离 tab(active:false + 折叠分组)默认 visibility=hidden,
4
+ // 导致 Input.dispatchKeyEvent/dispatchMouseEvent 被 Chromium 丢弃。
5
+ // 这些命令发送前需要 Page.bringToFront 让页面变 visible + 恢复焦点。
6
+ var SYNTHETIC_INPUT_METHODS = [
7
+ 'Input.dispatchKeyEvent',
8
+ 'Input.dispatchMouseEvent'
9
+ ];
10
+
2
11
  function execute(context) {
3
12
  var id = context.id;
4
13
  var method = context.method;
@@ -19,11 +28,52 @@ var ForwardHandler = (function() {
19
28
  }
20
29
 
21
30
  Logger.debug('[Forward]', method, '-> tabId:', tabId);
31
+
32
+ // 合成输入事件需要页面 visible:先 ensureVisible 再发命令
33
+ if (SYNTHETIC_INPUT_METHODS.indexOf(method) >= 0) {
34
+ return ensureVisible(tabId).then(function() {
35
+ return chrome.debugger.sendCommand({ tabId: tabId }, method, params);
36
+ }).then(function(result) {
37
+ return result || {};
38
+ });
39
+ }
40
+
22
41
  return chrome.debugger.sendCommand({ tabId: tabId }, method, params).then(function(result) {
23
42
  return result || {};
24
43
  });
25
44
  }
26
45
 
46
+ /**
47
+ * 让 tab 变 visible:Page.bringToFront + 等 visibilitychange + 恢复焦点。
48
+ * bringToFront 会重置页面元素焦点,需要保存/恢复。
49
+ */
50
+ function ensureVisible(tabId) {
51
+ // 1. 保存焦点:给 activeElement 打标记
52
+ return chrome.debugger.sendCommand({ tabId: tabId }, 'Runtime.evaluate', {
53
+ expression: '(function(){var el=document.activeElement;if(el&&el!==document.body&&el.focus){el.setAttribute("data-cdp-saved-focus","1");return 1}return 0})()',
54
+ returnByValue: true
55
+ }).catch(function() { return { result: { value: 0 } }; }).then(function(res) {
56
+ var hadFocus = res && res.result && res.result.value;
57
+
58
+ // 2. bringToFront 让 visibility 从 hidden→visible
59
+ return chrome.debugger.sendCommand({ tabId: tabId }, 'Page.bringToFront', {}).then(function() {
60
+ // 3. 等 visibilitychange 事件 + 双 rAF(确保 renderer 完成切换)
61
+ return chrome.debugger.sendCommand({ tabId: tabId }, 'Runtime.evaluate', {
62
+ expression: 'new Promise(function(r){function ok(){requestAnimationFrame(function(){requestAnimationFrame(function(){r(1)})})}if(document.visibilityState==="visible"){ok()}else{var d=function(){if(document.visibilityState==="visible"){document.removeEventListener("visibilitychange",d);ok()}};document.addEventListener("visibilitychange",d);setTimeout(function(){document.removeEventListener("visibilitychange",d);ok()},3000)}})',
63
+ awaitPromise: true
64
+ });
65
+ }).then(function() {
66
+ // 4. 恢复焦点
67
+ if (hadFocus) {
68
+ return chrome.debugger.sendCommand({ tabId: tabId }, 'Runtime.evaluate', {
69
+ expression: '(function(){var el=document.querySelector("[data-cdp-saved-focus]");if(el){el.removeAttribute("data-cdp-saved-focus");el.focus();return 1}return 0})()',
70
+ returnByValue: true
71
+ }).catch(function() {});
72
+ }
73
+ });
74
+ });
75
+ }
76
+
27
77
  function resolveTabId(sessionId, state) {
28
78
  if (!state) return null;
29
79
  if (sessionId) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "2.10.12",
4
+ "version": "2.10.14",
5
5
  "description": "Chrome DevTools Protocol Bridge for Playwright/Puppeteer automation",
6
6
  "permissions": [
7
7
  "debugger",
@@ -1,5 +1,5 @@
1
1
  var Config = {
2
- WS_URL: 'ws://localhost:19286/plugin',
2
+ WS_URL: 'ws://localhost:9221/plugin',
3
3
  RECONNECT_DELAY: 3000,
4
4
  DEBUGGER_VERSION: '1.3',
5
5
  HEARTBEAT_INTERVAL: 25000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.10.12",
3
+ "version": "2.10.14",
4
4
  "description": "Bridge Chrome's debugger API to WebSocket — control your existing browser with Playwright/Puppeteer via CDP",
5
5
  "main": "server/proxy-server.js",
6
6
  "bin": "./cli/index.js",
@@ -394,7 +394,31 @@ async function handleHttpRequest(req, res) {
394
394
  res.end(JSON.stringify(targetList));
395
395
  return;
396
396
  }
397
-
397
+
398
+ if (url.pathname === '/debug/maps') {
399
+ const stats = {};
400
+ for (const [pluginWs, ns] of pluginNamespaces) {
401
+ stats.targetIdToClientId = ns.targetIdToClientId.size;
402
+ stats.sessionToClientId = ns.sessionToClientId.size;
403
+ stats.browserContextToClientId = ns.browserContextToClientId.size;
404
+ stats.clientIdToBrowserContext = ns.clientIdToBrowserContext.size;
405
+ stats.pendingAttachedEvents = ns.pendingAttachedEvents.size;
406
+ stats.pendingTargetCreatedEvents = ns.pendingTargetCreatedEvents.size;
407
+ stats.pendingSessionToClientId = (ns.pendingSessionToClientId || new Map()).size;
408
+ stats.discoveringClientIds = ns.discoveringClientIds.size;
409
+ stats.cachedTargets = ns.cachedTargets.length;
410
+ }
411
+ stats.globalRequestIdMap = globalRequestIdMap.size;
412
+ stats.connectionPairs = connectionPairs.size;
413
+ stats.clientById = clientById.size;
414
+ stats.clientIdToPlugin = clientIdToPlugin.size;
415
+ stats.clientConnections = clientConnections.size;
416
+ stats.pluginConnections = pluginConnections.size;
417
+ res.writeHead(200, { 'Content-Type': 'application/json' });
418
+ res.end(JSON.stringify(stats, null, 2));
419
+ return;
420
+ }
421
+
398
422
  res.writeHead(404, { 'Content-Type': 'text/plain' });
399
423
  res.end('Not Found');
400
424
  }
@@ -543,6 +567,33 @@ function cleanupClient(ws, id, reason) {
543
567
  for (const [tId, cId] of ns.targetIdToClientId.entries()) {
544
568
  if (cId === id) ns.targetIdToClientId.delete(tId);
545
569
  }
570
+ // session 清理:value 可能是 clientId(正常)或 targetId(旧 bug 残留,兼容清理)
571
+ const clientTargetIds = new Set();
572
+ for (const [tId, cId] of ns.targetIdToClientId.entries()) {
573
+ if (cId === id) clientTargetIds.add(tId);
574
+ }
575
+ for (const [sId, val] of ns.sessionToClientId.entries()) {
576
+ if (val === id || clientTargetIds.has(val)) {
577
+ ns.sessionToClientId.delete(sId);
578
+ }
579
+ }
580
+ // 清理 pending session(归属未定的暂存)
581
+ if (ns.pendingSessionToClientId) {
582
+ for (const [pSid, pTid] of ns.pendingSessionToClientId.entries()) {
583
+ if (clientTargetIds.has(pTid)) ns.pendingSessionToClientId.delete(pSid);
584
+ }
585
+ }
586
+ // 清理 pending 事件(targetCreated/attachedToTarget 缓存,防止泄漏)
587
+ if (ns.pendingAttachedEvents) {
588
+ for (const [pTid] of ns.pendingAttachedEvents.entries()) {
589
+ if (clientTargetIds.has(pTid)) ns.pendingAttachedEvents.delete(pTid);
590
+ }
591
+ }
592
+ if (ns.pendingTargetCreatedEvents) {
593
+ for (const [pTid] of ns.pendingTargetCreatedEvents.entries()) {
594
+ if (clientTargetIds.has(pTid)) ns.pendingTargetCreatedEvents.delete(pTid);
595
+ }
596
+ }
546
597
  for (const [bcId, cId] of ns.browserContextToClientId.entries()) {
547
598
  if (cId === id) ns.browserContextToClientId.delete(bcId);
548
599
  }
@@ -874,8 +925,11 @@ function handlePluginConnection(ws, clientInfo, request) {
874
925
  ns.sessionToClientId.set(sessionId, clientId);
875
926
  console.log(`[SESSION MAPPED] sessionId=${sessionId?.substring(0,8) || 'none'} -> clientId=${clientId?.substring(0,8) || 'none'}`);
876
927
  } else {
877
- ns.sessionToClientId.set(sessionId, targetId);
878
- console.log(`[SESSION MAPPED] sessionId=${sessionId?.substring(0,8) || 'none'} -> targetId=${targetId?.substring(0,8) || 'none'} (no pairedClientId)`);
928
+ // 以前这里存 targetId 作为 value,导致 cleanupClient 按 clientId 匹配时清不掉(泄漏)
929
+ // 改为:暂存到 pendingSessionToClientId,等 targetId 归属绑定时再补绑
930
+ if (!ns.pendingSessionToClientId) ns.pendingSessionToClientId = new Map();
931
+ ns.pendingSessionToClientId.set(sessionId, targetId);
932
+ console.log(`[SESSION MAPPED] sessionId=${sessionId?.substring(0,8) || 'none'} -> targetId=${targetId?.substring(0,8) || 'none'} (pending, no clientId yet)`);
879
933
  }
880
934
  }
881
935
  }
@@ -940,7 +994,19 @@ function handlePluginConnection(ws, clientInfo, request) {
940
994
  const targetId = parsed.result.targetId;
941
995
  ns.targetIdToClientId.set(targetId, mapping.clientId);
942
996
  console.log(`[TARGET MAPPED] targetId=${targetId} -> clientId=${mapping.clientId} mapSize=${ns.targetIdToClientId.size}`);
943
-
997
+
998
+ // 补绑 pending 的 session(之前 targetId 归属未定时暂存的)
999
+ if (ns.pendingSessionToClientId && ns.pendingSessionToClientId.size > 0) {
1000
+ const pendingSessionId = null;
1001
+ for (const [pSid, pTid] of ns.pendingSessionToClientId.entries()) {
1002
+ if (pTid === targetId) {
1003
+ ns.sessionToClientId.set(pSid, mapping.clientId);
1004
+ ns.pendingSessionToClientId.delete(pSid);
1005
+ console.log(`[SESSION MAPPED from pending] sessionId=${pSid?.substring(0,8)} -> clientId=${mapping.clientId?.substring(0,8)} (targetId=${targetId?.substring(0,8)})`);
1006
+ }
1007
+ }
1008
+ }
1009
+
944
1010
  const cachedCreated = ns.pendingTargetCreatedEvents.get(targetId);
945
1011
  if (cachedCreated) {
946
1012
  clientWs.send(cachedCreated.cdpData);