Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit 19b4e8c

Browse files
OrKoNwolfib
authored andcommitted
refactor: move emulation settings to context (ChromeDevTools#1000)
- reduces boilerplate - allows easily to restore emulation settings This is in preparation for the integration with Lighthouse.
1 parent 822892f commit 19b4e8c

7 files changed

Lines changed: 160 additions & 173 deletions

File tree

src/McpContext.ts

Lines changed: 123 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
} from './DevtoolsUtils.js';
1717
import type {ListenerMap, UncaughtError} from './PageCollector.js';
1818
import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
19-
import {Locator} from './third_party/index.js';
2019
import type {DevTools} from './third_party/index.js';
2120
import 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';
3332
import {type ToolGroup} from './tools/inPage.js';
3433
import {listPages} from './tools/pages.js';
3534
import {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+
6876
interface 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 {

src/tools/ToolDefinition.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,20 @@ export type Context = Readonly<{
118118
selectPage(page: Page): void;
119119
getElementByUid(uid: string): Promise<ElementHandle<Element>>;
120120
getAXNodeByUid(uid: string): TextSnapshotNode | undefined;
121-
setNetworkConditions(conditions: string | null): void;
122-
setCpuThrottlingRate(rate: number): void;
123-
setGeolocation(geolocation: GeolocationOptions | null): void;
124-
setViewport(viewport: Viewport | null): void;
121+
emulate(options: {
122+
networkConditions?: string | null;
123+
cpuThrottlingRate?: number | null;
124+
geolocation?: GeolocationOptions | null;
125+
userAgent?: string | null;
126+
colorScheme?: 'dark' | 'light' | 'auto' | null;
127+
viewport?: Viewport | null;
128+
}): Promise<void>;
129+
getNetworkConditions(): string | null;
130+
getCpuThrottlingRate(): number;
131+
getGeolocation(): GeolocationOptions | null;
125132
getViewport(): Viewport | null;
126-
setUserAgent(userAgent: string | null): void;
127133
getUserAgent(): string | null;
128-
setColorScheme(scheme: 'dark' | 'light' | null): void;
134+
getColorScheme(): 'dark' | 'light' | null;
129135
saveTemporaryFile(
130136
data: Uint8Array<ArrayBufferLike>,
131137
mimeType: 'image/png' | 'image/jpeg' | 'image/webp',

src/tools/emulation.ts

Lines changed: 1 addition & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -104,97 +104,6 @@ export const emulate = defineTool({
104104
),
105105
},
106106
handler: async (request, _response, context) => {
107-
const page = context.getSelectedPage();
108-
const {
109-
networkConditions,
110-
cpuThrottlingRate,
111-
geolocation,
112-
userAgent,
113-
viewport,
114-
} = request.params;
115-
116-
if (networkConditions) {
117-
if (networkConditions === 'No emulation') {
118-
await page.emulateNetworkConditions(null);
119-
context.setNetworkConditions(null);
120-
} else if (networkConditions === 'Offline') {
121-
await page.emulateNetworkConditions({
122-
offline: true,
123-
download: 0,
124-
upload: 0,
125-
latency: 0,
126-
});
127-
context.setNetworkConditions('Offline');
128-
} else if (networkConditions in PredefinedNetworkConditions) {
129-
const networkCondition =
130-
PredefinedNetworkConditions[
131-
networkConditions as keyof typeof PredefinedNetworkConditions
132-
];
133-
await page.emulateNetworkConditions(networkCondition);
134-
context.setNetworkConditions(networkConditions);
135-
}
136-
}
137-
138-
if (cpuThrottlingRate) {
139-
await page.emulateCPUThrottling(cpuThrottlingRate);
140-
context.setCpuThrottlingRate(cpuThrottlingRate);
141-
}
142-
143-
if (geolocation !== undefined) {
144-
if (geolocation === null) {
145-
await page.setGeolocation({latitude: 0, longitude: 0});
146-
context.setGeolocation(null);
147-
} else {
148-
await page.setGeolocation(geolocation);
149-
context.setGeolocation(geolocation);
150-
}
151-
}
152-
153-
if (userAgent !== undefined) {
154-
if (userAgent === null) {
155-
await page.setUserAgent({
156-
userAgent: undefined,
157-
});
158-
context.setUserAgent(null);
159-
} else {
160-
await page.setUserAgent({
161-
userAgent,
162-
});
163-
context.setUserAgent(userAgent);
164-
}
165-
}
166-
167-
if (request.params.colorScheme) {
168-
if (request.params.colorScheme === 'auto') {
169-
await page.emulateMediaFeatures([
170-
{name: 'prefers-color-scheme', value: ''},
171-
]);
172-
context.setColorScheme(null);
173-
} else {
174-
await page.emulateMediaFeatures([
175-
{
176-
name: 'prefers-color-scheme',
177-
value: request.params.colorScheme,
178-
},
179-
]);
180-
context.setColorScheme(request.params.colorScheme);
181-
}
182-
}
183-
184-
if (viewport !== undefined) {
185-
if (viewport === null) {
186-
await page.setViewport(null);
187-
context.setViewport(null);
188-
} else {
189-
const defaults = {
190-
deviceScaleFactor: 1,
191-
isMobile: false,
192-
hasTouch: false,
193-
isLandscape: false,
194-
};
195-
await page.setViewport({...defaults, ...viewport});
196-
context.setViewport({...defaults, ...viewport});
197-
}
198-
}
107+
await context.emulate(request.params);
199108
},
200109
});

0 commit comments

Comments
 (0)