cdp-tunnel 1.0.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.
Files changed (46) hide show
  1. package/.github/workflows/publish.yml +92 -0
  2. package/.github/workflows/release-assets.yml +50 -0
  3. package/LICENSE +81 -0
  4. package/PUBLISH.md +65 -0
  5. package/README.md +228 -0
  6. package/cli/guide.html +753 -0
  7. package/cli/icon.svg +13 -0
  8. package/cli/icon128.png +0 -0
  9. package/cli/index.js +357 -0
  10. package/docs/README_CN.md +204 -0
  11. package/docs/config-page-screenshot.png +0 -0
  12. package/extension-new/background.js +294 -0
  13. package/extension-new/cdp/handler/forward.js +44 -0
  14. package/extension-new/cdp/handler/local.js +233 -0
  15. package/extension-new/cdp/handler/special.js +442 -0
  16. package/extension-new/cdp/index.js +104 -0
  17. package/extension-new/cdp/response.js +49 -0
  18. package/extension-new/config-page-preview.html +769 -0
  19. package/extension-new/config-page.js +318 -0
  20. package/extension-new/core/debugger.js +310 -0
  21. package/extension-new/core/state.js +384 -0
  22. package/extension-new/core/websocket.js +326 -0
  23. package/extension-new/features/automation-badge.js +113 -0
  24. package/extension-new/features/screencast.js +221 -0
  25. package/extension-new/icons/icon128.png +0 -0
  26. package/extension-new/icons/icon16.png +0 -0
  27. package/extension-new/icons/icon48.png +0 -0
  28. package/extension-new/manifest.json +39 -0
  29. package/extension-new/popup.html +72 -0
  30. package/extension-new/popup.js +34 -0
  31. package/extension-new/utils/config.js +20 -0
  32. package/extension-new/utils/diagnostics.js +560 -0
  33. package/extension-new/utils/helpers.js +25 -0
  34. package/extension-new/utils/logger.js +64 -0
  35. package/package.json +42 -0
  36. package/server/modules/config.js +28 -0
  37. package/server/modules/logger.js +197 -0
  38. package/server/proxy-server.js +1431 -0
  39. package/tests/playwright-demo.js +45 -0
  40. package/tests/playwright-interactive.js +261 -0
  41. package/tests/playwright-multi-demo.js +60 -0
  42. package/tests/playwright-multi.js +85 -0
  43. package/tests/playwright-single.js +41 -0
  44. package/tests/screenshot-config.js +35 -0
  45. package/tests/test-client.js +89 -0
  46. package/tests/test-multi-client.js +129 -0
