@@ -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,116 @@ 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 #includeAllPages: boolean ;
160+ readonly #universes = new WeakMap < Page , TargetUniverse > ( ) ;
161+
162+ /** Guard access to #universes so we don't create unnecessary universes */
163+ readonly #mutex = new Mutex ( ) ;
164+
165+ constructor (
166+ browser : Browser ,
167+ includeAllPages ?: boolean ,
168+ factory : TargetUniverseFactoryFn = DEFAULT_FACTORY ,
169+ ) {
170+ this . #browser = browser ;
171+ this . #createUniverseFor = factory ;
172+ this . #includeAllPages = Boolean ( includeAllPages ) ;
173+ }
174+
175+ async init ( ) {
176+ const pages = await this . #browser. pages ( this . #includeAllPages) ;
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