cdp-tunnel 3.5.0 → 3.6.1

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/README.md CHANGED
@@ -5,220 +5,309 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <strong>Chrome DevTools Protocol Bridge</strong>
8
+ <strong>Bridge Chrome's debugger API to WebSocket — control your browser with Playwright/Puppeteer via CDP</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- A Chrome extension that exposes your browser as a CDP endpoint,<br>
13
- supporting multiple Playwright/Puppeteer clients to connect simultaneously.
12
+ Control your **existing** browser (with your logins, cookies, extensions) via standard CDP protocol.<br>
13
+ No headless Chrome, no Selenium, no browser restart. Just connect and automate.
14
14
  </p>
15
15
 
16
16
  <p align="center">
17
- <a href="docs/README_CN.md">中文文档</a> |
18
- <a href="https://github.com/dyyz1993/cdp-tunnel">GitHub</a>
17
+ <a href="docs/README_CN.md">中文文档</a> ·
18
+ <a href="https://www.npmjs.com/package/cdp-tunnel">npm</a> ·
19
+ <a href="#quick-start">Quick Start</a>
19
20
  </p>
20
21
 
21
22
  <p align="center">
23
+ <img src="https://img.shields.io/npm/v/cdp-tunnel" alt="npm version">
22
24
  <img src="https://img.shields.io/github/stars/dyyz1993/cdp-tunnel?style=social" alt="GitHub stars">
23
25
  <img src="https://img.shields.io/github/forks/dyyz1993/cdp-tunnel?style=social" alt="GitHub forks">
24
- <img src="https://img.shields.io/github/watchers/dyyz1993/cdp-tunnel?style=social" alt="GitHub watchers">
25
26
  </p>
26
27
 
27
28
  ---
28
29
 
30
+ ## What It Does
31
+
32
+ CDP Tunnel lets standard CDP clients (Playwright/Puppeteer/CDP SDK) control your **real Chrome browser** — the one with your logins, cookies, and extensions. It works like `chrome --remote-debugging-port`, but **without restarting Chrome**.
33
+
34
+ **Key capabilities:**
35
+ - ✅ Control your existing browser (keep logins/cookies/extensions)
36
+ - ✅ Standard CDP protocol — Playwright/Puppeteer work out of the box
37
+ - ✅ Multiple isolated environments (port pool — each port = one independent "browser")
38
+ - ✅ API Key authentication for remote/cloud deployment
39
+ - ✅ Built-in admin console (browser list, key management, CDP operations)
40
+ - ✅ Tab Group isolation — automation tabs never mix with user tabs
41
+ - ✅ Takeover mode — attach to user's existing tabs
42
+
29
43
  ## Architecture
30
44
 
31
45
  ```
32
- ┌─────────────────────────────────────────────────────────────────┐
33
- Proxy Server
34
- (localhost:9221)
35
- │ │
36
- │ /plugin ←─── Chrome Extension (WebSocket) │
37
- HTTP ←─── Playwright/Puppeteer Clients
38
- └─────────────────────────────────────────────────────────────────┘
39
- ↑ ↑ ↑
40
-
41
- Client 1 Client 2 Client 3
42
- (clientId_1) (clientId_2) (clientId_3)
46
+ Playwright/Puppeteer Chrome Extension
47
+
48
+ ws://localhost:9221/client ws://localhost:9221/plugin
49
+ ▼ ▼
50
+ ┌─────────────────────────────────────────────┐
51
+ CDP Tunnel Proxy
52
+ │ (Node.js WebSocket) │
53
+ │ │
54
+ 9221 ── create mode (port pool #0)
55
+ │ 9220 ── takeover mode (attach user tabs) │
56
+ │ 9231+ ── port pool (each = isolated env)
57
+ │ /admin ── web management console │
58
+ └─────────────────────────────────────────────┘
59
+ │ │
60
+ ▼ ▼
61
+ chrome.debugger API Tab Group isolation
43
62
  ```
44
63
 
45
- ## Features
64
+ ## Quick Start
46
65
 
47
- - **Multi-client Support** - Multiple Playwright/Puppeteer clients can connect simultaneously
48
- - **Message Isolation** - Pages created by each client are owned by that client
49
- - **Configuration Page** - Visualize connection status, client list, and controlled pages
50
- - **Auto Reconnect** - Extension automatically reconnects to the server
66
+ ### 1. Install
51
67
 
