chore: introduce Page.viewportSizeChanged event (#35994)

This commit is contained in:
Dmitry Gozman 2025-05-19 20:39:41 +00:00 committed by GitHub
parent 6e8c67a3ce
commit 2a5d83a623
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 54 additions and 29 deletions

View File

@ -84,7 +84,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
_workers = new Set<Worker>();
private _closed = false;
readonly _closedOrCrashedScope = new LongStandingScope();
private _viewportSize: Size | null;
private _viewportSize: Size | undefined;
_routes: RouteHandler[] = [];
_webSocketRoutes: WebSocketRouteHandler[] = [];
@ -130,7 +130,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
this._mainFrame = Frame.from(initializer.mainFrame);
this._mainFrame._page = this;
this._frames.add(this._mainFrame);
this._viewportSize = initializer.viewportSize || null;
this._viewportSize = initializer.viewportSize;
this._closed = initializer.isClosed;
this._opener = Page.fromNullable(initializer.opener);
@ -151,6 +151,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
const artifactObject = Artifact.from(artifact);
this._forceVideo()._artifactReady(artifactObject);
});
this._channel.on('viewportSizeChanged', ({ viewportSize }) => this._viewportSize = viewportSize);
this._channel.on('webSocket', ({ webSocket }) => this.emit(Events.Page.WebSocket, WebSocket.from(webSocket)));
this._channel.on('worker', ({ worker }) => this._onWorker(Worker.from(worker)));
@ -506,7 +507,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
}
viewportSize(): Size | null {
return this._viewportSize;
return this._viewportSize || null;
}
async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {

View File

@ -1122,6 +1122,12 @@ scheme.PageDownloadEvent = tObject({
suggestedFilename: tString,
artifact: tChannel(['Artifact']),
});
scheme.PageViewportSizeChangedEvent = tObject({
viewportSize: tOptional(tObject({
width: tNumber,
height: tNumber,
})),
});
scheme.PageFileChooserEvent = tObject({
element: tChannel(['ElementHandle']),
isMultiple: tBoolean,

View File

@ -268,10 +268,10 @@ export class BidiPage implements PageDelegate {
async updateEmulatedViewportSize(): Promise<void> {
const options = this._browserContext._options;
const deviceSize = this._page.emulatedSize();
if (deviceSize === null)
const emulatedSize = this._page.emulatedSize();
if (!emulatedSize)
return;
const viewportSize = deviceSize.viewport;
const viewportSize = emulatedSize.viewport;
await this._session.send('browsingContext.setViewport', {
context: this._session.sessionId,
viewport: {

View File

@ -108,7 +108,7 @@ export class CRPage implements PageDelegate {
const features = opener._nextWindowOpenPopupFeatures.shift() || [];
const viewportSize = helper.getViewportSizeFromWindowFeatures(features);
if (viewportSize)
this._page.setEmulatedSize({ viewport: viewportSize, screen: viewportSize });
this._page.setEmulatedSizeFromWindowOpen({ viewport: viewportSize, screen: viewportSize });
}
const createdEvent = this._isBackgroundPage ? CRBrowserContext.CREvents.BackgroundPage : BrowserContext.Events.Page;
@ -953,7 +953,7 @@ class FrameSession {
assert(this._isMainFrame());
const options = this._crPage._browserContext._options;
const emulatedSize = this._page.emulatedSize();
if (emulatedSize === null)
if (!emulatedSize)
return;
const viewportSize = emulatedSize.viewport;
const screenSize = emulatedSize.screen;

View File

@ -66,7 +66,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
super(parentScope, page, 'Page', {
mainFrame,
viewportSize: page.viewportSize() || undefined,
viewportSize: page.emulatedSize()?.viewport,
isClosed: page.isClosed(),
opener: PageDispatcher.fromNullable(parentScope, page.opener())
});
@ -83,6 +83,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
// Artifact can outlive the page, so bind to the context scope.
this._dispatchEvent('download', { url: download.url, suggestedFilename: download.suggestedFilename(), artifact: ArtifactDispatcher.from(parentScope, download.artifact) });
});
this.addObjectListener(Page.Events.EmulatedSizeChanged, () => this._dispatchEvent('viewportSizeChanged', { viewportSize: page.emulatedSize()?.viewport }));
this.addObjectListener(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
element: ElementHandleDispatcher.from(mainFrame, fileChooser.element()),
isMultiple: fileChooser.isMultiple()

View File

@ -334,7 +334,7 @@ export class FFPage implements PageDelegate {
}
async updateEmulatedViewportSize(): Promise<void> {
const viewportSize = this._page.viewportSize();
const viewportSize = this._page.emulatedSize()?.viewport ?? null;
await this._session.send('Page.setViewportSize', { viewportSize });
}

View File

@ -125,6 +125,7 @@ export class Page extends SdkObject {
Close: 'close',
Crash: 'crash',
Download: 'download',
EmulatedSizeChanged: 'emulatedsizechanged',
FileChooser: 'filechooser',
FrameAttached: 'frameattached',
FrameDetached: 'framedetached',
@ -546,23 +547,24 @@ export class Page extends SdkObject {
}
async setViewportSize(viewportSize: types.Size) {
this._emulatedSize = { viewport: { ...viewportSize }, screen: { ...viewportSize } };
this._setEmulatedSize({ viewport: { ...viewportSize }, screen: { ...viewportSize } });
await this.delegate.updateEmulatedViewportSize();
}
viewportSize(): types.Size | null {
return this.emulatedSize()?.viewport || null;
setEmulatedSizeFromWindowOpen(emulatedSize: EmulatedSize) {
this._setEmulatedSize(emulatedSize);
}
setEmulatedSize(emulatedSize: EmulatedSize) {
private _setEmulatedSize(emulatedSize: EmulatedSize) {
this._emulatedSize = emulatedSize;
this.emit(Page.Events.EmulatedSizeChanged);
}
emulatedSize(): EmulatedSize | null {
emulatedSize(): EmulatedSize | undefined {
if (this._emulatedSize)
return this._emulatedSize;
const contextOptions = this.browserContext._options;
return contextOptions.viewport ? { viewport: contextOptions.viewport, screen: contextOptions.screen || contextOptions.viewport } : null;
return contextOptions.viewport ? { viewport: contextOptions.viewport, screen: contextOptions.screen || contextOptions.viewport } : undefined;
}
async bringToFront(): Promise<void> {

View File

@ -173,12 +173,11 @@ export class Screenshotter {
this._queue = new TaskQueue();
}
private async _originalViewportSize(progress: Progress): Promise<{ viewportSize: types.Size, originalViewportSize: types.Size | null }> {
const originalViewportSize = this._page.viewportSize();
let viewportSize = originalViewportSize;
private async _originalViewportSize(progress: Progress): Promise<types.Size> {
let viewportSize = this._page.emulatedSize()?.viewport;
if (!viewportSize)
viewportSize = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({ width: window.innerWidth, height: window.innerHeight }));
return { viewportSize, originalViewportSize };
return viewportSize;
}
private async _fullPageSize(progress: Progress): Promise<types.Size> {
@ -205,7 +204,7 @@ export class Screenshotter {
const format = validateScreenshotOptions(options);
return this._queue.postTask(async () => {
progress.log('taking page screenshot');
const { viewportSize } = await this._originalViewportSize(progress);
const viewportSize = await this._originalViewportSize(progress);
await this._preparePageForScreenshot(progress, this._page.mainFrame(), options.style, options.caret !== 'initial', options.animations === 'disabled');
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
@ -233,7 +232,7 @@ export class Screenshotter {
const format = validateScreenshotOptions(options);
return this._queue.postTask(async () => {
progress.log('taking element screenshot');
const { viewportSize } = await this._originalViewportSize(progress);
const viewportSize = await this._originalViewportSize(progress);
await this._preparePageForScreenshot(progress, handle._frame, options.style, options.caret !== 'initial', options.animations === 'disabled');
progress.throwIfAborted(); // Do not do extra work.

View File

@ -358,7 +358,7 @@ export class WKBrowserContext extends BrowserContext {
await this._browser._browserSession.send('Playwright.cancelDownload', { uuid });
}
_validateEmulatedViewport(viewportSize?: types.Size | null) {
_validateEmulatedViewport(viewportSize: types.Size | undefined) {
if (!viewportSize)
return;
if (process.platform === 'win32' && this._browser.options.headful && (viewportSize.width < 250 || viewportSize.height < 240))

View File

@ -109,7 +109,7 @@ export class WKPage implements PageDelegate {
const viewportSize = helper.getViewportSizeFromWindowFeatures(opener._nextWindowOpenPopupFeatures);
opener._nextWindowOpenPopupFeatures = undefined;
if (viewportSize)
this._page.setEmulatedSize({ viewport: viewportSize, screen: viewportSize });
this._page.setEmulatedSizeFromWindowOpen({ viewport: viewportSize, screen: viewportSize });
}
}
@ -677,7 +677,7 @@ export class WKPage implements PageDelegate {
}
async updateEmulatedViewportSize(): Promise<void> {
this._browserContext._validateEmulatedViewport(this._page.viewportSize());
this._browserContext._validateEmulatedViewport(this._page.emulatedSize()?.viewport);
await this._updateViewport();
}
@ -694,11 +694,11 @@ export class WKPage implements PageDelegate {
async _updateViewport(): Promise<void> {
const options = this._browserContext._options;
const deviceSize = this._page.emulatedSize();
if (deviceSize === null)
const emulatedSize = this._page.emulatedSize();
if (!emulatedSize)
return;
const viewportSize = deviceSize.viewport;
const screenSize = deviceSize.screen;
const viewportSize = emulatedSize.viewport;
const screenSize = emulatedSize.screen;
const promises: Promise<any>[] = [
this._pageProxySession.send('Emulation.setDeviceMetricsOverride', {
width: viewportSize.width,

View File

@ -2013,6 +2013,7 @@ export interface PageEventTarget {
on(event: 'close', callback: (params: PageCloseEvent) => void): this;
on(event: 'crash', callback: (params: PageCrashEvent) => void): this;
on(event: 'download', callback: (params: PageDownloadEvent) => void): this;
on(event: 'viewportSizeChanged', callback: (params: PageViewportSizeChangedEvent) => void): this;
on(event: 'fileChooser', callback: (params: PageFileChooserEvent) => void): this;
on(event: 'frameAttached', callback: (params: PageFrameAttachedEvent) => void): this;
on(event: 'frameDetached', callback: (params: PageFrameDetachedEvent) => void): this;
@ -2075,6 +2076,12 @@ export type PageDownloadEvent = {
suggestedFilename: string,
artifact: ArtifactChannel,
};
export type PageViewportSizeChangedEvent = {
viewportSize?: {
width: number,
height: number,
},
};
export type PageFileChooserEvent = {
element: ElementHandleChannel,
isMultiple: boolean,
@ -2566,6 +2573,7 @@ export interface PageEvents {
'close': PageCloseEvent;
'crash': PageCrashEvent;
'download': PageDownloadEvent;
'viewportSizeChanged': PageViewportSizeChangedEvent;
'fileChooser': PageFileChooserEvent;
'frameAttached': PageFrameAttachedEvent;
'frameDetached': PageFrameDetachedEvent;

View File

@ -2001,6 +2001,14 @@ Page:
suggestedFilename: string
artifact: Artifact
viewportSizeChanged:
parameters:
viewportSize:
type: object?
properties:
width: number
height: number
fileChooser:
parameters:
element: ElementHandle