This commit is contained in:
张成
2026-03-17 18:21:17 +08:00
parent 99748a59bf
commit f5880baacf
6 changed files with 220 additions and 124 deletions

View File

@@ -39,14 +39,20 @@ export function amazon_search_list(data, sendResponse) {
try { try {
const tab = await tab_task.open_async(); 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); 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同时会关闭 popup window
// tab && tab.remove && tab.remove(3500); tab.remove(0);
} catch (err) { } catch (err) {
const result = { const result = {
code: 30, code: 30,

View File

@@ -1,18 +1,9 @@
// 注入到页面的 Amazon 搜索列表解析逻辑 // 注入到页面的 Amazon 搜索列表解析逻辑
export function injected_amazon_search_list(params) { export function injected_amazon_search_list(params) {
const action = 'amazon_search_list';
const start_url = params && params.url ? String(params.url) : location.href; const start_url = params && params.url ? String(params.url) : location.href;
const category_keyword = params && params.category_keyword ? String(params.category_keyword).trim() : ''; 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) { function abs_url(href) {
try { try {
return new URL(href, location.origin).toString(); return new URL(href, location.origin).toString();
@@ -69,7 +60,12 @@ export function injected_amazon_search_list(params) {
const items = extract_results(); const items = extract_results();
send(action, { stage: 'start', start_url, href: location.href, category_keyword, total: items.length }); // 只返回一次结果,避免 send 太多影响判定
send(action, { stage: 'list', category_keyword, total: items.length, items }); return {
send(action, { stage: 'end', category_keyword, total: items.length }); start_url,
href: location.href,
category_keyword,
total: items.length,
items,
};
} }

View File

@@ -16,6 +16,15 @@ function attach_tab_helpers(tab) {
return await execute_script(tab.id, fn, args, run_at); 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; return tab;
} }
@@ -96,9 +105,48 @@ export function create_tab_task(url) {
return this; return this;
}, },
async open_async() { async open_async() {
// 直接返回 tab 对象(带 remove / execute_script // 用 chrome.windows.create 新开窗口承载 tab
const { tab } = await open_tab(this.url, { active: this.active !== false }); const win = await new Promise((resolve, reject) => {
return tab; 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);
}, },
}; };

View File

@@ -1,11 +1,9 @@
:root { :root {
--bg: #0b1220; --bg: #ffffff;
--card: #121b2d; --text: #111111;
--card2: #0f1728; --muted: #666666;
--text: #e7eefc; --border: #e5e7eb;
--muted: #9db0d1; --primary: #2563eb;
--border: rgba(255, 255, 255, 0.08);
--primary: #4f8cff;
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
@@ -13,16 +11,19 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", Arial, sans-serif; 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%), background: var(--bg);
radial-gradient(900px 600px at 90% 20%, rgba(167, 139, 250, 0.18), transparent 55%),
var(--bg);
color: var(--text); color: var(--text);
height: 100vh;
} }
.app { .app {
max-width: 1200px; max-width: none;
margin: 0 auto; margin: 0;
padding: 18px; padding: 18px;
height: 100vh;
display: flex;
flex-direction: column;
gap: 10px;
} }
.header { .header {
@@ -42,28 +43,37 @@ body {
.grid { .grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 300px 1fr;
gap: 14px; gap: 14px;
flex: 1;
min-height: 0;
} }
.card { .card {
border: 1px solid var(--border); border: 1px solid var(--border);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02)); background: #ffffff;
backdrop-filter: blur(8px); border-radius: 8px;
border-radius: 12px;
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0;
} }
.card_title { .card_title {
padding: 12px 14px; padding: 12px 14px;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
background: rgba(0, 0, 0, 0.15); background: #f9fafb;
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: 13px;
} }
.form { .form {
padding: 14px; padding: 14px;
display: flex;
flex-direction: column;
gap: 0;
flex: 1;
min-height: 0;
} }
.label { .label {
@@ -77,19 +87,20 @@ body {
.textarea { .textarea {
width: 100%; width: 100%;
border: 1px solid var(--border); border: 1px solid var(--border);
background: rgba(0, 0, 0, 0.25); background: #ffffff;
color: var(--text); color: var(--text);
border-radius: 10px; border-radius: 6px;
padding: 10px 10px; padding: 10px 10px;
outline: none; outline: none;
} }
.textarea { .textarea {
min-height: 160px; min-height: 220px;
resize: vertical; resize: vertical;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 12px; font-size: 12px;
line-height: 1.5; line-height: 1.5;
flex: 1;
} }
.row { .row {
@@ -100,21 +111,22 @@ body {
.btn { .btn {
border: 1px solid var(--border); border: 1px solid var(--border);
background: rgba(0, 0, 0, 0.25); background: #ffffff;
color: var(--text); color: var(--text);
border-radius: 10px; border-radius: 6px;
padding: 10px 12px; padding: 10px 12px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
} }
.btn.primary { .btn.primary {
border-color: rgba(79, 140, 255, 0.45); border-color: var(--primary);
background: rgba(79, 140, 255, 0.18); color: #ffffff;
background: var(--primary);
} }
.btn:hover { .btn:hover {
border-color: rgba(255, 255, 255, 0.18); border-color: #cbd5e1;
} }
.hint { .hint {
@@ -125,18 +137,26 @@ body {
} }
.hint code { .hint code {
color: #c7d2fe; color: #111111;
} }
.pre { .pre {
margin: 0; margin: 0;
padding: 14px; 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-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 12px; font-size: 12px;
line-height: 1.5; line-height: 1.5;
min-height: 260px; min-height: 0;
color: #dbeafe; color: #111111;
flex: 1;
overflow: auto;
}
.pre_small {
flex: 0 0 180px;
max-height: 180px;
overflow: auto;
} }
.pre_scroll { .pre_scroll {

View File

@@ -1,12 +1,14 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head>
<head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>mv2_simple_crx 控制台</title> <title>mv2_simple_crx 控制台</title>
<link rel="stylesheet" href="index.css" /> <link rel="stylesheet" href="index.css" />
</head> </head>
<body>
<body>
<div class="app"> <div class="app">
<div class="header"> <div class="header">
<div class="title">mv2_simple_crx 控制台</div> <div class="title">mv2_simple_crx 控制台</div>
@@ -37,10 +39,14 @@
<div class="hint"> <div class="hint">
说明:执行后会显示「响应」,同时如果页面内监听到 <code>__REQUEST_DONE</code> 命中接口,会在「推送事件」里持续追加。 说明:执行后会显示「响应」,同时如果页面内监听到 <code>__REQUEST_DONE</code> 命中接口,会在「推送事件」里持续追加。
</div> </div>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<label class="label">动作日志</label>
<pre id="action_log" class="pre pre_small"></pre>
<div class="card_title">响应</div> <div class="card_title">响应</div>
<pre id="last_response" class="pre"></pre> <pre id="last_response" class="pre"></pre>
</div> </div>
@@ -48,5 +54,6 @@
</div> </div>
<script src="index.js"></script> <script src="index.js"></script>
</body> </body>
</html> </html>

View File

@@ -4,8 +4,9 @@ const btn_run_el = document.getElementById('btn_run');
const btn_clear_el = document.getElementById('btn_clear'); const btn_clear_el = document.getElementById('btn_clear');
const btn_bg_reload_el = document.getElementById('btn_bg_reload'); const btn_bg_reload_el = document.getElementById('btn_bg_reload');
const last_response_el = document.getElementById('last_response'); const last_response_el = document.getElementById('last_response');
const action_log_el = document.getElementById('action_log');
let actions_meta = {}; let actions_meta = {};
const ui_state = { events: [] }; const ui_state = { last_result: null, actions: [] };
function now_time() { function now_time() {
const d = new Date(); const d = new Date();
@@ -21,20 +22,45 @@ function safe_json_parse(text) {
} }
} }
function set_last_response(obj) { function pick_main_result(res) {
last_response_el.textContent = JSON.stringify(obj, null, 2); // 右侧只展示最核心的数据,避免被 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() { function render_state() {
// 只在一个窗口展示所有数据,方便查看 last_response_el.textContent = JSON.stringify(ui_state.last_result, null, 2);
last_response_el.textContent = JSON.stringify(ui_state, null, 2); action_log_el.textContent = ui_state.actions.join('\n');
} }
function push_event(obj) { function push_action(obj) {
ui_state.events.push({ ts: now_time(), ...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) { if (ui_state.actions.length > 200) {
ui_state.events.splice(0, ui_state.events.length - 300); ui_state.actions.splice(0, ui_state.actions.length - 200);
} }
render_state(); render_state();
} }
@@ -43,22 +69,27 @@ btn_run_el.addEventListener('click', () => {
const action = action_name_el.value; const action = action_name_el.value;
const params = safe_json_parse(action_params_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) => { 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', () => { btn_clear_el.addEventListener('click', () => {
ui_state.events = []; ui_state.actions = [];
ui_state.last_result = null;
render_state(); render_state();
}); });
btn_bg_reload_el.addEventListener('click', () => { 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) => { 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; return;
} }
if (message.event_name === 'push') { // UI 动作都放左侧,不影响右侧主响应
push_event({ type: 'push', payload: message.payload }); push_action({ type: message.event_name, 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;
}
}); });
// 初始化:拉取 action 元信息,生成下拉 + 默认参数 // 初始化:拉取 action 元信息,生成下拉 + 默认参数
@@ -133,4 +152,4 @@ chrome.runtime.sendMessage({ action: 'meta_actions', data: {} }, (res) => {
}); });
render_state(); render_state();
push_event({ type: 'ready', hint: '点击右侧/上方执行按钮开始' }); push_action({ type: 'ready', hint: '点击右侧执行按钮开始' });