@pyrokine/mcp-chrome 2.4.0 → 2.4.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.
Files changed (63) hide show
  1. package/README.md +29 -13
  2. package/README_zh.md +22 -14
  3. package/dist/core/browser-driver.d.ts +6 -1
  4. package/dist/core/browser-driver.d.ts.map +1 -1
  5. package/dist/core/browser-driver.js.map +1 -1
  6. package/dist/core/session.d.ts +2 -2
  7. package/dist/core/session.d.ts.map +1 -1
  8. package/dist/core/session.js +1 -1
  9. package/dist/core/session.js.map +1 -1
  10. package/dist/core/unified-session.d.ts +10 -2
  11. package/dist/core/unified-session.d.ts.map +1 -1
  12. package/dist/core/unified-session.js +30 -6
  13. package/dist/core/unified-session.js.map +1 -1
  14. package/dist/extension/bridge.d.ts +8 -2
  15. package/dist/extension/bridge.d.ts.map +1 -1
  16. package/dist/extension/bridge.js +5 -1
  17. package/dist/extension/bridge.js.map +1 -1
  18. package/dist/extension/http-server.d.ts +17 -0
  19. package/dist/extension/http-server.d.ts.map +1 -1
  20. package/dist/extension/http-server.js +63 -8
  21. package/dist/extension/http-server.js.map +1 -1
  22. package/dist/extension/index.d.ts +1 -0
  23. package/dist/extension/index.d.ts.map +1 -1
  24. package/dist/extension/index.js.map +1 -1
  25. package/dist/tools/browse.d.ts.map +1 -1
  26. package/dist/tools/browse.js +1 -0
  27. package/dist/tools/browse.js.map +1 -1
  28. package/dist/tools/diagnostics.d.ts.map +1 -1
  29. package/dist/tools/diagnostics.js +5 -4
  30. package/dist/tools/diagnostics.js.map +1 -1
  31. package/dist/tools/evaluate.d.ts +8 -0
  32. package/dist/tools/evaluate.d.ts.map +1 -1
  33. package/dist/tools/evaluate.js +33 -7
  34. package/dist/tools/evaluate.js.map +1 -1
  35. package/dist/tools/extract.d.ts.map +1 -1
  36. package/dist/tools/extract.js +10 -9
  37. package/dist/tools/extract.js.map +1 -1
  38. package/dist/tools/input.d.ts +4 -0
  39. package/dist/tools/input.d.ts.map +1 -1
  40. package/dist/tools/input.js +103 -75
  41. package/dist/tools/input.js.map +1 -1
  42. package/dist/tools/logs.d.ts +7 -4
  43. package/dist/tools/logs.d.ts.map +1 -1
  44. package/dist/tools/logs.js +9 -6
  45. package/dist/tools/logs.js.map +1 -1
  46. package/dist/tools/network-sanitizer.d.ts +15 -0
  47. package/dist/tools/network-sanitizer.d.ts.map +1 -0
  48. package/dist/tools/network-sanitizer.js +61 -0
  49. package/dist/tools/network-sanitizer.js.map +1 -0
  50. package/dist/tools/post-condition.d.ts.map +1 -1
  51. package/dist/tools/post-condition.js +5 -1
  52. package/dist/tools/post-condition.js.map +1 -1
  53. package/dist/tools/target-diagnostics.d.ts.map +1 -1
  54. package/dist/tools/target-diagnostics.js +7 -1
  55. package/dist/tools/target-diagnostics.js.map +1 -1
  56. package/dist/tools/wait.js +1 -1
  57. package/dist/tools/wait.js.map +1 -1
  58. package/extension/dist/assets/content.ts-Cj_iiU2s.js +25 -0
  59. package/extension/dist/assets/{index.ts-BMmrcSQ6.js → index.ts-GG6dPUPX.js} +335 -282
  60. package/extension/dist/manifest.json +3 -3
  61. package/extension/dist/service-worker-loader.js +1 -1
  62. package/package.json +1 -1
  63. package/extension/dist/assets/content.ts-pEASbjEL.js +0 -6
@@ -15,6 +15,272 @@ function isExpectedOperationError(err) {
15
15
  return err instanceof ExpectedOperationError;
16
16
  }
17
17
  //#endregion