52
- ## Screenshot
68
+ ```bash
69
+ npm install -g cdp-tunnel
70
+ cdp-tunnel setup # Start server + auto-load extension
71
+ ```
53
72
 
54
- ![Config Page](docs/config-page-screenshot.png)
73
+ Or run from source:
55
74
 
56
- ## Quick Start
75
+ ```bash
76
+ git clone https://github.com/dyyz1993/cdp-tunnel.git
77
+ cd cdp-tunnel
78
+ npm install
79
+ node server/proxy-server.js
80
+ ```
57
81
 
58
- ### Option 1: Install Extension Only (No npm needed)
82
+ ### 2. Load Extension
59
83
 
60
- Download and install the Chrome extension directly:
84
+ 1. Open `chrome://extensions/`
85
+ 2. Enable **Developer mode**
86
+ 3. Click **Load unpacked** → select `extension-new/` directory
87
+ 4. Extension connects to `ws://localhost:9221/plugin` automatically
61
88
 
62
- 1. Download [`cdp-tunnel-extension.zip`](https://github.com/dyyz1993/cdp-tunnel/releases/latest/download/cdp-tunnel-extension.zip) from the latest release
63
- 2. Unzip it
64
- 3. Open `chrome://extensions/` in Chrome
65
- 4. Enable **Developer mode** (top right)
66
- 5. Click **Load unpacked** → select the unzipped folder
67
- 6. Start the proxy server separately (see Option 2 or 3 below)
89
+ ### 3. Connect Your Automation
68
90
 
69
- ### Option 2: Install via npm (Recommended)
91
+ ```javascript
92
+ // Playwright — control your real browser!
93
+ const { chromium } = require('playwright');
94
+ const browser = await chromium.connectOverCDP('http://localhost:9221');
95
+ const page = await browser.contexts()[0].newPage();
96
+ await page.goto('https://www.google.com');
97
+ console.log(await page.title());
98
+ ```
70
99
 
71
- ```bash
72
- # One command to set up everything
73
- npx cdp-tunnel setup
100
+ ```python
101
+ # Python Playwright
102
+ from playwright.sync_api import sync_playwright
74
103
 
75
- # Or install globally
76
- npm install -g cdp-tunnel
77
- cdp-tunnel setup # Start server + auto-load extension into Chrome
78
- cdp-tunnel status # Check status
79
- cdp-tunnel diagnose # Diagnose connection issues
104
+ with sync_playwright() as p:
105
+ browser = p.chromium.connect_over_cdp("http://localhost:9221")
106
+ page = browser.contexts[0].new_page()
107
+ page.goto("https://www.google.com")
108
+ print(page.title())
80
109
  ```
81
110
 
82
- ### Option 3: Manual Installation
111
+ ## Key Features
112
+
113
+ ### Port Pool — Multiple Isolated Environments
83
114
 
84
- #### 1. Start the Proxy Server
115
+ Each port = one independent "browser" with its own Tab Group. Different ports can't see each other's tabs.
85
116
 
86
117
  ```bash
87
- npx cdp-tunnel start
88
- # or manually:
89
- npx cdp-tunnel start -p 9221
118
+ # Default ports
119
+ 9221 ── Main create port (port pool #0)
120
+ 9220 ── Takeover port (attach user's existing tabs)
121
+ 9231-9239 ── Port pool (9 isolated environments)
122
+ ```
123
+
124
+ ```javascript
125
+ // Each port is independent
126
+ const browserA = await chromium.connectOverCDP('http://localhost:9231');
127
+ const browserB = await chromium.connectOverCDP('http://localhost:9232');
128
+ // browserA and browserB have completely separate tabs
90
129
  ```
91
130
 
92
- #### 2. Install Chrome Extension
131
+ ### Takeover Mode Control Existing Tabs
93
132
 
94
- 1. Open `chrome://extensions/`
95
- 2. Enable "Developer mode"
96
- 3. Click "Load unpacked"
97
- 4. Select the `extension-new` directory
133
+ Connect to port `9220` to take over the user's already-open tabs (no new Tab Group created).
134
+
135
+ ```javascript
136
+ const browser = await chromium.connectOverCDP('http://localhost:9220');
137
+ // Now you can see and control all user's open tabs
138
+ ```
139
+
140
+ ### API Key Authentication (Remote Deployment)
98
141
 
99
- #### 3. Connect the Extension
142
+ Enable authentication for cloud/remote deployment:
100
143
 
101
- Click the extension icon, then click **打开完整配置** to open the configuration page. Add a connection: fill in Name (e.g., "local"), WebSocket URL (default `ws://localhost:9221/plugin`), and Mode (create/takeover). The CDP address will be displayed (e.g., `http://localhost:9221` for create mode, `http://localhost:9222` for takeover mode). Copy it and use it in Playwright: `chromium.connectOverCDP('http://localhost:9221')`.
144
+ ```bash
145
+ REQUIRE_AUTH=true node server/proxy-server.js
102
146
 
103
- ### 3. Client Connection
147
+ # Create an API key
148
+ node server/saas/key-manager.js create my-browser
149
+ # Output: ws://localhost:9221/plugin?key=cdp_xxxxx
150
+ ```
104
151
 
105
152
  ```javascript
106
- // Playwright
107
- const { chromium } = require('playwright');
153
+ // Client connects with key
154
+ const browser = await chromium.connectOverCDP(
155
+ 'ws://your-server.com:9221/client?key=cdp_xxxxx'
156
+ );
157
+ ```
108
158
 
109
- const browser = await chromium.connectOverCDP('http://localhost:9221');
110
- const context = browser.contexts()[0];
111
- const page = await context.newPage();
112
- await page.goto('https://example.com');
159
+ **One key = one browser = one isolated Tab Group.** Different keys are completely isolated.
160
+
161
+ ### Admin Console
113
162
 
114
- // Puppeteer
115
- const puppeteer = require('puppeteer');
163
+ Built-in web UI at `/admin`:
116
164
 
117
- const browser = await puppeteer.connect({
118
- browserWSEndpoint: 'ws://localhost:9221'
119
- });
120
- const page = await browser.newPage();
121
- await page.goto('https://example.com');
165
+ ```
166
+ http://localhost:9221/admin
122
167
  ```
123
168
 
124
- ### 4. Using with agent-browser
169
+ Features:
170
+ - 📱 Online browser list (real-time)
171
+ - 🔑 API Key management (create/revoke + copy address)
172
+ - 🔧 Tab management (list/close/switch)
173
+ - ⚡ CDP operations (evaluate JS, screenshot, new tab)
174
+ - 🎬 One-click demo
125
175
 
126
- [agent-browser](https://github.com/nick1udwig/agent-browser) is a browser automation CLI that can connect to CDP Tunnel:
176
+ ### Version Check
127
177
 
128
178
  ```bash
129
- # Install agent-browser
130
- npm install -g agent-browser
131
-
132
- # Connect to CDP Tunnel
133
- agent-browser --cdp http://localhost:9221 open https://example.com
179
+ STRICT_VERSION=true node server/proxy-server.js
180
+ # Extension version must match proxy version, otherwise connection rejected
181
+ ```
134
182
 
135
- # Take a snapshot
136
- agent-browser --cdp http://localhost:9221 snapshot -i
183
+ ## Remote / Cloud Deployment
137
184
 
138
- # Interact with elements
139
- agent-browser --cdp http://localhost:9221 click @e1
140
- agent-browser --cdp http://localhost:9221 fill @e2 "hello"
185
+ CDP Tunnel can be deployed to a VPS for remote browser control:
141
186
 
142
- # Take screenshot
143
- agent-browser --cdp http://localhost:9221 screenshot output.png
187
+ ```
188
+ Remote Client ──wss──▶ Cloud Proxy ──wss──▶ User's Chrome + Extension
144
189
  ```
145
190
 
146
- This allows you to control the browser through CDP Tunnel using simple CLI commands.
191
+ See [`docs/DEPLOY-CLOUDFLARE.md`](docs/DEPLOY-CLOUDFLARE.md) for Cloudflare Tunnel setup.
147
192
 
148
- ## Multi-client Usage
193
+ Key environment variables:
149
194
 
150
- All clients connect to the same endpoint `http://localhost:9221`. The server automatically assigns a unique `clientId` to each connection.
195
+ | Variable | Default | Description |
196
+ |---|---|---|
197
+ | `PORT` | 9221 | Main create port |
198
+ | `TAKEOVER_PORT` | 9220 | Takeover port |
199
+ | `POOL_START` | 9231 | Port pool start |
200
+ | `POOL_SIZE` | 9 | Number of port pool ports |
201
+ | `REQUIRE_AUTH` | false | Require API key |
202
+ | `STRICT_VERSION` | false | Reject version mismatch |
203
+ | `ADMIN_TOKEN` | (none) | Admin console auth token |
151
204
 
152
- ```javascript
153
- // Multiple clients can connect simultaneously
154
- const browser1 = await chromium.connectOverCDP('http://localhost:9221');
155
- const browser2 = await chromium.connectOverCDP('http://localhost:9221');
156
- const browser3 = await chromium.connectOverCDP('http://localhost:9221');
205
+ ## CLI Commands
157
206
 
158
- // Pages created by each client are independent
159
- const page1 = await browser1.contexts()[0].newPage();
160
- const page2 = await browser2.contexts()[0].newPage();
161
- const page3 = await browser3.contexts()[0].newPage();
207
+ ```bash
208
+ cdp-tunnel setup # Start server + load extension
209
+ cdp-tunnel start # Start server only
210
+ cdp-tunnel stop # Stop server
211
+ cdp-tunnel status # Check status
212
+ cdp-tunnel diagnose # Diagnose connection
213
+ cdp-tunnel extension # Open extension installation guide
162
214
  ```
163
215
 
164
- ## Configuration Page
216
+ ## API Key Management
165
217
 
166
- Click the extension icon to open the configuration page, where you can view:
167
-
168
- - **CDP Client List** - Shows connected Playwright/Puppeteer clients
169
- - **Controlled Pages List** - Shows controlled pages with click-to-navigate support
170
- - **Activity Log** - Connection status change records
218
+ ```bash
219
+ # Create a key (returns connection address)
220
+ node server/saas/key-manager.js create "张三的浏览器"
171
221
 
172
- ## Project Structure
222
+ # List all keys
223
+ node server/saas/key-manager.js list
173
224
 
174
- ```
175
- cdp-tunnel/
176
- ├── server/
177
- │ └── proxy-server.js # Proxy server
178
- ├── extension-new/
179
- │ ├── background.js # Extension Service Worker
180
- │ ├── config-page-preview.html # Configuration page
181
- │ ├── config-page.js # Configuration page script
182
- │ ├── core/
183
- │ │ ├── state.js # State management
184
- │ │ └── websocket.js # WebSocket connection management
185
- │ └── features/
186
- │ ├── cdp-router.js # CDP message routing
187
- │ └── screencast.js # Screenshot functionality
188
- └── tests/
189
- ├── playwright-single.js # Single client test
190
- ├── playwright-multi.js # Multi-client test
191
- └── playwright-interactive.js # Interactive test
225
+ # Revoke a key
226
+ node server/saas/key-manager.js revoke <keyId>
192
227
  ```
193
228
 
194
229
  ## Testing
195
230
 
196
231
  ```bash
197
- # Single client test
198
- node tests/playwright-single.js
232
+ # Smoke tests (runs before every commit via husky pre-commit)
233
+ node tests/e2e/run-all.js
234
+
235
+ # A/B Gate (compare with direct Chrome CDP)
236
+ node tests/e2e/test-ab-gate.js
199
237
 
200
- # Multi-client test
201
- node tests/playwright-multi.js
238
+ # Key isolation test
239
+ node tests/e2e/test-key-isolation.js
202
240
 
203
- # Interactive test
204
- node tests/playwright-interactive.js
241
+ # Admin console test
242
+ node tests/e2e/test-admin-console.js
243
+
244
+ # Production deployment verification
245
+ PROD_WSS=wss://your-server:port PROD_KEY=cdp_xxx node tests/e2e/test-prod-deploy.js
205
246
  ```
206
247
 
207
- ## Notes
248
+ ## How It Works
208
249
 
209
- 1. **Port Availability** - Ensure port 9221 is not in use
210
- 2. **Extension Permissions** - The extension requires `debugger`, `tabs`, and other permissions
211
- 3. **Browser Limitation** - Only one extension can control a browser via debugger at a time
250
+ CDP Tunnel uses Chrome's `chrome.debugger` API (available to extensions) to bridge CDP commands:
212
251
 
213
- ## License
252
+ ```
253
+ Playwright command (e.g., Page.navigate)
254
+ → CDP Tunnel Proxy (WebSocket)
255
+ → Chrome Extension (chrome.debugger.attach)
256
+ → Chrome renders the page
257
+ → Events flow back: Chrome → Extension → Proxy → Playwright
258
+ ```
259
+
260
+ The extension acts as the bridge — it receives CDP commands via WebSocket and executes them via `chrome.debugger`. This means:
261
+ - Your browser keeps all logins, cookies, extensions
262
+ - No need to restart Chrome or use `--remote-debugging-port`
263
+ - Works with regular Chrome (not just Chromium)
264
+
265
+ **Synthetic input handling:** Chrome throttles synthetic events (keyboard/mouse) on non-active tabs. CDP Tunnel automatically calls `Page.bringToFront` before synthetic input commands, making it transparent to Playwright/Puppeteer.
214
266
 
215
- This project is licensed under the Apache License 2.0 with Attribution Requirement.
267
+ ## Project Structure
268
+
269
+ ```
270
+ cdp-tunnel/
271
+ ├── server/
272
+ │ ├── proxy-server.js # Main proxy server
273
+ │ ├── modules/
274
+ │ │ ├── config.js # Port configuration
275
+ │ │ └── port-pool.js # Port pool manager (v3.0+)
276
+ │ ├── saas/
277
+ │ │ ├── auth.js # API Key + JWT authentication
278
+ │ │ ├── key-manager.js # Key management CLI
279
+ │ │ └── db.js # SQLite database
280
+ │ └── admin-console.html # Admin web UI
281
+ ├── extension-new/ # Chrome Extension (Manifest V3)
282
+ │ ├── background.js # Service Worker
283
+ │ ├── core/
284
+ │ │ ├── websocket.js # WebSocket connection
285
+ │ │ ├── connection-state.js # State management
286
+ │ │ └── connection-manager.js# Multi-connection
287
+ │ ├── cdp/
288
+ │ │ ├── handler/
289
+ │ │ │ ├── special.js # Tab grouping logic
290
+ │ │ │ └── forward.js # CDP command forwarding
291
+ │ │ └── response.js # CDP response builder
292
+ │ └── utils/
293
+ │ ├── config.js # Extension config
294
+ │ └── helpers.js # Group naming/color
295
+ ├── cli/ # CLI tool
296
+ ├── tests/e2e/ # E2E tests
297
+ └── docs/ # Documentation
298
+ ```
299
+
300
+ ## Changelog
301
+
302
+ See [CHANGELOG.md](CHANGELOG.md) for version history.
303
+
304
+ ## License
216
305
 
217
- See [LICENSE](LICENSE) for details.
306
+ Apache License 2.0 with Attribution Requirement. See [LICENSE](LICENSE).
218
307
 
219
308
  ---
220
309
 
221
- If you use this project in your work, please include attribution:
310
+ If you use this project, please include attribution:
222
311
  - Project: CDP Tunnel
223
312
  - Author: dyyz1993
224
313
  - Source: https://github.com/dyyz1993/cdp-tunnel
@@ -76,7 +76,6 @@ importScripts('features/automation-badge.js');
76
76
  var primary = ConnectionManager.getPrimaryConnection();
77
77
  if (primary && result.currentTabId != null) {
78
78
  primary.state.currentTabId = result.currentTabId;
79
- primary.state.isAttached = result.isAttached;
80
79
  }
81
80
 
82
81
  ConnectionManager.connectAll();
@@ -196,9 +195,14 @@ importScripts('features/automation-badge.js');
196
195
  }
197
196
 
198
197
  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
199
- if (changeInfo.status === 'complete') {
198
+ if (changeInfo.status === 'complete' || changeInfo.title) {
200
199
  var entry = ConnectionManager.getConnectionByTabId(tabId);
201
200
  if (entry && entry.state.isTabAttached(tabId)) {
201
+ LocalHandler.getTargetInfoById(String(tabId)).then(function(targetInfo) {
202
+ if (targetInfo) {
203
+ EventBuilder.send('Target.targetInfoChanged', { targetInfo: targetInfo }, null, entry.wsManager);
204
+ }
205
+ });
202
206
  }
203
207
  }
204
208
 
@@ -29,7 +29,7 @@ var ForwardHandler = (function() {
29
29
 
30
30
  Logger.debug('[Forward]', method, '-> tabId:', tabId);
31
31
 
32
- // 合成输入事件需要页面 visible:先 ensureVisible 再发命令
32
+ // 合成输入事件 + 带 clip 的元素截图需要页面 visible:先 ensureVisible 再发命令
33
33
  if (SYNTHETIC_INPUT_METHODS.indexOf(method) >= 0) {
34
34
  return ensureVisible(tabId).then(function() {
35
35
  return chrome.debugger.sendCommand({ tabId: tabId }, method, params);
@@ -38,6 +38,15 @@ var ForwardHandler = (function() {
38
38
  });
39
39
  }
40
40
 
41
+ // 元素级截图(带 clip)在 hidden 状态下可能返回空数据,需要 ensureVisible 确保渲染完成
42
+ if (method === 'Page.captureScreenshot' && params && params.clip) {
43
+ return ensureVisible(tabId).then(function() {
44
+ return chrome.debugger.sendCommand({ tabId: tabId }, method, params);
45
+ }).then(function(result) {
46
+ return result || {};
47
+ });
48
+ }
49
+
41
50
  return chrome.debugger.sendCommand({ tabId: tabId }, method, params).then(function(result) {
42
51
  return result || {};
43
52
  });
@@ -5,6 +5,12 @@ var LocalHandler = (function() {
5
5
 
6
6
  function _getConnectionTag(ctx) {
7
7
  var wm = ctx._wsManager;
8
+ var state = ctx._state;
9
+ var clientId = ctx.clientId;
10
+ if (state && clientId && state.getTagForClient) {
11
+ var tag = state.getTagForClient(clientId);
12
+ if (tag) return tag;
13
+ }
8
14
  return (wm && wm.config && wm.config.tag) || null;
9
15
  }
10
16
 
@@ -32,11 +38,29 @@ var LocalHandler = (function() {
32
38
  };
33
39
  }
34
40
 
35
- function getWindowBounds() {
36
- return {
37
- bounds: { left: 0, top: 0, width: 1920, height: 1080, windowState: 'normal' }
38
- };
39
- }
41
+ function getWindowBounds() {
42
+ return {
43
+ bounds: { left: 0, top: 0, width: 1920, height: 1080, windowState: 'normal' }
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Browser.setWindowBounds — 设置窗口位置/大小,支持 focused。
49
+ * 当 bounds.focused = true 时,用 Chrome 扩展 API 将窗口弹到前台。
50
+ * 这是跨平台最可靠的方式(替代 CDP 原生 setWindowBounds 在 macOS 上的限制)。
51
+ */
52
+ function setWindowBounds(context) {
53
+ var params = context ? context.params : null;
54
+ var bounds = params ? params.bounds : null;
55
+ if (bounds && bounds.focused) {
56
+ chrome.windows.getCurrent(function(w) {
57
+ if (w && w.id) {
58
+ chrome.windows.update(w.id, { focused: true });
59
+ }
60
+ });
61
+ }
62
+ return {};
63
+ }
40
64
 
41
65
  function targetSetDiscoverTargets(context) {
42
66
  var state = _getState(context);
@@ -517,7 +541,8 @@ var LocalHandler = (function() {
517
541
  browserGetVersion: browserGetVersion,
518
542
  browserClose: browserClose,
519
543
  getWindowForTarget: getWindowForTarget,
520
- getWindowBounds: getWindowBounds,
544
+ getWindowBounds: getWindowBounds,
545
+ setWindowBounds: setWindowBounds,
521
546
  targetSetDiscoverTargets: targetSetDiscoverTargets,
522
547
  targetGetTargets: targetGetTargets,
523
548
  targetGetTargetInfo: targetGetTargetInfo,
@@ -20,6 +20,15 @@ var SpecialHandler = (function() {
20
20
  return (wm && wm.config && wm.config.tag) || null;
21
21
  }
22
22
 
23
+ // 从 state 读取 key 名称(用作分组名),doGroup 等分组操作调用
24
+ function _getGroupName(clientId, context) {
25
+ var state = context ? context._state : null;
26
+ if (state && clientId && state.getGroupNameForClient) {
27
+ return state.getGroupNameForClient(clientId);
28
+ }
29
+ return null;
30
+ }
31
+
23
32
  function muteTabIfNeeded(tabId) {
24
33
  Config.getAutoMute(function(enabled) {
25
34
  if (!enabled) return;
@@ -297,7 +306,7 @@ var SpecialHandler = (function() {
297
306
  return;
298
307
  }
299
308
  }
300
- var baseName = CDPUtils.getGroupBaseName(groupClientId, _getConnectionTag(context), context ? context.mode : null);
309
+ var baseName = CDPUtils.getGroupBaseName(groupClientId, _getConnectionTag(context), context ? context.mode : null, _getGroupName(groupClientId, context));
301
310
 
302
311
  Logger.info('[TabGroup] Grouping tab immediately for:', baseName);
303
312
  doGroup(tabId, groupClientId, baseName, 0, callback, context);
@@ -465,7 +474,8 @@ var SpecialHandler = (function() {
465
474
  chrome.tabs.query({ groupId: groupId }, function(tabs) {
466
475
  if (chrome.runtime.lastError || !tabs) return;
467
476
 
468
- var baseName = CDPUtils.getGroupBaseName(clientId, connectionTag, mode);
477
+ var groupName = (state && state.getGroupNameForClient) ? state.getGroupNameForClient(clientId) : null;
478
+ var baseName = CDPUtils.getGroupBaseName(clientId, connectionTag, mode, groupName);
469
479
  var newName = baseName + ' (' + tabs.length + ')';
470
480
 
471
481
  if (chrome.runtime.lastError || tabs.length === 0) return;
@@ -775,14 +785,6 @@ function checkTabVisibility(tabId) {
775
785
  });
776
786
  }
777
787
 
778
- function pageCreateIsolatedWorld(context) {
779
- return ForwardHandler.execute(context);
780
- }
781
-
782
- function pageAddScriptToEvaluateOnNewDocument(context) {
783
- return ForwardHandler.execute(context);
784
- }
785
-
786
788
  function domSetFileInputFiles(context) {
787
789
  var params = context.params;
788
790
  var sessionId = context.sessionId;
@@ -889,8 +891,6 @@ function checkTabVisibility(tabId) {
889
891
  pageStartScreencast: pageStartScreencast,
890
892
  pageStopScreencast: pageStopScreencast,
891
893
  pageScreencastFrameAck: pageScreencastFrameAck,
892
- pageCreateIsolatedWorld: pageCreateIsolatedWorld,
893
- pageAddScriptToEvaluateOnNewDocument: pageAddScriptToEvaluateOnNewDocument,
894
894
  runtimeRunIfWaitingForDebugger: runtimeRunIfWaitingForDebugger,
895
895
  domSetFileInputFiles: domSetFileInputFiles,
896
896
  updateTabGroupName: updateTabGroupName,
@@ -5,7 +5,7 @@ var CDP_HANDLERS = {
5
5
  'Browser.crash': { type: 'LOCAL', handler: LocalHandler.emptyResult },
6
6
  'Browser.crashGpuProcess': { type: 'LOCAL', handler: LocalHandler.emptyResult },
7
7
  'Browser.getWindowForTarget': { type: 'LOCAL', handler: LocalHandler.getWindowForTarget },
8
- 'Browser.setWindowBounds': { type: 'LOCAL', handler: LocalHandler.emptyResult },
8
+ 'Browser.setWindowBounds': { type: 'LOCAL', handler: LocalHandler.setWindowBounds },
9
9
  'Browser.getWindowBounds': { type: 'LOCAL', handler: LocalHandler.getWindowBounds },
10
10
  'Browser.getBrowserCommandLine': { type: 'LOCAL', handler: LocalHandler.emptyArray },
11
11
  'Browser.getHistograms': { type: 'LOCAL', handler: LocalHandler.emptyArray },
@@ -50,8 +50,6 @@ var CDP_HANDLERS = {
50
50
  'Page.startScreencast': { type: 'SPECIAL', handler: SpecialHandler.pageStartScreencast },
51
51
  'Page.stopScreencast': { type: 'SPECIAL', handler: SpecialHandler.pageStopScreencast },
52
52
  'Page.screencastFrameAck': { type: 'SPECIAL', handler: SpecialHandler.pageScreencastFrameAck },
53
- 'Page.createIsolatedWorld': { type: 'FORWARD', handler: SpecialHandler.pageCreateIsolatedWorld },
54
- 'Page.addScriptToEvaluateOnNewDocument': { type: 'FORWARD', handler: SpecialHandler.pageAddScriptToEvaluateOnNewDocument },
55
53
 
56
54
  'Runtime.runIfWaitingForDebugger': { type: 'SPECIAL', handler: SpecialHandler.runtimeRunIfWaitingForDebugger },
57
55