@@ -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,10 @@ import type {
2726 HTTPRequest ,
2827 Page ,
2928 SerializedAXNode ,
30- PredefinedNetworkConditions ,
3129 Viewport ,
3230} from './third_party/index.js' ;
31+ import { Locator } from './third_party/index.js' ;
32+ import { PredefinedNetworkConditions } from './third_party/index.js' ;
3333import { listPages } from './tools/pages.js' ;
3434import { takeSnapshot } from './tools/snapshot.js' ;
3535import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js' ;
@@ -64,6 +64,15 @@ export interface TextSnapshot {
6464 verbose : boolean ;
6565}
6666
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+
6776interface McpContextOptions {
6877 // Whether the DevTools windows are exposed as pages for debugging of DevTools.
6978 experimentalDevToolsDebugging : boolean ;
@@ -121,12 +130,7 @@ export class McpContext implements Context {
121130 #extensionRegistry = new ExtensionRegistry ( ) ;
122131
123132 #isRunningTrace = false ;
124- #networkConditionsMap = new WeakMap < Page , string > ( ) ;
125- #cpuThrottlingRateMap = new WeakMap < Page , number > ( ) ;
126- #geolocationMap = new WeakMap < Page , GeolocationOptions > ( ) ;
127- #viewportMap = new WeakMap < Page , Viewport > ( ) ;
128- #userAgentMap = new WeakMap < Page , string > ( ) ;
129- #colorSchemeMap = new WeakMap < Page , 'dark' | 'light' > ( ) ;
133+ #emulationSettingsMap = new WeakMap < Page , EmulationSettings > ( ) ;
130134 #dialog?: Dialog ;
131135
132136 #pageIdMap = new WeakMap < Page , number > ( ) ;
@@ -282,86 +286,146 @@ export class McpContext implements Context {
282286 return this . #networkCollector. getById ( this . getSelectedPage ( ) , reqid ) ;
283287 }
284288
285- setNetworkConditions ( conditions : string | null ) : void {
289+ async emulate ( options : {
290+ networkConditions ?: string | null ;
291+ cpuThrottlingRate ?: number | null ;
292+ geolocation ?: GeolocationOptions | null ;
293+ userAgent ?: string | null ;
294+ colorScheme ?: 'dark' | 'light' | 'auto' | null ;
295+ viewport ?: Viewport | null ;
296+ } ) : Promise < void > {
286297 const page = this . getSelectedPage ( ) ;
287- if ( conditions === null ) {
288- this . #networkConditionsMap. delete ( page ) ;
289- } else {
290- this . #networkConditionsMap. set ( page , conditions ) ;
298+ const currentSettings = this . #emulationSettingsMap. get ( page ) ?? { } ;
299+ const newSettings : EmulationSettings = { ...currentSettings } ;
300+ let timeoutsNeedUpdate = false ;
301+
302+ if ( options . networkConditions !== undefined ) {
303+ timeoutsNeedUpdate = true ;
304+ if (
305+ options . networkConditions === null ||
306+ options . networkConditions === 'No emulation'
307+ ) {
308+ await page . emulateNetworkConditions ( null ) ;
309+ delete newSettings . networkConditions ;
310+ } else if ( options . networkConditions === 'Offline' ) {
311+ await page . emulateNetworkConditions ( {
312+ offline : true ,
313+ download : 0 ,
314+ upload : 0 ,
315+ latency : 0 ,
316+ } ) ;
317+ newSettings . networkConditions = 'Offline' ;
318+ } else if ( options . networkConditions in PredefinedNetworkConditions ) {
319+ const networkCondition =
320+ PredefinedNetworkConditions [
321+ options . networkConditions as keyof typeof PredefinedNetworkConditions
322+ ] ;
323+ await page . emulateNetworkConditions ( networkCondition ) ;
324+ newSettings . networkConditions = options . networkConditions ;
325+ }
291326 }
292- this . #updateSelectedPageTimeouts( ) ;
293- }
294327
295- getNetworkConditions ( ) : string | null {
296- const page = this . getSelectedPage ( ) ;
297- return this . #networkConditionsMap. get ( page ) ?? null ;
298- }
328+ if ( options . cpuThrottlingRate !== undefined ) {
329+ timeoutsNeedUpdate = true ;
330+ if ( options . cpuThrottlingRate === null ) {
331+ await page . emulateCPUThrottling ( 1 ) ;
332+ delete newSettings . cpuThrottlingRate ;
333+ } else {
334+ await page . emulateCPUThrottling ( options . cpuThrottlingRate ) ;
335+ newSettings . cpuThrottlingRate = options . cpuThrottlingRate ;
336+ }
337+ }
299338
300- setCpuThrottlingRate ( rate : number ) : void {
301- const page = this . getSelectedPage ( ) ;
302- this . #cpuThrottlingRateMap. set ( page , rate ) ;
303- this . #updateSelectedPageTimeouts( ) ;
304- }
339+ if ( options . geolocation !== undefined ) {
340+ if ( options . geolocation === null ) {
341+ await page . setGeolocation ( { latitude : 0 , longitude : 0 } ) ;
342+ delete newSettings . geolocation ;
343+ } else {
344+ await page . setGeolocation ( options . geolocation ) ;
345+ newSettings . geolocation = options . geolocation ;
346+ }
347+ }
305348
306- getCpuThrottlingRate ( ) : number {
307- const page = this . getSelectedPage ( ) ;
308- return this . #cpuThrottlingRateMap. get ( page ) ?? 1 ;
309- }
349+ if ( options . userAgent !== undefined ) {
350+ if ( options . userAgent === null ) {
351+ await page . setUserAgent ( { userAgent : undefined } ) ;
352+ delete newSettings . userAgent ;
353+ } else {
354+ await page . setUserAgent ( { userAgent : options . userAgent } ) ;
355+ newSettings . userAgent = options . userAgent ;
356+ }
357+ }
310358
311- setGeolocation ( geolocation : GeolocationOptions | null ) : void {
312- const page = this . getSelectedPage ( ) ;
313- if ( geolocation === null ) {
314- this . #geolocationMap. delete ( page ) ;
359+ if ( options . colorScheme !== undefined ) {
360+ if ( options . colorScheme === null || options . colorScheme === 'auto' ) {
361+ await page . emulateMediaFeatures ( [
362+ { name : 'prefers-color-scheme' , value : '' } ,
363+ ] ) ;
364+ delete newSettings . colorScheme ;
365+ } else {
366+ await page . emulateMediaFeatures ( [
367+ { name : 'prefers-color-scheme' , value : options . colorScheme } ,
368+ ] ) ;
369+ newSettings . colorScheme = options . colorScheme ;
370+ }
371+ }
372+
373+ if ( options . viewport !== undefined ) {
374+ if ( options . viewport === null ) {
375+ await page . setViewport ( null ) ;
376+ delete newSettings . viewport ;
377+ } else {
378+ const defaults = {
379+ deviceScaleFactor : 1 ,
380+ isMobile : false ,
381+ hasTouch : false ,
382+ isLandscape : false ,
383+ } ;
384+ const viewport = { ...defaults , ...options . viewport } ;
385+ await page . setViewport ( viewport ) ;
386+ newSettings . viewport = viewport ;
387+ }
388+ }
389+
390+ if ( Object . keys ( newSettings ) . length ) {
391+ this . #emulationSettingsMap. set ( page , newSettings ) ;
315392 } else {
316- this . #geolocationMap . set ( page , geolocation ) ;
393+ this . #emulationSettingsMap . delete ( page ) ;
317394 }
318- }
319395
320- getGeolocation ( ) : GeolocationOptions | null {
321- const page = this . getSelectedPage ( ) ;
322- return this . #geolocationMap . get ( page ) ?? null ;
396+ if ( timeoutsNeedUpdate ) {
397+ this . #updateSelectedPageTimeouts ( ) ;
398+ }
323399 }
324400
325- setViewport ( viewport : Viewport | null ) : void {
401+ getNetworkConditions ( ) : string | null {
326402 const page = this . getSelectedPage ( ) ;
327- if ( viewport === null ) {
328- this . #viewportMap. delete ( page ) ;
329- } else {
330- this . #viewportMap. set ( page , viewport ) ;
331- }
403+ return this . #emulationSettingsMap. get ( page ) ?. networkConditions ?? null ;
332404 }
333405
334- getViewport ( ) : Viewport | null {
406+ getCpuThrottlingRate ( ) : number {
335407 const page = this . getSelectedPage ( ) ;
336- return this . #viewportMap . get ( page ) ?? null ;
408+ return this . #emulationSettingsMap . get ( page ) ?. cpuThrottlingRate ?? 1 ;
337409 }
338410
339- setUserAgent ( userAgent : string | null ) : void {
411+ getGeolocation ( ) : GeolocationOptions | null {
340412 const page = this . getSelectedPage ( ) ;
341- if ( userAgent === null ) {
342- this . #userAgentMap. delete ( page ) ;
343- } else {
344- this . #userAgentMap. set ( page , userAgent ) ;
345- }
413+ return this . #emulationSettingsMap. get ( page ) ?. geolocation ?? null ;
346414 }
347415
348- getUserAgent ( ) : string | null {
416+ getViewport ( ) : Viewport | null {
349417 const page = this . getSelectedPage ( ) ;
350- return this . #userAgentMap . get ( page ) ?? null ;
418+ return this . #emulationSettingsMap . get ( page ) ?. viewport ?? null ;
351419 }
352420
353- setColorScheme ( scheme : 'dark' | 'light' | null ) : void {
421+ getUserAgent ( ) : string | null {
354422 const page = this . getSelectedPage ( ) ;
355- if ( scheme === null ) {
356- this . #colorSchemeMap. delete ( page ) ;
357- } else {
358- this . #colorSchemeMap. set ( page , scheme ) ;
359- }
423+ return this . #emulationSettingsMap. get ( page ) ?. userAgent ?? null ;
360424 }
361425
362426 getColorScheme ( ) : 'dark' | 'light' | null {
363427 const page = this . getSelectedPage ( ) ;
364- return this . #colorSchemeMap . get ( page ) ?? null ;
428+ return this . #emulationSettingsMap . get ( page ) ?. colorScheme ?? null ;
365429 }
366430
367431 setIsRunningPerformanceTrace ( x : boolean ) : void {
0 commit comments