fix(extension): resolve active tab/window per caller context
This commit is contained in:
@@ -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 })
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user