1
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,52 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>mv2_simple_crx 控制台</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<div class="header">
|
||||
<div class="title">mv2_simple_crx 控制台</div>
|
||||
<div class="sub">选择 action,填参数,执行并查看响应/推送事件</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="card_title">调用</div>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>mv2_simple_crx 控制台</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
</head>
|
||||
|
||||
<div class="form">
|
||||
<label class="label">方法名(action)</label>
|
||||
<select id="action_name" class="input">
|
||||
<option value="zhipu_query_position_page">zhipu_query_position_page</option>
|
||||
<option value="amazon_top_list">amazon_top_list</option>
|
||||
<option value="amazon_search_list">amazon_search_list</option>
|
||||
</select>
|
||||
|
||||
<label class="label">参数(JSON)</label>
|
||||
<textarea id="action_params" class="textarea" spellcheck="false">{}</textarea>
|
||||
|
||||
<div class="row">
|
||||
<button id="btn_run" class="btn primary">执行</button>
|
||||
<button id="btn_clear" class="btn">清空日志</button>
|
||||
<button id="btn_bg_reload" class="btn">刷新后台</button>
|
||||
</div>
|
||||
|
||||
<div class="hint">
|
||||
说明:执行后会显示「响应」,同时如果页面内监听到 <code>__REQUEST_DONE</code> 命中接口,会在「推送事件」里持续追加。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card_title">响应</div>
|
||||
<pre id="last_response" class="pre"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<body>
|
||||
<div class="app">
|
||||
<div class="header">
|
||||
<div class="title">mv2_simple_crx 控制台</div>
|
||||
<div class="sub">选择 action,填参数,执行并查看响应/推送事件</div>
|
||||
</div>
|
||||
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="card_title">调用</div>
|
||||
|
||||
<div class="form">
|
||||
<label class="label">方法名(action)</label>
|
||||
<select id="action_name" class="input">
|
||||
<option value="zhipu_query_position_page">zhipu_query_position_page</option>
|
||||
<option value="amazon_top_list">amazon_top_list</option>
|
||||
<option value="amazon_search_list">amazon_search_list</option>
|
||||
</select>
|
||||
|
||||
<label class="label">参数(JSON)</label>
|
||||
<textarea id="action_params" class="textarea" spellcheck="false">{}</textarea>
|
||||
|
||||
<div class="row">
|
||||
<button id="btn_run" class="btn primary">执行</button>
|
||||
<button id="btn_clear" class="btn">清空日志</button>
|
||||
<button id="btn_bg_reload" class="btn">刷新后台</button>
|
||||
</div>
|
||||
|
||||
<div class="hint">
|
||||
说明:执行后会显示「响应」,同时如果页面内监听到 <code>__REQUEST_DONE</code> 命中接口,会在「推送事件」里持续追加。
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<label class="label">动作日志</label>
|
||||
<pre id="action_log" class="pre pre_small"></pre>
|
||||
<div class="card_title">响应</div>
|
||||
<pre id="last_response" class="pre"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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: '点击右侧执行按钮开始' });
|
||||
|
||||
Reference in New Issue
Block a user