Merge pull request #578 from alibaba/fix/wrong-active-tab-multi-window

fix: wrong active tab/window in multi-window scenarios
This commit is contained in:
Simon
2026-07-02 02:43:04 +08:00
committed by GitHub
3 changed files with 63 additions and 9 deletions
+4
View File
@@ -96,6 +96,10 @@ Returns: `Promise<ExecutionResult>`
Stop the current task. Stop the current task.
## Limitations
- **Normal browser windows only.** This extension relies on tab group API which does not work for pop-up window or PWA App window.
## Types ## Types
Install `@page-agent/core` for complete types: Install `@page-agent/core` for complete types:
@@ -7,6 +7,39 @@ const PREFIX = '[TabsController.background]'
const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`) const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
/**
* Resolve active tab.
*
* - `tabs.query({ active: true })` does not work in multi-window scenarios.
* - Extension pages (side panel, hub tab) can resolve their own windowId.
* We just find the active tab within that window.
* - Content scripts (PAGE_AGENT_EXT) can't self-report a windowId.
* Chrome populates `sender.tab` for every content-script message,
* which is the tab hosting the script.
*/
async function resolveActiveTab(
payload: { windowId?: number } | undefined,
sender: chrome.runtime.MessageSender
): Promise<chrome.tabs.Tab> {
const windowId = payload?.windowId
if (windowId != null) {
debug('get_active_tab: resolving via caller-reported windowId', windowId)
const [tab] = await chrome.tabs.query({ active: true, windowId })
if (!tab) throw new Error(`No active tab found in window ${windowId}.`)
return tab
}
if (sender.tab) {
debug('get_active_tab: resolving via sender.tab (content script)', sender.tab.id)
return sender.tab
}
throw new Error(
'Cannot resolve active tab: caller reported no windowId and is not a content script (no sender.tab).'
)
}
export function handleTabControlMessage( export function handleTabControlMessage(
message: { type: 'TAB_CONTROL'; action: TabAction; payload: any }, message: { type: 'TAB_CONTROL'; action: TabAction; payload: any },
sender: chrome.runtime.MessageSender, sender: chrome.runtime.MessageSender,
@@ -16,12 +49,11 @@ export function handleTabControlMessage(
switch (action as TabAction) { switch (action as TabAction) {
case 'get_active_tab': { case 'get_active_tab': {
debug('get_active_tab') debug('get_active_tab', payload)
chrome.tabs resolveActiveTab(payload, sender)
.query({ active: true }) .then((tab) => {
.then((tabs) => { debug('get_active_tab: success', tab)
debug('get_active_tab: success', tabs) sendResponse({ success: true, tab })
sendResponse({ success: true, tab: tabs[0] })
}) })
.catch((error) => { .catch((error) => {
sendResponse({ error: error instanceof Error ? error.message : String(error) }) sendResponse({ error: error instanceof Error ? error.message : String(error) })
@@ -46,7 +78,7 @@ export function handleTabControlMessage(
case 'open_new_tab': { case 'open_new_tab': {
debug('open_new_tab', payload) debug('open_new_tab', payload)
chrome.tabs chrome.tabs
.create({ url: payload.url, active: false }) .create({ url: payload.url, windowId: payload.windowId, active: false })
.then((newTab) => { .then((newTab) => {
debug('open_new_tab: success', newTab) debug('open_new_tab: success', newTab)
sendResponse({ success: true, tabId: newTab.id }) sendResponse({ success: true, tabId: newTab.id })
+20 -2
View File
@@ -15,6 +15,20 @@ function sendMessage(message: {
}) })
} }
/**
* Resolve the window hosting this script's own context, when knowable.
*
* Extension pages (side panel, hub tab) have `chrome.windows` access and can
* identify their own window directly via `getCurrent()`.
* Content scripts have no `chrome.windows` access; they resolve `undefined`
* here and the background script falls back to `sender.tab` instead.
*/
async function getOwnWindowId(): Promise<number | undefined> {
if (typeof chrome.windows === 'undefined') return undefined
const win = await chrome.windows.getCurrent()
return win.id
}
/** /**
* Controller for managing browser tabs. * Controller for managing browser tabs.
* - live in the agent env (extension page or content script) * - live in the agent env (extension page or content script)
@@ -57,6 +71,7 @@ export class TabsController {
const activeTabResult = await sendMessage({ const activeTabResult = await sendMessage({
type: 'TAB_CONTROL', type: 'TAB_CONTROL',
action: 'get_active_tab', action: 'get_active_tab',
payload: { windowId: await getOwnWindowId() },
}) })
this.initialTabId = activeTabResult.tab?.id this.initialTabId = activeTabResult.tab?.id
@@ -124,7 +139,7 @@ export class TabsController {
const result = await sendMessage({ const result = await sendMessage({
type: 'TAB_CONTROL', type: 'TAB_CONTROL',
action: 'open_new_tab', action: 'open_new_tab',
payload: { url }, payload: { url, windowId: this.windowId },
}) })
if (!result.success) { if (!result.success) {
@@ -307,7 +322,10 @@ export class TabsController {
if (message.action === 'created') { if (message.action === 'created') {
const tab = message.payload.tab as chrome.tabs.Tab const tab = message.payload.tab as chrome.tabs.Tab
const shouldTrack = this.experimentalIncludeAllTabs || tab.groupId === this.tabGroupId const shouldTrack =
tab.groupId === this.tabGroupId ||
// @note Never track tabs from other windows.
(this.experimentalIncludeAllTabs && tab.windowId === this.windowId)
if (shouldTrack && tab.id != null) { if (shouldTrack && tab.id != null) {
this.addTab({ id: tab.id, isInitial: false }) this.addTab({ id: tab.id, isInitial: false })
this.switchToTab(tab.id) this.switchToTab(tab.id)