TMWebDriver: add debug logs + CDP bridge refactor (action→cmd, extract buildExecScript, handleWsExec routing)
This commit is contained in:
@@ -30,6 +30,7 @@ class Session:
|
|||||||
self.connect_at = time.time()
|
self.connect_at = time.time()
|
||||||
self.disconnect_at = None
|
self.disconnect_at = None
|
||||||
def mark_disconnected(self):
|
def mark_disconnected(self):
|
||||||
|
print(f"Tab disconnected: {self.url} (Session: {self.id})")
|
||||||
self.disconnect_at = time.time()
|
self.disconnect_at = time.time()
|
||||||
|
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ class TMWebDriver:
|
|||||||
elif data.get('type') in ['ext_ready', 'tabs_update']:
|
elif data.get('type') in ['ext_ready', 'tabs_update']:
|
||||||
tabs = data.get('tabs', [])
|
tabs = data.get('tabs', [])
|
||||||
current_tab_ids = {str(tab['id']) for tab in tabs}
|
current_tab_ids = {str(tab['id']) for tab in tabs}
|
||||||
|
print(f"Received tabs update: {current_tab_ids}")
|
||||||
for sid in list(driver.sessions.keys()):
|
for sid in list(driver.sessions.keys()):
|
||||||
sess = driver.sessions[sid]
|
sess = driver.sessions[sid]
|
||||||
if sess.type == 'ext_ws' and sid not in current_tab_ids:
|
if sess.type == 'ext_ws' and sid not in current_tab_ids:
|
||||||
@@ -150,7 +152,9 @@ class TMWebDriver:
|
|||||||
print(f"Error handling message: {e}")
|
print(f"Error handling message: {e}")
|
||||||
if hasattr(self, 'data'): print(self.data)
|
if hasattr(self, 'data'): print(self.data)
|
||||||
def connected(self): (f"New connection from {self.address}")
|
def connected(self): (f"New connection from {self.address}")
|
||||||
def handle_close(self): driver._unregister_client(self)
|
def handle_close(self):
|
||||||
|
print(f"WS Connection closed: {self.address}")
|
||||||
|
driver._unregister_client(self)
|
||||||
|
|
||||||
self.server = WebSocketServer(self.host, self.port, JSExecutor)
|
self.server = WebSocketServer(self.host, self.port, JSExecutor)
|
||||||
server_thread = threading.Thread(target=self.server.serve_forever)
|
server_thread = threading.Thread(target=self.server.serve_forever)
|
||||||
|
|||||||
@@ -15,40 +15,38 @@ chrome.runtime.onInstalled.addListener(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function handleExtMessage(msg, sender) {
|
||||||
|
if (msg.cmd === 'cookies') return await handleCookies(msg, sender);
|
||||||
|
if (msg.cmd === 'cdp') return await handleCDP(msg, sender);
|
||||||
|
if (msg.cmd === 'batch') return await handleBatch(msg, sender);
|
||||||
|
if (msg.cmd === 'tabs') {
|
||||||
|
try {
|
||||||
|
if (msg.method === 'switch') {
|
||||||
|
const tab = await chrome.tabs.update(msg.tabId, { active: true });
|
||||||
|
await chrome.windows.update(tab.windowId, { focused: true });
|
||||||
|
return { ok: true };
|
||||||
|
} else {
|
||||||
|
const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(t.url));
|
||||||
|
const data = tabs.map(t => ({ id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId }));
|
||||||
|
return { ok: true, data };
|
||||||
|
}
|
||||||
|
} catch (e) { return { ok: false, error: e.message }; }
|
||||||
|
}
|
||||||
|
return { ok: false, error: 'Unknown cmd: ' + msg.cmd };
|
||||||
|
}
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
if (msg.action === 'cookies') {
|
handleExtMessage(msg, sender).then(sendResponse);
|
||||||
handleCookies(msg, sender).then(sendResponse);
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (msg.action === 'cdp') {
|
|
||||||
handleCDP(msg, sender).then(sendResponse);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (msg.action === 'batch') {
|
|
||||||
handleBatch(msg, sender).then(sendResponse);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (msg.action === 'tabs') {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
if (msg.method === 'switch') {
|
|
||||||
const tab = await chrome.tabs.update(msg.tabId, { active: true });
|
|
||||||
await chrome.windows.update(tab.windowId, { focused: true });
|
|
||||||
sendResponse({ ok: true });
|
|
||||||
} else {
|
|
||||||
const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(t.url));
|
|
||||||
const data = tabs.map(t => ({ id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId }));
|
|
||||||
sendResponse({ ok: true, data });
|
|
||||||
}
|
|
||||||
} catch (e) { sendResponse({ ok: false, error: e.message }); }
|
|
||||||
})();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleCookies(msg, sender) {
|
async function handleCookies(msg, sender) {
|
||||||
try {
|
try {
|
||||||
const url = msg.url || sender.tab?.url;
|
let url = msg.url || sender.tab?.url;
|
||||||
|
if (!url && msg.tabId) {
|
||||||
|
const tab = await chrome.tabs.get(msg.tabId);
|
||||||
|
url = tab.url;
|
||||||
|
}
|
||||||
const origin = url.match(/^https?:\/\/[^\/]+/)[0];
|
const origin = url.match(/^https?:\/\/[^\/]+/)[0];
|
||||||
const all = await chrome.cookies.getAll({ url });
|
const all = await chrome.cookies.getAll({ url });
|
||||||
const part = await chrome.cookies.getAll({ url, partitionKey: { topLevelSite: origin } }).catch(() => []);
|
const part = await chrome.cookies.getAll({ url, partitionKey: { topLevelSite: origin } }).catch(() => []);
|
||||||
@@ -69,6 +67,7 @@ async function handleBatch(msg, sender) {
|
|||||||
(_, i, path) => { let v = R[+i]; for (const k of path.split('.')) v = v[k]; return JSON.stringify(v); }));
|
(_, i, path) => { let v = R[+i]; for (const k of path.split('.')) v = v[k]; return JSON.stringify(v); }));
|
||||||
try {
|
try {
|
||||||
for (const c of msg.commands) {
|
for (const c of msg.commands) {
|
||||||
|
if (c.tabId === undefined && msg.tabId !== undefined) c.tabId = msg.tabId;
|
||||||
if (c.cmd === 'cookies') {
|
if (c.cmd === 'cookies') {
|
||||||
R.push(await handleCookies(c, sender));
|
R.push(await handleCookies(c, sender));
|
||||||
} else if (c.cmd === 'tabs') {
|
} else if (c.cmd === 'tabs') {
|
||||||
@@ -110,8 +109,8 @@ async function handleCDP(msg, sender) {
|
|||||||
// Filter out chrome:// and other internal tabs that can't be scripted
|
// Filter out chrome:// and other internal tabs that can't be scripted
|
||||||
const isScriptable = url => url && /^https?:/.test(url);
|
const isScriptable = url => url && /^https?:/.test(url);
|
||||||
|
|
||||||
// --- Shared page-script builder (used by both executeScript and CDP fallback) ---
|
// --- Shared page/CDP script builder core ---
|
||||||
function buildPageScript(code) {
|
function buildExecScript(code, errorHandler) {
|
||||||
return `(async () => {
|
return `(async () => {
|
||||||
function smartProcessResult(result) {
|
function smartProcessResult(result) {
|
||||||
if (result === null || result === undefined || typeof result !== 'object') return result;
|
if (result === null || result === undefined || typeof result !== 'object') return result;
|
||||||
@@ -146,52 +145,23 @@ function buildPageScript(code) {
|
|||||||
}
|
}
|
||||||
return { ok: true, data: smartProcessResult(r) };
|
return { ok: true, data: smartProcessResult(r) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errMsg = e.message || String(e);
|
${errorHandler}
|
||||||
return { ok: false, error: { name: e.name || 'Error', message: errMsg, stack: e.stack || '' },
|
|
||||||
csp: errMsg.includes('Refused to evaluate') || errMsg.includes('unsafe-eval') || errMsg.includes('Content Security Policy') };
|
|
||||||
}
|
}
|
||||||
})()`;
|
})()`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CDP script: includes smartProcessResult to avoid "Object reference chain is too long" ---
|
function buildPageScript(code) {
|
||||||
|
return buildExecScript(code, `
|
||||||
|
const errMsg = e.message || String(e);
|
||||||
|
return { ok: false, error: { name: e.name || 'Error', message: errMsg, stack: e.stack || '' },
|
||||||
|
csp: errMsg.includes('Refused to evaluate') || errMsg.includes('unsafe-eval') || errMsg.includes('Content Security Policy') };
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
function buildCdpScript(code) {
|
function buildCdpScript(code) {
|
||||||
return `(async () => {
|
return buildExecScript(code, `
|
||||||
function smartProcessResult(result) {
|
|
||||||
if (result === null || result === undefined || typeof result !== 'object') return result;
|
|
||||||
if (typeof jQuery !== 'undefined' && result instanceof jQuery) {
|
|
||||||
const elements = []; for (let i = 0; i < result.length; i++) { if (result[i] && result[i].nodeType === 1) elements.push(result[i].outerHTML); } return elements;
|
|
||||||
}
|
|
||||||
if (result instanceof NodeList || result instanceof HTMLCollection) {
|
|
||||||
const elements = []; for (let i = 0; i < result.length; i++) { if (result[i] && result[i].nodeType === 1) elements.push(result[i].outerHTML); } return elements;
|
|
||||||
}
|
|
||||||
if (result.nodeType === 1) return result.outerHTML;
|
|
||||||
if (!Array.isArray(result) && typeof result === 'object' && 'length' in result && typeof result.length === 'number') {
|
|
||||||
const firstElement = result[0];
|
|
||||||
if (firstElement && firstElement.nodeType === 1) {
|
|
||||||
const elements = []; const length = Math.min(result.length, 100);
|
|
||||||
for (let i = 0; i < length; i++) { const elem = result[i]; if (elem && elem.nodeType === 1) elements.push(elem.outerHTML); } return elements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try { return JSON.parse(JSON.stringify(result, function(key, value) { if (typeof value === 'object' && value !== null) { if (value.nodeType === 1) return value.outerHTML; if (value === window || value === document) return '[Object]'; } return value; })); } catch (e) { return '[无法序列化: ' + e.message + ']'; }
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const jsCode = ${JSON.stringify(code)}.trim();
|
|
||||||
const lines = jsCode.split(/\\r?\\n/).filter(l => l.trim());
|
|
||||||
const lastLine = lines.length > 0 ? lines[lines.length - 1].trim() : '';
|
|
||||||
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
||||||
let r;
|
|
||||||
if (lastLine.startsWith('return')) {
|
|
||||||
r = await (new AsyncFunction(jsCode))();
|
|
||||||
} else {
|
|
||||||
try { r = eval(jsCode); if (r instanceof Promise) r = await r; } catch (e) {
|
|
||||||
if (e instanceof SyntaxError && (/return/i.test(e.message) || /await/i.test(e.message))) { r = await (new AsyncFunction(jsCode))(); } else throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { ok: true, data: smartProcessResult(r) };
|
|
||||||
} catch (e) {
|
|
||||||
return { ok: false, error: { name: e.name || 'Error', message: e.message || String(e), stack: e.stack || '' } };
|
return { ok: false, error: { name: e.name || 'Error', message: e.message || String(e), stack: e.stack || '' } };
|
||||||
}
|
`);
|
||||||
})()`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- WebSocket Client for TMWebDriver ---
|
// --- WebSocket Client for TMWebDriver ---
|
||||||
@@ -242,6 +212,66 @@ chrome.alarms.onAlarm.addListener(async (alarm) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function handleWsExec(data) {
|
||||||
|
const tabId = data.tabId;
|
||||||
|
console.log('[TMWD-WS] Exec request', data.id, 'on tab', tabId);
|
||||||
|
ws.send(JSON.stringify({ type: 'ack', id: data.id }));
|
||||||
|
if (!tabId) {
|
||||||
|
ws.send(JSON.stringify({ type: 'error', id: data.id, error: 'No tabId provided' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const tabsBefore = new Set((await chrome.tabs.query({})).map(t => t.id));
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
const result = await chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
world: 'MAIN',
|
||||||
|
func: async (s) => await eval(s),
|
||||||
|
args: [buildPageScript(data.code)]
|
||||||
|
});
|
||||||
|
res = result[0]?.result;
|
||||||
|
if (res === null || res === undefined) {
|
||||||
|
console.log('[TMWD-WS] executeScript returned null/undefined, treating as CSP issue');
|
||||||
|
res = { ok: false, error: { name: 'Error', message: 'executeScript returned null (possible CSP or context issue)', stack: '' }, csp: true };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[TMWD-WS] scripting.executeScript failed:', e.message);
|
||||||
|
res = { ok: false, error: { name: e.name || 'Error', message: e.message || String(e), stack: e.stack || '' }, csp: true };
|
||||||
|
}
|
||||||
|
// CDP fallback for CSP-restricted pages
|
||||||
|
if (res && !res.ok && res.csp) {
|
||||||
|
console.log('[TMWD-WS] CDP fallback for tab', tabId);
|
||||||
|
const wrappedCode = buildCdpScript(data.code);
|
||||||
|
try {
|
||||||
|
await chrome.debugger.attach({ tabId }, '1.3');
|
||||||
|
const cdpRes = await chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', {
|
||||||
|
expression: wrappedCode, awaitPromise: true, returnByValue: true
|
||||||
|
});
|
||||||
|
await chrome.debugger.detach({ tabId });
|
||||||
|
if (cdpRes.exceptionDetails) {
|
||||||
|
const desc = cdpRes.exceptionDetails.exception?.description || 'CDP Error';
|
||||||
|
res = { ok: false, error: { name: 'Error', message: desc, stack: desc } };
|
||||||
|
} else {
|
||||||
|
res = cdpRes.result.value;
|
||||||
|
}
|
||||||
|
} catch (cdpErr) {
|
||||||
|
try { await chrome.debugger.detach({ tabId }); } catch (_) {}
|
||||||
|
res = { ok: false, error: { name: 'Error', message: 'CDP fallback failed: ' + cdpErr.message, stack: '' } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newTabs = (await chrome.tabs.query({})).filter(t => !tabsBefore.has(t.id)).map(t => ({id: t.id, url: t.url, title: t.title}));
|
||||||
|
if (res?.ok) {
|
||||||
|
ws.send(JSON.stringify({ type: 'result', id: data.id, result: res.data, newTabs }));
|
||||||
|
} else {
|
||||||
|
console.log(res);
|
||||||
|
ws.send(JSON.stringify({ type: 'error', id: data.id, error: res?.error || 'Unknown error', newTabs }));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ws.send(JSON.stringify({ type: 'error', id: data.id, error: { name: e.name || 'Error', message: e.message || String(e), stack: e.stack || '' } }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function connectWS() {
|
function connectWS() {
|
||||||
if (ws && ws.readyState <= 1) return; // CONNECTING or OPEN
|
if (ws && ws.readyState <= 1) return; // CONNECTING or OPEN
|
||||||
ws = null;
|
ws = null;
|
||||||
@@ -268,63 +298,24 @@ function connectWS() {
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.id && data.code) {
|
if (data.id && data.code) {
|
||||||
const tabId = data.tabId;
|
let code = data.code;
|
||||||
console.log('[TMWD-WS] Exec request', data.id, 'on tab', tabId);
|
// If code is a JSON string representing an object, parse it
|
||||||
// Send ACK immediately so Python side resets timeout timer
|
if (typeof code === 'string') {
|
||||||
ws.send(JSON.stringify({ type: 'ack', id: data.id }));
|
try { const p = JSON.parse(code); if (p && typeof p === 'object') code = p; } catch (_) {}
|
||||||
if (!tabId) {
|
|
||||||
ws.send(JSON.stringify({ type: 'error', id: data.id, error: 'No tabId provided' }));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
try {
|
if (typeof code === 'object' && code !== null && code.cmd) {
|
||||||
const tabsBefore = new Set((await chrome.tabs.query({})).map(t => t.id));
|
// Custom protocol message → route to handleExtMessage
|
||||||
let res;
|
if (code.tabId === undefined && data.tabId !== undefined) code.tabId = data.tabId;
|
||||||
try {
|
const res = await handleExtMessage(code, {});
|
||||||
const result = await chrome.scripting.executeScript({
|
ws.send(JSON.stringify({ type: res.ok ? 'result' : 'error', id: data.id, result: res.data ?? res.results ?? res, error: res.error }));
|
||||||
target: { tabId },
|
} else if (typeof code === 'string') {
|
||||||
world: 'MAIN',
|
// Plain JS code
|
||||||
func: async (s) => await eval(s),
|
await handleWsExec(data);
|
||||||
args: [buildPageScript(data.code)]
|
} else if (typeof code === 'object' && code !== null) {
|
||||||
});
|
// Object without cmd → legacy extension message
|
||||||
res = result[0]?.result;
|
const msg = code.tabId === undefined && data.tabId !== undefined ? { ...code, tabId: data.tabId } : code;
|
||||||
if (res === null || res === undefined) {
|
const res = await handleExtMessage(msg, {});
|
||||||
console.log('[TMWD-WS] executeScript returned null/undefined, treating as CSP issue');
|
ws.send(JSON.stringify({ type: res.ok ? 'result' : 'error', id: data.id, result: res.data ?? res.results ?? res, error: res.error }));
|
||||||
res = { ok: false, error: { name: 'Error', message: 'executeScript returned null (possible CSP or context issue)', stack: '' }, csp: true };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('[TMWD-WS] scripting.executeScript failed:', e.message);
|
|
||||||
res = { ok: false, error: { name: e.name || 'Error', message: e.message || String(e), stack: e.stack || '' }, csp: true };
|
|
||||||
}
|
|
||||||
// CDP fallback for CSP-restricted pages
|
|
||||||
if (res && !res.ok && res.csp) {
|
|
||||||
console.log('[TMWD-WS] CDP fallback for tab', tabId);
|
|
||||||
const wrappedCode = buildCdpScript(data.code);
|
|
||||||
try {
|
|
||||||
await chrome.debugger.attach({ tabId }, '1.3');
|
|
||||||
const cdpRes = await chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', {
|
|
||||||
expression: wrappedCode, awaitPromise: true, returnByValue: true
|
|
||||||
});
|
|
||||||
await chrome.debugger.detach({ tabId });
|
|
||||||
if (cdpRes.exceptionDetails) {
|
|
||||||
const desc = cdpRes.exceptionDetails.exception?.description || 'CDP Error';
|
|
||||||
res = { ok: false, error: { name: 'Error', message: desc, stack: desc } };
|
|
||||||
} else {
|
|
||||||
res = cdpRes.result.value; // Already {ok, data/error} from the wrapper
|
|
||||||
}
|
|
||||||
} catch (cdpErr) {
|
|
||||||
try { await chrome.debugger.detach({ tabId }); } catch (_) {}
|
|
||||||
res = { ok: false, error: { name: 'Error', message: 'CDP fallback failed: ' + cdpErr.message, stack: '' } };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const newTabs = (await chrome.tabs.query({})).filter(t => !tabsBefore.has(t.id)).map(t => ({id: t.id, url: t.url, title: t.title}));
|
|
||||||
if (res?.ok) {
|
|
||||||
ws.send(JSON.stringify({ type: 'result', id: data.id, result: res.data, newTabs }));
|
|
||||||
} else {
|
|
||||||
console.log(res);
|
|
||||||
ws.send(JSON.stringify({ type: 'error', id: data.id, error: res?.error || 'Unknown error', newTabs }));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
ws.send(JSON.stringify({ type: 'error', id: data.id, error: { name: e.name || 'Error', message: e.message || String(e), stack: e.stack || '' } }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ async function handle(el) {
|
|||||||
const cmd = req.cmd || 'cookies';
|
const cmd = req.cmd || 'cookies';
|
||||||
let resp;
|
let resp;
|
||||||
if (cmd === 'cookies') {
|
if (cmd === 'cookies') {
|
||||||
resp = await chrome.runtime.sendMessage({ action: 'cookies', url: req.url || location.href });
|
resp = await chrome.runtime.sendMessage({ cmd: 'cookies', url: req.url || location.href });
|
||||||
} else if (cmd === 'cdp') {
|
} else if (cmd === 'cdp') {
|
||||||
resp = await chrome.runtime.sendMessage({ action: 'cdp', method: req.method, params: req.params || {}, tabId: req.tabId });
|
resp = await chrome.runtime.sendMessage({ cmd: 'cdp', method: req.method, params: req.params || {}, tabId: req.tabId });
|
||||||
} else if (cmd === 'batch') {
|
} else if (cmd === 'batch') {
|
||||||
resp = await chrome.runtime.sendMessage({ action: 'batch', commands: req.commands, tabId: req.tabId });
|
resp = await chrome.runtime.sendMessage({ cmd: 'batch', commands: req.commands, tabId: req.tabId });
|
||||||
} else if (cmd === 'tabs') {
|
} else if (cmd === 'tabs') {
|
||||||
resp = await chrome.runtime.sendMessage({ action: 'tabs', method: req.method, tabId: req.tabId });
|
resp = await chrome.runtime.sendMessage({ cmd: 'tabs', method: req.method, tabId: req.tabId });
|
||||||
} else {
|
} else {
|
||||||
resp = { ok: false, error: 'unknown cmd: ' + cmd };
|
resp = { ok: false, error: 'unknown cmd: ' + cmd };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,19 +53,20 @@ fetch('PDF_URL').then(r=>r.blob()).then(b=>{
|
|||||||
## CDP桥(tmwd_cdp_bridge扩展) ⭐首选
|
## CDP桥(tmwd_cdp_bridge扩展) ⭐首选
|
||||||
扩展路径:`assets/tmwd_cdp_bridge/`(需安装,含debugger权限)
|
扩展路径:`assets/tmwd_cdp_bridge/`(需安装,含debugger权限)
|
||||||
⚠TID密钥:首次运行自动生成到`assets/tmwd_cdp_bridge/config.js`(已gitignore),扩展通过manifest引用
|
⚠TID密钥:首次运行自动生成到`assets/tmwd_cdp_bridge/config.js`(已gitignore),扩展通过manifest引用
|
||||||
调用:MutationObserver监听addedNodes(id=TID),⚠每次必须remove旧→createElement新→设textContent JSON→appendChild
|
调用:`web_execute_js` script直传JSON字符串(工具层自动识别对象格式,走WS→background.js cmd路由)
|
||||||
```js
|
```js
|
||||||
// TID从assets/tmwd_cdp_bridge/config.js读取
|
// 直接传JSON字符串作为script参数,无需DOM操作
|
||||||
const old = document.getElementById(TID);
|
web_execute_js script='{"cmd": "cookies"}'
|
||||||
if (old) old.remove();
|
web_execute_js script='{"cmd": "tabs"}'
|
||||||
const el = document.createElement('div');
|
web_execute_js script='{"cmd": "cdp", "tabId": N, "method": "...", "params": {...}}'
|
||||||
el.id = TID; el.style.display = 'none';
|
web_execute_js script='{"cmd": "batch", "commands": [...]}'
|
||||||
el.textContent = JSON.stringify({cmd:'...', ...});
|
// 返回值直接是JSON结果
|
||||||
document.body.appendChild(el); // 响应写回el.textContent
|
|
||||||
```
|
```
|
||||||
|
⚠旧DOM方式(TID元素+MutationObserver)仍可用但已不推荐
|
||||||
单命令:`{cmd:'tabs'}` | `{cmd:'cookies'}` | `{cmd:'cdp', tabId:N, method:'...', params:{...}}`
|
单命令:`{cmd:'tabs'}` | `{cmd:'cookies'}` | `{cmd:'cdp', tabId:N, method:'...', params:{...}}`
|
||||||
- ⭐batch混合:`{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}`
|
- ⭐batch混合:`{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}`
|
||||||
- 返回`{ok:true, results:[...]}`,一次请求多命令,CDP懒attach复用session
|
- 返回`{ok:true, results:[...]}`,一次请求多命令,CDP懒attach复用session
|
||||||
|
- 子命令会自动继承外层batch的tabId(如cookies命令可正确获取当前页面URL)
|
||||||
- `$N.path`引用第N个结果字段(0-indexed),如`"nodeId":"$2.root.nodeId"`
|
- `$N.path`引用第N个结果字段(0-indexed),如`"nodeId":"$2.root.nodeId"`
|
||||||
- ⚠batch前序命令失败时后续`$N`引用拿到undefined,整条链路**静默失败不报错**,需检查返回results数组中每项的ok状态(未验证,BBS#46)
|
- ⚠batch前序命令失败时后续`$N`引用拿到undefined,整条链路**静默失败不报错**,需检查返回results数组中每项的ok状态(未验证,BBS#46)
|
||||||
- 典型:文件上传三连 getDocument(**depth:1**性能优化,200ms+→个位数ms)→querySelector(input[type=file])→setFileInputFiles(未验证,BBS#38)
|
- 典型:文件上传三连 getDocument(**depth:1**性能优化,200ms+→个位数ms)→querySelector(input[type=file])→setFileInputFiles(未验证,BBS#38)
|
||||||
|
|||||||
Reference in New Issue
Block a user