bb-browser 0.1.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.
@@ -0,0 +1,339 @@
1
+ let isRecording = false;
2
+ function getXPath(element) {
3
+ if (element.id) {
4
+ return `//*[@id="${element.id}"]`;
5
+ }
6
+ if (element === document.body) {
7
+ return "/html/body";
8
+ }
9
+ let index = 1;
10
+ const siblings = element.parentNode?.children;
11
+ if (siblings) {
12
+ for (let i = 0; i < siblings.length; i++) {
13
+ const sibling = siblings[i];
14
+ if (sibling === element) {
15
+ const parentPath = element.parentElement ? getXPath(element.parentElement) : "";
16
+ return `${parentPath}/${element.tagName.toLowerCase()}[${index}]`;
17
+ }
18
+ if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
19
+ index++;
20
+ }
21
+ }
22
+ }
23
+ return element.tagName.toLowerCase();
24
+ }
25
+ function getHighlightIndex(element) {
26
+ let current = element;
27
+ while (current) {
28
+ const attr = current.getAttribute("data-highlight-index");
29
+ if (attr !== null) {
30
+ const index = parseInt(attr, 10);
31
+ if (!isNaN(index)) {
32
+ return index;
33
+ }
34
+ }
35
+ current = current.parentElement;
36
+ }
37
+ return void 0;
38
+ }
39
+ function extractSemanticInfo(element) {
40
+ const tag = element.tagName.toLowerCase();
41
+ let role = element.getAttribute("role") || "";
42
+ if (!role) {
43
+ switch (tag) {
44
+ case "button":
45
+ role = "button";
46
+ break;
47
+ case "a":
48
+ role = "link";
49
+ break;
50
+ case "input": {
51
+ const type = element.type;
52
+ switch (type) {
53
+ case "text":
54
+ case "email":
55
+ case "password":
56
+ case "search":
57
+ case "tel":
58
+ case "url":
59
+ role = "textbox";
60
+ break;
61
+ case "checkbox":
62
+ role = "checkbox";
63
+ break;
64
+ case "radio":
65
+ role = "radio";
66
+ break;
67
+ case "submit":
68
+ case "button":
69
+ role = "button";
70
+ break;
71
+ default:
72
+ role = "textbox";
73
+ }
74
+ break;
75
+ }
76
+ case "textarea":
77
+ role = "textbox";
78
+ break;
79
+ case "select":
80
+ role = "combobox";
81
+ break;
82
+ case "img":
83
+ role = "img";
84
+ break;
85
+ default:
86
+ role = tag;
87
+ }
88
+ }
89
+ let name = "";
90
+ name = element.getAttribute("aria-label") || "";
91
+ if (!name) {
92
+ const labelledBy = element.getAttribute("aria-labelledby");
93
+ if (labelledBy) {
94
+ const labelElement = document.getElementById(labelledBy);
95
+ if (labelElement) {
96
+ name = labelElement.textContent?.trim() || "";
97
+ }
98
+ }
99
+ }
100
+ if (!name) {
101
+ if (element.id) {
102
+ const label = document.querySelector(`label[for="${element.id}"]`);
103
+ if (label) {
104
+ name = label.textContent?.trim() || "";
105
+ }
106
+ }
107
+ }
108
+ if (!name) {
109
+ name = element.getAttribute("title") || element.getAttribute("alt") || element.placeholder || element.textContent?.trim().slice(0, 50) || "";
110
+ }
111
+ return { role, name, tag };
112
+ }
113
+ function getCssSelector(element) {
114
+ const parts = [];
115
+ let current = element;
116
+ while (current && current !== document.body) {
117
+ let selector = current.tagName.toLowerCase();
118
+ if (current.id) {
119
+ selector = `#${current.id}`;
120
+ parts.unshift(selector);
121
+ break;
122
+ }
123
+ if (current.className) {
124
+ const classes = current.className.split(/\s+/).filter((c) => c && /^[a-zA-Z_]/.test(c));
125
+ if (classes.length > 0) {
126
+ selector += "." + classes.slice(0, 2).join(".");
127
+ }
128
+ }
129
+ parts.unshift(selector);
130
+ current = current.parentElement;
131
+ }
132
+ return parts.join(" > ");
133
+ }
134
+ function handleClick(event) {
135
+ if (!isRecording) return;
136
+ const target = event.target;
137
+ if (!target) return;
138
+ const semanticInfo = extractSemanticInfo(target);
139
+ const inputType = target.type?.toLowerCase();
140
+ const isCheckbox = target.tagName.toLowerCase() === "input" && inputType === "checkbox";
141
+ const traceEvent = {
142
+ type: isCheckbox ? "check" : "click",
143
+ timestamp: Date.now(),
144
+ url: window.location.href,
145
+ ref: getHighlightIndex(target),
146
+ xpath: getXPath(target),
147
+ cssSelector: getCssSelector(target),
148
+ elementRole: semanticInfo.role,
149
+ elementName: semanticInfo.name,
150
+ elementTag: semanticInfo.tag,
151
+ // 如果是 checkbox,记录状态
152
+ checked: isCheckbox ? target.checked : void 0
153
+ };
154
+ console.log("[Trace] Click event:", traceEvent);
155
+ chrome.runtime.sendMessage({ type: "TRACE_EVENT", payload: traceEvent });
156
+ }
157
+ let inputDebounceTimer = null;
158
+ let lastInputElement = null;
159
+ let lastInputValue = "";
160
+ function handleInput(event) {
161
+ if (!isRecording) return;
162
+ const target = event.target;
163
+ if (!target || !("value" in target)) return;
164
+ if (inputDebounceTimer) {
165
+ clearTimeout(inputDebounceTimer);
166
+ }
167
+ lastInputElement = target;
168
+ lastInputValue = target.value;
169
+ inputDebounceTimer = setTimeout(() => {
170
+ if (!lastInputElement) return;
171
+ const semanticInfo = extractSemanticInfo(lastInputElement);
172
+ const isPassword = lastInputElement.type === "password";
173
+ const traceEvent = {
174
+ type: "fill",
175
+ timestamp: Date.now(),
176
+ url: window.location.href,
177
+ ref: getHighlightIndex(lastInputElement),
178
+ xpath: getXPath(lastInputElement),
179
+ cssSelector: getCssSelector(lastInputElement),
180
+ value: isPassword ? "********" : lastInputValue,
181
+ elementRole: semanticInfo.role,
182
+ elementName: semanticInfo.name,
183
+ elementTag: semanticInfo.tag
184
+ };
185
+ console.log("[Trace] Input event:", traceEvent);
186
+ chrome.runtime.sendMessage({ type: "TRACE_EVENT", payload: traceEvent });
187
+ inputDebounceTimer = null;
188
+ lastInputElement = null;
189
+ lastInputValue = "";
190
+ }, 500);
191
+ }
192
+ function handleChange(event) {
193
+ if (!isRecording) return;
194
+ const target = event.target;
195
+ if (!target || target.tagName !== "SELECT") return;
196
+ const semanticInfo = extractSemanticInfo(target);
197
+ const selectedOption = target.options[target.selectedIndex];
198
+ const traceEvent = {
199
+ type: "select",
200
+ timestamp: Date.now(),
201
+ url: window.location.href,
202
+ ref: getHighlightIndex(target),
203
+ xpath: getXPath(target),
204
+ cssSelector: getCssSelector(target),
205
+ value: selectedOption?.text || target.value,
206
+ elementRole: semanticInfo.role,
207
+ elementName: semanticInfo.name,
208
+ elementTag: semanticInfo.tag
209
+ };
210
+ console.log("[Trace] Select event:", traceEvent);
211
+ chrome.runtime.sendMessage({ type: "TRACE_EVENT", payload: traceEvent });
212
+ }
213
+ const CAPTURED_KEYS = /* @__PURE__ */ new Set([
214
+ "Enter",
215
+ "Tab",
216
+ "Escape",
217
+ "ArrowUp",
218
+ "ArrowDown",
219
+ "ArrowLeft",
220
+ "ArrowRight",
221
+ "Home",
222
+ "End",
223
+ "PageUp",
224
+ "PageDown",
225
+ "Backspace",
226
+ "Delete"
227
+ ]);
228
+ function handleKeydown(event) {
229
+ if (!isRecording) return;
230
+ const key = event.key;
231
+ let keyToLog = "";
232
+ if (CAPTURED_KEYS.has(key)) {
233
+ keyToLog = key;
234
+ } else if ((event.ctrlKey || event.metaKey) && key.length === 1 && /[a-zA-Z0-9]/.test(key)) {
235
+ const modifier = event.metaKey ? "Meta" : "Control";
236
+ keyToLog = `${modifier}+${key.toLowerCase()}`;
237
+ }
238
+ if (!keyToLog) return;
239
+ const target = event.target;
240
+ const semanticInfo = target ? extractSemanticInfo(target) : { role: "", name: "", tag: "document" };
241
+ const traceEvent = {
242
+ type: "press",
243
+ timestamp: Date.now(),
244
+ url: window.location.href,
245
+ ref: target ? getHighlightIndex(target) : void 0,
246
+ xpath: target ? getXPath(target) : void 0,
247
+ cssSelector: target ? getCssSelector(target) : void 0,
248
+ key: keyToLog,
249
+ elementRole: semanticInfo.role,
250
+ elementName: semanticInfo.name,
251
+ elementTag: semanticInfo.tag
252
+ };
253
+ console.log("[Trace] Keydown event:", traceEvent);
254
+ chrome.runtime.sendMessage({ type: "TRACE_EVENT", payload: traceEvent });
255
+ }
256
+ let scrollDebounceTimer = null;
257
+ let scrollStartY = 0;
258
+ function handleScroll() {
259
+ if (!isRecording) return;
260
+ if (!scrollDebounceTimer) {
261
+ scrollStartY = window.scrollY;
262
+ } else {
263
+ clearTimeout(scrollDebounceTimer);
264
+ }
265
+ scrollDebounceTimer = setTimeout(() => {
266
+ const scrollEndY = window.scrollY;
267
+ const deltaY = scrollEndY - scrollStartY;
268
+ if (Math.abs(deltaY) < 50) {
269
+ scrollDebounceTimer = null;
270
+ return;
271
+ }
272
+ const direction = deltaY > 0 ? "down" : "up";
273
+ const pixels = Math.abs(deltaY);
274
+ const traceEvent = {
275
+ type: "scroll",
276
+ timestamp: Date.now(),
277
+ url: window.location.href,
278
+ direction,
279
+ pixels
280
+ };
281
+ console.log("[Trace] Scroll event:", traceEvent);
282
+ chrome.runtime.sendMessage({ type: "TRACE_EVENT", payload: traceEvent });
283
+ scrollDebounceTimer = null;
284
+ }, 300);
285
+ }
286
+ function startRecording() {
287
+ if (isRecording) return;
288
+ console.log("[Trace] Starting recording on:", window.location.href);
289
+ isRecording = true;
290
+ document.addEventListener("click", handleClick, true);
291
+ document.addEventListener("input", handleInput, true);
292
+ document.addEventListener("change", handleChange, true);
293
+ document.addEventListener("keydown", handleKeydown, true);
294
+ window.addEventListener("scroll", handleScroll, { passive: true });
295
+ }
296
+ function stopRecording() {
297
+ if (!isRecording) return;
298
+ console.log("[Trace] Stopping recording on:", window.location.href);
299
+ isRecording = false;
300
+ document.removeEventListener("click", handleClick, true);
301
+ document.removeEventListener("input", handleInput, true);
302
+ document.removeEventListener("change", handleChange, true);
303
+ document.removeEventListener("keydown", handleKeydown, true);
304
+ window.removeEventListener("scroll", handleScroll);
305
+ if (inputDebounceTimer) {
306
+ clearTimeout(inputDebounceTimer);
307
+ inputDebounceTimer = null;
308
+ }
309
+ if (scrollDebounceTimer) {
310
+ clearTimeout(scrollDebounceTimer);
311
+ scrollDebounceTimer = null;
312
+ }
313
+ }
314
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
315
+ if (message.type === "TRACE_START") {
316
+ startRecording();
317
+ sendResponse({ success: true });
318
+ } else if (message.type === "TRACE_STOP") {
319
+ stopRecording();
320
+ sendResponse({ success: true });
321
+ } else if (message.type === "TRACE_STATUS") {
322
+ sendResponse({ recording: isRecording });
323
+ }
324
+ return true;
325
+ });
326
+ chrome.runtime.sendMessage({ type: "GET_TRACE_STATUS" }, (response) => {
327
+ if (chrome.runtime.lastError) {
328
+ console.log("[Trace] Error getting initial status:", chrome.runtime.lastError.message);
329
+ return;
330
+ }
331
+ if (response?.recording) {
332
+ startRecording();
333
+ }
334
+ });
335
+ window.addEventListener("beforeunload", () => {
336
+ stopRecording();
337
+ });
338
+ console.log("[Trace] Content script loaded on:", window.location.href);
339
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sources":["../../src/content/trace.ts"],"sourcesContent":["/**\n * Trace Content Script\n * \n * 负责在页面中监听用户操作(click, input, select, keydown, scroll)\n * 并将事件发送到 background service worker\n */\n\n// 录制状态\nlet isRecording = false;\n\n// ============================================================================\n// 工具函数\n// ============================================================================\n\n/**\n * 生成元素的 XPath\n */\nfunction getXPath(element: HTMLElement): string {\n if (element.id) {\n return `//*[@id=\"${element.id}\"]`;\n }\n \n if (element === document.body) {\n return '/html/body';\n }\n\n let index = 1;\n const siblings = element.parentNode?.children;\n if (siblings) {\n for (let i = 0; i < siblings.length; i++) {\n const sibling = siblings[i];\n if (sibling === element) {\n const parentPath = element.parentElement \n ? getXPath(element.parentElement) \n : '';\n return `${parentPath}/${element.tagName.toLowerCase()}[${index}]`;\n }\n if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {\n index++;\n }\n }\n }\n \n return element.tagName.toLowerCase();\n}\n\n/**\n * 获取元素的 highlightIndex(向上查找)\n * buildDomTree.js 会在元素上设置 data-highlight-index 属性\n */\nfunction getHighlightIndex(element: HTMLElement): number | undefined {\n let current: HTMLElement | null = element;\n \n while (current) {\n const attr = current.getAttribute('data-highlight-index');\n if (attr !== null) {\n const index = parseInt(attr, 10);\n if (!isNaN(index)) {\n return index;\n }\n }\n current = current.parentElement;\n }\n \n return undefined;\n}\n\n/**\n * 提取元素的语义信息\n */\nfunction extractSemanticInfo(element: HTMLElement): {\n role: string;\n name: string;\n tag: string;\n} {\n const tag = element.tagName.toLowerCase();\n \n // 获取 role(优先使用 aria-role,其次推断)\n let role = element.getAttribute('role') || '';\n if (!role) {\n switch (tag) {\n case 'button':\n role = 'button';\n break;\n case 'a':\n role = 'link';\n break;\n case 'input': {\n const type = (element as HTMLInputElement).type;\n switch (type) {\n case 'text':\n case 'email':\n case 'password':\n case 'search':\n case 'tel':\n case 'url':\n role = 'textbox';\n break;\n case 'checkbox':\n role = 'checkbox';\n break;\n case 'radio':\n role = 'radio';\n break;\n case 'submit':\n case 'button':\n role = 'button';\n break;\n default:\n role = 'textbox';\n }\n break;\n }\n case 'textarea':\n role = 'textbox';\n break;\n case 'select':\n role = 'combobox';\n break;\n case 'img':\n role = 'img';\n break;\n default:\n role = tag;\n }\n }\n\n // 获取 name(可访问名称)\n let name = '';\n \n // 优先级:aria-label > aria-labelledby > title > alt > placeholder > textContent\n name = element.getAttribute('aria-label') || '';\n \n if (!name) {\n const labelledBy = element.getAttribute('aria-labelledby');\n if (labelledBy) {\n const labelElement = document.getElementById(labelledBy);\n if (labelElement) {\n name = labelElement.textContent?.trim() || '';\n }\n }\n }\n \n if (!name) {\n // 对于 input 元素,查找关联的 label\n if (element.id) {\n const label = document.querySelector(`label[for=\"${element.id}\"]`);\n if (label) {\n name = label.textContent?.trim() || '';\n }\n }\n }\n \n if (!name) {\n name = element.getAttribute('title') || \n element.getAttribute('alt') || \n (element as HTMLInputElement).placeholder ||\n element.textContent?.trim().slice(0, 50) || '';\n }\n\n return { role, name, tag };\n}\n\n/**\n * 生成 CSS 选择器\n */\nfunction getCssSelector(element: HTMLElement): string {\n const parts: string[] = [];\n let current: HTMLElement | null = element;\n \n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase();\n \n if (current.id) {\n selector = `#${current.id}`;\n parts.unshift(selector);\n break;\n }\n \n if (current.className) {\n const classes = current.className.split(/\\s+/).filter(c => c && /^[a-zA-Z_]/.test(c));\n if (classes.length > 0) {\n selector += '.' + classes.slice(0, 2).join('.');\n }\n }\n \n parts.unshift(selector);\n current = current.parentElement;\n }\n \n return parts.join(' > ');\n}\n\n// ============================================================================\n// 事件处理器\n// ============================================================================\n\n/**\n * 处理点击事件\n */\nfunction handleClick(event: MouseEvent): void {\n if (!isRecording) return;\n \n const target = event.target as HTMLElement;\n if (!target) return;\n\n const semanticInfo = extractSemanticInfo(target);\n const inputType = (target as HTMLInputElement).type?.toLowerCase();\n \n // 判断是否是 checkbox\n const isCheckbox = target.tagName.toLowerCase() === 'input' && inputType === 'checkbox';\n \n const traceEvent = {\n type: isCheckbox ? 'check' : 'click' as const,\n timestamp: Date.now(),\n url: window.location.href,\n ref: getHighlightIndex(target),\n xpath: getXPath(target),\n cssSelector: getCssSelector(target),\n elementRole: semanticInfo.role,\n elementName: semanticInfo.name,\n elementTag: semanticInfo.tag,\n // 如果是 checkbox,记录状态\n checked: isCheckbox ? (target as HTMLInputElement).checked : undefined,\n };\n\n console.log('[Trace] Click event:', traceEvent);\n chrome.runtime.sendMessage({ type: 'TRACE_EVENT', payload: traceEvent });\n}\n\n/**\n * 处理输入事件(防抖)\n */\nlet inputDebounceTimer: ReturnType<typeof setTimeout> | null = null;\nlet lastInputElement: HTMLElement | null = null;\nlet lastInputValue = '';\n\nfunction handleInput(event: Event): void {\n if (!isRecording) return;\n \n const target = event.target as HTMLInputElement | HTMLTextAreaElement;\n if (!target || !('value' in target)) return;\n\n // 防抖处理:同一个元素的连续输入合并\n if (inputDebounceTimer) {\n clearTimeout(inputDebounceTimer);\n }\n \n lastInputElement = target;\n lastInputValue = target.value;\n\n inputDebounceTimer = setTimeout(() => {\n if (!lastInputElement) return;\n \n const semanticInfo = extractSemanticInfo(lastInputElement);\n const isPassword = (lastInputElement as HTMLInputElement).type === 'password';\n \n const traceEvent = {\n type: 'fill' as const,\n timestamp: Date.now(),\n url: window.location.href,\n ref: getHighlightIndex(lastInputElement),\n xpath: getXPath(lastInputElement),\n cssSelector: getCssSelector(lastInputElement),\n value: isPassword ? '********' : lastInputValue,\n elementRole: semanticInfo.role,\n elementName: semanticInfo.name,\n elementTag: semanticInfo.tag,\n };\n\n console.log('[Trace] Input event:', traceEvent);\n chrome.runtime.sendMessage({ type: 'TRACE_EVENT', payload: traceEvent });\n \n inputDebounceTimer = null;\n lastInputElement = null;\n lastInputValue = '';\n }, 500); // 500ms 防抖\n}\n\n/**\n * 处理 select 变化事件\n */\nfunction handleChange(event: Event): void {\n if (!isRecording) return;\n \n const target = event.target as HTMLSelectElement;\n if (!target || target.tagName !== 'SELECT') return;\n\n const semanticInfo = extractSemanticInfo(target);\n const selectedOption = target.options[target.selectedIndex];\n \n const traceEvent = {\n type: 'select' as const,\n timestamp: Date.now(),\n url: window.location.href,\n ref: getHighlightIndex(target),\n xpath: getXPath(target),\n cssSelector: getCssSelector(target),\n value: selectedOption?.text || target.value,\n elementRole: semanticInfo.role,\n elementName: semanticInfo.name,\n elementTag: semanticInfo.tag,\n };\n\n console.log('[Trace] Select event:', traceEvent);\n chrome.runtime.sendMessage({ type: 'TRACE_EVENT', payload: traceEvent });\n}\n\n/**\n * 处理键盘事件\n */\nconst CAPTURED_KEYS = new Set([\n 'Enter', 'Tab', 'Escape',\n 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',\n 'Home', 'End', 'PageUp', 'PageDown',\n 'Backspace', 'Delete',\n]);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (!isRecording) return;\n \n const key = event.key;\n let keyToLog = '';\n\n // 检查是否是需要捕获的特殊键\n if (CAPTURED_KEYS.has(key)) {\n keyToLog = key;\n }\n // 检查 Ctrl/Cmd + 字母/数字 组合键\n else if ((event.ctrlKey || event.metaKey) && key.length === 1 && /[a-zA-Z0-9]/.test(key)) {\n const modifier = event.metaKey ? 'Meta' : 'Control';\n keyToLog = `${modifier}+${key.toLowerCase()}`;\n }\n\n if (!keyToLog) return;\n\n const target = event.target as HTMLElement;\n const semanticInfo = target ? extractSemanticInfo(target) : { role: '', name: '', tag: 'document' };\n \n const traceEvent = {\n type: 'press' as const,\n timestamp: Date.now(),\n url: window.location.href,\n ref: target ? getHighlightIndex(target) : undefined,\n xpath: target ? getXPath(target) : undefined,\n cssSelector: target ? getCssSelector(target) : undefined,\n key: keyToLog,\n elementRole: semanticInfo.role,\n elementName: semanticInfo.name,\n elementTag: semanticInfo.tag,\n };\n\n console.log('[Trace] Keydown event:', traceEvent);\n chrome.runtime.sendMessage({ type: 'TRACE_EVENT', payload: traceEvent });\n}\n\n/**\n * 处理滚动事件(防抖)\n */\nlet scrollDebounceTimer: ReturnType<typeof setTimeout> | null = null;\nlet scrollStartY = 0;\n\nfunction handleScroll(): void {\n if (!isRecording) return;\n\n if (!scrollDebounceTimer) {\n scrollStartY = window.scrollY;\n } else {\n clearTimeout(scrollDebounceTimer);\n }\n\n scrollDebounceTimer = setTimeout(() => {\n const scrollEndY = window.scrollY;\n const deltaY = scrollEndY - scrollStartY;\n \n if (Math.abs(deltaY) < 50) {\n scrollDebounceTimer = null;\n return; // 忽略小幅滚动\n }\n\n const direction = deltaY > 0 ? 'down' : 'up';\n const pixels = Math.abs(deltaY);\n \n const traceEvent = {\n type: 'scroll' as const,\n timestamp: Date.now(),\n url: window.location.href,\n direction: direction as 'up' | 'down',\n pixels,\n };\n\n console.log('[Trace] Scroll event:', traceEvent);\n chrome.runtime.sendMessage({ type: 'TRACE_EVENT', payload: traceEvent });\n \n scrollDebounceTimer = null;\n }, 300); // 300ms 防抖\n}\n\n// ============================================================================\n// 录制控制\n// ============================================================================\n\n/**\n * 开始录制\n */\nfunction startRecording(): void {\n if (isRecording) return;\n \n console.log('[Trace] Starting recording on:', window.location.href);\n isRecording = true;\n \n document.addEventListener('click', handleClick, true);\n document.addEventListener('input', handleInput, true);\n document.addEventListener('change', handleChange, true);\n document.addEventListener('keydown', handleKeydown, true);\n window.addEventListener('scroll', handleScroll, { passive: true });\n}\n\n/**\n * 停止录制\n */\nfunction stopRecording(): void {\n if (!isRecording) return;\n \n console.log('[Trace] Stopping recording on:', window.location.href);\n isRecording = false;\n \n document.removeEventListener('click', handleClick, true);\n document.removeEventListener('input', handleInput, true);\n document.removeEventListener('change', handleChange, true);\n document.removeEventListener('keydown', handleKeydown, true);\n window.removeEventListener('scroll', handleScroll);\n \n // 清理防抖定时器\n if (inputDebounceTimer) {\n clearTimeout(inputDebounceTimer);\n inputDebounceTimer = null;\n }\n if (scrollDebounceTimer) {\n clearTimeout(scrollDebounceTimer);\n scrollDebounceTimer = null;\n }\n}\n\n// ============================================================================\n// 消息监听\n// ============================================================================\n\n// 监听来自 background 的消息\nchrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {\n if (message.type === 'TRACE_START') {\n startRecording();\n sendResponse({ success: true });\n } else if (message.type === 'TRACE_STOP') {\n stopRecording();\n sendResponse({ success: true });\n } else if (message.type === 'TRACE_STATUS') {\n sendResponse({ recording: isRecording });\n }\n return true;\n});\n\n// 请求初始状态\nchrome.runtime.sendMessage({ type: 'GET_TRACE_STATUS' }, (response) => {\n if (chrome.runtime.lastError) {\n console.log('[Trace] Error getting initial status:', chrome.runtime.lastError.message);\n return;\n }\n if (response?.recording) {\n startRecording();\n }\n});\n\n// 页面卸载时清理\nwindow.addEventListener('beforeunload', () => {\n stopRecording();\n});\n\nconsole.log('[Trace] Content script loaded on:', window.location.href);\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "bb-browser",
4
+ "version": "0.1.0",
5
+ "description": "Browser automation for AI agents",
6
+ "permissions": ["tabs", "activeTab", "scripting", "debugger", "storage", "alarms"],
7
+ "host_permissions": ["<all_urls>"],
8
+ "background": {
9
+ "service_worker": "background.js",
10
+ "type": "module"
11
+ },
12
+ "content_scripts": [
13
+ {
14
+ "matches": ["http://*/*", "https://*/*"],
15
+ "js": ["content/trace.js"],
16
+ "run_at": "document_idle"
17
+ }
18
+ ],
19
+ "web_accessible_resources": [
20
+ {
21
+ "resources": ["buildDomTree.js"],
22
+ "matches": ["<all_urls>"]
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "bb-browser",
4
+ "version": "0.1.0",
5
+ "description": "Browser automation for AI agents",
6
+ "permissions": ["tabs", "activeTab", "scripting", "debugger", "storage", "alarms"],
7
+ "host_permissions": ["<all_urls>"],
8
+ "background": {
9
+ "service_worker": "background.js",
10
+ "type": "module"
11
+ },
12
+ "content_scripts": [
13
+ {
14
+ "matches": ["http://*/*", "https://*/*"],
15
+ "js": ["content/trace.js"],
16
+ "run_at": "document_idle"
17
+ }
18
+ ],
19
+ "web_accessible_resources": [
20
+ {
21
+ "resources": ["buildDomTree.js"],
22
+ "matches": ["<all_urls>"]
23
+ }
24
+ ]
25
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "bb-browser",
3
+ "version": "0.1.0",
4
+ "description": "AI Agent browser automation CLI - control Chrome with user's login state",
5
+ "type": "module",
6
+ "bin": {
7
+ "bb-browser": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/cli.js",
10
+ "files": [
11
+ "dist",
12
+ "extension",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "turbo run build && pnpm build:release",
18
+ "build:release": "tsup && pnpm build:extension",
19
+ "build:extension": "cp -r packages/extension/dist extension",
20
+ "dev": "turbo run dev",
21
+ "test": "turbo run test",
22
+ "lint": "turbo run lint",
23
+ "clean": "turbo run clean && rm -rf node_modules dist extension",
24
+ "prepublishOnly": "pnpm build:release"
25
+ },
26
+ "keywords": [
27
+ "browser",
28
+ "automation",
29
+ "ai",
30
+ "agent",
31
+ "chrome",
32
+ "extension",
33
+ "cli",
34
+ "puppeteer",
35
+ "playwright",
36
+ "cdp"
37
+ ],
38
+ "author": "",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/yan5xu/bb-browser.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/yan5xu/bb-browser/issues"
46
+ },
47
+ "homepage": "https://github.com/yan5xu/bb-browser#readme",
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "packageManager": "pnpm@9.15.0",
52
+ "dependencies": {
53
+ "ws": "^8.18.0"
54
+ },
55
+ "devDependencies": {
56
+ "tsup": "^8.0.0",
57
+ "turbo": "^2.3.0",
58
+ "typescript": "^5.7.0"
59
+ }
60
+ }