@@ -8,15 +8,26 @@ import {
88 type Issue ,
99 type AggregatedIssue ,
1010 type IssuesManagerEventTypes ,
11+ type Target ,
12+ DebuggerModel ,
13+ Foundation ,
14+ TargetManager ,
1115 MarkdownIssueDescription ,
1216 Marked ,
1317 ProtocolClient ,
1418 Common ,
1519 I18n ,
1620} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js' ;
1721
22+ import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js' ;
1823import { ISSUE_UTILS } from './issue-descriptions.js' ;
1924import { logger } from './logger.js' ;
25+ import { Mutex } from './Mutex.js' ;
26+ import type {
27+ Browser ,
28+ Page ,
29+ Target as PuppeteerTarget ,
30+ } from './third_party/index.js' ;
2031
2132export function extractUrlLikeFromDevToolsTitle (
2233 title : string ,
@@ -138,3 +149,112 @@ I18n.DevToolsLocale.DevToolsLocale.instance({
138149 } ,
139150} ) ;
140151I18n . i18n . registerLocaleDataForTest ( 'en-US' , { } ) ;
152+
153+ export interface TargetUniverse {
154+ /** The DevTools target corresponding to the puppeteer Page */
155+ target : Target ;
156+ universe : Foundation . Universe . Universe ;
157+ }
158+ export type TargetUniverseFactoryFn = ( page : Page ) => Promise < TargetUniverse > ;
159+
160+ export class UniverseManager {
161+ readonly #browser: Browser ;
162+ readonly #createUniverseFor: TargetUniverseFactoryFn ;
163+ readonly #universes = new WeakMap < Page , TargetUniverse > ( ) ;
164+
165+ /** Guard access to #universes so we don't create unnecessary universes */
166+ readonly #mutex = new Mutex ( ) ;
167+
168+ constructor (
169+ browser : Browser ,
170+ factory : TargetUniverseFactoryFn = DEFAULT_FACTORY ,
171+ ) {
172+ this . #browser = browser ;
173+ this . #createUniverseFor = factory ;
174+ }
175+
176+ async init ( pages : Page [ ] ) {
177+ try {
178+ await this . #mutex. acquire ( ) ;
179+ const promises = [ ] ;
180+ for ( const page of pages ) {
181+ promises . push (
182+ this . #createUniverseFor( page ) . then ( targetUniverse =>
183+ this . #universes. set ( page , targetUniverse ) ,
184+ ) ,
185+ ) ;
186+ }
187+
188+ this . #browser. on ( 'targetcreated' , this . #onTargetCreated) ;
189+ this . #browser. on ( 'targetdestroyed' , this . #onTargetDestroyed) ;
190+
191+ await Promise . all ( promises ) ;
192+ } finally {
193+ this . #mutex. release ( ) ;
194+ }
195+ }
196+
197+ get ( page : Page ) : TargetUniverse | null {
198+ return this . #universes. get ( page ) ?? null ;
199+ }
200+
201+ dispose ( ) {
202+ this . #browser. off ( 'targetcreated' , this . #onTargetCreated) ;
203+ this . #browser. off ( 'targetdestroyed' , this . #onTargetDestroyed) ;
204+ }
205+
206+ #onTargetCreated = async ( target : PuppeteerTarget ) => {
207+ const page = await target . page ( ) ;
208+ try {
209+ await this . #mutex. acquire ( ) ;
210+ if ( ! page || this . #universes. has ( page ) ) {
211+ return ;
212+ }
213+
214+ this . #universes. set ( page , await this . #createUniverseFor( page ) ) ;
215+ } finally {
216+ this . #mutex. release ( ) ;
217+ }
218+ } ;
219+
220+ #onTargetDestroyed = async ( target : PuppeteerTarget ) => {
221+ const page = await target . page ( ) ;
222+ try {
223+ await this . #mutex. acquire ( ) ;
224+ if ( ! page || ! this . #universes. has ( page ) ) {
225+ return ;
226+ }
227+ this . #universes. delete ( page ) ;
228+ } finally {
229+ this . #mutex. release ( ) ;
230+ }
231+ } ;
232+ }
233+
234+ const DEFAULT_FACTORY : TargetUniverseFactoryFn = async ( page : Page ) => {
235+ const settingStorage = new Common . Settings . SettingsStorage ( { } ) ;
236+ const universe = new Foundation . Universe . Universe ( {
237+ settingsCreationOptions : {
238+ syncedStorage : settingStorage ,
239+ globalStorage : settingStorage ,
240+ localStorage : settingStorage ,
241+ settingRegistrations : Common . SettingRegistration . getRegisteredSettings ( ) ,
242+ } ,
243+ overrideAutoStartModels : new Set ( [ DebuggerModel ] ) ,
244+ } ) ;
245+
246+ const session = await page . createCDPSession ( ) ;
247+ const connection = new PuppeteerDevToolsConnection ( session ) ;
248+
249+ const targetManager = universe . context . get ( TargetManager ) ;
250+ const target = targetManager . createTarget (
251+ 'main' ,
252+ '' ,
253+ 'frame' as any , // eslint-disable-line @typescript-eslint/no-explicit-any
254+ /* parentTarget */ null ,
255+ session . id ( ) ,
256+ undefined ,
257+ connection ,
258+ ) ;
259+ return { target, universe} ;
260+ } ;
0 commit comments