feat: 增强内容脚本与后台通信,添加 PING 响应和确保脚本就绪的机制

This commit is contained in:
QLHazyCoder
2026-04-11 02:01:36 +08:00
parent bac7113c0c
commit 359566131d
3 changed files with 102 additions and 41 deletions
+67 -22
View File
@@ -330,6 +330,55 @@ async function closeTabsByUrlPrefix(prefix, options = {}) {
return matchedIds.length; return matchedIds.length;
} }
async function pingContentScriptOnTab(tabId) {
if (!Number.isInteger(tabId)) return null;
try {
return await chrome.tabs.sendMessage(tabId, {
type: 'PING',
source: 'background',
payload: {},
});
} catch {
return null;
}
}
async function ensureContentScriptReadyOnTab(source, tabId, options = {}) {
const { inject = null, injectSource = null } = options;
const pong = await pingContentScriptOnTab(tabId);
if (pong?.ok && (!pong.source || pong.source === source)) {
await registerTab(source, tabId);
return;
}
if (!inject || !inject.length) {
throw new Error(`${getSourceLabel(source)} 内容脚本未就绪,且未提供可用的注入文件。`);
}
const registry = await getTabRegistry();
if (registry[source]) {
registry[source].ready = false;
await setState({ tabRegistry: registry });
}
if (injectSource) {
await chrome.scripting.executeScript({
target: { tabId },
func: (injectedSource) => {
window.__MULTIPAGE_SOURCE = injectedSource;
},
args: [injectSource],
});
}
await chrome.scripting.executeScript({
target: { tabId },
files: inject,
});
}
// ============================================================ // ============================================================
// Command Queue (for content scripts not yet ready) // Command Queue (for content scripts not yet ready)
// ============================================================ // ============================================================
@@ -2302,8 +2351,9 @@ async function executeStep8(state) {
resolved = true; resolved = true;
cleanupListener(); cleanupListener();
clearTimeout(timeout); clearTimeout(timeout);
completeStepFromBackground(8, { localhostUrl: details.url }).then(() => { addLog(`步骤 8:已捕获 localhost 地址:${details.url}`, 'ok').then(() => {
addLog(`步骤 8:已捕获 localhost 地址:${details.url}`, 'ok'); return completeStepFromBackground(8, { localhostUrl: details.url });
}).then(() => {
resolve(); resolve();
}).catch((err) => { }).catch((err) => {
reject(err); reject(err);
@@ -2367,23 +2417,14 @@ async function executeStep9(state) {
await addLog('步骤 9:正在打开 CPA 面板...'); await addLog('步骤 9:正在打开 CPA 面板...');
const injectFiles = ['content/utils.js', 'content/vps-panel.js'];
let tabId = await getTabId('vps-panel'); let tabId = await getTabId('vps-panel');
const alive = tabId && await isTabAlive('vps-panel'); const alive = tabId && await isTabAlive('vps-panel');
if (!alive) { if (!alive) {
await closeConflictingTabsForSource('vps-panel', state.vpsUrl); tabId = await reuseOrCreateTab('vps-panel', state.vpsUrl, {
// Create new tab inject: injectFiles,
const tab = await chrome.tabs.create({ url: state.vpsUrl, active: true }); reloadIfSameUrl: true,
tabId = tab.id;
await rememberSourceLastUrl('vps-panel', state.vpsUrl);
await new Promise(resolve => {
const listener = (tid, info) => {
if (tid === tabId && info.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}
};
chrome.tabs.onUpdated.addListener(listener);
}); });
} else { } else {
await closeConflictingTabsForSource('vps-panel', state.vpsUrl, { excludeTabIds: [tabId] }); await closeConflictingTabsForSource('vps-panel', state.vpsUrl, { excludeTabIds: [tabId] });
@@ -2391,21 +2432,25 @@ async function executeStep9(state) {
await rememberSourceLastUrl('vps-panel', state.vpsUrl); await rememberSourceLastUrl('vps-panel', state.vpsUrl);
} }
// Inject scripts directly and wait for them to be ready await ensureContentScriptReadyOnTab('vps-panel', tabId, {
await chrome.scripting.executeScript({ inject: injectFiles,
target: { tabId },
files: ['content/utils.js', 'content/vps-panel.js'],
}); });
await new Promise(r => setTimeout(r, 1000));
// Send command directly — bypass queue/ready mechanism
await addLog('步骤 9:正在填写回调地址...'); await addLog('步骤 9:正在填写回调地址...');
await chrome.tabs.sendMessage(tabId, { const result = await sendToContentScriptResilient('vps-panel', {
type: 'EXECUTE_STEP', type: 'EXECUTE_STEP',
step: 9, step: 9,
source: 'background', source: 'background',
payload: { localhostUrl: state.localhostUrl, vpsPassword: state.vpsPassword }, payload: { localhostUrl: state.localhostUrl, vpsPassword: state.vpsPassword },
}, {
timeoutMs: 30000,
retryDelayMs: 700,
logMessage: '步骤 9:CPA 面板通信未就绪,正在等待页面恢复...',
}); });
if (result?.error) {
throw new Error(result.error);
}
} }
// ============================================================ // ============================================================
+9 -1
View File
@@ -17,10 +17,18 @@ const LOG_PREFIX = `[MultiPage:${SCRIPT_SOURCE}]`;
const STOP_ERROR_MESSAGE = '流程已被用户停止。'; const STOP_ERROR_MESSAGE = '流程已被用户停止。';
let flowStopped = false; let flowStopped = false;
chrome.runtime.onMessage.addListener((message) => { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'STOP_FLOW') { if (message.type === 'STOP_FLOW') {
flowStopped = true; flowStopped = true;
console.warn(LOG_PREFIX, STOP_ERROR_MESSAGE); console.warn(LOG_PREFIX, STOP_ERROR_MESSAGE);
return;
}
if (message.type === 'PING') {
sendResponse({
ok: true,
source: SCRIPT_SOURCE,
});
} }
}); });
+26 -18
View File
@@ -25,24 +25,32 @@
console.log('[MultiPage:vps-panel] Content script loaded on', location.href); console.log('[MultiPage:vps-panel] Content script loaded on', location.href);
// Listen for commands from Background const VPS_PANEL_LISTENER_SENTINEL = 'data-multipage-vps-panel-listener';
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'EXECUTE_STEP') { if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1') {
resetStopState(); document.documentElement.setAttribute(VPS_PANEL_LISTENER_SENTINEL, '1');
handleStep(message.step, message.payload).then(() => {
sendResponse({ ok: true }); // Listen for commands from Background
}).catch(err => { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (isStopError(err)) { if (message.type === 'EXECUTE_STEP') {
log(`步骤 ${message.step}:已被用户停止。`, 'warn'); resetStopState();
sendResponse({ stopped: true, error: err.message }); handleStep(message.step, message.payload).then(() => {
return; sendResponse({ ok: true });
} }).catch(err => {
reportError(message.step, err.message); if (isStopError(err)) {
sendResponse({ error: err.message }); log(`步骤 ${message.step}:已被用户停止。`, 'warn');
}); sendResponse({ stopped: true, error: err.message });
return true; return;
} }
}); reportError(message.step, err.message);
sendResponse({ error: err.message });
});
return true;
}
});
} else {
console.log('[MultiPage:vps-panel] 消息监听已存在,跳过重复注册');
}
async function handleStep(step, payload) { async function handleStep(step, payload) {
switch (step) { switch (step) {