cdp-tunnel 3.0.2 → 3.0.3

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,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "3.0.2",
4
+ "version": "3.0.3",
5
5
  "description": "Chrome DevTools Protocol Bridge for Playwright/Puppeteer automation",
6
6
  "permissions": [
7
7
  "debugger",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
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",
@@ -181,17 +181,25 @@ class PortPoolManager {
181
181
  return;
182
182
  }
183
183
 
184
+ // 合成输入命令需要 ensureVisible(和 forward.js 的逻辑一致)
185
+ const SYNTHETIC_INPUT = ['Input.dispatchKeyEvent', 'Input.dispatchMouseEvent'];
186
+
184
187
  // 消息处理:client → plugin(带 portIndex 标记)
185
- ws.on('message', (data) => {
188
+ ws.on('message', async (data) => {
186
189
  let msg;
187
190
  try { msg = JSON.parse(data.toString()); } catch { return; }
188
191
 
189
192
  if (msg.id !== undefined) {
193
+ // 合成输入命令:先 bringToFront + 等待,再发原命令
194
+ if (SYNTHETIC_INPUT.indexOf(msg.method) >= 0 && msg.sessionId) {
195
+ await this._ensureVisible(session, pluginWs, msg.sessionId);
196
+ }
197
+
190
198
  // 命令:分配新 id,记录映射(含 method 名供响应后处理),转发给 plugin
191
199
  const newId = `pool${session.portIndex}_${msg.id}`;
192
200
  session.pendingRequests.set(newId, {
193
201
  originalId: msg.id,
194
- method: msg.method, // 记录方法名,响应时按需过滤
202
+ method: msg.method,
195
203
  clientWs: ws
196
204
  });
197
205
 
@@ -223,6 +231,10 @@ class PortPoolManager {
223
231
 
224
232
  // 1. 响应消息:id 以 pool 开头
225
233
  if (msg.id && typeof msg.id === 'string' && msg.id.startsWith('pool')) {
234
+ // 内部命令(ensureVisible 用的)响应直接丢弃
235
+ if (msg.id.includes('_internal')) {
236
+ return true;
237
+ }
226
238
  const match = msg.id.match(/^pool(\d+)_(.+)$/);
227
239
  if (!match) return false;
228
240
 
@@ -300,6 +312,54 @@ class PortPoolManager {
300
312
  return isNaN(n) ? idStr : n;
301
313
  }
302
314
 
315
+ /**
316
+ * 让 tab 变 visible:Page.bringToFront + 等待 + 恢复焦点。
317
+ * 内部命令用 _internal_ 前缀,handlePluginMessage 会丢弃它们的响应。
318
+ */
319
+ async _ensureVisible(session, pluginWs, sessionId) {
320
+ const pfx = `pool${session.portIndex}_internal`;
321
+
322
+ // 1. 保存焦点
323
+ pluginWs.send(JSON.stringify({
324
+ id: `${pfx}_save_${Date.now()}`,
325
+ method: 'Runtime.evaluate',
326
+ params: { expression: '(function(){var el=document.activeElement;if(el&&el!==document.body&&el.focus){el.setAttribute("data-cdp-saved-focus","1");return 1}return 0})()', returnByValue: true },
327
+ sessionId, __portIndex: session.portIndex
328
+ }));
329
+
330
+ // 2. bringToFront
331
+ pluginWs.send(JSON.stringify({
332
+ id: `${pfx}_front_${Date.now()}`,
333
+ method: 'Page.bringToFront',
334
+ params: {},
335
+ sessionId, __portIndex: session.portIndex
336
+ }));
337
+
338
+ // 3. 等 visibilitychange + 双 rAF(renderer 完成切换)
339
+ pluginWs.send(JSON.stringify({
340
+ id: `${pfx}_vis_${Date.now()}`,
341
+ method: 'Runtime.evaluate',
342
+ params: {
343
+ 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)}})',
344
+ awaitPromise: true
345
+ },
346
+ sessionId, __portIndex: session.portIndex
347
+ }));
348
+
349
+ // 给 bringToFront + visibility 等待时间(不等具体响应,内部命令响应被丢弃)
350
+ await new Promise(r => setTimeout(r, 300));
351
+
352
+ // 4. 恢复焦点
353
+ pluginWs.send(JSON.stringify({
354
+ id: `${pfx}_restore_${Date.now()}`,
355
+ method: 'Runtime.evaluate',
356
+ params: { expression: '(function(){var el=document.querySelector("[data-cdp-saved-focus]");if(el){el.removeAttribute("data-cdp-saved-focus");el.focus();return 1}return 0})()', returnByValue: true },
357
+ sessionId, __portIndex: session.portIndex
358
+ }));
359
+
360
+ await new Promise(r => setTimeout(r, 50));
361
+ }
362
+
303
363
  _broadcastToPort(portIndex, msg) {
304
364
  const session = this.portSessions[portIndex];
305
365
  if (!session) return;