perf: use CDP to find open DevTools pages. (#1150)

This should reduce the flakiness and issue with finding open DevTools
tabs for the corresponding pages.
Currently we do multiple loops over all the targets, so this should have
a nice performance improvement when multiple pages are opened.
This commit is contained in:
Nikolay Vitkov authored and GitHub committed 2026-03-11 13:30:30 +00:00
1 parent a06d8e64c9
commit 94de19cdcd
3 files changed
+16 -165

No files matched your search

-55
View File
@@ -15,61 +15,6 @@ import type {
Target as PuppeteerTarget,
} from './third_party/index.js';
export function extractUrlLikeFromDevToolsTitle(
title: string,
): string | undefined {
const match = title.match(new RegExp(`DevTools - (.*)`));
return match?.[1] ?? undefined;
}
export function urlsEqual(url1: string, url2: string): boolean {
const normalizedUrl1 = normalizeUrl(url1);
const normalizedUrl2 = normalizeUrl(url2);
return normalizedUrl1 === normalizedUrl2;
}
/**
* For the sake of the MCP server, when we determine if two URLs are equal we
* remove some parts:
*
* 1. We do not care about the protocol.
* 2. We do not care about trailing slashes.
* 3. We do not care about "www".
* 4. We ignore the hash parts.
*
* For example, if the user types "record a trace on foo.com", we would want to
* match a tab in the connected Chrome instance that is showing "www.foo.com/"
*/
function normalizeUrl(url: string): string {
let result = url.trim();
// Remove protocols
if (result.startsWith('https://')) {
result = result.slice(8);
} else if (result.startsWith('http://')) {
result = result.slice(7);
}
// Remove 'www.'. This ensures that we find the right URL regardless of if the user adds `www` or not.
if (result.startsWith('www.')) {
result = result.slice(4);
}
// We use target URLs to locate DevTools but those often do
// no include hash.
const hashIdx = result.lastIndexOf('#');
if (hashIdx !== -1) {
result = result.slice(0, hashIdx);
}
// Remove trailing slash
if (result.endsWith('/')) {
result = result.slice(0, -1);
}
return result;
}
/**
* A mock implementation of an issues manager that only implements the methods
* that are actually used by the IssuesAggregator
+15 -35
View File
@@ -8,11 +8,7 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import type {TargetUniverse} from './DevtoolsUtils.js';
import {
extractUrlLikeFromDevToolsTitle,
UniverseManager,
urlsEqual,
} from './DevtoolsUtils.js';
import {UniverseManager} from './DevtoolsUtils.js';
import {McpPage} from './McpPage.js';
import type {ListenerMap, UncaughtError} from './PageCollector.js';
import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
@@ -653,37 +649,21 @@ export class McpContext implements Context {
async detectOpenDevToolsWindows() {
this.logger('Detecting open DevTools windows');
const {pages} = await this.#getAllPages();
// Clear all devToolsPage references before re-detecting.
for (const mcpPage of this.#mcpPages.values()) {
mcpPage.devToolsPage = undefined;
}
for (const devToolsPage of pages) {
if (devToolsPage.url().startsWith('devtools://')) {
try {
this.logger('Calling getTargetInfo for ' + devToolsPage.url());
const data = await devToolsPage
// @ts-expect-error no types for _client().
._client()
.send('Target.getTargetInfo');
const devtoolsPageTitle = data.targetInfo.title;
const urlLike = extractUrlLikeFromDevToolsTitle(devtoolsPageTitle);
if (!urlLike) {
continue;
}
// TODO: lookup without a loop.
for (const page of this.#pages) {
if (urlsEqual(page.url(), urlLike)) {
const mcpPage = this.#mcpPages.get(page);
if (mcpPage) {
mcpPage.devToolsPage = devToolsPage;
}
}
}
} catch (error) {
this.logger('Issue occurred while trying to find DevTools', error);
await Promise.all(
pages.map(async page => {
const mcpPage = this.#mcpPages.get(page);
if (!mcpPage) {
return;
}
}
}
if (await page.hasDevTools()) {
mcpPage.devToolsPage = await page.openDevTools();
} else {
mcpPage.devToolsPage = undefined;
}
}),
);
}
getExtensionServiceWorkers(): ExtensionServiceWorker[] {
+1 -75
View File
@@ -9,11 +9,7 @@ import {afterEach, describe, it} from 'node:test';
import sinon from 'sinon';
import {
extractUrlLikeFromDevToolsTitle,
urlsEqual,
UniverseManager,
} from '../src/DevtoolsUtils.js';
import {UniverseManager} from '../src/DevtoolsUtils.js';
import {DevTools} from '../src/third_party/index.js';
import type {Browser, Target} from '../src/third_party/index.js';
@@ -24,76 +20,6 @@ import {
withBrowser,
} from './utils.js';
describe('extractUrlFromDevToolsTitle', () => {
it('deals with no trailing /', () => {
assert.strictEqual(
extractUrlLikeFromDevToolsTitle('DevTools - example.com'),
'example.com',
);
});
it('deals with a trailing /', () => {
assert.strictEqual(
extractUrlLikeFromDevToolsTitle('DevTools - example.com/'),
'example.com/',
);
});
it('deals with www', () => {
assert.strictEqual(
extractUrlLikeFromDevToolsTitle('DevTools - www.example.com/'),
'www.example.com/',
);
});
it('deals with complex url', () => {
assert.strictEqual(
extractUrlLikeFromDevToolsTitle(
'DevTools - www.example.com/path.html?a=b#3',
),
'www.example.com/path.html?a=b#3',
);
});
});
describe('urlsEqual', () => {
it('ignores trailing slashes', () => {
assert.strictEqual(
urlsEqual('https://google.com/', 'https://google.com'),
true,
);
});
it('ignores www', () => {
assert.strictEqual(
urlsEqual('https://google.com/', 'https://www.google.com'),
true,
);
});
it('ignores protocols', () => {
assert.strictEqual(
urlsEqual('https://google.com/', 'http://www.google.com'),
true,
);
});
it('does not ignore other subdomains', () => {
assert.strictEqual(
urlsEqual('https://google.com/', 'https://photos.google.com'),
false,
);
});
it('ignores hash', () => {
assert.strictEqual(
urlsEqual('https://google.com/#', 'http://www.google.com'),
true,
);
assert.strictEqual(
urlsEqual('https://google.com/#21', 'http://www.google.com#12'),
true,
);
});
});
describe('UniverseManager', () => {
afterEach(() => {
sinon.restore();