From c7a865ab3a5d3c58b78f74101440728e7fa6299d Mon Sep 17 00:00:00 2001 From: tqjason <37337136+tqjason@users.noreply.github.com> Date: Fri, 7 Aug 2026 09:47:56 +0800 Subject: [PATCH] fix: include pending background tabs in tab list (use pendingUrl, push on URL commit); add tabs create/switch to batch fix(tmwdb): show newly created background tabs before page fully loads Tabs created via chrome.tabs.create({active: false}) were missing from the pushed tab list until the page reached 'complete' status, because: 1. sendTabsUpdate() ran on tabs.onCreated while the tab's navigation had not committed, so tab.url was "" and isScriptable() filtered it out (the real URL lives in tab.pendingUrl at that point). 2. The tabs.onUpdated listener only fired on status==='complete', so no update was pushed when the URL committed during loading. Introduce tabUrl() = t.url || t.pendingUrl || '' and use it everywhere a tab URL is read (ext_ready, tabs_update, tabs list, batch tabs, handleWsExec newTabs capture, tabs create response, handleCookies). Also push tabs_update on changeInfo.url so a new tab appears as soon as its navigation commits instead of only after full page load. Additionally: - Fix handleExtMessage tabs branching to if/else-if/else so 'create' no longer relies on early return to skip the list branch. - Support tabs create/switch sub-commands inside batch requests. --- assets/tmwd_cdp_bridge/background.js | 46 +++++++++++++++++++--------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/assets/tmwd_cdp_bridge/background.js b/assets/tmwd_cdp_bridge/background.js index 6dbd4b796..6aa48b6b8 100644 --- a/assets/tmwd_cdp_bridge/background.js +++ b/assets/tmwd_cdp_bridge/background.js @@ -29,16 +29,14 @@ async function handleExtMessage(msg, sender) { windowId: msg.windowId, openerTabId: msg.openerTabId }); - return { ok: true, data: { id: tab.id, url: tab.url, title: tab.title } }; - } - if (msg.method === 'switch') { + return { ok: true, data: { id: tab.id, url: tabUrl(tab), title: tab.title } }; + } else 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 }; + const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(tabUrl(t))); + return { ok: true, data: tabs.map(t => ({ id: t.id, url: tabUrl(t), title: t.title, active: t.active, windowId: t.windowId })) }; } } catch (e) { return { ok: false, error: e.message }; } } @@ -88,7 +86,7 @@ async function handleCookies(msg, sender) { let url = msg.url || sender.tab?.url; if (!url && msg.tabId) { const tab = await chrome.tabs.get(msg.tabId); - url = tab.url; + url = tabUrl(tab); } const origin = url.match(/^https?:\/\/[^\/]+/)[0]; const all = await chrome.cookies.getAll({ url }); @@ -114,8 +112,23 @@ async function handleBatch(msg, sender) { if (c.cmd === 'cookies') { R.push(await handleCookies(c, sender)); } else if (c.cmd === 'tabs') { - const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(t.url)); - R.push({ ok: true, data: tabs.map(t => ({ id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId })) }); + if (c.method === 'create') { + const tab = await chrome.tabs.create({ + url: c.url, + active: c.active !== undefined ? c.active : false, + index: c.index, + windowId: c.windowId, + openerTabId: c.openerTabId + }); + R.push({ ok: true, data: { id: tab.id, url: tabUrl(tab), title: tab.title } }); + } else if (c.method === 'switch') { + const tab = await chrome.tabs.update(c.tabId, { active: true }); + await chrome.windows.update(tab.windowId, { focused: true }); + R.push({ ok: true }); + } else { + const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(tabUrl(t))); + R.push({ ok: true, data: tabs.map(t => ({ id: t.id, url: tabUrl(t), title: t.title, active: t.active, windowId: t.windowId })) }); + } } else if (c.cmd === 'cdp') { const tabId = c.tabId || msg.tabId || sender.tab?.id; if (attached !== tabId) { @@ -151,6 +164,8 @@ async function handleCDP(msg, sender) { } // Filter out chrome:// and other internal tabs that can't be scripted const isScriptable = url => url && /^https?:/.test(url); +// Newly created background tabs have url="" until navigation commits; the target is in pendingUrl +const tabUrl = t => t.url || t.pendingUrl || ''; // --- Shared page/CDP script builder core --- function buildExecScript(code, errorHandler) { @@ -318,7 +333,7 @@ async function handleWsExec(data) { // Get full info for captured new tabs const newTabs = []; for (const id of newTabIds) { - try { const t = await chrome.tabs.get(id); newTabs.push({id: t.id, url: t.url, title: t.title}); } catch (_) {} + try { const t = await chrome.tabs.get(id); newTabs.push({id: t.id, url: tabUrl(t), title: t.title}); } catch (_) {} } if (res?.ok) { ws.send(JSON.stringify({ type: 'result', id: data.id, result: res.data, newTabs })); @@ -348,10 +363,10 @@ function connectWS() { ws.onopen = async () => { console.log('[TMWD-WS] Connected!'); scheduleKeepalive(); // Keep SW alive while connected - const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(t.url)); + const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(tabUrl(t))); ws.send(JSON.stringify({ type: 'ext_ready', - tabs: tabs.map(t => ({ id: t.id, url: t.url, title: t.title })) + tabs: tabs.map(t => ({ id: t.id, url: tabUrl(t), title: t.title })) })); console.log('[TMWD-WS] Sent ext_ready with', tabs.length, 'tabs'); }; @@ -402,14 +417,15 @@ chrome.runtime.onInstalled.addListener(() => connectWS()); // Sync tab list on changes async function sendTabsUpdate() { if (!ws || ws.readyState !== WebSocket.OPEN) return; - const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(t.url) && !/streamlit/i.test(t.title)); + const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(tabUrl(t)) && !/streamlit/i.test(t.title)); ws.send(JSON.stringify({ type: 'tabs_update', - tabs: tabs.map(t => ({ id: t.id, url: t.url, title: t.title })) + tabs: tabs.map(t => ({ id: t.id, url: tabUrl(t), title: t.title })) })); } chrome.tabs.onUpdated.addListener((_, changeInfo) => { - if (changeInfo.status === 'complete') sendTabsUpdate(); + // changeInfo.url fires when navigation commits (still loading); complete fires at full load + if (changeInfo.status === 'complete' || changeInfo.url) sendTabsUpdate(); }); chrome.tabs.onRemoved.addListener(() => sendTabsUpdate()); chrome.tabs.onCreated.addListener(() => sendTabsUpdate());