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 +222 -133
- package/extension-new/background.js +6 -2
- package/extension-new/cdp/handler/forward.js +10 -1
- package/extension-new/cdp/handler/local.js +31 -6
- package/extension-new/cdp/handler/special.js +12 -12
- package/extension-new/cdp/index.js +1 -3
- package/extension-new/core/connection-state.js +9 -13
- package/extension-new/core/debugger.js +0 -5
- package/extension-new/core/state.js +0 -8
- package/extension-new/core/websocket.js +8 -18
- package/extension-new/manifest.json +1 -1
- package/extension-new/utils/config.js +1 -1
- package/extension-new/utils/helpers.js +9 -3
- package/package.json +1 -1
- package/server/admin-console.html +481 -0
- package/server/modules/port-pool.js +48 -6
- package/server/proxy-server.js +269 -99
package/README.md
CHANGED
|
@@ -5,220 +5,309 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>Chrome
|
|
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
|
-
|
|
13
|
-
|
|
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://
|
|
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
|
-
│
|
|
34
|
-
│
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
│
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
##
|
|
64
|
+
## Quick Start
|
|
46
65
|
|
|
47
|
-
|
|
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
|
-
|
|
68
|
+
```bash
|
|
69
|
+
npm install -g cdp-tunnel
|
|
70
|
+
cdp-tunnel setup # Start server + auto-load extension
|
|
71
|
+
```
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
Or run from source:
|
|
55
74
|
|
|
56
|
-
|
|
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
|
-
###
|
|
82
|
+
### 2. Load Extension
|
|
59
83
|
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
72
|
-
#
|
|
73
|
-
|
|
100
|
+
```python
|
|
101
|
+
# Python Playwright
|
|
102
|
+
from playwright.sync_api import sync_playwright
|
|
74
103
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
111
|
+
## Key Features
|
|
112
|
+
|
|
113
|
+
### Port Pool — Multiple Isolated Environments
|
|
83
114
|
|
|
84
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
131
|
+
### Takeover Mode — Control Existing Tabs
|
|
93
132
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
142
|
+
Enable authentication for cloud/remote deployment:
|
|
100
143
|
|
|
101
|
-
|
|
144
|
+
```bash
|
|
145
|
+
REQUIRE_AUTH=true node server/proxy-server.js
|
|
102
146
|
|
|
103
|
-
|
|
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
|
-
//
|
|
107
|
-
const
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
const puppeteer = require('puppeteer');
|
|
163
|
+
Built-in web UI at `/admin`:
|
|
116
164
|
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
+
### Version Check
|
|
127
177
|
|
|
128
178
|
```bash
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
136
|
-
agent-browser --cdp http://localhost:9221 snapshot -i
|
|
183
|
+
## Remote / Cloud Deployment
|
|
137
184
|
|
|
138
|
-
|
|
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
|
-
|
|
143
|
-
|
|
187
|
+
```
|
|
188
|
+
Remote Client ──wss──▶ Cloud Proxy ──wss──▶ User's Chrome + Extension
|
|
144
189
|
```
|
|
145
190
|
|
|
146
|
-
|
|
191
|
+
See [`docs/DEPLOY-CLOUDFLARE.md`](docs/DEPLOY-CLOUDFLARE.md) for Cloudflare Tunnel setup.
|
|
147
192
|
|
|
148
|
-
|
|
193
|
+
Key environment variables:
|
|
149
194
|
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
##
|
|
216
|
+
## API Key Management
|
|
165
217
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
222
|
+
# List all keys
|
|
223
|
+
node server/saas/key-manager.js list
|
|
173
224
|
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
#
|
|
198
|
-
node tests/
|
|
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
|
-
#
|
|
201
|
-
node tests/
|
|
238
|
+
# Key isolation test
|
|
239
|
+
node tests/e2e/test-key-isolation.js
|
|
202
240
|
|
|
203
|
-
#
|
|
204
|
-
node tests/
|
|
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
|
-
##
|
|
248
|
+
## How It Works
|
|
208
249
|
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
306
|
+
Apache License 2.0 with Attribution Requirement. See [LICENSE](LICENSE).
|
|
218
307
|
|
|
219
308
|
---
|
|
220
309
|
|
|
221
|
-
If you use this project
|
|
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
|
-
//
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|