Browser: add URL fallback for relay tab matching (#1999)

Co-authored-by: João Paulo Furtado <jonit-dev@users.noreply.github.com>
This commit is contained in:
Shadow
2026-01-25 21:04:41 -06:00
parent 159f6bfddd
commit 5d2ef89e03
2 changed files with 49 additions and 4 deletions

View File

@@ -13,6 +13,7 @@ Status: unreleased.
- Docs: add Render deployment guide. (#1975) Thanks @anurag. - Docs: add Render deployment guide. (#1975) Thanks @anurag.
- CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi. - CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi.
- macOS: avoid crash when rendering code blocks by bumping Textual to 0.3.1. (#2033) Thanks @garricn. - macOS: avoid crash when rendering code blocks by bumping Textual to 0.3.1. (#2033) Thanks @garricn.
- Browser: fall back to URL matching for extension relay target resolution. (#1999) Thanks @jonit-dev.
## 2026.1.24-3 ## 2026.1.24-3

View File

@@ -337,12 +337,56 @@ async function pageTargetId(page: Page): Promise<string | null> {
} }
} }
async function findPageByTargetId(browser: Browser, targetId: string): Promise<Page | null> { async function findPageByTargetId(
browser: Browser,
targetId: string,
cdpUrl?: string,
): Promise<Page | null> {
const pages = await getAllPages(browser); const pages = await getAllPages(browser);
// First, try the standard CDP session approach
for (const page of pages) { for (const page of pages) {
const tid = await pageTargetId(page).catch(() => null); const tid = await pageTargetId(page).catch(() => null);
if (tid && tid === targetId) return page; if (tid && tid === targetId) return page;
} }
// If CDP sessions fail (e.g., extension relay blocks Target.attachToBrowserTarget),
// fall back to URL-based matching using the /json/list endpoint
if (cdpUrl) {
try {
const baseUrl = cdpUrl
.replace(/\/+$/, "")
.replace(/^ws:/, "http:")
.replace(/\/cdp$/, "");
const response = await fetch(`${baseUrl}/json/list`);
if (response.ok) {
const targets = (await response.json()) as Array<{
id: string;
url: string;
title?: string;
}>;
const target = targets.find((t) => t.id === targetId);
if (target) {
// Try to find a page with matching URL
const urlMatch = pages.filter((p) => p.url() === target.url);
if (urlMatch.length === 1) {
return urlMatch[0];
}
// If multiple URL matches, use index-based matching as fallback
// This works when Playwright and the relay enumerate tabs in the same order
if (urlMatch.length > 1) {
const sameUrlTargets = targets.filter((t) => t.url === target.url);
if (sameUrlTargets.length === urlMatch.length) {
const idx = sameUrlTargets.findIndex((t) => t.id === targetId);
if (idx >= 0 && idx < urlMatch.length) {
return urlMatch[idx];
}
}
}
}
}
} catch {
// Ignore fetch errors and fall through to return null
}
}
return null; return null;
} }
@@ -355,7 +399,7 @@ export async function getPageForTargetId(opts: {
if (!pages.length) throw new Error("No pages available in the connected browser."); if (!pages.length) throw new Error("No pages available in the connected browser.");
const first = pages[0]; const first = pages[0];
if (!opts.targetId) return first; if (!opts.targetId) return first;
const found = await findPageByTargetId(browser, opts.targetId); const found = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
if (!found) { if (!found) {
// Extension relays can block CDP attachment APIs (e.g. Target.attachToBrowserTarget), // Extension relays can block CDP attachment APIs (e.g. Target.attachToBrowserTarget),
// which prevents us from resolving a page's targetId via newCDPSession(). If Playwright // which prevents us from resolving a page's targetId via newCDPSession(). If Playwright
@@ -496,7 +540,7 @@ export async function closePageByTargetIdViaPlaywright(opts: {
targetId: string; targetId: string;
}): Promise<void> { }): Promise<void> {
const { browser } = await connectBrowser(opts.cdpUrl); const { browser } = await connectBrowser(opts.cdpUrl);
const page = await findPageByTargetId(browser, opts.targetId); const page = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
if (!page) { if (!page) {
throw new Error("tab not found"); throw new Error("tab not found");
} }
@@ -512,7 +556,7 @@ export async function focusPageByTargetIdViaPlaywright(opts: {
targetId: string; targetId: string;
}): Promise<void> { }): Promise<void> {
const { browser } = await connectBrowser(opts.cdpUrl); const { browser } = await connectBrowser(opts.cdpUrl);
const page = await findPageByTargetId(browser, opts.targetId); const page = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
if (!page) { if (!page) {
throw new Error("tab not found"); throw new Error("tab not found");
} }