@@ -0,0 +1,318 @@
1
+ (function() {
2
+ var state = {
3
+ connected: false,
4
+ serverAddress: 'ws://localhost:9221/plugin',
5
+ cdpClients: [],
6
+ attachedPages: [],
7
+ logs: [],
8
+ startTime: Date.now(),
9
+ cdpCommandCount: 0
10
+ };
11
+
12
+ var elements = {
13
+ statusBadge: document.getElementById('statusBadge'),
14
+ statusText: document.getElementById('statusText'),
15
+ activeConnections: document.getElementById('activeConnections'),
16
+ controlledPages: document.getElementById('controlledPages'),
17
+ cdpCommands: document.getElementById('cdpCommands'),
18
+ uptime: document.getElementById('uptime'),
19
+ clientCount: document.getElementById('clientCount'),
20
+ pageCount: document.getElementById('pageCount'),
21
+ clientList: document.getElementById('clientList'),
22
+ pageList: document.getElementById('pageList'),
23
+ activityLog: document.getElementById('activityLog'),
24
+ serverAddress: document.getElementById('serverAddress'),
25
+ connectBtn: document.getElementById('connectBtn'),
26
+ refreshBtn: document.getElementById('refreshBtn'),
27
+ toast: document.getElementById('toast')
28
+ };
29
+
30
+ function showToast(message, type) {
31
+ type = type || 'success';
32
+ var icon = type === 'success' ? '✓' : type === 'error' ? '✕' : 'ℹ';
33
+ elements.toast.querySelector('.icon').textContent = icon;
34
+ elements.toast.querySelector('.message').textContent = message;
35
+ elements.toast.className = 'toast show ' + type;
36
+ setTimeout(function() {
37
+ elements.toast.className = 'toast';
38
+ }, 2000);
39
+ }
40
+
41
+ function updateStatus(connected) {
42
+ state.connected = connected;
43
+ if (connected) {
44
+ elements.statusBadge.classList.remove('disconnected');
45
+ elements.statusText.textContent = '已连接';
46
+ } else {
47
+ elements.statusBadge.classList.add('disconnected');
48
+ elements.statusText.textContent = '未连接';
49
+ }
50
+ }
51
+
52
+ function updateStats() {
53
+ elements.activeConnections.textContent = state.cdpClients.length;
54
+ elements.controlledPages.textContent = state.attachedPages.length;
55
+ elements.cdpCommands.textContent = state.cdpCommandCount;
56
+
57
+ var elapsed = Math.floor((Date.now() - state.startTime) / 1000);
58
+ if (elapsed < 60) {
59
+ elements.uptime.textContent = elapsed + 's';
60
+ } else if (elapsed < 3600) {
61
+ elements.uptime.textContent = Math.floor(elapsed / 60) + 'm';
62
+ } else {
63
+ elements.uptime.textContent = Math.floor(elapsed / 3600) + 'h ' + Math.floor((elapsed % 3600) / 60) + 'm';
64
+ }
65
+
66
+ elements.clientCount.textContent = state.cdpClients.length + ' 个';
67
+ elements.pageCount.textContent = state.attachedPages.length + ' 个';
68
+ }
69
+
70
+ function renderClients() {
71
+ if (state.cdpClients.length === 0) {
72
+ elements.clientList.innerHTML =
73
+ '<div class="empty-state">' +
74
+ '<div class="icon">🔌</div>' +
75
+ '<div class="title">暂无客户端</div>' +
76
+ '<div class="desc">等待 Playwright/Puppeteer 连接...</div>' +
77
+ '</div>';
78
+ return;
79
+ }
80
+
81
+ var html = '';
82
+ state.cdpClients.forEach(function(client) {
83
+ var clientId = client.id || '';
84
+ var shortId = clientId.length > 25 ? clientId.substring(0, 25) + '...' : clientId;
85
+ var connectedAt = client.connectedAt ? new Date(client.connectedAt).toLocaleTimeString() : 'N/A';
86
+
87
+ html +=
88
+ '<div class="connection-item active">' +
89
+ '<div class="status-indicator online"></div>' +
90
+ '<div class="connection-info">' +
91
+ '<div class="connection-header">' +
92
+ '<span class="connection-name" title="' + clientId + '">' + shortId + '</span>' +
93
+ '<span class="connection-tag playwright">CDP</span>' +
94
+ '</div>' +
95
+ '<div class="connection-details">' +
96
+ '<span>🔗 Playwright/Puppeteer</span>' +
97
+ '<span>⏰ ' + connectedAt + '</span>' +
98
+ '</div>' +
99
+ '</div>' +
100
+ '</div>';
101
+ });
102
+ elements.clientList.innerHTML = html;
103
+ }
104
+
105
+ function renderPages() {
106
+ if (state.attachedPages.length === 0) {
107
+ elements.pageList.innerHTML =
108
+ '<div class="empty-state">' +
109
+ '<div class="icon">📄</div>' +
110
+ '<div class="title">暂无页面</div>' +
111
+ '<div class="desc">客户端创建页面后会显示在这里</div>' +
112
+ '</div>';
113
+ return;
114
+ }
115
+
116
+ var html = '';
117
+ state.attachedPages.forEach(function(page) {
118
+ var pageUrl = page.url || '';
119
+ var displayUrl = pageUrl.length > 35 ? pageUrl.substring(0, 35) + '...' : pageUrl;
120
+ var pageTitle = page.title || 'Untitled';
121
+
122
+ html +=
123
+ '<div class="connection-item active">' +
124
+ '<div class="status-indicator online"></div>' +
125
+ '<div class="connection-info">' +
126
+ '<div class="connection-header">' +
127
+ '<span class="connection-name" title="' + pageTitle + '">' + pageTitle + '</span>' +
128
+ '<span class="connection-tag cdp">Tab #' + page.tabId + '</span>' +
129
+ '</div>' +
130
+ '<div class="connection-details">' +
131
+ '<span title="' + pageUrl + '">' + displayUrl + '</span>' +
132
+ '</div>' +
133
+ '</div>' +
134
+ '<div class="connection-actions">' +
135
+ '<button class="action-btn" title="切换到此标签页" data-tabid="' + page.tabId + '">👁️</button>' +
136
+ '</div>' +
137
+ '</div>';
138
+ });
139
+ elements.pageList.innerHTML = html;
140
+ }
141
+
142
+ function renderLogs() {
143
+ if (state.logs.length === 0) {
144
+ elements.activityLog.innerHTML =
145
+ '<div class="empty-state">' +
146
+ '<div class="icon">📋</div>' +
147
+ '<div class="title">暂无日志</div>' +
148
+ '<div class="desc">活动将显示在这里</div>' +
149
+ '</div>';
150
+ return;
151
+ }
152
+
153
+ var html = '';
154
+ state.logs.slice(0, 20).forEach(function(log) {
155
+ html +=
156
+ '<div class="log-item">' +
157
+ '<div class="log-icon ' + log.type + '">' + log.icon + '</div>' +
158
+ '<div class="log-content">' +
159
+ '<div class="log-message">' + log.message + '</div>' +
160
+ '<div class="log-time">' + log.time + '</div>' +
161
+ '</div>' +
162
+ '</div>';
163
+ });
164
+ elements.activityLog.innerHTML = html;
165
+ }
166
+
167
+ function addLog(type, message) {
168
+ var icons = {
169
+ connect: '✓',
170
+ disconnect: '✕',
171
+ action: '⚡',
172
+ page: '📄'
173
+ };
174
+
175
+ state.logs.unshift({
176
+ type: type,
177
+ icon: icons[type] || '•',
178
+ message: message,
179
+ time: new Date().toLocaleTimeString()
180
+ });
181
+
182
+ renderLogs();
183
+ }
184
+
185
+ function fetchState() {
186
+ return new Promise(function(resolve) {
187
+ try {
188
+ if (typeof chrome !== 'undefined' && chrome.runtime) {
189
+ chrome.runtime.sendMessage({ type: 'getState' }, function(response) {
190
+ if (response) {
191
+ state.connected = response.connected || false;
192
+ state.serverAddress = response.serverAddress || state.serverAddress;
193
+ state.cdpClients = response.cdpClients || [];
194
+ state.attachedPages = response.attachedPages || [];
195
+ elements.serverAddress.value = state.serverAddress;
196
+ updateStatus(state.connected);
197
+ updateStats();
198
+ renderClients();
199
+ renderPages();
200
+ resolve(true);
201
+ return;
202
+ }
203
+ resolve(false);
204
+ });
205
+ return;
206
+ }
207
+ } catch (e) {
208
+ console.error('[Config] Failed to fetch state:', e);
209
+ }
210
+
211
+ updateStatus(false);
212
+ updateStats();
213
+ renderClients();
214
+ renderPages();
215
+ resolve(false);
216
+ });
217
+ }
218
+
219
+ function saveAndConnect() {
220
+ var address = elements.serverAddress.value.trim();
221
+
222
+ if (!address) {
223
+ showToast('请输入服务器地址', 'error');
224
+ return;
225
+ }
226
+
227
+ state.serverAddress = address;
228
+
229
+ if (typeof chrome !== 'undefined' && chrome.storage) {
230
+ chrome.storage.local.set({ wsAddress: address });
231
+ }
232
+
233
+ if (typeof chrome !== 'undefined' && chrome.runtime) {
234
+ try {
235
+ chrome.runtime.sendMessage({
236
+ type: 'connect',
237
+ serverAddress: address
238
+ });
239
+ showToast('正在连接...');
240
+ } catch (e) {
241
+ showToast('连接请求发送失败', 'error');
242
+ }
243
+ } else {
244
+ showToast('请在扩展环境中使用', 'error');
245
+ }
246
+ }
247
+
248
+ function refresh() {
249
+ fetchState();
250
+ showToast('已刷新');
251
+ }
252
+
253
+ function init() {
254
+ if (typeof chrome !== 'undefined' && chrome.storage) {
255
+ chrome.storage.local.get('wsAddress', function(result) {
256
+ if (result.wsAddress) {
257
+ state.serverAddress = result.wsAddress;
258
+ elements.serverAddress.value = result.wsAddress;
259
+ }
260
+ });
261
+ }
262
+
263
+ fetchState();
264
+
265
+ setInterval(fetchState, 5000);
266
+ setInterval(updateStats, 1000);
267
+ }
268
+
269
+ elements.connectBtn.addEventListener('click', saveAndConnect);
270
+ elements.refreshBtn.addEventListener('click', refresh);
271
+
272
+ elements.pageList.addEventListener('click', function(e) {
273
+ var btn = e.target.closest('.action-btn');
274
+ if (btn) {
275
+ var tabId = parseInt(btn.dataset.tabid);
276
+ if (tabId) {
277
+ chrome.tabs.update(tabId, { active: true });
278
+ chrome.tabs.get(tabId, function(tab) {
279
+ if (tab && tab.windowId) {
280
+ chrome.windows.update(tab.windowId, { focused: true });
281
+ }
282
+ });
283
+ }
284
+ }
285
+ });
286
+
287
+ if (typeof chrome !== 'undefined' && chrome.runtime) {
288
+ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
289
+ if (message.type === 'stateUpdate') {
290
+ var wasConnected = state.connected;
291
+ state.connected = message.connected;
292
+ state.cdpClients = message.cdpClients || [];
293
+ state.attachedPages = message.attachedPages || [];
294
+ updateStatus(state.connected);
295
+ updateStats();
296
+ renderClients();
297
+ renderPages();
298
+
299
+ if (state.connected && !wasConnected) {
300
+ addLog('connect', '已连接到服务器');
301
+ } else if (!state.connected && wasConnected) {
302
+ addLog('disconnect', '已断开连接');
303
+ }
304
+
305
+ if (state.cdpClients.length > 0) {
306
+ addLog('connect', state.cdpClients.length + ' 个 CDP 客户端在线');
307
+ }
308
+ if (state.attachedPages.length > 0) {
309
+ addLog('page', state.attachedPages.length + ' 个页面受控');
310
+ }
311
+ } else if (message.type === 'log') {
312
+ addLog(message.logType, message.message);
313
+ }
314
+ });
315
+ }
316
+
317
+ init();
318
+ })();
@@ -0,0 +1,310 @@
1
+ var DebuggerManager = (function() {
2
+ var INTERNAL_URL_BLOCK_SCRIPT = `
3
+ (function() {
4
+ if (window.__internalUrlBlockInjected) return;
5
+ window.__internalUrlBlockInjected = true;
6
+
7
+ var blockedProtocols = ['bitbrowser:', 'chrome:', 'edge:', 'chrome-extension:'];
8
+
9
+ function isInternalUrl(url) {
10
+ if (!url) return false;
11
+ for (var i = 0; i < blockedProtocols.length; i++) {
12
+ if (url.startsWith(blockedProtocols[i])) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+
19
+ // 拦截 iframe 创建
20
+ var originalCreateElement = document.createElement;
21
+ document.createElement = function(tagName) {
22
+ var element = originalCreateElement.call(document, tagName);
23
+ if (tagName.toLowerCase() === 'iframe') {
24
+ var originalSetAttribute = element.setAttribute;
25
+ element.setAttribute = function(name, value) {
26
+ if (name.toLowerCase() === 'src' && isInternalUrl(value)) {
27
+ console.warn('[CDP-BLOCK] Blocked iframe src:', value);
28
+ return;
29
+ }
30
+ return originalSetAttribute.call(this, name, value);
31
+ };
32
+
33
+ Object.defineProperty(element, 'src', {
34
+ set: function(value) {
35
+ if (isInternalUrl(value)) {
36
+ console.warn('[CDP-BLOCK] Blocked iframe src via property:', value);
37
+ return;
38
+ }
39
+ originalSetAttribute.call(this, 'src', value);
40
+ },
41
+ get: function() {
42
+ return element.getAttribute('src');
43
+ }
44
+ });
45
+ }
46
+ return element;
47
+ };
48
+
49
+ // 拦截 window.open
50
+ var originalWindowOpen = window.open;
51
+ window.open = function(url) {
52
+ if (isInternalUrl(url)) {
53
+ console.warn('[CDP-BLOCK] Blocked window.open:', url);
54
+ return null;
55
+ }
56
+ return originalWindowOpen.apply(this, arguments);
57
+ };
58
+
59
+ // 拦截 location 修改
60
+ var locationDescriptor = Object.getOwnPropertyDescriptor(window, 'location');
61
+ Object.defineProperty(window, 'location', {
62
+ set: function(value) {
63
+ if (isInternalUrl(String(value))) {
64
+ console.warn('[CDP-BLOCK] Blocked location change:', value);
65
+ return;
66
+ }
67
+ locationDescriptor.set.call(window, value);
68
+ },
69
+ get: function() {
70
+ return locationDescriptor.get.call(window);
71
+ }
72
+ });
73
+
74
+ console.log('[CDP-BLOCK] Internal URL block script injected');
75
+ })();
76
+ `;
77
+
78
+ function attach(tabId) {
79
+ if (tabId == null) {
80
+ return Promise.resolve(false);
81
+ }
82
+
83
+ return ensureTabExists(tabId).then(function(exists) {
84
+ if (!exists) {
85
+ Logger.warn('[Debugger] Tab', tabId, 'does not exist');
86
+ State.persist(null, false);
87
+ return false;
88
+ }
89
+
90
+ return getActualAttachState(tabId).then(function(isAttached) {
91
+ if (isAttached) {
92
+ Logger.info('[Debugger] Tab', tabId, 'already attached, detaching first...');
93
+ return chrome.debugger.detach({ tabId: tabId }).catch(function() {}).then(function() {
94
+ return doAttach(tabId);
95
+ });
96
+ }
97
+ return doAttach(tabId);
98
+ });
99
+ });
100
+ }
101
+
102
+ function doAttach(tabId) {
103
+ return chrome.debugger.attach({ tabId: tabId }, Config.DEBUGGER_VERSION)
104
+ .then(function() {
105
+ Logger.info('[Debugger] Attached to tab', tabId);
106
+ State.addAttachedTab(tabId);
107
+ State.setCurrentTabId(tabId);
108
+ State.persist(tabId, true);
109
+ AutomationBadge.inject(tabId);
110
+
111
+ // 注入内部URL拦截脚本
112
+ return injectInternalUrlBlocker(tabId);
113
+ })
114
+ .then(function() {
115
+ return true;
116
+ })
117
+ .catch(function(error) {
118
+ Logger.error('[Debugger] Failed to attach to tab', tabId, ':', error.message);
119
+ return false;
120
+ });
121
+ }
122
+
123
+ function injectInternalUrlBlocker(tabId) {
124
+ return chrome.debugger.sendCommand(
125
+ { tabId: tabId },
126
+ 'Runtime.evaluate',
127
+ {
128
+ expression: INTERNAL_URL_BLOCK_SCRIPT,
129
+ runImmediately: true
130
+ }
131
+ ).then(function(result) {
132
+ if (result.exceptionDetails) {
133
+ Logger.warn('[Debugger] Failed to inject internal URL blocker:', result.exceptionDetails);
134
+ } else {
135
+ Logger.info('[Debugger] Internal URL blocker injected');
136
+ }
137
+ }).catch(function(e) {
138
+ Logger.warn('[Debugger] Failed to inject internal URL blocker:', e.message);
139
+ });
140
+ }
141
+
142
+ function detach(tabId) {
143
+ if (tabId == null) {
144
+ return Promise.resolve();
145
+ }
146
+
147
+ Logger.info('[Debugger] Attempting to detach from tab', tabId);
148
+
149
+ return chrome.debugger.detach({ tabId: tabId })
150
+ .then(function() {
151
+ Logger.info('[Debugger] Detached from tab', tabId);
152
+ State.removeAttachedTab(tabId);
153
+ AutomationBadge.remove(tabId);
154
+ Screencast.stopPolling(tabId);
155
+ if (State.getCurrentTabId() === tabId) {
156
+ State.persist(null, false);
157
+ }
158
+ })
159
+ .catch(function(error) {
160
+ Logger.error('[Debugger] Failed to detach from tab', tabId, ':', error.message);
161
+ State.removeAttachedTab(tabId);
162
+ AutomationBadge.remove(tabId);
163
+ });
164
+ }
165
+
166
+ function isAttached(tabId) {
167
+ return getActualAttachState(tabId);
168
+ }
169
+
170
+ function getActualAttachState(tabId) {
171
+ return chrome.debugger.getTargets().then(function(targets) {
172
+ var target = targets.find(function(t) {
173
+ return t.tabId === tabId;
174
+ });
175
+ return target ? target.attached : false;
176
+ });
177
+ }
178
+
179
+ function ensureTabExists(tabId) {
180
+ return new Promise(function(resolve) {
181
+ chrome.tabs.get(tabId, function(tab) {
182
+ resolve(!!(tab && tab.id));
183
+ });
184
+ }).catch(function() {
185
+ return false;
186
+ });
187
+ }
188
+
189
+ function handleDebuggerEvent(source, method, params) {
190
+ if (!State.isTabAttached(source.tabId)) {
191
+ return;
192
+ }
193
+
194
+ if (!State.hasConnectedClient()) {
195
+ return;
196
+ }
197
+
198
+ var sessionId = State.findSessionByTabId(source.tabId);
199
+ Logger.info('[Event] method=' + method + ' tabId=' + source.tabId + ' sessionId=' + (sessionId || 'null'));
200
+
201
+ if (method === 'Runtime.executionContextCreated' && params && params.context) {
202
+ var context = params.context;
203
+ var isPlaywrightContext = context.name && context.name.indexOf('__playwright') === 0;
204
+ var isDefaultContext = context.auxData && context.auxData.isDefault;
205
+
206
+ if (!isPlaywrightContext && !isDefaultContext) {
207
+ Logger.info('[Event] Filtering non-Playwright Runtime.executionContextCreated:', context.name);
208
+ return;
209
+ }
210
+
211
+ // 在新的执行上下文中也注入拦截脚本
212
+ if (isDefaultContext) {
213
+ chrome.debugger.sendCommand(
214
+ { tabId: source.tabId },
215
+ 'Runtime.evaluate',
216
+ {
217
+ expression: INTERNAL_URL_BLOCK_SCRIPT,
218
+ contextId: context.id,
219
+ runImmediately: true
220
+ }
221
+ ).catch(function() {});
222
+ }
223
+ }
224
+
225
+ if (method === 'Page.frameRequestedNavigation' && params) {
226
+ var url = params.url || '';
227
+ var reason = params.reason || '';
228
+ var disposition = params.disposition || '';
229
+ var frameId = params.frameId || '';
230
+ Logger.warn('[NAVIGATION] frameRequestedNavigation:');
231
+ Logger.warn(' URL:', url);
232
+ Logger.warn(' Reason:', reason);
233
+ Logger.warn(' Disposition:', disposition);
234
+ Logger.warn(' FrameId:', frameId);
235
+
236
+ if (url.startsWith('chrome://') ||
237
+ url.startsWith('bitbrowser://') ||
238
+ url.startsWith('edge://') ||
239
+ url.startsWith('chrome-extension://')) {
240
+ Logger.error('[NAVIGATION] ⚠️ 检测到导航到内部页面,尝试阻止!');
241
+ Logger.error('[NAVIGATION] 目标URL:', url);
242
+
243
+ // 尝试方法1: 停止页面加载
244
+ chrome.debugger.sendCommand(
245
+ { tabId: source.tabId },
246
+ 'Page.stopLoading',
247
+ {}
248
+ ).then(function() {
249
+ Logger.info('[NAVIGATION] 已发送 Page.stopLoading 命令');
250
+ }).catch(function(e) {
251
+ Logger.error('[NAVIGATION] Page.stopLoading 失败:', e.message);
252
+ });
253
+
254
+ // 尝试方法2: 获取当前页面URL并导航回去
255
+ chrome.tabs.get(source.tabId, function(tab) {
256
+ if (tab && tab.url && !tab.url.startsWith('bitbrowser://')) {
257
+ Logger.info('[NAVIGATION] 尝试导航回原页面:', tab.url);
258
+ setTimeout(function() {
259
+ chrome.debugger.sendCommand(
260
+ { tabId: source.tabId },
261
+ 'Page.navigate',
262
+ { url: tab.url }
263
+ ).then(function() {
264
+ Logger.info('[NAVIGATION] 已导航回原页面');
265
+ }).catch(function(e) {
266
+ Logger.error('[NAVIGATION] Page.navigate 失败:', e.message);
267
+ });
268
+ }, 100);
269
+ }
270
+ });
271
+
272
+ // 不转发这个导航事件给客户端
273
+ Logger.warn('[NAVIGATION] 阻止转发导航事件到客户端');
274
+ return;
275
+ }
276
+ }
277
+
278
+ EventBuilder.send(method, params, sessionId);
279
+ }
280
+
281
+ function handleDetach(source, reason) {
282
+ Logger.info('[Debugger] Detached from tab', source.tabId, ', reason:', reason);
283
+ State.removeAttachedTab(source.tabId);
284
+ Screencast.stopPolling(source.tabId);
285
+ AutomationBadge.remove(source.tabId);
286
+
287
+ var sessionId = State.findSessionByTabId(source.tabId);
288
+ if (sessionId) {
289
+ var targetId = State.getTargetIdBySession(sessionId);
290
+ EventBuilder.send('Target.detachedFromTarget', {
291
+ sessionId: sessionId,
292
+ targetId: targetId
293
+ });
294
+ State.unmapSession(sessionId);
295
+ }
296
+
297
+ if (State.getCurrentTabId() === source.tabId) {
298
+ State.persist(null, false);
299
+ }
300
+ }
301
+
302
+ return {
303
+ attach: attach,
304
+ detach: detach,
305
+ isAttached: isAttached,
306
+ getActualAttachState: getActualAttachState,
307
+ handleDebuggerEvent: handleDebuggerEvent,
308
+ handleDetach: handleDetach
309
+ };
310
+ })();