diff --git a/mv2_simple_crx/src/actions/amazon.js b/mv2_simple_crx/src/actions/amazon.js index a62e067..0b33af6 100644 --- a/mv2_simple_crx/src/actions/amazon.js +++ b/mv2_simple_crx/src/actions/amazon.js @@ -39,14 +39,20 @@ export function amazon_search_list(data, sendResponse) { try { const tab = await tab_task.open_async(); - await tab.execute_script(injected_amazon_search_list, [{ url, category_keyword }], 'document_idle'); + const injected_result_list = await tab.execute_script(injected_amazon_search_list, [{ url, category_keyword }], 'document_idle'); + const injected_result = Array.isArray(injected_result_list) ? injected_result_list[0] : null; - const result = { code: 0, status: true, message: 'ok', data: { tab_id: tab.id, url, category_keyword } }; + const result = { + code: 0, + status: true, + message: 'ok', + data: { tab_id: tab.id, url, category_keyword, result: injected_result }, + }; send_action('amazon_search_list', result); - resolve({ tab_id: tab.id, url, category_keyword }); + resolve({ tab_id: tab.id, url, category_keyword, result: injected_result }); - // 可选:自动关 tab(默认不关,方便调试) - // tab && tab.remove && tab.remove(3500); + // 成功后关闭打开的 tab(同时会关闭 popup window) + tab.remove(0); } catch (err) { const result = { code: 30, diff --git a/mv2_simple_crx/src/injected/amazon_search_list.js b/mv2_simple_crx/src/injected/amazon_search_list.js index 2bd50be..4065150 100644 --- a/mv2_simple_crx/src/injected/amazon_search_list.js +++ b/mv2_simple_crx/src/injected/amazon_search_list.js @@ -1,18 +1,9 @@ // 注入到页面的 Amazon 搜索列表解析逻辑 export function injected_amazon_search_list(params) { - const action = 'amazon_search_list'; const start_url = params && params.url ? String(params.url) : location.href; const category_keyword = params && params.category_keyword ? String(params.category_keyword).trim() : ''; - function send(action_name, data) { - try { - chrome.runtime.sendMessage({ type: 'push', action: action_name, data }); - } catch (e) { - // ignore - } - } - function abs_url(href) { try { return new URL(href, location.origin).toString(); @@ -69,7 +60,12 @@ export function injected_amazon_search_list(params) { const items = extract_results(); - send(action, { stage: 'start', start_url, href: location.href, category_keyword, total: items.length }); - send(action, { stage: 'list', category_keyword, total: items.length, items }); - send(action, { stage: 'end', category_keyword, total: items.length }); + // 只返回一次结果,避免 send 太多影响判定 + return { + start_url, + href: location.href, + category_keyword, + total: items.length, + items, + }; } diff --git a/mv2_simple_crx/src/libs/tabs.js b/mv2_simple_crx/src/libs/tabs.js index dd00358..7ccd7c1 100644 --- a/mv2_simple_crx/src/libs/tabs.js +++ b/mv2_simple_crx/src/libs/tabs.js @@ -16,6 +16,15 @@ function attach_tab_helpers(tab) { return await execute_script(tab.id, fn, args, run_at); }; + tab.close_window = function close_window(delay_ms) { + delay_ms = Number.isFinite(delay_ms) ? delay_ms : 0; + setTimeout(() => { + if (tab.windowId) { + chrome.windows.remove(tab.windowId, () => void 0); + } + }, Math.max(0, delay_ms)); + }; + return tab; } @@ -96,9 +105,48 @@ export function create_tab_task(url) { return this; }, async open_async() { - // 直接返回 tab 对象(带 remove / execute_script) - const { tab } = await open_tab(this.url, { active: this.active !== false }); - return tab; + // 用 chrome.windows.create 新开窗口承载 tab + const win = await new Promise((resolve, reject) => { + chrome.windows.create( + { + url: 'about:blank', + type: 'popup', + focused: true, + top: this.top, + left: this.left, + width: this.width, + height: this.height, + }, + (w) => { + if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message)); + resolve(w); + }, + ); + }); + + const tab0 = win && win.tabs && win.tabs[0] ? win.tabs[0] : null; + if (!tab0 || !tab0.id) { + throw new Error('popup window 创建失败'); + } + + await new Promise((resolve, reject) => { + chrome.tabs.update(tab0.id, { url: this.url, active: this.active !== false }, () => { + if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message)); + resolve(true); + }); + }); + + const tab_done = await new Promise((resolve) => { + const on_updated = (tab_id, change_info, tab) => { + if (tab_id !== tab0.id) return; + if (change_info.status !== 'complete') return; + chrome.tabs.onUpdated.removeListener(on_updated); + resolve(tab); + }; + chrome.tabs.onUpdated.addListener(on_updated); + }); + + return attach_tab_helpers(tab_done); }, }; diff --git a/mv2_simple_crx/src/ui/index.css b/mv2_simple_crx/src/ui/index.css index 418519b..9055c1a 100644 --- a/mv2_simple_crx/src/ui/index.css +++ b/mv2_simple_crx/src/ui/index.css @@ -1,11 +1,9 @@ :root { - --bg: #0b1220; - --card: #121b2d; - --card2: #0f1728; - --text: #e7eefc; - --muted: #9db0d1; - --border: rgba(255, 255, 255, 0.08); - --primary: #4f8cff; + --bg: #ffffff; + --text: #111111; + --muted: #666666; + --border: #e5e7eb; + --primary: #2563eb; } * { box-sizing: border-box; } @@ -13,16 +11,19 @@ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", Arial, sans-serif; - background: radial-gradient(1200px 700px at 20% 10%, rgba(79, 140, 255, 0.25), transparent 60%), - radial-gradient(900px 600px at 90% 20%, rgba(167, 139, 250, 0.18), transparent 55%), - var(--bg); + background: var(--bg); color: var(--text); + height: 100vh; } .app { - max-width: 1200px; - margin: 0 auto; + max-width: none; + margin: 0; padding: 18px; + height: 100vh; + display: flex; + flex-direction: column; + gap: 10px; } .header { @@ -42,28 +43,37 @@ body { .grid { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: 300px 1fr; gap: 14px; + flex: 1; + min-height: 0; } .card { border: 1px solid var(--border); - background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02)); - backdrop-filter: blur(8px); - border-radius: 12px; + background: #ffffff; + border-radius: 8px; overflow: hidden; + display: flex; + flex-direction: column; + min-height: 0; } .card_title { padding: 12px 14px; border-bottom: 1px solid var(--border); - background: rgba(0, 0, 0, 0.15); + background: #f9fafb; font-weight: 600; font-size: 13px; } .form { padding: 14px; + display: flex; + flex-direction: column; + gap: 0; + flex: 1; + min-height: 0; } .label { @@ -77,19 +87,20 @@ body { .textarea { width: 100%; border: 1px solid var(--border); - background: rgba(0, 0, 0, 0.25); + background: #ffffff; color: var(--text); - border-radius: 10px; + border-radius: 6px; padding: 10px 10px; outline: none; } .textarea { - min-height: 160px; + min-height: 220px; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 12px; line-height: 1.5; + flex: 1; } .row { @@ -100,21 +111,22 @@ body { .btn { border: 1px solid var(--border); - background: rgba(0, 0, 0, 0.25); + background: #ffffff; color: var(--text); - border-radius: 10px; + border-radius: 6px; padding: 10px 12px; cursor: pointer; font-weight: 600; } .btn.primary { - border-color: rgba(79, 140, 255, 0.45); - background: rgba(79, 140, 255, 0.18); + border-color: var(--primary); + color: #ffffff; + background: var(--primary); } .btn:hover { - border-color: rgba(255, 255, 255, 0.18); + border-color: #cbd5e1; } .hint { @@ -125,18 +137,26 @@ body { } .hint code { - color: #c7d2fe; + color: #111111; } .pre { margin: 0; padding: 14px; - background: rgba(0, 0, 0, 0.22); + background: #ffffff; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 12px; line-height: 1.5; - min-height: 260px; - color: #dbeafe; + min-height: 0; + color: #111111; + flex: 1; + overflow: auto; +} + +.pre_small { + flex: 0 0 180px; + max-height: 180px; + overflow: auto; } .pre_scroll { diff --git a/mv2_simple_crx/src/ui/index.html b/mv2_simple_crx/src/ui/index.html index 2359984..9530670 100644 --- a/mv2_simple_crx/src/ui/index.html +++ b/mv2_simple_crx/src/ui/index.html @@ -1,52 +1,59 @@ - - - - mv2_simple_crx 控制台 - - - -
-
-
mv2_simple_crx 控制台
-
选择 action,填参数,执行并查看响应/推送事件
-
-
-
-
调用
+ + + + mv2_simple_crx 控制台 + + -
- - - - - - -
- - - -
- -
- 说明:执行后会显示「响应」,同时如果页面内监听到 __REQUEST_DONE 命中接口,会在「推送事件」里持续追加。 -
-
-
- -
-
响应
-

-        
-
+ +
+
+
mv2_simple_crx 控制台
+
选择 action,填参数,执行并查看响应/推送事件
- - - +
+
+
调用
+ +
+ + + + + + +
+ + + +
+ +
+ 说明:执行后会显示「响应」,同时如果页面内监听到 __REQUEST_DONE 命中接口,会在「推送事件」里持续追加。 +
+ + +
+
+ +
+ +

+        
响应
+

+      
+
+
+ + + + + \ No newline at end of file diff --git a/mv2_simple_crx/src/ui/index.js b/mv2_simple_crx/src/ui/index.js index 7624d06..e23adb6 100644 --- a/mv2_simple_crx/src/ui/index.js +++ b/mv2_simple_crx/src/ui/index.js @@ -4,8 +4,9 @@ const btn_run_el = document.getElementById('btn_run'); const btn_clear_el = document.getElementById('btn_clear'); const btn_bg_reload_el = document.getElementById('btn_bg_reload'); const last_response_el = document.getElementById('last_response'); +const action_log_el = document.getElementById('action_log'); let actions_meta = {}; -const ui_state = { events: [] }; +const ui_state = { last_result: null, actions: [] }; function now_time() { const d = new Date(); @@ -21,20 +22,45 @@ function safe_json_parse(text) { } } -function set_last_response(obj) { - last_response_el.textContent = JSON.stringify(obj, null, 2); +function pick_main_result(res) { + // 右侧只展示最核心的数据,避免被 ok/request_id 包裹层干扰 + if (res && res.ok && res.data) { + // 约定:action 返回的核心结果放在 data.result(例如 amazon_search_list 的 stage=list) + if (res.data.result) return res.data.result; + return res.data; + } + return res; } function render_state() { - // 只在一个窗口展示所有数据,方便查看 - last_response_el.textContent = JSON.stringify(ui_state, null, 2); + last_response_el.textContent = JSON.stringify(ui_state.last_result, null, 2); + action_log_el.textContent = ui_state.actions.join('\n'); } -function push_event(obj) { - ui_state.events.push({ ts: now_time(), ...obj }); +function push_action(obj) { + // 动作日志只保留单行文本,避免 JSON 换行太长 + const ts = now_time(); + const type = obj && obj.type ? String(obj.type) : 'action'; + const action = obj && obj.action ? String(obj.action) : ''; + const hint = obj && obj.hint ? String(obj.hint) : ''; + const payload = obj && Object.prototype.hasOwnProperty.call(obj, 'payload') ? obj.payload : null; + const res = obj && Object.prototype.hasOwnProperty.call(obj, 'res') ? obj.res : null; + + let extra = ''; + if (hint) extra = hint; + else if (action) extra = action; + else if (payload !== null) extra = JSON.stringify(payload); + else if (res !== null) extra = JSON.stringify(res); + + // 截断避免一行过长 + if (extra && extra.length > 180) { + extra = extra.slice(0, 180) + '...'; + } + + ui_state.actions.push(`[${ts}] ${type}${extra ? ' ' + extra : ''}`); // 简单限长,避免无限增长 - if (ui_state.events.length > 300) { - ui_state.events.splice(0, ui_state.events.length - 300); + if (ui_state.actions.length > 200) { + ui_state.actions.splice(0, ui_state.actions.length - 200); } render_state(); } @@ -43,22 +69,27 @@ btn_run_el.addEventListener('click', () => { const action = action_name_el.value; const params = safe_json_parse(action_params_el.value || '{}'); - push_event({ type: 'call', action, params }); + push_action({ type: 'call', action, params }); + ui_state.last_result = { running: true, action, params }; + render_state(); chrome.runtime.sendMessage({ action, data: params }, (res) => { - push_event({ type: 'reply', action, res }); + push_action({ type: 'reply', action, res }); + ui_state.last_result = pick_main_result(res); + render_state(); }); }); btn_clear_el.addEventListener('click', () => { - ui_state.events = []; + ui_state.actions = []; + ui_state.last_result = null; render_state(); }); btn_bg_reload_el.addEventListener('click', () => { - push_event({ type: 'ui', action: 'reload_background' }); + push_action({ type: 'ui', action: 'reload_background' }); chrome.runtime.sendMessage({ action: 'reload_background', data: {} }, (res) => { - push_event({ type: 'ui', action: 'reload_background_done', res }); + push_action({ type: 'ui', action: 'reload_background_done', res }); }); }); @@ -108,20 +139,8 @@ chrome.runtime.onMessage.addListener((message) => { return; } - if (message.event_name === 'push') { - push_event({ type: 'push', payload: message.payload }); - return; - } - - if (message.event_name === 'request') { - push_event({ type: 'request', payload: message.payload }); - return; - } - - if (message.event_name === 'response') { - push_event({ type: 'bg_response', payload: message.payload }); - return; - } + // UI 动作都放左侧,不影响右侧主响应 + push_action({ type: message.event_name, payload: message.payload }); }); // 初始化:拉取 action 元信息,生成下拉 + 默认参数 @@ -133,4 +152,4 @@ chrome.runtime.sendMessage({ action: 'meta_actions', data: {} }, (res) => { }); render_state(); -push_event({ type: 'ready', hint: '点击右侧/上方执行按钮开始' }); +push_action({ type: 'ready', hint: '点击右侧执行按钮开始' });