18
+ //#region src/background/tab-state.ts
19
+ /**
20
+ * Managed tab/window 状态与 storage 恢复。
21
+ */
22
+ var STORAGE_KEY = "mcp_managed_tab_ids";
23
+ var mcpTabGroupId = null;
24
+ var mcpWindowId = null;
25
+ var managedTabIds = /* @__PURE__ */ new Set();
26
+ function getMcpTabGroupId() {
27
+ return mcpTabGroupId;
28
+ }
29
+ function getMcpWindowId() {
30
+ return mcpWindowId;
31
+ }
32
+ function setMcpTabGroupId(groupId) {
33
+ mcpTabGroupId = groupId;
34
+ }
35
+ function setMcpWindowId(windowId) {
36
+ mcpWindowId = windowId;
37
+ }
38
+ async function markManagedTab(tabId) {
39
+ managedTabIds.add(tabId);
40
+ await chrome.storage.local.set({ [STORAGE_KEY]: [...managedTabIds] });
41
+ }
42
+ async function unmarkManagedTab(tabId) {
43
+ managedTabIds.delete(tabId);
44
+ await chrome.storage.local.set({ [STORAGE_KEY]: [...managedTabIds] });
45
+ }
46
+ function isManagedTab(tab, context) {
47
+ if (tab.id !== void 0 && managedTabIds.has(tab.id)) return true;
48
+ return context.mcpTabGroupId !== null && tab.groupId === context.mcpTabGroupId;
49
+ }
50
+ /** Extension 启动时调用,从 storage 恢复 managed tab 集合 */
51
+ async function restoreManagedTabs() {
52
+ const result = await chrome.storage.local.get(STORAGE_KEY);
53
+ const stored = Array.isArray(result[STORAGE_KEY]) ? result[STORAGE_KEY] : [];
54
+ for (const id of stored) managedTabIds.add(id);
55
+ const tabs = await chrome.tabs.query({});
56
+ const existingIds = new Set(tabs.map((t) => t.id).filter((id) => id !== void 0));
57
+ for (const id of managedTabIds) if (!existingIds.has(id)) managedTabIds.delete(id);
58
+ await chrome.storage.local.set({ [STORAGE_KEY]: [...managedTabIds] });
59
+ }
60
+ //#endregion
61
+ //#region src/background/action-utils.ts
62
+ function isRestrictedUrl(url) {
63
+ if (!url) return true;
64
+ return url === "about:blank" || url.startsWith("about:") || url.startsWith("chrome://") || url.startsWith("chrome-extension://");
65
+ }
66
+ async function assertScriptable(tabId) {
67
+ const tab = await chrome.tabs.get(tabId);
68
+ if (isRestrictedUrl(tab.url)) throw new ExpectedOperationError(`Cannot execute on restricted URL "${tab.url}". Navigate to an http/https page first.`);
69
+ }
70
+ async function getTargetTabId(tabId) {
71
+ if (tabId !== void 0 && tabId !== null) {
72
+ try {
73
+ await chrome.tabs.get(tabId);
74
+ } catch {
75
+ throw new ExpectedOperationError(`Tab ${tabId} 不存在(可能已被关闭)`);
76
+ }
77
+ return tabId;
78
+ }
79
+ const [activeTab] = await chrome.tabs.query({
80
+ active: true,
81
+ currentWindow: true
82
+ });
83
+ if (!activeTab?.id) throw new ExpectedOperationError("No active tab found");
84
+ return activeTab.id;
85
+ }
86
+ function tabToInfo(tab, context) {
87
+ return {
88
+ id: tab.id,
89
+ url: tab.url || "",
90
+ title: tab.title || "",
91
+ active: tab.active,
92
+ windowId: tab.windowId,
93
+ index: tab.index,
94
+ groupId: tab.groupId,
95
+ pinned: tab.pinned,
96
+ incognito: tab.incognito,
97
+ managed: isManagedTab(tab, context),
98
+ status: tab.status || "unknown"
99
+ };
100
+ }
101
+ async function assertManagedTab(tabId, context, operation) {
102
+ let tab;
103
+ try {
104
+ tab = await chrome.tabs.get(tabId);
105
+ } catch {
106
+ throw new ExpectedOperationError(`Tab ${tabId} 不存在(可能已被关闭)`);
107
+ }
108
+ if (!isManagedTab(tab, context)) throw new ExpectedOperationError(`${operation} 拒绝操作非托管 tab, ${JSON.stringify({
109
+ tabId,
110
+ managed: false,
111
+ groupId: tab.groupId,
112
+ mcpTabGroupId: context.mcpTabGroupId,
113
+ windowId: tab.windowId,
114
+ mcpWindowId: context.mcpWindowId
115
+ })}, 请使用 browse(action="list") 选择 managed=true 的受控 tab, 或用 manage(action="newPage") 创建受控页面`);
116
+ return tab;
117
+ }
118
+ var FRAME_PROBE_TIMEOUT_MS = 500;
119
+ var pendingDomFrameProbes = /* @__PURE__ */ new Map();
120
+ function recordDomFrameProbe(message, sender) {
121
+ const pending = pendingDomFrameProbes.get(message.token);
122
+ if (!pending || sender.tab?.id !== pending.tabId || sender.frameId === void 0 || sender.frameId === 0 || !Number.isInteger(message.index) || message.index < 0) return { accepted: false };
123
+ const frameIds = pending.frameIdsByIndex.get(message.index) ?? [];
124
+ if (!frameIds.includes(sender.frameId)) {
125
+ frameIds.push(sender.frameId);
126
+ pending.frameIdsByIndex.set(message.index, frameIds);
127
+ }
128
+ return { accepted: true };
129
+ }
130
+ /**
131
+ * 将父文档里的 DOM iframe 与 Chrome frameId 建立可验证映射
132
+ *
133
+ * webNavigation 可能包含页面 DOM 不可见的其他 Extension frame,因此不能按两个列表的顺序配对
134
+ * 这里向每个 DOM iframe 的 contentWindow 发送一次随机 probe,再从对应 content script 的 sender 读取实际 frameId
135
+ */
136
+ async function getDomFrameSnapshot(tabId, parentFrameId, selection) {
137
+ const token = crypto.randomUUID();
138
+ const pendingProbe = {
139
+ tabId,
140
+ frameIdsByIndex: /* @__PURE__ */ new Map()
141
+ };
142
+ pendingDomFrameProbes.set(token, pendingProbe);
143
+ try {
144
+ const parentResult = (await chrome.scripting.executeScript({
145
+ target: {
146
+ tabId,
147
+ frameIds: [parentFrameId]
148
+ },
149
+ func: async (probeToken, requestedSelection, timeoutMs) => {
150
+ const iframes = Array.from(document.querySelectorAll("iframe, frame"));
151
+ let selectedIndex = null;
152
+ let selectionStatus = "not-requested";
153
+ let selectionError;
154
+ if (typeof requestedSelection === "number") if (requestedSelection < 0 || requestedSelection >= iframes.length) selectionStatus = "out-of-range";
155
+ else {
156
+ selectedIndex = requestedSelection;
157
+ selectionStatus = "found";
158
+ }
159
+ else if (typeof requestedSelection === "string") try {
160
+ const selected = document.querySelector(requestedSelection);
161
+ if (!selected) selectionStatus = "not-found";
162
+ else if (!selected.matches("iframe, frame")) selectionStatus = "not-frame";
163
+ else {
164
+ selectedIndex = iframes.indexOf(selected);
165
+ selectionStatus = selectedIndex >= 0 ? "found" : "not-found";
166
+ }
167
+ } catch (error) {
168
+ selectionStatus = "invalid-selector";
169
+ selectionError = (error instanceof Error ? error.message : String(error)).slice(0, 300);
170
+ }
171
+ const acknowledged = await Promise.all(iframes.map((frame, index) => new Promise((resolve) => {
172
+ const child = frame.contentWindow;
173
+ if (!child) {
174
+ resolve(false);
175
+ return;
176
+ }
177
+ let settled = false;
178
+ const finish = (value) => {
179
+ if (settled) return;
180
+ settled = true;
181
+ window.clearTimeout(timer);
182
+ window.removeEventListener("message", onMessage);
183
+ resolve(value);
184
+ };
185
+ const onMessage = (event) => {
186
+ const data = event.data;
187
+ if (event.source === child && data?.type === "mcp-frame-probe-ack" && data.token === probeToken && data.index === index) finish(true);
188
+ };
189
+ const timer = window.setTimeout(() => finish(false), timeoutMs);
190
+ window.addEventListener("message", onMessage);
191
+ child.postMessage({
192
+ type: "mcp-frame-probe",
193
+ token: probeToken,
194
+ index
195
+ }, "*");
196
+ })));
197
+ return {
198
+ parent: {
199
+ title: document.title || null,
200
+ rect: {
201
+ x: 0,
202
+ y: 0,
203
+ width: window.innerWidth,
204
+ height: window.innerHeight
205
+ }
206
+ },
207
+ frames: iframes.map((frame, index) => {
208
+ const rect = frame.getBoundingClientRect();
209
+ return {
210
+ index,
211
+ acknowledged: acknowledged[index],
212
+ url: frame.src || "",
213
+ title: frame.title || null,
214
+ name: frame.name || null,
215
+ selector: frame.id ? `#${CSS.escape(frame.id)}` : `${frame.tagName.toLowerCase()}:nth-of-type(${index + 1})`,
216
+ rect: {
217
+ x: rect.x,
218
+ y: rect.y,
219
+ width: rect.width,
220
+ height: rect.height
221
+ },
222
+ contentOffset: {
223
+ x: rect.x + frame.clientLeft,
224
+ y: rect.y + frame.clientTop
225
+ }
226
+ };
227
+ }),
228
+ selectedIndex,
229
+ selectionStatus,
230
+ selectionError
231
+ };
232
+ },
233
+ args: [
234
+ token,
235
+ selection ?? null,
236
+ FRAME_PROBE_TIMEOUT_MS
237
+ ]
238
+ }))[0]?.result;
239
+ if (!parentResult) throw new ExpectedOperationError(`无法读取 parent frame ${parentFrameId} 的 iframe DOM`);
240
+ return {
241
+ parent: parentResult.parent,
242
+ frames: parentResult.frames.map(({ acknowledged, ...frame }) => {
243
+ const candidateFrameIds = acknowledged ? pendingProbe.frameIdsByIndex.get(frame.index) ?? [] : [];
244
+ return {
245
+ ...frame,
246
+ frameId: candidateFrameIds.length === 1 ? candidateFrameIds[0] : null,
247
+ candidateFrameIds: candidateFrameIds.slice(0, 10)
248
+ };
249
+ }),
250
+ selectedIndex: parentResult.selectedIndex,
251
+ selectionStatus: parentResult.selectionStatus,
252
+ ...parentResult.selectionError ? { selectionError: parentResult.selectionError } : {}
253
+ };
254
+ } finally {
255
+ pendingDomFrameProbes.delete(token);
256
+ }
257
+ }
258
+ /**
259
+ * 递归计算 iframe 在页面坐标系中的偏移量
260
+ *
261
+ * 对于嵌套 iframe,递归累加各层偏移,clientLeft/clientTop 补偿 iframe border
262
+ */
263
+ async function getFrameOffset(tabId, frameId) {
264
+ const targetFrame = (await chrome.webNavigation.getAllFrames({ tabId }))?.find((frame) => frame.frameId === frameId);
265
+ if (!targetFrame || targetFrame.parentFrameId === -1) return null;
266
+ if (isRestrictedUrl((await chrome.tabs.get(tabId)).url)) return null;
267
+ let parentOffset = {
268
+ x: 0,
269
+ y: 0
270
+ };
271
+ if (targetFrame.parentFrameId !== 0) {
272
+ const resolvedParentOffset = await getFrameOffset(tabId, targetFrame.parentFrameId);
273
+ if (!resolvedParentOffset) return null;
274
+ parentOffset = resolvedParentOffset;
275
+ }
276
+ const mappedFrame = (await getDomFrameSnapshot(tabId, targetFrame.parentFrameId)).frames.find((frame) => frame.frameId === frameId);
277
+ if (!mappedFrame) return null;
278
+ return {
279
+ x: parentOffset.x + mappedFrame.contentOffset.x,
280
+ y: parentOffset.y + mappedFrame.contentOffset.y
281
+ };
282
+ }
283
+ //#endregion
18
284
  //#region node_modules/zod/v4/core/core.js
