1
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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: '点击右侧执行按钮开始' });
|
||||||
|
|||||||
Reference in New Issue
Block a user