diff --git a/packages/puppeteer-core/src/common/EmulationManager.ts b/packages/puppeteer-core/src/common/EmulationManager.ts index 27aa57581c8..45d1fa7056d 100644 --- a/packages/puppeteer-core/src/common/EmulationManager.ts +++ b/packages/puppeteer-core/src/common/EmulationManager.ts @@ -15,6 +15,10 @@ */ import {Protocol} from 'devtools-protocol'; +import {GeolocationOptions, MediaFeature} from '../api/Page.js'; +import {assert} from '../util/assert.js'; +import {isErrorLike} from '../util/ErrorLike.js'; + import {CDPSession} from './Connection.js'; import {Viewport} from './PuppeteerViewport.js'; @@ -25,11 +29,16 @@ export class EmulationManager { #client: CDPSession; #emulatingMobile = false; #hasTouch = false; + #javascriptEnabled = true; constructor(client: CDPSession) { this.#client = client; } + get javascriptEnabled(): boolean { + return this.#javascriptEnabled; + } + async emulateViewport(viewport: Viewport): Promise { const mobile = viewport.isMobile || false; const width = viewport.width; @@ -60,4 +69,149 @@ export class EmulationManager { this.#hasTouch = hasTouch; return reloadNeeded; } + + async emulateIdleState(overrides?: { + isUserActive: boolean; + isScreenUnlocked: boolean; + }): Promise { + if (overrides) { + await this.#client.send('Emulation.setIdleOverride', { + isUserActive: overrides.isUserActive, + isScreenUnlocked: overrides.isScreenUnlocked, + }); + } else { + await this.#client.send('Emulation.clearIdleOverride'); + } + } + + async emulateTimezone(timezoneId?: string): Promise { + try { + await this.#client.send('Emulation.setTimezoneOverride', { + timezoneId: timezoneId || '', + }); + } catch (error) { + if (isErrorLike(error) && error.message.includes('Invalid timezone')) { + throw new Error(`Invalid timezone ID: ${timezoneId}`); + } + throw error; + } + } + + async emulateVisionDeficiency( + type?: Protocol.Emulation.SetEmulatedVisionDeficiencyRequest['type'] + ): Promise { + const visionDeficiencies = new Set< + Protocol.Emulation.SetEmulatedVisionDeficiencyRequest['type'] + >([ + 'none', + 'achromatopsia', + 'blurredVision', + 'deuteranopia', + 'protanopia', + 'tritanopia', + ]); + try { + assert( + !type || visionDeficiencies.has(type), + `Unsupported vision deficiency: ${type}` + ); + await this.#client.send('Emulation.setEmulatedVisionDeficiency', { + type: type || 'none', + }); + } catch (error) { + throw error; + } + } + + async emulateCPUThrottling(factor: number | null): Promise { + assert( + factor === null || factor >= 1, + 'Throttling rate should be greater or equal to 1' + ); + await this.#client.send('Emulation.setCPUThrottlingRate', { + rate: factor ?? 1, + }); + } + + async emulateMediaFeatures(features?: MediaFeature[]): Promise { + if (!features) { + await this.#client.send('Emulation.setEmulatedMedia', {}); + } + if (Array.isArray(features)) { + for (const mediaFeature of features) { + const name = mediaFeature.name; + assert( + /^(?:prefers-(?:color-scheme|reduced-motion)|color-gamut)$/.test( + name + ), + 'Unsupported media feature: ' + name + ); + } + await this.#client.send('Emulation.setEmulatedMedia', { + features: features, + }); + } + } + + async emulateMediaType(type?: string): Promise { + assert( + type === 'screen' || + type === 'print' || + (type ?? undefined) === undefined, + 'Unsupported media type: ' + type + ); + await this.#client.send('Emulation.setEmulatedMedia', { + media: type || '', + }); + } + + async setGeolocation(options: GeolocationOptions): Promise { + const {longitude, latitude, accuracy = 0} = options; + if (longitude < -180 || longitude > 180) { + throw new Error( + `Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.` + ); + } + if (latitude < -90 || latitude > 90) { + throw new Error( + `Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.` + ); + } + if (accuracy < 0) { + throw new Error( + `Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.` + ); + } + await this.#client.send('Emulation.setGeolocationOverride', { + longitude, + latitude, + accuracy, + }); + } + + /** + * Resets default white background + */ + async resetDefaultBackgroundColor(): Promise { + await this.#client.send('Emulation.setDefaultBackgroundColorOverride'); + } + + /** + * Hides default white background + */ + async setTransparentBackgroundColor(): Promise { + await this.#client.send('Emulation.setDefaultBackgroundColorOverride', { + color: {r: 0, g: 0, b: 0, a: 0}, + }); + } + + async setJavaScriptEnabled(enabled: boolean): Promise { + if (this.#javascriptEnabled === enabled) { + return; + } + this.#javascriptEnabled = enabled; + await this.#client.send('Emulation.setScriptExecutionDisabled', { + value: !enabled, + }); + } } diff --git a/packages/puppeteer-core/src/common/Page.ts b/packages/puppeteer-core/src/common/Page.ts index f8d657b2865..7ef40ca991c 100644 --- a/packages/puppeteer-core/src/common/Page.ts +++ b/packages/puppeteer-core/src/common/Page.ts @@ -143,7 +143,6 @@ export class CDPPage extends Page { #bindings = new Map(); #exposedFunctions = new Map(); #coverage: Coverage; - #javascriptEnabled = true; #viewport: Viewport | null; #screenshotTaskQueue: TaskQueue; #workers = new Map(); @@ -361,7 +360,7 @@ export class CDPPage extends Page { } override isJavaScriptEnabled(): boolean { - return this.#javascriptEnabled; + return this.#emulationManager.javascriptEnabled; } override waitForFileChooser( @@ -391,27 +390,7 @@ export class CDPPage extends Page { } override async setGeolocation(options: GeolocationOptions): Promise { - const {longitude, latitude, accuracy = 0} = options; - if (longitude < -180 || longitude > 180) { - throw new Error( - `Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.` - ); - } - if (latitude < -90 || latitude > 90) { - throw new Error( - `Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.` - ); - } - if (accuracy < 0) { - throw new Error( - `Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.` - ); - } - await this.#client.send('Emulation.setGeolocationOverride', { - longitude, - latitude, - accuracy, - }); + return await this.#emulationManager.setGeolocation(options); } override target(): Target { @@ -874,22 +853,6 @@ export class CDPPage extends Page { this.emit(PageEmittedEvents.Dialog, dialog); } - /** - * Resets default white background - */ - async #resetDefaultBackgroundColor() { - await this.#client.send('Emulation.setDefaultBackgroundColorOverride'); - } - - /** - * Hides default white background - */ - async #setTransparentBackgroundColor(): Promise { - await this.#client.send('Emulation.setDefaultBackgroundColorOverride', { - color: {r: 0, g: 0, b: 0, a: 0}, - }); - } - override url(): string { return this.mainFrame().url(); } @@ -1068,13 +1031,7 @@ export class CDPPage extends Page { } override async setJavaScriptEnabled(enabled: boolean): Promise { - if (this.#javascriptEnabled === enabled) { - return; - } - this.#javascriptEnabled = enabled; - await this.#client.send('Emulation.setScriptExecutionDisabled', { - value: !enabled, - }); + return await this.#emulationManager.setJavaScriptEnabled(enabled); } override async setBypassCSP(enabled: boolean): Promise { @@ -1082,100 +1039,34 @@ export class CDPPage extends Page { } override async emulateMediaType(type?: string): Promise { - assert( - type === 'screen' || - type === 'print' || - (type ?? undefined) === undefined, - 'Unsupported media type: ' + type - ); - await this.#client.send('Emulation.setEmulatedMedia', { - media: type || '', - }); + return await this.#emulationManager.emulateMediaType(type); } override async emulateCPUThrottling(factor: number | null): Promise { - assert( - factor === null || factor >= 1, - 'Throttling rate should be greater or equal to 1' - ); - await this.#client.send('Emulation.setCPUThrottlingRate', { - rate: factor ?? 1, - }); + return await this.#emulationManager.emulateCPUThrottling(factor); } override async emulateMediaFeatures( features?: MediaFeature[] ): Promise { - if (!features) { - await this.#client.send('Emulation.setEmulatedMedia', {}); - } - if (Array.isArray(features)) { - for (const mediaFeature of features) { - const name = mediaFeature.name; - assert( - /^(?:prefers-(?:color-scheme|reduced-motion)|color-gamut)$/.test( - name - ), - 'Unsupported media feature: ' + name - ); - } - await this.#client.send('Emulation.setEmulatedMedia', { - features: features, - }); - } + return await this.#emulationManager.emulateMediaFeatures(features); } override async emulateTimezone(timezoneId?: string): Promise { - try { - await this.#client.send('Emulation.setTimezoneOverride', { - timezoneId: timezoneId || '', - }); - } catch (error) { - if (isErrorLike(error) && error.message.includes('Invalid timezone')) { - throw new Error(`Invalid timezone ID: ${timezoneId}`); - } - throw error; - } + return await this.#emulationManager.emulateTimezone(timezoneId); } override async emulateIdleState(overrides?: { isUserActive: boolean; isScreenUnlocked: boolean; }): Promise { - if (overrides) { - await this.#client.send('Emulation.setIdleOverride', { - isUserActive: overrides.isUserActive, - isScreenUnlocked: overrides.isScreenUnlocked, - }); - } else { - await this.#client.send('Emulation.clearIdleOverride'); - } + return await this.#emulationManager.emulateIdleState(overrides); } override async emulateVisionDeficiency( type?: Protocol.Emulation.SetEmulatedVisionDeficiencyRequest['type'] ): Promise { - const visionDeficiencies = new Set< - Protocol.Emulation.SetEmulatedVisionDeficiencyRequest['type'] - >([ - 'none', - 'achromatopsia', - 'blurredVision', - 'deuteranopia', - 'protanopia', - 'tritanopia', - ]); - try { - assert( - !type || visionDeficiencies.has(type), - `Unsupported vision deficiency: ${type}` - ); - await this.#client.send('Emulation.setEmulatedVisionDeficiency', { - type: type || 'none', - }); - } catch (error) { - throw error; - } + return await this.#emulationManager.emulateVisionDeficiency(type); } override async setViewport(viewport: Viewport): Promise { @@ -1378,7 +1269,7 @@ export class CDPPage extends Page { const shouldSetDefaultBackground = options.omitBackground && (format === 'png' || format === 'webp'); if (shouldSetDefaultBackground) { - await this.#setTransparentBackgroundColor(); + await this.#emulationManager.setTransparentBackgroundColor(); } const result = await this.#client.send('Page.captureScreenshot', { @@ -1392,7 +1283,7 @@ export class CDPPage extends Page { fromSurface, }); if (shouldSetDefaultBackground) { - await this.#resetDefaultBackgroundColor(); + await this.#emulationManager.resetDefaultBackgroundColor(); } if (options.fullPage && this.#viewport) { @@ -1435,7 +1326,7 @@ export class CDPPage extends Page { } = this._getPDFOptions(options); if (omitBackground) { - await this.#setTransparentBackgroundColor(); + await this.#emulationManager.setTransparentBackgroundColor(); } const printCommandPromise = this.#client.send('Page.printToPDF', { @@ -1463,7 +1354,7 @@ export class CDPPage extends Page { ); if (omitBackground) { - await this.#resetDefaultBackgroundColor(); + await this.#emulationManager.resetDefaultBackgroundColor(); } assert(result.stream, '`stream` is missing from `Page.printToPDF');