19
285
  var _a$1;
20
286
  function $constructor(name, initializer, params) {
@@ -4567,172 +4833,10 @@ var EvaluateInFrameSchema = object({
4567
4833
  tabId: tabIdOpt,
4568
4834
  frameId: number().int().nonnegative(),
4569
4835
  expression: string().min(1),
4570
- timeout: number().nonnegative().optional()
4836
+ timeout: number().nonnegative().optional(),
4837
+ staleContextRetry: _enum(["never", "readOnly"]).default("never")
4571
4838
  });
4572
4839
  //#endregion
4573
- //#region src/background/tab-state.ts
4574
- /**
4575
- * Managed tab/window 状态与 storage 恢复。
4576
- */
4577
- var STORAGE_KEY = "mcp_managed_tab_ids";
4578
- var mcpTabGroupId = null;
4579
- var mcpWindowId = null;
4580
- var managedTabIds = /* @__PURE__ */ new Set();
4581
- function getMcpTabGroupId() {
4582
- return mcpTabGroupId;
4583
- }
4584
- function getMcpWindowId() {
4585
- return mcpWindowId;
4586
- }
4587
- function setMcpTabGroupId(groupId) {
4588
- mcpTabGroupId = groupId;
4589
- }
4590
- function setMcpWindowId(windowId) {
4591
- mcpWindowId = windowId;
4592
- }
4593
- async function markManagedTab(tabId) {
4594
- managedTabIds.add(tabId);
4595
- await chrome.storage.local.set({ [STORAGE_KEY]: [...managedTabIds] });
4596
- }
4597
- async function unmarkManagedTab(tabId) {
4598
- managedTabIds.delete(tabId);
4599
- await chrome.storage.local.set({ [STORAGE_KEY]: [...managedTabIds] });
4600
- }
4601
- function isManagedTab(tab, context) {
4602
- if (tab.id !== void 0 && managedTabIds.has(tab.id)) return true;
4603
- return context.mcpTabGroupId !== null && tab.groupId === context.mcpTabGroupId;
4604
- }
4605
- /** Extension 启动时调用,从 storage 恢复 managed tab 集合 */
4606
- async function restoreManagedTabs() {
4607
- const result = await chrome.storage.local.get(STORAGE_KEY);
4608
- const stored = Array.isArray(result[STORAGE_KEY]) ? result[STORAGE_KEY] : [];
4609
- for (const id of stored) managedTabIds.add(id);
4610
- const tabs = await chrome.tabs.query({});
4611
- const existingIds = new Set(tabs.map((t) => t.id).filter((id) => id !== void 0));
4612
- for (const id of managedTabIds) if (!existingIds.has(id)) managedTabIds.delete(id);
4613
- await chrome.storage.local.set({ [STORAGE_KEY]: [...managedTabIds] });
4614
- }
4615
- //#endregion
4616
- //#region src/background/action-utils.ts
4617
- /**
4618
- * Action 处理器共享工具函数
4619
- *
4620
- * 纯工具函数,不依赖任何实例状态,可被多个 handler 模块直接 import 使用
4621
- */
4622
- function isRestrictedUrl(url) {
4623
- if (!url) return true;
4624
- return url === "about:blank" || url.startsWith("about:") || url.startsWith("chrome://") || url.startsWith("chrome-extension://");
4625
- }
4626
- async function assertScriptable(tabId) {
4627
- const tab = await chrome.tabs.get(tabId);
4628
- if (isRestrictedUrl(tab.url)) throw new ExpectedOperationError(`Cannot execute on restricted URL "${tab.url}". Navigate to an http/https page first.`);
4629
- }
4630
- async function getTargetTabId(tabId) {
4631
- if (tabId !== void 0 && tabId !== null) {
4632
- try {
4633
- await chrome.tabs.get(tabId);
4634
- } catch {
4635
- throw new ExpectedOperationError(`Tab ${tabId} 不存在(可能已被关闭)`);
4636
- }
4637
- return tabId;
4638
- }
4639
- const [activeTab] = await chrome.tabs.query({
4640
- active: true,
4641
- currentWindow: true
4642
- });
4643
- if (!activeTab?.id) throw new ExpectedOperationError("No active tab found");
4644
- return activeTab.id;
4645
- }
4646
- function tabToInfo(tab, context) {
4647
- return {
4648
- id: tab.id,
4649
- url: tab.url || "",
4650
- title: tab.title || "",
4651
- active: tab.active,
4652
- windowId: tab.windowId,
4653
- index: tab.index,
4654
- groupId: tab.groupId,
4655
- pinned: tab.pinned,
4656
- incognito: tab.incognito,
4657
- managed: isManagedTab(tab, context),
4658
- status: tab.status || "unknown"
4659
- };
4660
- }
4661
- async function assertManagedTab(tabId, context, operation) {
4662
- let tab;
4663
- try {
4664
- tab = await chrome.tabs.get(tabId);
4665
- } catch {
4666
- throw new ExpectedOperationError(`Tab ${tabId} 不存在(可能已被关闭)`);
4667
- }
4668
- if (!isManagedTab(tab, context)) throw new ExpectedOperationError(`${operation} 拒绝操作非托管 tab, ${JSON.stringify({
4669
- tabId,
4670
- managed: false,
4671
- groupId: tab.groupId,
4672
- mcpTabGroupId: context.mcpTabGroupId,
4673
- windowId: tab.windowId,
4674
- mcpWindowId: context.mcpWindowId
4675
- })}, 请使用 browse(action="list") 选择 managed=true 的受控 tab, 或用 manage(action="newPage") 创建受控页面`);
4676
- return tab;
4677
- }
4678
- /**
4679
- * 递归计算 iframe 在页面坐标系中的偏移量
4680
- *
4681
- * 对于嵌套 iframe,递归累加各层偏移,
4682
- * 当多个 iframe 共享同一 URL 时,优先按同 URL 的出现顺序匹配,
4683
- * clientLeft/clientTop 补偿 iframe 的 border,确保坐标指向内容区域起点
4684
- */
4685
- async function getFrameOffset(tabId, frameId) {
4686
- const allFrames = await chrome.webNavigation.getAllFrames({ tabId });
4687
- if (!allFrames) return null;
4688
- const targetFrame = allFrames.find((f) => f.frameId === frameId);
4689
- if (!targetFrame || targetFrame.parentFrameId === -1) return null;
4690
- if (isRestrictedUrl((await chrome.tabs.get(tabId)).url)) return null;
4691
- let parentOffset = {
4692
- x: 0,
4693
- y: 0
4694
- };
4695
- if (targetFrame.parentFrameId !== 0) {
4696
- const po = await getFrameOffset(tabId, targetFrame.parentFrameId);
4697
- if (po) parentOffset = po;
4698
- }
4699
- const siblingFrames = allFrames.filter((f) => f.parentFrameId === targetFrame.parentFrameId && f.frameId !== targetFrame.parentFrameId);
4700
- const frameIndex = siblingFrames.findIndex((f) => f.frameId === frameId);
4701
- const sameUrlIndex = siblingFrames.filter((f) => f.url === targetFrame.url).findIndex((f) => f.frameId === frameId);
4702
- const localOffset = (await chrome.scripting.executeScript({
4703
- target: {
4704
- tabId,
4705
- frameIds: [targetFrame.parentFrameId]
4706
- },
4707
- world: "MAIN",
4708
- func: (frameUrl, frameIndex, sameUrlIndex) => {
4709
- const iframes = Array.from(document.querySelectorAll("iframe, frame"));
4710
- const urlMatches = frameUrl ? iframes.filter((iframe) => iframe.src === frameUrl) : [];
4711
- let target;
4712
- if (urlMatches.length === 1) target = urlMatches[0];
4713
- else if (sameUrlIndex >= 0 && sameUrlIndex < urlMatches.length) target = urlMatches[sameUrlIndex];
4714
- else if (frameIndex >= 0 && frameIndex < iframes.length) target = iframes[frameIndex];
4715
- else if (urlMatches.length > 0) target = urlMatches[0];
4716
- if (!target) return null;
4717
- const rect = target.getBoundingClientRect();
4718
- return {
4719
- x: rect.x + target.clientLeft,
4720
- y: rect.y + target.clientTop
4721
- };
4722
- },
4723
- args: [
4724
- targetFrame.url,
4725
- frameIndex,
4726
- sameUrlIndex
4727
- ]
4728
- }))[0]?.result;
4729
- if (!localOffset) return null;
4730
- return {
4731
- x: parentOffset.x + localOffset.x,
4732
- y: parentOffset.y + localOffset.y
4733
- };
4734
- }
4735
- //#endregion
4736
4840
  //#region src/background/debugger-manager.ts
4737
4841
  var DebuggerBlockedError = class extends ExpectedOperationError {
4738
4842
  code = "DEBUGGER_BLOCKED";
@@ -6510,53 +6614,28 @@ var FrameResolver = class {
6510
6614
  async resolveFrame(params, context) {
6511
6615
  const p = ResolveFrameSchema.parse(params);
6512
6616
  const tabId = await this.getManagedScriptableTabId(p.tabId, context, "resolve_frame");
6513
- const allFrames = await chrome.webNavigation.getAllFrames({ tabId });
6514
- if (!allFrames) throw new Error("Failed to get frames");
6515
- const allChildFrames = allFrames.filter((f) => f.frameId !== 0);
6516
- if (allChildFrames.length === 0) throw new Error("No iframes found in page");
6517
- const directChildFrames = allFrames.filter((f) => f.parentFrameId === 0 && f.frameId !== 0);
6518
- const info = (await chrome.scripting.executeScript({
6519
- target: {
6520
- tabId,
6521
- frameIds: [0]
6522
- },
6523
- world: "MAIN",
6524
- func: (frame) => {
6525
- const iframes = Array.from(document.querySelectorAll("iframe, frame"));
6526
- let target;
6527
- let index = -1;
6528
- if (typeof frame === "number") {
6529
- if (frame < 0 || frame >= iframes.length) return null;
6530
- target = iframes[frame];
6531
- index = frame;
6532
- } else {
6533
- target = document.querySelector(frame);
6534
- if (target) index = iframes.indexOf(target);
6535
- }
6536
- if (!target) return null;
6537
- let absoluteSrc = target.src || "";
6538
- if (absoluteSrc && !absoluteSrc.startsWith("http") && !absoluteSrc.startsWith("about:")) try {
6539
- absoluteSrc = new URL(absoluteSrc, location.href).href;
6540
- } catch {}
6541
- return {
6542
- src: absoluteSrc,
6543
- index
6544
- };
6545
- },
6546
- args: [p.frame]
6547
- }))[0]?.result;
6548
- if (!info) throw new ExpectedOperationError(`iframe not found: ${typeof p.frame === "number" ? `index ${p.frame}` : `selector "${p.frame}"`}`);
6549
- let matchedFrameId;
6550
- if (info.src) {
6551
- const urlMatches = allChildFrames.filter((f) => f.url === info.src);
6552
- if (urlMatches.length === 1) matchedFrameId = urlMatches[0].frameId;
6617
+ const snapshot = await getDomFrameSnapshot(tabId, 0, p.frame);
6618
+ if (snapshot.selectionStatus !== "found" || snapshot.selectedIndex === null) {
6619
+ const desc = typeof p.frame === "number" ? `index ${p.frame}` : `selector "${p.frame}"`;
6620
+ const detail = snapshot.selectionError ? `: ${snapshot.selectionError}` : "";
6621
+ throw new ExpectedOperationError(`iframe not found: ${desc} (${snapshot.selectionStatus})${detail}`);
6553
6622
  }
6554
- if (matchedFrameId === void 0 && info.index >= 0 && info.index < directChildFrames.length) matchedFrameId = directChildFrames[info.index].frameId;
6555
- if (matchedFrameId === void 0) throw new Error(`Cannot resolve iframe to frameId. src: "${info.src}", directChildren: ${directChildFrames.length}, allFrames: ${allChildFrames.length}`);
6556
- const offset = await getFrameOffset(tabId, matchedFrameId);
6623
+ const selected = snapshot.frames.find((frame) => frame.index === snapshot.selectedIndex);
6624
+ if (!selected || selected.frameId === null) throw new ExpectedOperationError(JSON.stringify({ error: {
6625
+ code: "FRAME_IDENTITY_UNAVAILABLE",
6626
+ message: "Cannot map the selected DOM iframe to one Chrome frameId",
6627
+ suggestion: "请等待 iframe 完成加载后重试;工具不会按 webNavigation 列表顺序选择其他 frame",
6628
+ context: {
6629
+ tabId,
6630
+ selection: p.frame,
6631
+ domIndex: snapshot.selectedIndex,
6632
+ candidateFrameIds: selected?.candidateFrameIds ?? [],
6633
+ domFrameCount: snapshot.frames.length
6634
+ }
6635
+ } }));
6557
6636
  return {
6558
- frameId: matchedFrameId,
6559
- offset
6637
+ frameId: selected.frameId,
6638
+ offset: selected.contentOffset
6560
6639
  };
6561
6640
  }
6562
6641
  async getAllFrames(params, context) {
@@ -6564,80 +6643,20 @@ var FrameResolver = class {
6564
6643
  const tabId = await this.getManagedScriptableTabId(p.tabId, context, "get_all_frames");
6565
6644
  const frames = await chrome.webNavigation.getAllFrames({ tabId });
6566
6645
  if (!frames) throw new Error("Failed to get frames");
6567
- let mainFrameInfo = {
6568
- title: null,
6569
- rect: {
6570
- x: 0,
6571
- y: 0,
6572
- width: 0,
6573
- height: 0
6574
- }
6575
- };
6576
- let domFrames = [];
6577
- try {
6578
- const pageFrames = (await chrome.scripting.executeScript({
6579
- target: {
6580
- tabId,
6581
- frameIds: [0]
6582
- },
6583
- world: "MAIN",
6584
- func: () => ({
6585
- main: {
6586
- title: document.title || null,
6587
- rect: {
6588
- x: 0,
6589
- y: 0,
6590
- width: window.innerWidth,
6591
- height: window.innerHeight
6592
- }
6593
- },
6594
- frames: Array.from(document.querySelectorAll("iframe, frame")).map((frame, index) => {
6595
- const el = frame;
6596
- const rect = el.getBoundingClientRect();
6597
- const selector = el.id ? `#${CSS.escape(el.id)}` : `${el.tagName.toLowerCase()}:nth-of-type(${index + 1})`;
6598
- return {
6599
- index,
6600
- url: el.src || "",
6601
- title: el.title || null,
6602
- name: el.name || null,
6603
- selector,
6604
- rect: {
6605
- x: rect.x,
6606
- y: rect.y,
6607
- width: rect.width,
6608
- height: rect.height
6609
- }
6610
- };
6611
- })
6612
- })
6613
- }))[0]?.result;
6614
- mainFrameInfo = pageFrames?.main ?? mainFrameInfo;
6615
- domFrames = pageFrames?.frames ?? [];
6616
- } catch {
6617
- domFrames = [];
6618
- }
6619
- const directChildFrames = frames.filter((frame) => frame.parentFrameId === 0 && frame.frameId !== 0);
6620
- const getDomFrame = (frame) => {
6621
- if (frame.parentFrameId !== 0) return;
6622
- const directIndex = directChildFrames.findIndex((candidate) => candidate.frameId === frame.frameId);
6623
- const byIndex = directIndex >= 0 ? domFrames[directIndex] : void 0;
6624
- if (byIndex) return byIndex;
6625
- const navigationMatches = directChildFrames.filter((candidate) => candidate.url === frame.url);
6626
- const domMatches = domFrames.filter((candidate) => candidate.url === frame.url);
6627
- return navigationMatches.length === 1 && domMatches.length === 1 ? domMatches[0] : void 0;
6628
- };
6646
+ const snapshot = await getDomFrameSnapshot(tabId, 0);
6647
+ const domFramesByFrameId = new Map(snapshot.frames.filter((frame) => frame.frameId !== null).map((frame) => [frame.frameId, frame]));
6629
6648
  return { frames: frames.map((frame, index) => {
6630
6649
  const isMainFrame = frame.frameId === 0;
6631
- const dom = isMainFrame ? void 0 : getDomFrame(frame);
6650
+ const dom = isMainFrame ? void 0 : domFramesByFrameId.get(frame.frameId);
6632
6651
  return {
6633
6652
  index,
6634
6653
  frameId: frame.frameId,
6635
6654
  parentFrameId: isMainFrame ? null : frame.parentFrameId,
6636
6655
  url: frame.url,
6637
- title: isMainFrame ? mainFrameInfo.title : dom?.title ?? null,
6656
+ title: isMainFrame ? snapshot.parent.title : dom?.title ?? null,
6638
6657
  name: isMainFrame ? null : dom?.name ?? null,
6639
6658
  selector: isMainFrame ? null : dom?.selector ?? null,
6640
- rect: isMainFrame ? mainFrameInfo.rect : dom?.rect ?? null
6659
+ rect: isMainFrame ? snapshot.parent.rect : dom?.rect ?? null
6641
6660
  };
6642
6661
  }) };
6643
6662
  }
@@ -6656,7 +6675,8 @@ var FrameResolver = class {
6656
6675
  const originalFrame = await this.getExtensionFrameIdentity(tabId, p.frameId);
6657
6676
  let retryAttempted = false;
6658
6677
  let retryReason;
6659
- for (let attempt = 0; attempt < 2; attempt++) {
6678
+ const maxAttempts = p.staleContextRetry === "readOnly" ? 2 : 1;
6679
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
6660
6680
  const remaining = p.timeout === void 0 ? void 0 : p.timeout - (Date.now() - startedAt);
6661
6681
  if (remaining !== void 0 && remaining <= 0) throw this.frameEvaluationError("FRAME_EVALUATION_TIMEOUT", "Frame evaluation timed out before resolution", {
6662
6682
  tabId,
@@ -6713,6 +6733,7 @@ var FrameResolver = class {
6713
6733
  ...await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", evalParams),
6714
6734
  retryAttempted,
6715
6735
  retryReason,
6736
+ staleContextRetry: p.staleContextRetry,
6716
6737
  frameContext: {
6717
6738
  tabId,
6718
6739
  extensionFrameId: identity.frameId,
@@ -6736,6 +6757,14 @@ var FrameResolver = class {
6736
6757
  retryReason
6737
6758
  });
6738
6759
  if (!staleContext) throw error;
6760
+ if (p.staleContextRetry !== "readOnly") throw this.frameEvaluationError("FRAME_STALE_CONTEXT", "Execution context became stale; script was not replayed because staleContextRetry is never", {
6761
+ tabId,
6762
+ ...this.frameIdentitySummary(identity),
6763
+ cdpFrameId: resolved.cdpFrameId,
6764
+ executionContextId: targetCtx.id,
6765
+ retryAttempted: false,
6766
+ staleContextRetry: p.staleContextRetry
6767
+ });
6739
6768
  retryAttempted = true;
6740
6769
  retryReason = message.slice(0, 500);
6741
6770
  this.debuggerManager.invalidateExecutionContext(tabId, targetCtx.id);
@@ -8260,6 +8289,25 @@ function randomHex(bytes = 16) {
8260
8289
  crypto.getRandomValues(data);
8261
8290
  return bytesToHex(data);
8262
8291
  }
8292
+ var backgroundBundleHashPromise;
8293
+ async function loadBackgroundBundleHash() {
8294
+ try {
8295
+ const response = await fetch(chrome.runtime.getURL("service-worker-loader.js"), { cache: "no-store" });
8296
+ if (!response.ok) {
8297
+ console.warn(`[HTTP] Cannot read Extension service worker loader: HTTP ${response.status}`);
8298
+ return;
8299
+ }
8300
+ const digest = await crypto.subtle.digest("SHA-256", await response.arrayBuffer());
8301
+ return `sha256:${bytesToHex(new Uint8Array(digest))}`;
8302
+ } catch (error) {
8303
+ console.warn("[HTTP] Cannot compute Extension background bundle hash:", error instanceof Error ? error.message : String(error));
8304
+ return;
8305
+ }
8306
+ }
8307
+ function getBackgroundBundleHash() {
8308
+ backgroundBundleHashPromise ??= loadBackgroundBundleHash();
8309
+ return backgroundBundleHashPromise;
8310
+ }
8263
8311
  async function hmacHex(token, payload) {
8264
8312
  const encoder = new TextEncoder();
8265
8313
  const key = await crypto.subtle.importKey("raw", encoder.encode(token), {
@@ -8427,10 +8475,14 @@ var HttpClient = class {
8427
8475
  };
8428
8476
  this.connections.set(port, conn);
8429
8477
  this.resetHeartbeat(port);
8430
- ws.send(JSON.stringify({
8431
- type: "hello",
8432
- version: chrome.runtime.getManifest().version
8433
- }));
8478
+ getBackgroundBundleHash().then((backgroundBundleHash) => {
8479
+ if (ws.readyState !== WebSocket.OPEN) return;
8480
+ ws.send(JSON.stringify({
8481
+ type: "hello",
8482
+ version: chrome.runtime.getManifest().version,
8483
+ ...backgroundBundleHash ? { backgroundBundleHash } : {}
8484
+ }));
8485
+ });
8434
8486
  console.log(`[HTTP] Connected to MCP Server at port ${port} (total: ${this.connections.size})`);
8435
8487
  this.statusHandler?.("connected", this.connections.size);
8436
8488
  resolve(true);
@@ -8635,11 +8687,11 @@ httpClient.onStatusChange((status, count) => {
8635
8687
  updateBadge(status, count);
8636
8688
  broadcastStatus(status, count);
8637
8689
  });
8638
- chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
8639
- handleInternalMessage(message).then(sendResponse);
8690
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
8691
+ handleInternalMessage(message, sender).then(sendResponse);
8640
8692
  return true;
8641
8693
  });
8642
- async function handleInternalMessage(message) {
8694
+ async function handleInternalMessage(message, sender) {
8643
8695
  switch (message.type) {
8644
8696
  case "CONNECT": return httpClient.connect();
8645
8697
  case "DISCONNECT":
@@ -8657,6 +8709,7 @@ async function handleInternalMessage(message) {
8657
8709
  pairingTokenConfigured: await httpClient.hasPairingToken(),
8658
8710
  allowInsecureNoToken: await httpClient.isAllowInsecureNoToken()
8659
8711
  };
8712
+ case "MCP_FRAME_PROBE": return recordDomFrameProbe(message, sender);
8660
8713
  default: return { error: "Unknown message type" };
8661
8714
  }
8662
8715
  }