diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index 180e8a651e..a50217975a 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -22,6 +22,7 @@ import * as dom from '../dom'; import * as dialog from '../dialog'; import type * as frames from '../frames'; import { Page } from '../page'; +import type * as channels from '@protocol/channels'; import type { InitScript, PageDelegate } from '../page'; import type { Progress } from '../progress'; import type * as types from '../types'; @@ -32,6 +33,7 @@ import * as bidi from './third_party/bidiProtocol'; import { BidiExecutionContext } from './bidiExecutionContext'; import { BidiNetworkManager } from './bidiNetworkManager'; import { BrowserContext } from '../browserContext'; +import { BidiPDF } from './bidiPdf'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const kPlaywrightBindingChannel = 'playwrightChannel'; @@ -48,6 +50,7 @@ export class BidiPage implements PageDelegate { private _sessionListeners: RegisteredListener[] = []; readonly _browserContext: BidiBrowserContext; readonly _networkManager: BidiNetworkManager; + private readonly _pdf: BidiPDF; _initializedPage: Page | null = null; private _initScriptIds: string[] = []; @@ -61,6 +64,7 @@ export class BidiPage implements PageDelegate { this._page = new Page(this, browserContext); this._browserContext = browserContext; this._networkManager = new BidiNetworkManager(this._session, this._page, this._onNavigationResponseStarted.bind(this)); + this._pdf = new BidiPDF(this._session); this._page.on(Page.Events.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false)); this._sessionListeners = [ eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)), @@ -279,6 +283,9 @@ export class BidiPage implements PageDelegate { } async bringToFront(): Promise { + await this._session.send('browsingContext.activate', { + context: this._session.sessionId, + }); } private async _updateViewport(): Promise { @@ -555,6 +562,10 @@ export class BidiPage implements PageDelegate { async resetForReuse(): Promise { } + async pdf(options: channels.PagePdfParams): Promise { + return this._pdf.generate(options); + } + async getFrameElement(frame: frames.Frame): Promise { const parent = frame.parentFrame(); if (!parent) diff --git a/packages/playwright-core/src/server/bidi/bidiPdf.ts b/packages/playwright-core/src/server/bidi/bidiPdf.ts new file mode 100644 index 0000000000..89fefb5260 --- /dev/null +++ b/packages/playwright-core/src/server/bidi/bidiPdf.ts @@ -0,0 +1,109 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { assert } from '../../utils'; +import type * as channels from '@protocol/channels'; +import type { BidiSession } from './bidiConnection'; + +const PagePaperFormats: { [key: string]: { width: number, height: number }} = { + letter: { width: 8.5, height: 11 }, + legal: { width: 8.5, height: 14 }, + tabloid: { width: 11, height: 17 }, + ledger: { width: 17, height: 11 }, + a0: { width: 33.1, height: 46.8 }, + a1: { width: 23.4, height: 33.1 }, + a2: { width: 16.54, height: 23.4 }, + a3: { width: 11.7, height: 16.54 }, + a4: { width: 8.27, height: 11.7 }, + a5: { width: 5.83, height: 8.27 }, + a6: { width: 4.13, height: 5.83 }, +}; + +const unitToPixels: { [key: string]: number } = { + 'px': 1, + 'in': 96, + 'cm': 37.8, + 'mm': 3.78 +}; + +function convertPrintParameterToInches(text: string | undefined): number | undefined { + if (text === undefined) + return undefined; + let unit = text.substring(text.length - 2).toLowerCase(); + let valueText = ''; + if (unitToPixels.hasOwnProperty(unit)) { + valueText = text.substring(0, text.length - 2); + } else { + // In case of unknown unit try to parse the whole parameter as number of pixels. + // This is consistent with phantom's paperSize behavior. + unit = 'px'; + valueText = text; + } + const value = Number(valueText); + assert(!isNaN(value), 'Failed to parse parameter value: ' + text); + const pixels = value * unitToPixels[unit]; + return pixels / 96; +} + +export class BidiPDF { + private _session: BidiSession; + + constructor(session: BidiSession) { + this._session = session; + } + + async generate(options: channels.PagePdfParams): Promise { + const { + scale = 1, + printBackground = false, + landscape = false, + pageRanges = '', + margin = {}, + } = options; + + let paperWidth = 8.5; + let paperHeight = 11; + if (options.format) { + const format = PagePaperFormats[options.format.toLowerCase()]; + assert(format, 'Unknown paper format: ' + options.format); + paperWidth = format.width; + paperHeight = format.height; + } else { + paperWidth = convertPrintParameterToInches(options.width) || paperWidth; + paperHeight = convertPrintParameterToInches(options.height) || paperHeight; + } + + const { data } = await this._session.send('browsingContext.print', { + context: this._session.sessionId, + background: printBackground, + margin: { + bottom: convertPrintParameterToInches(margin.bottom) || 0, + left: convertPrintParameterToInches(margin.left) || 0, + right: convertPrintParameterToInches(margin.right) || 0, + top: convertPrintParameterToInches(margin.top) || 0 + }, + orientation: landscape ? 'landscape' : 'portrait', + page: { + width: paperWidth, + height: paperHeight + }, + pageRanges: pageRanges ? pageRanges.split(',').map(r => r.trim()) : undefined, + scale, + }); + return Buffer.from(data, 'base64'); + } +}