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