@@ -16,7 +16,6 @@ import {
1616} from './DevtoolsUtils.js' ;
1717import type { ListenerMap , UncaughtError } from './PageCollector.js' ;
1818import { NetworkCollector , ConsoleCollector } from './PageCollector.js' ;
19- import { Locator } from './third_party/index.js' ;
2019import type { DevTools } from './third_party/index.js' ;
2120import type {
2221 Browser ,
@@ -27,9 +26,9 @@ import type {
2726 HTTPRequest ,
2827 Page ,
2928 SerializedAXNode ,
30- PredefinedNetworkConditions ,
3129 Viewport ,
3230} from './third_party/index.js' ;
31+ import { Locator , PredefinedNetworkConditions } from './third_party/index.js' ;
3332import { type ToolGroup } from './tools/inPage.js' ;
3433import { listPages } from './tools/pages.js' ;
3534import { takeSnapshot } from './tools/snapshot.js' ;
@@ -65,6 +64,15 @@ export interface TextSnapshot {
6564 verbose : boolean ;
6665}
6766
67+ interface EmulationSettings {
68+ networkConditions ?: string | null ;
69+ cpuThrottlingRate ?: number | null ;
70+ geolocation ?: GeolocationOptions | null ;
71+ userAgent ?: string | null ;
72+ colorScheme ?: 'dark' | 'light' | null ;
73+ viewport ?: Viewport | null ;
74+ }
75+
6876interface McpContextOptions {
6977 // Whether the DevTools windows are exposed as pages for debugging of DevTools.
7078 experimentalDevToolsDebugging : boolean ;
@@ -122,12 +130,7 @@ export class McpContext implements Context {
122130 #extensionRegistry = new ExtensionRegistry ( ) ;
123131
124132 #isRunningTrace = false ;
125- #networkConditionsMap = new WeakMap < Page , string > ( ) ;
126- #cpuThrottlingRateMap = new WeakMap < Page , number > ( ) ;
127- #geolocationMap = new WeakMap < Page , GeolocationOptions > ( ) ;
128- #viewportMap = new WeakMap < Page , Viewport > ( ) ;
129- #userAgentMap = new WeakMap < Page , string > ( ) ;
130- #colorSchemeMap = new WeakMap < Page , 'dark' | 'light' > ( ) ;
133+ #emulationSettingsMap = new WeakMap < Page , EmulationSettings > ( ) ;
131134 #dialog?: Dialog ;
132135 #inPageTools?: ToolGroup | null ;
133136
@@ -284,86 +287,146 @@ export class McpContext implements Context {
284287 return this . #networkCollector. getById ( this . getSelectedPage ( ) , reqid ) ;
285288 }
286289
287- setNetworkConditions ( conditions : string | null ) : void {
290+ async emulate ( options : {
291+ networkConditions ?: string | null ;
292+ cpuThrottlingRate ?: number | null ;
293+ geolocation ?: GeolocationOptions | null ;
294+ userAgent ?: string | null ;
295+ colorScheme ?: 'dark' | 'light' | 'auto' | null ;
296+ viewport ?: Viewport | null ;
297+ } ) : Promise < void > {
288298 const page = this . getSelectedPage ( ) ;
289- if ( conditions === null ) {
290- this . #networkConditionsMap. delete ( page ) ;
291- } else {
292- this . #networkConditionsMap. set ( page , conditions ) ;
299+ const currentSettings = this . #emulationSettingsMap. get ( page ) ?? { } ;
300+ const newSettings : EmulationSettings = { ...currentSettings } ;
301+ let timeoutsNeedUpdate = false ;
302+
303+ if ( options . networkConditions !== undefined ) {
304+ timeoutsNeedUpdate = true ;
305+ if (
306+ options . networkConditions === null ||
307+ options . networkConditions === 'No emulation'
308+ ) {
309+ await page . emulateNetworkConditions ( null ) ;
310+ delete newSettings . networkConditions ;
311+ } else if ( options . networkConditions === 'Offline' ) {
312+ await page . emulateNetworkConditions ( {
313+ offline : true ,
314+ download : 0 ,
315+ upload : 0 ,
316+ latency : 0 ,
317+ } ) ;
318+ newSettings . networkConditions = 'Offline' ;
319+ } else if ( options . networkConditions in PredefinedNetworkConditions ) {
320+ const networkCondition =
321+ PredefinedNetworkConditions [
322+ options . networkConditions as keyof typeof PredefinedNetworkConditions
323+ ] ;
324+ await page . emulateNetworkConditions ( networkCondition ) ;
325+ newSettings . networkConditions = options . networkConditions ;
326+ }
293327 }
294- this . #updateSelectedPageTimeouts( ) ;
295- }
296328
297- getNetworkConditions ( ) : string | null {
298- const page = this . getSelectedPage ( ) ;
299- return this . #networkConditionsMap. get ( page ) ?? null ;
300- }
329+ if ( options . cpuThrottlingRate !== undefined ) {
330+ timeoutsNeedUpdate = true ;
331+ if ( options . cpuThrottlingRate === null ) {
332+ await page . emulateCPUThrottling ( 1 ) ;
333+ delete newSettings . cpuThrottlingRate ;
334+ } else {
335+ await page . emulateCPUThrottling ( options . cpuThrottlingRate ) ;
336+ newSettings . cpuThrottlingRate = options . cpuThrottlingRate ;
337+ }
338+ }
301339
302- setCpuThrottlingRate ( rate : number ) : void {
303- const page = this . getSelectedPage ( ) ;
304- this . #cpuThrottlingRateMap. set ( page , rate ) ;
305- this . #updateSelectedPageTimeouts( ) ;
306- }
340+ if ( options . geolocation !== undefined ) {
341+ if ( options . geolocation === null ) {
342+ await page . setGeolocation ( { latitude : 0 , longitude : 0 } ) ;
343+ delete newSettings . geolocation ;
344+ } else {
345+ await page . setGeolocation ( options . geolocation ) ;
346+ newSettings . geolocation = options . geolocation ;
347+ }
348+ }
307349
308- getCpuThrottlingRate ( ) : number {
309- const page = this . getSelectedPage ( ) ;
310- return this . #cpuThrottlingRateMap. get ( page ) ?? 1 ;
311- }
350+ if ( options . userAgent !== undefined ) {
351+ if ( options . userAgent === null ) {
352+ await page . setUserAgent ( { userAgent : undefined } ) ;
353+ delete newSettings . userAgent ;
354+ } else {
355+ await page . setUserAgent ( { userAgent : options . userAgent } ) ;
356+ newSettings . userAgent = options . userAgent ;
357+ }
358+ }
312359
313- setGeolocation ( geolocation : GeolocationOptions | null ) : void {
314- const page = this . getSelectedPage ( ) ;
315- if ( geolocation === null ) {
316- this . #geolocationMap. delete ( page ) ;
360+ if ( options . colorScheme !== undefined ) {
361+ if ( options . colorScheme === null || options . colorScheme === 'auto' ) {
362+ await page . emulateMediaFeatures ( [
363+ { name : 'prefers-color-scheme' , value : '' } ,
364+ ] ) ;
365+ delete newSettings . colorScheme ;
366+ } else {
367+ await page . emulateMediaFeatures ( [
368+ { name : 'prefers-color-scheme' , value : options . colorScheme } ,
369+ ] ) ;
370+ newSettings . colorScheme = options . colorScheme ;
371+ }
372+ }
373+
374+ if ( options . viewport !== undefined ) {
375+ if ( options . viewport === null ) {
376+ await page . setViewport ( null ) ;
377+ delete newSettings . viewport ;
378+ } else {
379+ const defaults = {
380+ deviceScaleFactor : 1 ,
381+ isMobile : false ,
382+ hasTouch : false ,
383+ isLandscape : false ,
384+ } ;
385+ const viewport = { ...defaults , ...options . viewport } ;
386+ await page . setViewport ( viewport ) ;
387+ newSettings . viewport = viewport ;
388+ }
389+ }
390+
391+ if ( Object . keys ( newSettings ) . length ) {
392+ this . #emulationSettingsMap. set ( page , newSettings ) ;
317393 } else {
318- this . #geolocationMap . set ( page , geolocation ) ;
394+ this . #emulationSettingsMap . delete ( page ) ;
319395 }
320- }
321396
322- getGeolocation ( ) : GeolocationOptions | null {
323- const page = this . getSelectedPage ( ) ;
324- return this . #geolocationMap . get ( page ) ?? null ;
397+ if ( timeoutsNeedUpdate ) {
398+ this . #updateSelectedPageTimeouts ( ) ;
399+ }
325400 }
326401
327- setViewport ( viewport : Viewport | null ) : void {
402+ getNetworkConditions ( ) : string | null {
328403 const page = this . getSelectedPage ( ) ;
329- if ( viewport === null ) {
330- this . #viewportMap. delete ( page ) ;
331- } else {
332- this . #viewportMap. set ( page , viewport ) ;
333- }
404+ return this . #emulationSettingsMap. get ( page ) ?. networkConditions ?? null ;
334405 }
335406
336- getViewport ( ) : Viewport | null {
407+ getCpuThrottlingRate ( ) : number {
337408 const page = this . getSelectedPage ( ) ;
338- return this . #viewportMap . get ( page ) ?? null ;
409+ return this . #emulationSettingsMap . get ( page ) ?. cpuThrottlingRate ?? 1 ;
339410 }
340411
341- setUserAgent ( userAgent : string | null ) : void {
412+ getGeolocation ( ) : GeolocationOptions | null {
342413 const page = this . getSelectedPage ( ) ;
343- if ( userAgent === null ) {
344- this . #userAgentMap. delete ( page ) ;
345- } else {
346- this . #userAgentMap. set ( page , userAgent ) ;
347- }
414+ return this . #emulationSettingsMap. get ( page ) ?. geolocation ?? null ;
348415 }
349416
350- getUserAgent ( ) : string | null {
417+ getViewport ( ) : Viewport | null {
351418 const page = this . getSelectedPage ( ) ;
352- return this . #userAgentMap . get ( page ) ?? null ;
419+ return this . #emulationSettingsMap . get ( page ) ?. viewport ?? null ;
353420 }
354421
355- setColorScheme ( scheme : 'dark' | 'light' | null ) : void {
422+ getUserAgent ( ) : string | null {
356423 const page = this . getSelectedPage ( ) ;
357- if ( scheme === null ) {
358- this . #colorSchemeMap. delete ( page ) ;
359- } else {
360- this . #colorSchemeMap. set ( page , scheme ) ;
361- }
424+ return this . #emulationSettingsMap. get ( page ) ?. userAgent ?? null ;
362425 }
363426
364427 getColorScheme ( ) : 'dark' | 'light' | null {
365428 const page = this . getSelectedPage ( ) ;
366- return this . #colorSchemeMap . get ( page ) ?? null ;
429+ return this . #emulationSettingsMap . get ( page ) ?. colorScheme ?? null ;
367430 }
368431
369432 setIsRunningPerformanceTrace ( x : boolean ) : void {
0 commit comments