@vforsh/argus-watcher 0.1.3 → 0.1.4
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 +1 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/cdp/connection.d.ts +16 -1
- package/dist/cdp/connection.d.ts.map +1 -1
- package/dist/cdp/connection.js +1 -1
- package/dist/cdp/connection.js.map +1 -1
- package/dist/cdp/dom/selector.d.ts.map +1 -1
- package/dist/cdp/dom/selector.js +22 -0
- package/dist/cdp/dom/selector.js.map +1 -1
- package/dist/cdp/dom/setFile.d.ts.map +1 -1
- package/dist/cdp/dom/setFile.js +5 -62
- package/dist/cdp/dom/setFile.js.map +1 -1
- package/dist/cdp/dom/types.d.ts +0 -1
- package/dist/cdp/dom/types.d.ts.map +1 -1
- package/dist/cdp/dom.d.ts +0 -2
- package/dist/cdp/dom.d.ts.map +1 -1
- package/dist/cdp/dom.js +0 -1
- package/dist/cdp/dom.js.map +1 -1
- package/dist/cdp/editor.d.ts +23 -0
- package/dist/cdp/editor.d.ts.map +1 -0
- package/dist/cdp/editor.js +266 -0
- package/dist/cdp/editor.js.map +1 -0
- package/dist/cdp/eval.d.ts +1 -0
- package/dist/cdp/eval.d.ts.map +1 -1
- package/dist/cdp/eval.js +57 -12
- package/dist/cdp/eval.js.map +1 -1
- package/dist/cdp/mouse.d.ts.map +1 -1
- package/dist/cdp/mouse.js +12 -8
- package/dist/cdp/mouse.js.map +1 -1
- package/dist/cdp/screenshot.d.ts.map +1 -1
- package/dist/cdp/screenshot.js +2 -6
- package/dist/cdp/screenshot.js.map +1 -1
- package/dist/cdp/text-filter.d.ts +0 -13
- package/dist/cdp/text-filter.d.ts.map +1 -1
- package/dist/cdp/text-filter.js +2 -20
- package/dist/cdp/text-filter.js.map +1 -1
- package/dist/cdp/tracing.d.ts +1 -0
- package/dist/cdp/tracing.d.ts.map +1 -1
- package/dist/cdp/tracing.js +49 -5
- package/dist/cdp/tracing.js.map +1 -1
- package/dist/events.d.ts +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/http/router.d.ts.map +1 -1
- package/dist/http/router.js +6 -2
- package/dist/http/router.js.map +1 -1
- package/dist/http/routes/postAttach.d.ts.map +1 -1
- package/dist/http/routes/postAttach.js +4 -3
- package/dist/http/routes/postAttach.js.map +1 -1
- package/dist/http/routes/postCodeEdit.d.ts +3 -0
- package/dist/http/routes/postCodeEdit.d.ts.map +1 -0
- package/dist/http/routes/postCodeEdit.js +32 -0
- package/dist/http/routes/postCodeEdit.js.map +1 -0
- package/dist/http/routes/postCodeGrep.d.ts +3 -0
- package/dist/http/routes/postCodeGrep.d.ts.map +1 -0
- package/dist/http/routes/postCodeGrep.js +26 -0
- package/dist/http/routes/postCodeGrep.js.map +1 -0
- package/dist/http/routes/postCodeList.d.ts +3 -0
- package/dist/http/routes/postCodeList.d.ts.map +1 -0
- package/dist/http/routes/postCodeList.js +20 -0
- package/dist/http/routes/postCodeList.js.map +1 -0
- package/dist/http/routes/postCodeRead.d.ts +3 -0
- package/dist/http/routes/postCodeRead.d.ts.map +1 -0
- package/dist/http/routes/postCodeRead.js +30 -0
- package/dist/http/routes/postCodeRead.js.map +1 -0
- package/dist/http/routes/postDetach.d.ts.map +1 -1
- package/dist/http/routes/postDetach.js +4 -3
- package/dist/http/routes/postDetach.js.map +1 -1
- package/dist/http/routes/postDomSetFile.d.ts.map +1 -1
- package/dist/http/routes/postDomSetFile.js +0 -9
- package/dist/http/routes/postDomSetFile.js.map +1 -1
- package/dist/http/routes/postEval.d.ts.map +1 -1
- package/dist/http/routes/postEval.js +1 -0
- package/dist/http/routes/postEval.js.map +1 -1
- package/dist/http/server.d.ts +3 -1
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -63
- package/dist/index.js.map +1 -1
- package/dist/native-messaging/session-manager.d.ts +4 -1
- package/dist/native-messaging/session-manager.d.ts.map +1 -1
- package/dist/native-messaging/session-manager.js +11 -3
- package/dist/native-messaging/session-manager.js.map +1 -1
- package/dist/native-messaging/types.d.ts +36 -3
- package/dist/native-messaging/types.d.ts.map +1 -1
- package/dist/sources/cdp-source.d.ts.map +1 -1
- package/dist/sources/cdp-source.js +1 -0
- package/dist/sources/cdp-source.js.map +1 -1
- package/dist/sources/extension-frame-state.d.ts +64 -0
- package/dist/sources/extension-frame-state.d.ts.map +1 -0
- package/dist/sources/extension-frame-state.js +117 -0
- package/dist/sources/extension-frame-state.js.map +1 -0
- package/dist/sources/extension-log-events.d.ts +15 -0
- package/dist/sources/extension-log-events.d.ts.map +1 -0
- package/dist/sources/extension-log-events.js +101 -0
- package/dist/sources/extension-log-events.js.map +1 -0
- package/dist/sources/extension-source.d.ts.map +1 -1
- package/dist/sources/extension-source.js +416 -185
- package/dist/sources/extension-source.js.map +1 -1
- package/dist/sources/types.d.ts +19 -2
- package/dist/sources/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2,103 +2,88 @@
|
|
|
2
2
|
* Extension source for CDP access via Chrome extension Native Messaging.
|
|
3
3
|
* Wraps SessionManager and provides a unified source interface.
|
|
4
4
|
*/
|
|
5
|
-
import { previewStringify } from '@vforsh/argus-core';
|
|
6
5
|
import { createNativeMessaging } from '../native-messaging/messaging.js';
|
|
7
6
|
import { SessionManager } from '../native-messaging/session-manager.js';
|
|
7
|
+
import { buildFrameTargets, buildPageTarget, collectFrameTree, createEmptyFrameState, createNotAttachedError, frameToTarget, formatPageTargetId, parseExecutionContext, parseExtensionTargetId, parseFrame, resolveRequestedFrameId, } from './extension-frame-state.js';
|
|
8
|
+
import { toConsoleEvent, toExceptionEvent } from './extension-log-events.js';
|
|
8
9
|
/**
|
|
9
10
|
* Create an extension source that connects to Chrome via Native Messaging.
|
|
10
11
|
* Returns a handle that can be used to control the source and access CDP session.
|
|
11
12
|
*/
|
|
12
13
|
export const createExtensionSource = (options) => {
|
|
13
|
-
const { events, ignoreMatcher, stripUrlPrefixes } = options;
|
|
14
|
+
const { events, ignoreMatcher, stripUrlPrefixes, watcherId, watcherHost, watcherPort } = options;
|
|
14
15
|
const messaging = createNativeMessaging();
|
|
16
|
+
const hostInfo = {
|
|
17
|
+
watcherId: watcherId ?? 'extension',
|
|
18
|
+
watcherHost: watcherHost ?? '127.0.0.1',
|
|
19
|
+
watcherPort: watcherPort ?? 0,
|
|
20
|
+
watcherPid: process.pid,
|
|
21
|
+
};
|
|
15
22
|
let currentSession = null;
|
|
16
23
|
let stopping = false;
|
|
17
|
-
|
|
24
|
+
const frameStateByTabId = new Map();
|
|
25
|
+
const getCurrentExtensionSession = () => {
|
|
26
|
+
if (!currentSession) {
|
|
27
|
+
throw createNotAttachedError();
|
|
28
|
+
}
|
|
29
|
+
return currentSession;
|
|
30
|
+
};
|
|
18
31
|
const sessionManager = new SessionManager(messaging, {
|
|
19
32
|
onAttach: (session) => {
|
|
20
33
|
console.error(`[ExtensionSource] Tab attached: ${session.tabId} - ${session.url}`);
|
|
21
34
|
currentSession = session;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
session.handle.onEvent('Runtime.consoleAPICalled', (params) => {
|
|
25
|
-
const logEvent = toConsoleEvent(params, session, { ignoreMatcher, stripUrlPrefixes });
|
|
26
|
-
events.onLog(logEvent);
|
|
27
|
-
});
|
|
28
|
-
// Subscribe to exception events
|
|
29
|
-
session.handle.onEvent('Runtime.exceptionThrown', (params) => {
|
|
30
|
-
const logEvent = toExceptionEvent(params, session, { ignoreMatcher, stripUrlPrefixes });
|
|
31
|
-
events.onLog(logEvent);
|
|
32
|
-
});
|
|
33
|
-
// Notify status change
|
|
34
|
-
const target = {
|
|
35
|
-
id: String(session.tabId),
|
|
36
|
-
title: session.title,
|
|
37
|
-
url: session.url,
|
|
38
|
-
type: 'page',
|
|
39
|
-
faviconUrl: session.faviconUrl,
|
|
40
|
-
attached: true,
|
|
41
|
-
};
|
|
42
|
-
events.onStatus({
|
|
43
|
-
attached: true,
|
|
44
|
-
target: {
|
|
45
|
-
title: session.title,
|
|
46
|
-
url: session.url,
|
|
47
|
-
type: 'page',
|
|
48
|
-
parentId: null,
|
|
49
|
-
},
|
|
50
|
-
reason: null,
|
|
51
|
-
});
|
|
52
|
-
void events.onAttach?.(session.handle, target);
|
|
35
|
+
seedFrameState(session);
|
|
36
|
+
void bootstrapAttachedSession(session);
|
|
53
37
|
},
|
|
54
38
|
onDetach: (tabId, reason) => {
|
|
55
39
|
console.error(`[ExtensionSource] Tab detached: ${tabId} - ${reason}`);
|
|
56
40
|
if (currentSession?.tabId === tabId) {
|
|
57
41
|
currentSession = null;
|
|
58
42
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
target: null,
|
|
62
|
-
reason,
|
|
63
|
-
});
|
|
43
|
+
frameStateByTabId.delete(tabId);
|
|
44
|
+
emitStatus(null, reason);
|
|
64
45
|
events.onDetach?.(reason);
|
|
65
46
|
},
|
|
66
47
|
onTabsUpdated: () => {
|
|
67
48
|
// Tabs list updated - no action needed
|
|
68
49
|
},
|
|
69
|
-
|
|
70
|
-
// Create a proxy session handle that delegates to the current session
|
|
71
|
-
const proxySession = {
|
|
72
|
-
isAttached: () => currentSession?.handle.isAttached() ?? false,
|
|
73
|
-
sendAndWait: async (method, params, options) => {
|
|
50
|
+
onTargetSelected: (tabId, frameId) => {
|
|
74
51
|
const session = currentSession;
|
|
75
|
-
if (!session) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
throw error;
|
|
52
|
+
if (!session || session.tabId !== tabId) {
|
|
53
|
+
getOrCreateFrameState(tabId).requestedFrameId = frameId;
|
|
54
|
+
return;
|
|
79
55
|
}
|
|
80
|
-
|
|
56
|
+
requestTargetSelection(session, frameId);
|
|
81
57
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
58
|
+
});
|
|
59
|
+
// Proxy session follows the currently selected page/frame target inside the attached tab.
|
|
60
|
+
const proxySession = createDelegatingSession({
|
|
61
|
+
getTargetContext: () => getCurrentTargetContext() ?? { kind: 'page' },
|
|
62
|
+
mapParams: (method, params) => {
|
|
63
|
+
const targetContext = getCurrentTargetContext();
|
|
64
|
+
if (method !== 'Runtime.evaluate' || targetContext?.kind !== 'frame' || params?.contextId != null) {
|
|
65
|
+
return params;
|
|
66
|
+
}
|
|
67
|
+
if (targetContext.executionContextId == null) {
|
|
68
|
+
throw new Error(`Selected frame is not ready yet: ${targetContext.frameId}`);
|
|
88
69
|
}
|
|
89
|
-
return
|
|
70
|
+
return {
|
|
71
|
+
...(params ?? {}),
|
|
72
|
+
contextId: targetContext.executionContextId,
|
|
73
|
+
};
|
|
90
74
|
},
|
|
91
|
-
};
|
|
92
|
-
//
|
|
75
|
+
});
|
|
76
|
+
// Page session always stays at the top-level tab context so page-level features
|
|
77
|
+
// (indicator, inject-on-attach, similar lifecycle hooks) don't accidentally run inside an iframe.
|
|
78
|
+
const pageSession = createDelegatingSession({
|
|
79
|
+
getTargetContext: () => ({ kind: 'page' }),
|
|
80
|
+
});
|
|
93
81
|
messaging.start();
|
|
82
|
+
sendHostInfo();
|
|
94
83
|
messaging.onDisconnect(() => {
|
|
95
84
|
console.error('[ExtensionSource] Extension disconnected');
|
|
96
85
|
if (!stopping) {
|
|
97
|
-
|
|
98
|
-
attached: false,
|
|
99
|
-
target: null,
|
|
100
|
-
reason: 'extension_disconnected',
|
|
101
|
-
});
|
|
86
|
+
emitStatus(null, 'extension_disconnected');
|
|
102
87
|
events.onDetach?.('extension_disconnected');
|
|
103
88
|
}
|
|
104
89
|
});
|
|
@@ -108,149 +93,395 @@ export const createExtensionSource = (options) => {
|
|
|
108
93
|
};
|
|
109
94
|
const listTargets = async () => {
|
|
110
95
|
const tabs = await sessionManager.listTabs();
|
|
111
|
-
return tabs.
|
|
96
|
+
return tabs.flatMap((tab) => {
|
|
97
|
+
const session = currentSession?.tabId === tab.tabId ? currentSession : null;
|
|
98
|
+
const state = frameStateByTabId.get(tab.tabId);
|
|
99
|
+
if (!session || !state || state.frames.size === 0) {
|
|
100
|
+
return [tabToTarget(tab)];
|
|
101
|
+
}
|
|
102
|
+
const activeFrameId = state.activeFrameId;
|
|
103
|
+
const targets = [buildPageTarget(session, { attached: activeFrameId == null })];
|
|
104
|
+
for (const frame of buildFrameTargets(state, tab.tabId, activeFrameId, tab.faviconUrl)) {
|
|
105
|
+
targets.push(frame);
|
|
106
|
+
}
|
|
107
|
+
return targets;
|
|
108
|
+
});
|
|
112
109
|
};
|
|
113
110
|
const attachTarget = (targetId) => {
|
|
114
|
-
|
|
111
|
+
const target = parseExtensionTargetId(targetId);
|
|
112
|
+
const session = currentSession;
|
|
113
|
+
if (!session || session.tabId !== target.tabId) {
|
|
114
|
+
getOrCreateFrameState(target.tabId).requestedFrameId = target.frameId;
|
|
115
|
+
sessionManager.attachTab(target.tabId);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
requestTargetSelection(session, target.frameId);
|
|
115
119
|
};
|
|
116
120
|
const detachTarget = (targetId) => {
|
|
117
|
-
sessionManager.detachTab(targetId);
|
|
121
|
+
sessionManager.detachTab(parseExtensionTargetId(targetId).tabId);
|
|
118
122
|
};
|
|
119
123
|
return {
|
|
120
124
|
session: proxySession,
|
|
125
|
+
pageSession,
|
|
126
|
+
syncWatcherInfo: (info) => {
|
|
127
|
+
hostInfo.watcherId = info.watcherId;
|
|
128
|
+
hostInfo.watcherHost = info.watcherHost;
|
|
129
|
+
hostInfo.watcherPort = info.watcherPort;
|
|
130
|
+
hostInfo.watcherPid = info.watcherPid;
|
|
131
|
+
sendHostInfo();
|
|
132
|
+
},
|
|
121
133
|
stop,
|
|
122
134
|
listTargets,
|
|
123
135
|
attachTarget,
|
|
124
136
|
detachTarget,
|
|
125
137
|
};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
type: 'page',
|
|
135
|
-
faviconUrl: tab.faviconUrl,
|
|
136
|
-
attached: tab.attached,
|
|
137
|
-
});
|
|
138
|
-
/**
|
|
139
|
-
* Convert Runtime.consoleAPICalled event to LogEvent.
|
|
140
|
-
*/
|
|
141
|
-
const toConsoleEvent = (params, session, config) => {
|
|
142
|
-
const record = params;
|
|
143
|
-
const args = record.args?.map((a) => a.value) ?? [];
|
|
144
|
-
const text = formatArgs(record.args ?? []);
|
|
145
|
-
const frame = selectBestFrame(record.stackTrace?.callFrames, config.ignoreMatcher);
|
|
146
|
-
return {
|
|
147
|
-
ts: record.timestamp ?? Date.now(),
|
|
148
|
-
level: normalizeLevel(record.type ?? 'log'),
|
|
149
|
-
text,
|
|
150
|
-
args,
|
|
151
|
-
source: 'console',
|
|
152
|
-
file: applyStripPrefixes(frame?.url ?? null, config.stripUrlPrefixes),
|
|
153
|
-
line: frame?.lineNumber != null ? frame.lineNumber + 1 : null,
|
|
154
|
-
column: frame?.columnNumber != null ? frame.columnNumber + 1 : null,
|
|
155
|
-
pageUrl: session.url,
|
|
156
|
-
pageTitle: session.title,
|
|
157
|
-
};
|
|
158
|
-
};
|
|
159
|
-
/**
|
|
160
|
-
* Convert Runtime.exceptionThrown event to LogEvent.
|
|
161
|
-
*/
|
|
162
|
-
const toExceptionEvent = (params, session, config) => {
|
|
163
|
-
const record = params;
|
|
164
|
-
const details = record.exceptionDetails;
|
|
165
|
-
const text = details?.exception?.description ?? details?.text ?? 'Exception';
|
|
166
|
-
const args = details?.exception ? [details.exception.value ?? details.exception.description] : [];
|
|
167
|
-
const frame = selectBestFrame(details?.stackTrace?.callFrames, config.ignoreMatcher);
|
|
168
|
-
return {
|
|
169
|
-
ts: record.timestamp ?? Date.now(),
|
|
170
|
-
level: 'exception',
|
|
171
|
-
text,
|
|
172
|
-
args,
|
|
173
|
-
source: 'exception',
|
|
174
|
-
file: applyStripPrefixes(frame?.url ?? null, config.stripUrlPrefixes),
|
|
175
|
-
line: frame?.lineNumber != null ? frame.lineNumber + 1 : null,
|
|
176
|
-
column: frame?.columnNumber != null ? frame.columnNumber + 1 : null,
|
|
177
|
-
pageUrl: session.url,
|
|
178
|
-
pageTitle: session.title,
|
|
179
|
-
};
|
|
180
|
-
};
|
|
181
|
-
/**
|
|
182
|
-
* Format console args for display.
|
|
183
|
-
*/
|
|
184
|
-
const formatArgs = (args) => {
|
|
185
|
-
if (args.length === 0) {
|
|
186
|
-
return '';
|
|
138
|
+
function sendHostInfo() {
|
|
139
|
+
messaging.send({
|
|
140
|
+
type: 'host_info',
|
|
141
|
+
watcherId: hostInfo.watcherId,
|
|
142
|
+
watcherHost: hostInfo.watcherHost,
|
|
143
|
+
watcherPort: hostInfo.watcherPort,
|
|
144
|
+
pid: hostInfo.watcherPid,
|
|
145
|
+
});
|
|
187
146
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
147
|
+
async function bootstrapAttachedSession(session) {
|
|
148
|
+
try {
|
|
149
|
+
registerSessionEventHandlers(session);
|
|
150
|
+
await enableBootstrapDomains(session);
|
|
151
|
+
await refreshFrameTree(session);
|
|
152
|
+
if (currentSession?.tabId !== session.tabId) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const target = buildPageTarget(session, { attached: true });
|
|
156
|
+
emitStatus(target, null);
|
|
157
|
+
await events.onAttach?.(session.handle, target);
|
|
158
|
+
reconcileTargetSelection(session);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
162
|
+
console.error(`[ExtensionSource] Failed to bootstrap attached tab ${session.tabId}: ${message}`);
|
|
163
|
+
if (currentSession?.tabId === session.tabId) {
|
|
164
|
+
emitStatus(null, message);
|
|
165
|
+
events.onDetach?.(message);
|
|
166
|
+
}
|
|
192
167
|
}
|
|
193
|
-
return arg.description ?? `[${arg.type}]`;
|
|
194
|
-
})
|
|
195
|
-
.join(' ');
|
|
196
|
-
};
|
|
197
|
-
/**
|
|
198
|
-
* Normalize console log type to LogLevel.
|
|
199
|
-
*/
|
|
200
|
-
const normalizeLevel = (type) => {
|
|
201
|
-
switch (type) {
|
|
202
|
-
case 'log':
|
|
203
|
-
case 'info':
|
|
204
|
-
case 'debug':
|
|
205
|
-
case 'dir':
|
|
206
|
-
case 'dirxml':
|
|
207
|
-
case 'table':
|
|
208
|
-
case 'trace':
|
|
209
|
-
case 'count':
|
|
210
|
-
case 'timeEnd':
|
|
211
|
-
case 'timeLog':
|
|
212
|
-
return type === 'debug' ? 'debug' : type === 'info' ? 'info' : 'log';
|
|
213
|
-
case 'warn':
|
|
214
|
-
case 'warning':
|
|
215
|
-
return 'warning';
|
|
216
|
-
case 'error':
|
|
217
|
-
case 'assert':
|
|
218
|
-
case 'exception':
|
|
219
|
-
return type === 'exception' ? 'exception' : 'error';
|
|
220
|
-
default:
|
|
221
|
-
return 'log';
|
|
222
168
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Subscribe before enabling Runtime/Page so we don't miss the initial execution contexts
|
|
171
|
+
* that Chrome emits as soon as Runtime becomes active.
|
|
172
|
+
*/
|
|
173
|
+
async function enableBootstrapDomains(session) {
|
|
174
|
+
await session.handle.sendAndWait('Runtime.enable');
|
|
175
|
+
await session.handle.sendAndWait('Page.enable');
|
|
230
176
|
}
|
|
231
|
-
|
|
232
|
-
|
|
177
|
+
function registerSessionEventHandlers(session) {
|
|
178
|
+
session.handle.onEvent('Runtime.executionContextCreated', (params, meta) => {
|
|
179
|
+
const context = parseExecutionContext(params);
|
|
180
|
+
if (!context?.isDefault || !context.frameId) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
184
|
+
state.executionContexts.set(context.frameId, context.id);
|
|
185
|
+
const frame = state.frames.get(context.frameId);
|
|
186
|
+
if (frame) {
|
|
187
|
+
frame.sessionId = meta.sessionId ?? null;
|
|
188
|
+
}
|
|
189
|
+
void refreshFrameTitle(session, context.frameId, context.id);
|
|
190
|
+
reconcileTargetSelection(session);
|
|
191
|
+
});
|
|
192
|
+
session.handle.onEvent('Runtime.executionContextsCleared', () => {
|
|
193
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
194
|
+
state.executionContexts.clear();
|
|
195
|
+
state.pendingTitleLoads.clear();
|
|
196
|
+
});
|
|
197
|
+
session.handle.onEvent('Runtime.executionContextDestroyed', (params) => {
|
|
198
|
+
const record = params;
|
|
199
|
+
if (record.executionContextId == null) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
203
|
+
for (const [frameId, contextId] of state.executionContexts.entries()) {
|
|
204
|
+
if (contextId === record.executionContextId) {
|
|
205
|
+
state.executionContexts.delete(frameId);
|
|
206
|
+
state.pendingTitleLoads.delete(frameId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
session.handle.onEvent('Page.frameNavigated', (params, meta) => {
|
|
211
|
+
const frame = parseFrame(params);
|
|
212
|
+
if (!frame) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
216
|
+
state.frames.set(frame.frameId, frame);
|
|
217
|
+
frame.sessionId = meta.sessionId ?? null;
|
|
218
|
+
if (!frame.parentFrameId) {
|
|
219
|
+
if (!meta.sessionId) {
|
|
220
|
+
state.topFrameId = frame.frameId;
|
|
221
|
+
session.url = frame.url;
|
|
222
|
+
}
|
|
223
|
+
else if (state.topFrameId == null) {
|
|
224
|
+
state.topFrameId = frame.parentFrameId;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (state.executionContexts.has(frame.frameId)) {
|
|
228
|
+
void refreshFrameTitle(session, frame.frameId);
|
|
229
|
+
}
|
|
230
|
+
currentSession = session;
|
|
231
|
+
if (!frame.parentFrameId && !meta.sessionId) {
|
|
232
|
+
events.onPageNavigation?.({ url: frame.url, title: session.title ?? null });
|
|
233
|
+
}
|
|
234
|
+
if (reconcileTargetSelection(session)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if ((!frame.parentFrameId && state.activeFrameId == null) || state.activeFrameId === frame.frameId) {
|
|
238
|
+
emitTargetChanged(session);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
session.handle.onEvent('Page.frameAttached', (params, meta) => {
|
|
242
|
+
const record = params;
|
|
243
|
+
if (!record.frameId) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
247
|
+
state.frames.set(record.frameId, {
|
|
248
|
+
frameId: record.frameId,
|
|
249
|
+
parentFrameId: record.parentFrameId ?? state.topFrameId ?? null,
|
|
250
|
+
url: '',
|
|
251
|
+
title: null,
|
|
252
|
+
sessionId: meta.sessionId ?? null,
|
|
253
|
+
});
|
|
254
|
+
reconcileTargetSelection(session);
|
|
255
|
+
});
|
|
256
|
+
session.handle.onEvent('Page.frameDetached', (params) => {
|
|
257
|
+
const record = params;
|
|
258
|
+
if (!record.frameId) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
removeFrame(session.tabId, record.frameId);
|
|
262
|
+
reconcileTargetSelection(session);
|
|
263
|
+
});
|
|
264
|
+
session.handle.onEvent('Page.domContentEventFired', () => {
|
|
265
|
+
events.onPageLoad?.();
|
|
266
|
+
});
|
|
267
|
+
session.handle.onEvent('Runtime.consoleAPICalled', (params) => {
|
|
268
|
+
events.onLog(toConsoleEvent(params, session, { ignoreMatcher, stripUrlPrefixes }));
|
|
269
|
+
});
|
|
270
|
+
session.handle.onEvent('Runtime.exceptionThrown', (params) => {
|
|
271
|
+
events.onLog(toExceptionEvent(params, session, { ignoreMatcher, stripUrlPrefixes }));
|
|
272
|
+
});
|
|
233
273
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
274
|
+
function getCurrentTargetContext() {
|
|
275
|
+
const session = currentSession;
|
|
276
|
+
if (!session) {
|
|
277
|
+
return null;
|
|
237
278
|
}
|
|
279
|
+
const state = frameStateByTabId.get(session.tabId);
|
|
280
|
+
if (!state?.activeFrameId) {
|
|
281
|
+
return { kind: 'page' };
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
kind: 'frame',
|
|
285
|
+
frameId: state.activeFrameId,
|
|
286
|
+
executionContextId: state.executionContexts.get(state.activeFrameId) ?? null,
|
|
287
|
+
sessionId: state.frames.get(state.activeFrameId)?.sessionId ?? null,
|
|
288
|
+
};
|
|
238
289
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
290
|
+
function getOrCreateFrameState(tabId) {
|
|
291
|
+
const existing = frameStateByTabId.get(tabId);
|
|
292
|
+
if (existing) {
|
|
293
|
+
return existing;
|
|
294
|
+
}
|
|
295
|
+
const created = createEmptyFrameState();
|
|
296
|
+
frameStateByTabId.set(tabId, created);
|
|
297
|
+
return created;
|
|
298
|
+
}
|
|
299
|
+
function seedFrameState(session) {
|
|
300
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
301
|
+
if (state.frames.size > 0 || session.frames.length === 0) {
|
|
302
|
+
return state;
|
|
303
|
+
}
|
|
304
|
+
state.topFrameId = session.topFrameId;
|
|
305
|
+
for (const frame of session.frames) {
|
|
306
|
+
state.frames.set(frame.frameId, {
|
|
307
|
+
frameId: frame.frameId,
|
|
308
|
+
parentFrameId: frame.parentFrameId,
|
|
309
|
+
url: frame.url,
|
|
310
|
+
title: frame.title,
|
|
311
|
+
sessionId: frame.sessionId,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return state;
|
|
315
|
+
}
|
|
316
|
+
function emitStatus(target, reason) {
|
|
317
|
+
events.onStatus({
|
|
318
|
+
attached: Boolean(target),
|
|
319
|
+
target: target
|
|
320
|
+
? {
|
|
321
|
+
title: target.title,
|
|
322
|
+
url: target.url,
|
|
323
|
+
type: target.type ?? 'page',
|
|
324
|
+
parentId: target.parentId ?? null,
|
|
325
|
+
}
|
|
326
|
+
: null,
|
|
327
|
+
reason,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function emitTargetChanged(session) {
|
|
331
|
+
const target = getSelectedTarget(session);
|
|
332
|
+
if (!target) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
336
|
+
if (state.activeAttachedAt == null) {
|
|
337
|
+
state.activeAttachedAt = Date.now();
|
|
338
|
+
}
|
|
339
|
+
emitStatus(target, null);
|
|
340
|
+
messaging.send({
|
|
341
|
+
type: 'target_info',
|
|
342
|
+
targetId: target.id,
|
|
343
|
+
title: target.title,
|
|
344
|
+
url: target.url,
|
|
345
|
+
attachedAt: state.activeAttachedAt,
|
|
346
|
+
});
|
|
347
|
+
events.onTargetChanged?.(session.handle, target);
|
|
348
|
+
}
|
|
349
|
+
function getSelectedTarget(session) {
|
|
350
|
+
const state = frameStateByTabId.get(session.tabId);
|
|
351
|
+
if (!state?.activeFrameId) {
|
|
352
|
+
return buildPageTarget(session, { attached: true });
|
|
353
|
+
}
|
|
354
|
+
const frame = state.frames.get(state.activeFrameId);
|
|
355
|
+
if (!frame) {
|
|
356
|
+
return buildPageTarget(session, { attached: true });
|
|
357
|
+
}
|
|
358
|
+
return frameToTarget(session.tabId, frame, { attached: true, faviconUrl: session.faviconUrl, topFrameId: state.topFrameId });
|
|
248
359
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
360
|
+
function requestTargetSelection(session, frameId) {
|
|
361
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
362
|
+
state.requestedFrameId = frameId;
|
|
363
|
+
reconcileTargetSelection(session);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Popup selection can arrive before the iframe's session is fully bootstrapped.
|
|
367
|
+
* Keep the request pending until the frame is discovered instead of routing commands
|
|
368
|
+
* to the parent page and pretending the switch already happened.
|
|
369
|
+
*/
|
|
370
|
+
function reconcileTargetSelection(session) {
|
|
371
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
372
|
+
const nextActiveFrameId = resolveRequestedFrameId(state, state.requestedFrameId);
|
|
373
|
+
if (state.requestedFrameId == null) {
|
|
374
|
+
if (state.activeFrameId == null) {
|
|
375
|
+
if (state.activeAttachedAt == null) {
|
|
376
|
+
state.activeAttachedAt = Date.now();
|
|
377
|
+
emitTargetChanged(session);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
state.activeFrameId = null;
|
|
383
|
+
state.activeAttachedAt = Date.now();
|
|
384
|
+
emitTargetChanged(session);
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
if (nextActiveFrameId == null || state.activeFrameId === nextActiveFrameId) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
state.activeFrameId = nextActiveFrameId;
|
|
391
|
+
state.activeAttachedAt = Date.now();
|
|
392
|
+
emitTargetChanged(session);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
async function refreshFrameTree(session) {
|
|
396
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
397
|
+
const frameTree = (await session.handle.sendAndWait('Page.getFrameTree'));
|
|
398
|
+
collectFrameTree(frameTree.frameTree, state);
|
|
399
|
+
await Promise.all([...state.executionContexts.keys()].map((frameId) => refreshFrameTitle(session, frameId)));
|
|
400
|
+
}
|
|
401
|
+
function removeFrame(tabId, frameId) {
|
|
402
|
+
const state = getOrCreateFrameState(tabId);
|
|
403
|
+
const childIds = [...state.frames.values()].filter((frame) => frame.parentFrameId === frameId).map((frame) => frame.frameId);
|
|
404
|
+
for (const childId of childIds) {
|
|
405
|
+
removeFrame(tabId, childId);
|
|
406
|
+
}
|
|
407
|
+
if (state.requestedFrameId === frameId) {
|
|
408
|
+
state.requestedFrameId = null;
|
|
252
409
|
}
|
|
410
|
+
state.frames.delete(frameId);
|
|
411
|
+
state.executionContexts.delete(frameId);
|
|
412
|
+
state.pendingTitleLoads.delete(frameId);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Frame tree metadata does not include the iframe document title, so we resolve it from the
|
|
416
|
+
* frame's default execution context and cache it alongside the rest of the frame state.
|
|
417
|
+
*/
|
|
418
|
+
async function refreshFrameTitle(session, frameId, contextId) {
|
|
419
|
+
const state = getOrCreateFrameState(session.tabId);
|
|
420
|
+
if (frameId === state.topFrameId || state.pendingTitleLoads.has(frameId)) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const executionContextId = contextId ?? state.executionContexts.get(frameId);
|
|
424
|
+
if (executionContextId == null) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const frame = state.frames.get(frameId);
|
|
428
|
+
if (!frame) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
state.pendingTitleLoads.add(frameId);
|
|
432
|
+
try {
|
|
433
|
+
const evaluated = (await session.handle.sendAndWait('Runtime.evaluate', {
|
|
434
|
+
expression: 'document.title',
|
|
435
|
+
contextId: executionContextId,
|
|
436
|
+
returnByValue: true,
|
|
437
|
+
silent: true,
|
|
438
|
+
}, frame.sessionId ? { sessionId: frame.sessionId } : undefined));
|
|
439
|
+
const title = typeof evaluated.result?.value === 'string' ? evaluated.result.value.trim() : '';
|
|
440
|
+
const latestFrame = state.frames.get(frameId);
|
|
441
|
+
if (!latestFrame) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
latestFrame.title = title || null;
|
|
445
|
+
if (state.activeFrameId === frameId) {
|
|
446
|
+
emitTargetChanged(session);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
// Ignore transient frame-title lookup failures during navigation/bootstrap.
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
state.pendingTitleLoads.delete(frameId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function createDelegatingSession(config) {
|
|
457
|
+
return {
|
|
458
|
+
isAttached: () => currentSession?.handle.isAttached() ?? false,
|
|
459
|
+
sendAndWait: async (method, params, options) => {
|
|
460
|
+
const session = getCurrentExtensionSession();
|
|
461
|
+
const targetContext = config.getTargetContext();
|
|
462
|
+
const nextParams = config.mapParams ? config.mapParams(method, params) : params;
|
|
463
|
+
const nextOptions = targetContext.kind === 'frame' && targetContext.sessionId ? { ...(options ?? {}), sessionId: targetContext.sessionId } : options;
|
|
464
|
+
return session.handle.sendAndWait(method, nextParams, nextOptions);
|
|
465
|
+
},
|
|
466
|
+
onEvent: (method, handler) => {
|
|
467
|
+
if (!currentSession) {
|
|
468
|
+
return () => { };
|
|
469
|
+
}
|
|
470
|
+
return currentSession.handle.onEvent(method, handler);
|
|
471
|
+
},
|
|
472
|
+
getTargetContext: config.getTargetContext,
|
|
473
|
+
};
|
|
253
474
|
}
|
|
254
|
-
return file;
|
|
255
475
|
};
|
|
476
|
+
/**
|
|
477
|
+
* Convert TabInfo to CdpSourceTarget.
|
|
478
|
+
*/
|
|
479
|
+
const tabToTarget = (tab) => ({
|
|
480
|
+
id: formatPageTargetId(tab.tabId),
|
|
481
|
+
title: tab.title,
|
|
482
|
+
url: tab.url,
|
|
483
|
+
type: 'page',
|
|
484
|
+
faviconUrl: tab.faviconUrl,
|
|
485
|
+
attached: tab.attached,
|
|
486
|
+
});
|
|
256
487
|
//# sourceMappingURL=extension-source.js.map
|