diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/.DS_Store differ
diff --git a/chrome-extension/js/background.js b/chrome-extension/js/background.js
index 36b2706..395e5e4 100644
--- a/chrome-extension/js/background.js
+++ b/chrome-extension/js/background.js
@@ -1,6 +1,11 @@
(function () {
"use strict";
+ let popupPort = null;
+ const eventHistoryMap = {};
+ const recordingStateMap = {};
+
+
const taskIdMap = {};
const lastPageGoToTimestamps = {};
let collectorHost = "127.0.0.1";
@@ -124,6 +129,57 @@
} else if (port.name === 'content-script') {
handleContentScriptConnection(port);
}
+ else if (port.name === 'popup') {
+ popupPort = port;
+ port.onDisconnect.addListener(() => { popupPort = null; });
+
+ chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => {
+ const tid = tabs[0]?.id;
+ console.log(`🔍 [BG→popup] popup connected, querying history for tabId =`, tid,
+ 'history length =', (eventHistoryMap[tid] || []).length);
+ if (tid != null) {
+ (eventHistoryMap[tid] || []).forEach((ev, i) => {
+ console.log(` → event[${i}] =`, ev);
+ popupPort.postMessage({ type: 'event', event: ev });
+ });
+ popupPort.postMessage({
+ type: 'recording-state',
+ tabId: tid,
+ recording: !!recordingStateMap[tid]
+ });
+ }
+ });
+
+ port.onMessage.addListener(msg => {
+ const { type, tabId, desc } = msg;
+
+ // —— 1) 拉回历史
+ if (type === 'get-history' && typeof tabId === 'number') {
+ const history = eventHistoryMap[tabId] || [];
+ history.forEach(ev => popupPort.postMessage({ type: 'event', event: ev }));
+ return;
+ }
+
+ // —— 2) 拉回当前录制状态
+ if (type === 'get-recording-state' && typeof tabId === 'number') {
+ const isRec = !!recordingStateMap[tabId];
+ popupPort.postMessage({ type: 'recording-state', tabId, recording: isRec });
+ return;
+ }
+
+ // —— 3) popup 发来的开始/停止录制命令
+ if (type === 'start-record' && typeof tabId === 'number') {
+ const p = devToolsPorts[tabId];
+ if (p) p.postMessage({ type: 'popup-start', desc });
+ return;
+ }
+ if (type === 'stop-record' && typeof tabId === 'number') {
+ const p = devToolsPorts[tabId];
+ if (p) p.postMessage({ type: 'popup-stop' });
+ return;
+ }
+ });
+ }
});
var devToolsPorts = {};
@@ -149,7 +205,20 @@
console.log('[OTA DOM Background]: Script injected successfully', injectionResults);
}
});
- } else {
+ }
+ else if (message.type === 'panel-record-started') {
+ recordingStateMap[tabId] = true;
+ if (popupPort) {
+ popupPort.postMessage({ type: 'recording-state', tabId, recording: true });
+ }
+ }
+ else if (message.type === 'panel-record-stopped') {
+ recordingStateMap[tabId] = false;
+ if (popupPort) {
+ popupPort.postMessage({ type: 'recording-state', tabId, recording: false });
+ }
+ }
+ else {
//pass message from DevTools panel to a content script
if (contentScriptPorts[tabId]) {
contentScriptPorts[tabId].postMessage(message);
@@ -175,9 +244,22 @@
console.log('[OTA DOM Background]: content script status: ', message.type, ', tab ID: ', tabId);
//pass message from content script to the appropriate DevTools panel
+ if (message.type === 'event') { //如果有信息就缓存起来,到时候给popup发
+ if (!eventHistoryMap[tabId]) eventHistoryMap[tabId] = [];
+ eventHistoryMap[tabId].push(message.event);
+ console.log(
+ `🔍 [BG] eventHistoryMap[${tabId}].length =`,
+ eventHistoryMap[tabId].length
+ );
+ }else{
+ console.log('[BG] NOT caching type=', message.type);
+ }
if (devToolsPorts[tabId]) {
devToolsPorts[tabId].postMessage(message);
}
+ if (popupPort) {
+ popupPort.postMessage(message);
+ }
};
port.onMessage.addListener(messageListener);
@@ -191,6 +273,9 @@
type: 'disconnected'
});
}
+ if (popupPort) {
+ popupPort.postMessage({ type: 'disconnected' });
+ }
});
}
diff --git a/chrome-extension/js/panel.js b/chrome-extension/js/panel.js
index 482e68e..cc3e8da 100644
--- a/chrome-extension/js/panel.js
+++ b/chrome-extension/js/panel.js
@@ -155,6 +155,8 @@
taskVisibilityBtn.innerText = 'Hide Task';
taskSection.style.display = 'block'; // Task visible by default
+ sendToBG({ type: 'panel-record-started' });
+
/* Optional intro fade-out (unchanged) */
if (intro.style.display !== 'none') {
intro.animate([{ opacity: 1 }, { opacity: 0 }], 300)
@@ -185,6 +187,8 @@
eventTable.clear();
+ sendToBG({ type: 'panel-record-stopped' });
+
/* Optional intro fade-out (unchanged) */
intro.style.display = 'block'; // make it visible immediately
intro.style.opacity = 0; // start transparent
@@ -384,6 +388,40 @@
eventTable.clear();
break;
+
+ case 'popup-start': {
+ // 来自 popup 的开始录制
+ const desc = message.desc;
+ // 复用 recordBtnHandler 里的 Start 流程(不重复 UI 逻辑)
+ ContentScriptProxy.startRecording(desc);
+ // 更新面板 UI
+ showLabel(desc);
+ recordBtn.innerText = 'Finish Record';
+ pauseResumeBtn.disabled = false;
+ recording = true;
+ // 请求并显示 taskId
+ setTimeout(() => getCurrentTaskId(), 1000);
+ // 通知 background “面板已开始”
+ sendToBG({ type: 'panel-record-started' });
+ break;
+ }
+
+ case 'popup-stop': {
+ // 来自 popup 的停止录制
+ ContentScriptProxy.finishRecording();
+ // 恢复面板 UI
+ showInput(true);
+ recordBtn.innerText = 'Start Record';
+ pauseResumeBtn.disabled = true;
+ taskVisibilityBtn.hidden = true;
+ eventTable.clear();
+ recording = false;
+ // 通知 background “面板已停止”
+ sendToBG({ type: 'panel-record-stopped' });
+ break;
+ }
+
+
/* you may add more cases as needed */
default:
diff --git a/chrome-extension/js/popup.js b/chrome-extension/js/popup.js
new file mode 100644
index 0000000..c3dd748
--- /dev/null
+++ b/chrome-extension/js/popup.js
@@ -0,0 +1,62 @@
+(function(){
+ "use strict";
+
+ // —— UI 绑定 ——
+ const clearBtn = document.querySelector('.clear');
+ const recordBtn = document.querySelector('.record-btn');
+ const descInput = document.querySelector('.task-desc-input');
+ const table = document.querySelector('.events');
+ const eventTable = new EventTable(table);
+
+ let currentTabId, recording = false;
+
+ // —— 建立和 background 的长连接 ——
+ const port = chrome.runtime.connect({ name: 'popup' });
+ port.onMessage.addListener(msg => {
+ console.log('📝 popup received message:', msg);
+ const { tabId, event, recording: isRec } = msg;
+ //if (tabId !== currentTabId) return;
+
+ // —— 只要有 event 对象,就渲染它 ——
+ if (event) {
+ eventTable.addEvent(event);
+ const rowCount = document.querySelector('.events tbody').children.length;
+ console.log(`📝 popup: tbody now has ${rowCount} rows`);
+ return;
+ }
+
+ // —— 同步录制状态 ——
+ if (msg.type === 'recording-state') {
+ recording = isRec;
+ recordBtn.innerText = recording ? 'Stop Record' : 'Start Record';
+ descInput.disabled = recording;
+ }
+ });
+
+ // —— 一打开就抓 tabId ——
+ chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
+ currentTabId = tabs[0].id;
+ });
+
+ // —— 清空表格 ——
+ clearBtn.addEventListener('click', () => {
+ eventTable.clear();
+ });
+
+ // —— 开始/停止录制 ——
+ recordBtn.addEventListener('click', () => {
+ const desc = descInput.value.trim();
+ console.log('📝 popup click recordBtn, recording=', recording, 'desc=', desc);
+
+ if (!recording) {
+ if (!desc) {
+ console.warn('⚠️ popup: empty description, abort start-record');
+ descInput.classList.add('invalid');
+ return;
+ }
+ port.postMessage({ type: 'start-record', tabId: currentTabId, desc });
+ } else {
+ port.postMessage({ type: 'stop-record', tabId: currentTabId });
+ }
+ });
+})();
diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json
index 11962f9..50f0f40 100644
--- a/chrome-extension/manifest.json
+++ b/chrome-extension/manifest.json
@@ -22,6 +22,13 @@
"devtools_page": "devtools.html",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
+ },
+ "action": {
+ "default_icon": {
+ "128": "ico/ota-logo-128.png",
+ "48": "ico/ota-logo-48.png"
+ },
+ "default_popup": "popup.html"
}
}
\ No newline at end of file
diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html
new file mode 100644
index 0000000..f5b1af9
--- /dev/null
+++ b/chrome-extension/popup.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+ OTA Events
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+