mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: merge PageDelegate, FrameDelegate and ScreenshotterDelegate (#213)
This commit is contained in:
parent
ce21019c7d
commit
b70eebc4b2
@ -32,7 +32,6 @@ import { toConsoleMessageLocation, exceptionToError, releaseObject } from './pro
|
|||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import { PageDelegate } from '../page';
|
import { PageDelegate } from '../page';
|
||||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||||
import { CRScreenshotDelegate } from './Screenshotter';
|
|
||||||
import { Accessibility } from './features/accessibility';
|
import { Accessibility } from './features/accessibility';
|
||||||
import { Coverage } from './features/coverage';
|
import { Coverage } from './features/coverage';
|
||||||
import { PDF } from './features/pdf';
|
import { PDF } from './features/pdf';
|
||||||
@ -60,7 +59,7 @@ type FrameData = {
|
|||||||
id: string,
|
id: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
|
export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
_client: CDPSession;
|
_client: CDPSession;
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
private _networkManager: NetworkManager;
|
private _networkManager: NetworkManager;
|
||||||
@ -70,14 +69,12 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
private _mainFrame: frames.Frame;
|
private _mainFrame: frames.Frame;
|
||||||
rawMouse: RawMouseImpl;
|
rawMouse: RawMouseImpl;
|
||||||
rawKeyboard: RawKeyboardImpl;
|
rawKeyboard: RawKeyboardImpl;
|
||||||
screenshotterDelegate: CRScreenshotDelegate;
|
|
||||||
|
|
||||||
constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) {
|
constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) {
|
||||||
super();
|
super();
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this.rawKeyboard = new RawKeyboardImpl(client);
|
this.rawKeyboard = new RawKeyboardImpl(client);
|
||||||
this.rawMouse = new RawMouseImpl(client);
|
this.rawMouse = new RawMouseImpl(client);
|
||||||
this.screenshotterDelegate = new CRScreenshotDelegate(client);
|
|
||||||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
|
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
|
||||||
this._page = new Page(this, browserContext);
|
this._page = new Page(this, browserContext);
|
||||||
(this._page as any).accessibility = new Accessibility(client);
|
(this._page as any).accessibility = new Accessibility(client);
|
||||||
@ -272,7 +269,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
return;
|
return;
|
||||||
assert(parentFrameId);
|
assert(parentFrameId);
|
||||||
const parentFrame = this._frames.get(parentFrameId);
|
const parentFrame = this._frames.get(parentFrameId);
|
||||||
const frame = new frames.Frame(this, this._page, parentFrame);
|
const frame = new frames.Frame(this._page, parentFrame);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: frameId,
|
id: frameId,
|
||||||
};
|
};
|
||||||
@ -302,7 +299,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
data.id = framePayload.id;
|
data.id = framePayload.id;
|
||||||
} else {
|
} else {
|
||||||
// Initial main frame navigation.
|
// Initial main frame navigation.
|
||||||
frame = new frames.Frame(this, this._page, null);
|
frame = new frames.Frame(this._page, null);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: framePayload.id,
|
id: framePayload.id,
|
||||||
};
|
};
|
||||||
@ -542,6 +539,34 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
else
|
else
|
||||||
await (this._page.browser() as Browser)._closePage(this._page);
|
await (this._page.browser() as Browser)._closePage(this._page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null> {
|
||||||
|
const rect = await handle.boundingBox();
|
||||||
|
if (!rect)
|
||||||
|
return rect;
|
||||||
|
const { layoutViewport: { pageX, pageY } } = await this._client.send('Page.getLayoutMetrics');
|
||||||
|
rect.x += pageX;
|
||||||
|
rect.y += pageY;
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
canScreenshotOutsideViewport(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void> {
|
||||||
|
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
||||||
|
}
|
||||||
|
|
||||||
|
async takeScreenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<Buffer> {
|
||||||
|
const clip = options.clip ? { ...options.clip, scale: 1 } : undefined;
|
||||||
|
const result = await this._client.send('Page.captureScreenshot', { format, quality: options.quality, clip });
|
||||||
|
return Buffer.from(result.data, 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetViewport(): Promise<void> {
|
||||||
|
await this._client.send('Emulation.setDeviceMetricsOverride', { mobile: false, width: 0, height: 0, deviceScaleFactor: 0 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertNoLegacyNavigationOptions(options: frames.NavigateOptions) {
|
function assertNoLegacyNavigationOptions(options: frames.NavigateOptions) {
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
import * as dom from '../dom';
|
|
||||||
import { ScreenshotterDelegate } from '../screenshotter';
|
|
||||||
import * as types from '../types';
|
|
||||||
import { CDPSession } from './api';
|
|
||||||
|
|
||||||
export class CRScreenshotDelegate implements ScreenshotterDelegate {
|
|
||||||
private _session: CDPSession;
|
|
||||||
|
|
||||||
constructor(session: CDPSession) {
|
|
||||||
this._session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async getBoundingBox(handle: dom.ElementHandle<Node>): Promise<types.Rect | null> {
|
|
||||||
const rect = await handle.boundingBox();
|
|
||||||
if (!rect)
|
|
||||||
return rect;
|
|
||||||
const { layoutViewport: { pageX, pageY } } = await this._session.send('Page.getLayoutMetrics');
|
|
||||||
rect.x += pageX;
|
|
||||||
rect.y += pageY;
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
canCaptureOutsideViewport(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void> {
|
|
||||||
await this._session.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
|
||||||
}
|
|
||||||
|
|
||||||
async screenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<Buffer> {
|
|
||||||
const clip = options.clip ? { ...options.clip, scale: 1 } : undefined;
|
|
||||||
const result = await this._session.send('Page.captureScreenshot', { format, quality: options.quality, clip });
|
|
||||||
return Buffer.from(result.data, 'base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetViewport(): Promise<void> {
|
|
||||||
await this._session.send('Emulation.setDeviceMetricsOverride', { mobile: false, width: 0, height: 0, deviceScaleFactor: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,8 +30,6 @@ import * as dialog from '../dialog';
|
|||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import * as input from '../input';
|
import * as input from '../input';
|
||||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||||
import { FFScreenshotDelegate } from './Screenshotter';
|
|
||||||
import { Browser } from './Browser';
|
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { Interception } from './features/interception';
|
import { Interception } from './features/interception';
|
||||||
import { Accessibility } from './features/accessibility';
|
import { Accessibility } from './features/accessibility';
|
||||||
@ -51,10 +49,9 @@ type FrameData = {
|
|||||||
frameId: string,
|
frameId: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
|
export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
readonly rawMouse: RawMouseImpl;
|
readonly rawMouse: RawMouseImpl;
|
||||||
readonly rawKeyboard: RawKeyboardImpl;
|
readonly rawKeyboard: RawKeyboardImpl;
|
||||||
readonly screenshotterDelegate: FFScreenshotDelegate;
|
|
||||||
readonly _session: JugglerSession;
|
readonly _session: JugglerSession;
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
private readonly _networkManager: NetworkManager;
|
private readonly _networkManager: NetworkManager;
|
||||||
@ -68,7 +65,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
this._session = session;
|
this._session = session;
|
||||||
this.rawKeyboard = new RawKeyboardImpl(session);
|
this.rawKeyboard = new RawKeyboardImpl(session);
|
||||||
this.rawMouse = new RawMouseImpl(session);
|
this.rawMouse = new RawMouseImpl(session);
|
||||||
this.screenshotterDelegate = new FFScreenshotDelegate(session, this);
|
|
||||||
this._networkManager = new NetworkManager(session, this);
|
this._networkManager = new NetworkManager(session, this);
|
||||||
this._mainFrame = null;
|
this._mainFrame = null;
|
||||||
this._frames = new Map();
|
this._frames = new Map();
|
||||||
@ -182,7 +178,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
|
|
||||||
_onFrameAttached(params) {
|
_onFrameAttached(params) {
|
||||||
const parentFrame = this._frames.get(params.parentFrameId) || null;
|
const parentFrame = this._frames.get(params.parentFrameId) || null;
|
||||||
const frame = new frames.Frame(this, this._page, parentFrame);
|
const frame = new frames.Frame(this._page, parentFrame);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
frameId: params.frameId,
|
frameId: params.frameId,
|
||||||
};
|
};
|
||||||
@ -399,6 +395,36 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
async closePage(runBeforeUnload: boolean): Promise<void> {
|
async closePage(runBeforeUnload: boolean): Promise<void> {
|
||||||
await this._session.send('Page.close', { runBeforeUnload });
|
await this._session.send('Page.close', { runBeforeUnload });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null> {
|
||||||
|
const frameId = this._frameData(handle.executionContext().frame()).frameId;
|
||||||
|
return this._session.send('Page.getBoundingBox', {
|
||||||
|
frameId,
|
||||||
|
objectId: handle._remoteObject.objectId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canScreenshotOutsideViewport(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void> {
|
||||||
|
if (color)
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
async takeScreenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<Buffer> {
|
||||||
|
const { data } = await this._session.send('Page.screenshot', {
|
||||||
|
mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'),
|
||||||
|
fullPage: options.fullPage,
|
||||||
|
clip: options.clip,
|
||||||
|
});
|
||||||
|
return Buffer.from(data, 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetViewport(): Promise<void> {
|
||||||
|
await this._session.send('Page.setViewport', { viewport: null });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {
|
export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
import { ScreenshotterDelegate } from '../screenshotter';
|
|
||||||
import * as types from '../types';
|
|
||||||
import * as dom from '../dom';
|
|
||||||
import { JugglerSession } from './Connection';
|
|
||||||
import { FrameManager } from './FrameManager';
|
|
||||||
|
|
||||||
export class FFScreenshotDelegate implements ScreenshotterDelegate {
|
|
||||||
private _session: JugglerSession;
|
|
||||||
private _frameManager: FrameManager;
|
|
||||||
|
|
||||||
constructor(session: JugglerSession, frameManager: FrameManager) {
|
|
||||||
this._session = session;
|
|
||||||
this._frameManager = frameManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
getBoundingBox(handle: dom.ElementHandle<Node>): Promise<types.Rect | null> {
|
|
||||||
const frameId = this._frameManager._frameData(handle.executionContext().frame()).frameId;
|
|
||||||
return this._session.send('Page.getBoundingBox', {
|
|
||||||
frameId,
|
|
||||||
objectId: handle._remoteObject.objectId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
canCaptureOutsideViewport(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void> {
|
|
||||||
}
|
|
||||||
|
|
||||||
async screenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<Buffer> {
|
|
||||||
const { data } = await this._session.send('Page.screenshot', {
|
|
||||||
mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'),
|
|
||||||
fullPage: options.fullPage,
|
|
||||||
clip: options.clip,
|
|
||||||
});
|
|
||||||
return Buffer.from(data, 'base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetViewport(): Promise<void> {
|
|
||||||
await this._session.send('Page.setViewport', { viewport: null });
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,10 +22,9 @@ import * as dom from './dom';
|
|||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
import { helper, assert, RegisteredListener } from './helper';
|
import { helper, assert, RegisteredListener } from './helper';
|
||||||
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from './input';
|
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from './input';
|
||||||
import { TimeoutSettings } from './TimeoutSettings';
|
|
||||||
import { TimeoutError } from './Errors';
|
import { TimeoutError } from './Errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { EventEmitter } from 'events';
|
import { Page } from './page';
|
||||||
|
|
||||||
const readFileAsync = helper.promisify(fs.readFile);
|
const readFileAsync = helper.promisify(fs.readFile);
|
||||||
|
|
||||||
@ -46,22 +45,9 @@ export type GotoOptions = NavigateOptions & {
|
|||||||
referer?: string,
|
referer?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FrameDelegate {
|
|
||||||
navigateFrame(frame: Frame, url: string, options?: GotoOptions): Promise<network.Response | null>;
|
|
||||||
waitForFrameNavigation(frame: Frame, options?: NavigateOptions): Promise<network.Response | null>;
|
|
||||||
setFrameContent(frame: Frame, html: string, options?: NavigateOptions): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Page extends EventEmitter {
|
|
||||||
_lifecycleWatchers: Set<LifecycleWatcher>;
|
|
||||||
_timeoutSettings: TimeoutSettings;
|
|
||||||
_disconnectedPromise: Promise<Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LifecycleEvent = 'load' | 'domcontentloaded';
|
export type LifecycleEvent = 'load' | 'domcontentloaded';
|
||||||
|
|
||||||
export class Frame {
|
export class Frame {
|
||||||
readonly _delegate: FrameDelegate;
|
|
||||||
readonly _firedLifecycleEvents: Set<LifecycleEvent>;
|
readonly _firedLifecycleEvents: Set<LifecycleEvent>;
|
||||||
_lastDocumentId: string;
|
_lastDocumentId: string;
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
@ -72,8 +58,7 @@ export class Frame {
|
|||||||
private _childFrames = new Set<Frame>();
|
private _childFrames = new Set<Frame>();
|
||||||
private _name: string;
|
private _name: string;
|
||||||
|
|
||||||
constructor(delegate: FrameDelegate, page: Page, parentFrame: Frame | null) {
|
constructor(page: Page, parentFrame: Frame | null) {
|
||||||
this._delegate = delegate;
|
|
||||||
this._firedLifecycleEvents = new Set();
|
this._firedLifecycleEvents = new Set();
|
||||||
this._lastDocumentId = '';
|
this._lastDocumentId = '';
|
||||||
this._page = page;
|
this._page = page;
|
||||||
@ -89,11 +74,11 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async goto(url: string, options?: GotoOptions): Promise<network.Response | null> {
|
async goto(url: string, options?: GotoOptions): Promise<network.Response | null> {
|
||||||
return this._delegate.navigateFrame(this, url, options);
|
return this._page._delegate.navigateFrame(this, url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForNavigation(options?: NavigateOptions): Promise<network.Response | null> {
|
async waitForNavigation(options?: NavigateOptions): Promise<network.Response | null> {
|
||||||
return this._delegate.waitForFrameNavigation(this, options);
|
return this._page._delegate.waitForFrameNavigation(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
_mainContext(): Promise<js.ExecutionContext> {
|
_mainContext(): Promise<js.ExecutionContext> {
|
||||||
@ -174,7 +159,7 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setContent(html: string, options?: NavigateOptions): Promise<void> {
|
async setContent(html: string, options?: NavigateOptions): Promise<void> {
|
||||||
return this._delegate.setFrameContent(this, html, options);
|
return this._page._delegate.setFrameContent(this, html, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
name(): string {
|
name(): string {
|
||||||
|
20
src/page.ts
20
src/page.ts
@ -22,7 +22,7 @@ import { assert, debugError, helper } from './helper';
|
|||||||
import * as input from './input';
|
import * as input from './input';
|
||||||
import * as js from './javascript';
|
import * as js from './javascript';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
import { Screenshotter, ScreenshotterDelegate } from './screenshotter';
|
import { Screenshotter } from './screenshotter';
|
||||||
import { TimeoutSettings } from './TimeoutSettings';
|
import { TimeoutSettings } from './TimeoutSettings';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
@ -32,9 +32,7 @@ import { ConsoleMessage, ConsoleMessageLocation } from './console';
|
|||||||
export interface PageDelegate {
|
export interface PageDelegate {
|
||||||
readonly rawMouse: input.RawMouse;
|
readonly rawMouse: input.RawMouse;
|
||||||
readonly rawKeyboard: input.RawKeyboard;
|
readonly rawKeyboard: input.RawKeyboard;
|
||||||
readonly screenshotterDelegate: ScreenshotterDelegate;
|
|
||||||
mainFrame(): frames.Frame;
|
|
||||||
frames(): frames.Frame[];
|
|
||||||
reload(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
reload(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||||
goBack(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
goBack(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||||
goForward(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
goForward(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||||
@ -44,6 +42,12 @@ export interface PageDelegate {
|
|||||||
// TODO: reverse didClose call sequence.
|
// TODO: reverse didClose call sequence.
|
||||||
didClose(): void;
|
didClose(): void;
|
||||||
|
|
||||||
|
mainFrame(): frames.Frame;
|
||||||
|
frames(): frames.Frame[];
|
||||||
|
navigateFrame(frame: frames.Frame, url: string, options?: frames.GotoOptions): Promise<network.Response | null>;
|
||||||
|
waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||||
|
setFrameContent(frame: frames.Frame, html: string, options?: frames.NavigateOptions): Promise<void>;
|
||||||
|
|
||||||
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>;
|
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>;
|
||||||
setUserAgent(userAgent: string): Promise<void>;
|
setUserAgent(userAgent: string): Promise<void>;
|
||||||
setJavaScriptEnabled(enabled: boolean): Promise<void>;
|
setJavaScriptEnabled(enabled: boolean): Promise<void>;
|
||||||
@ -51,6 +55,12 @@ export interface PageDelegate {
|
|||||||
setViewport(viewport: types.Viewport): Promise<void>;
|
setViewport(viewport: types.Viewport): Promise<void>;
|
||||||
setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void>;
|
setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void>;
|
||||||
setCacheEnabled(enabled: boolean): Promise<void>;
|
setCacheEnabled(enabled: boolean): Promise<void>;
|
||||||
|
|
||||||
|
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null>;
|
||||||
|
canScreenshotOutsideViewport(): boolean;
|
||||||
|
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
||||||
|
takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer>;
|
||||||
|
resetViewport(oldSize: types.Size): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageState = {
|
type PageState = {
|
||||||
@ -106,7 +116,7 @@ export class Page extends EventEmitter {
|
|||||||
this.keyboard = new input.Keyboard(delegate.rawKeyboard);
|
this.keyboard = new input.Keyboard(delegate.rawKeyboard);
|
||||||
this.mouse = new input.Mouse(delegate.rawMouse, this.keyboard);
|
this.mouse = new input.Mouse(delegate.rawMouse, this.keyboard);
|
||||||
this._timeoutSettings = new TimeoutSettings();
|
this._timeoutSettings = new TimeoutSettings();
|
||||||
this._screenshotter = new Screenshotter(this, delegate.screenshotterDelegate, browserContext.browser());
|
this._screenshotter = new Screenshotter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_didClose() {
|
_didClose() {
|
||||||
|
@ -20,36 +20,22 @@ import * as mime from 'mime';
|
|||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import { assert, helper } from './helper';
|
import { assert, helper } from './helper';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
|
import { Page } from './page';
|
||||||
|
|
||||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||||
|
|
||||||
export interface Page {
|
|
||||||
viewport(): types.Viewport | null;
|
|
||||||
setViewport(v: types.Viewport): Promise<void>;
|
|
||||||
evaluate(f: () => any): Promise<types.Rect>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScreenshotterDelegate {
|
|
||||||
getBoundingBox(handle: dom.ElementHandle<Node>): Promise<types.Rect | null>;
|
|
||||||
canCaptureOutsideViewport(): boolean;
|
|
||||||
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
|
||||||
screenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer>;
|
|
||||||
resetViewport(oldSize: types.Size): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Screenshotter {
|
export class Screenshotter {
|
||||||
private _queue = new TaskQueue();
|
private _queue = new TaskQueue();
|
||||||
private _delegate: ScreenshotterDelegate;
|
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
|
|
||||||
constructor(page: Page, delegate: ScreenshotterDelegate, browserObject: any) {
|
constructor(page: Page) {
|
||||||
this._delegate = delegate;
|
|
||||||
this._page = page;
|
this._page = page;
|
||||||
|
|
||||||
this._queue = browserObject[taskQueueSymbol];
|
const browser = page.browser();
|
||||||
|
this._queue = browser[taskQueueSymbol];
|
||||||
if (!this._queue) {
|
if (!this._queue) {
|
||||||
this._queue = new TaskQueue();
|
this._queue = new TaskQueue();
|
||||||
browserObject[taskQueueSymbol] = this._queue;
|
browser[taskQueueSymbol] = this._queue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +51,7 @@ export class Screenshotter {
|
|||||||
height: Math.max(document.body.offsetHeight, document.documentElement.offsetHeight)
|
height: Math.max(document.body.offsetHeight, document.documentElement.offsetHeight)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (options.fullPage && !this._delegate.canCaptureOutsideViewport()) {
|
if (options.fullPage && !this._page._delegate.canScreenshotOutsideViewport()) {
|
||||||
const fullPageRect = await this._page.evaluate(() => ({
|
const fullPageRect = await this._page.evaluate(() => ({
|
||||||
width: Math.max(
|
width: Math.max(
|
||||||
document.body.scrollWidth, document.documentElement.scrollWidth,
|
document.body.scrollWidth, document.documentElement.scrollWidth,
|
||||||
@ -90,7 +76,7 @@ export class Screenshotter {
|
|||||||
if (viewport)
|
if (viewport)
|
||||||
await this._page.setViewport(viewport);
|
await this._page.setViewport(viewport);
|
||||||
else
|
else
|
||||||
await this._delegate.resetViewport(viewportSize);
|
await this._page._delegate.resetViewport(viewportSize);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@ -102,14 +88,14 @@ export class Screenshotter {
|
|||||||
return this._queue.postTask(async () => {
|
return this._queue.postTask(async () => {
|
||||||
let overridenViewport: types.Viewport | undefined;
|
let overridenViewport: types.Viewport | undefined;
|
||||||
|
|
||||||
let boundingBox = await this._delegate.getBoundingBox(handle);
|
let boundingBox = await this._page._delegate.getBoundingBoxForScreenshot(handle);
|
||||||
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
|
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
|
||||||
assert(boundingBox.width !== 0, 'Node has 0 width.');
|
assert(boundingBox.width !== 0, 'Node has 0 width.');
|
||||||
assert(boundingBox.height !== 0, 'Node has 0 height.');
|
assert(boundingBox.height !== 0, 'Node has 0 height.');
|
||||||
boundingBox = enclosingIntRect(boundingBox);
|
boundingBox = enclosingIntRect(boundingBox);
|
||||||
const viewport = this._page.viewport();
|
const viewport = this._page.viewport();
|
||||||
|
|
||||||
if (!this._delegate.canCaptureOutsideViewport()) {
|
if (!this._page._delegate.canScreenshotOutsideViewport()) {
|
||||||
if (boundingBox.width > viewport.width || boundingBox.height > viewport.height) {
|
if (boundingBox.width > viewport.width || boundingBox.height > viewport.height) {
|
||||||
overridenViewport = {
|
overridenViewport = {
|
||||||
...viewport,
|
...viewport,
|
||||||
@ -120,7 +106,7 @@ export class Screenshotter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await handle._scrollIntoViewIfNeeded();
|
await handle._scrollIntoViewIfNeeded();
|
||||||
boundingBox = enclosingIntRect(await this._delegate.getBoundingBox(handle));
|
boundingBox = enclosingIntRect(await this._page._delegate.getBoundingBoxForScreenshot(handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!overridenViewport)
|
if (!overridenViewport)
|
||||||
@ -138,10 +124,10 @@ export class Screenshotter {
|
|||||||
private async _screenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer> {
|
private async _screenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer> {
|
||||||
const shouldSetDefaultBackground = options.omitBackground && format === 'png';
|
const shouldSetDefaultBackground = options.omitBackground && format === 'png';
|
||||||
if (shouldSetDefaultBackground)
|
if (shouldSetDefaultBackground)
|
||||||
await this._delegate.setBackgroundColor({ r: 0, g: 0, b: 0, a: 0});
|
await this._page._delegate.setBackgroundColor({ r: 0, g: 0, b: 0, a: 0});
|
||||||
const buffer = await this._delegate.screenshot(format, options, viewport);
|
const buffer = await this._page._delegate.takeScreenshot(format, options, viewport);
|
||||||
if (shouldSetDefaultBackground)
|
if (shouldSetDefaultBackground)
|
||||||
await this._delegate.setBackgroundColor();
|
await this._page._delegate.setBackgroundColor();
|
||||||
if (options.path)
|
if (options.path)
|
||||||
await writeFileAsync(options.path, buffer);
|
await writeFileAsync(options.path, buffer);
|
||||||
return buffer;
|
return buffer;
|
||||||
|
@ -32,9 +32,10 @@ import * as dialog from '../dialog';
|
|||||||
import { Browser } from './Browser';
|
import { Browser } from './Browser';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||||
import { WKScreenshotDelegate } from './Screenshotter';
|
|
||||||
import * as input from '../input';
|
import * as input from '../input';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
|
import * as jpeg from 'jpeg-js';
|
||||||
|
import { PNG } from 'pngjs';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
||||||
@ -54,10 +55,9 @@ type FrameData = {
|
|||||||
|
|
||||||
let lastDocumentId = 0;
|
let lastDocumentId = 0;
|
||||||
|
|
||||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
|
export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
readonly rawMouse: RawMouseImpl;
|
readonly rawMouse: RawMouseImpl;
|
||||||
readonly rawKeyboard: RawKeyboardImpl;
|
readonly rawKeyboard: RawKeyboardImpl;
|
||||||
readonly screenshotterDelegate: WKScreenshotDelegate;
|
|
||||||
_session: TargetSession;
|
_session: TargetSession;
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
private readonly _networkManager: NetworkManager;
|
private readonly _networkManager: NetworkManager;
|
||||||
@ -72,7 +72,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
super();
|
super();
|
||||||
this.rawKeyboard = new RawKeyboardImpl();
|
this.rawKeyboard = new RawKeyboardImpl();
|
||||||
this.rawMouse = new RawMouseImpl();
|
this.rawMouse = new RawMouseImpl();
|
||||||
this.screenshotterDelegate = new WKScreenshotDelegate();
|
|
||||||
this._networkManager = new NetworkManager(this);
|
this._networkManager = new NetworkManager(this);
|
||||||
this._frames = new Map();
|
this._frames = new Map();
|
||||||
this._contextIdToContext = new Map();
|
this._contextIdToContext = new Map();
|
||||||
@ -90,7 +89,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
this._session = session;
|
this._session = session;
|
||||||
this.rawKeyboard.setSession(session);
|
this.rawKeyboard.setSession(session);
|
||||||
this.rawMouse.setSession(session);
|
this.rawMouse.setSession(session);
|
||||||
this.screenshotterDelegate.setSession(session);
|
|
||||||
this._addSessionListeners();
|
this._addSessionListeners();
|
||||||
this._networkManager.setSession(session);
|
this._networkManager.setSession(session);
|
||||||
this._isolatedWorlds = new Set();
|
this._isolatedWorlds = new Set();
|
||||||
@ -222,7 +220,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
return;
|
return;
|
||||||
assert(parentFrameId);
|
assert(parentFrameId);
|
||||||
const parentFrame = this._frames.get(parentFrameId);
|
const parentFrame = this._frames.get(parentFrameId);
|
||||||
const frame = new frames.Frame(this, this._page, parentFrame);
|
const frame = new frames.Frame(this._page, parentFrame);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: frameId,
|
id: frameId,
|
||||||
};
|
};
|
||||||
@ -250,7 +248,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
}
|
}
|
||||||
} else if (isMainFrame) {
|
} else if (isMainFrame) {
|
||||||
// Initial frame navigation.
|
// Initial frame navigation.
|
||||||
frame = new frames.Frame(this, this._page, null);
|
frame = new frames.Frame(this._page, null);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: framePayload.id,
|
id: framePayload.id,
|
||||||
};
|
};
|
||||||
@ -526,4 +524,31 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
|||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
(this._page.browser() as Browser)._closePage(this._page);
|
(this._page.browser() as Browser)._closePage(this._page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null> {
|
||||||
|
return handle.boundingBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
canScreenshotOutsideViewport(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void> {
|
||||||
|
// TODO: line below crashes, sort it out.
|
||||||
|
this._session.send('Page.setDefaultBackgroundColorOverride', { color });
|
||||||
|
}
|
||||||
|
|
||||||
|
async takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer> {
|
||||||
|
const rect = options.clip || { x: 0, y: 0, width: viewport.width, height: viewport.height };
|
||||||
|
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: options.fullPage ? 'Page' : 'Viewport' });
|
||||||
|
const prefix = 'data:image/png;base64,';
|
||||||
|
let buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64');
|
||||||
|
if (format === 'jpeg')
|
||||||
|
buffer = jpeg.encode(PNG.sync.read(buffer)).data;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetViewport(oldSize: types.Size): Promise<void> {
|
||||||
|
await this._session.send('Emulation.setDeviceMetricsOverride', { ...oldSize, deviceScaleFactor: 0 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
import * as jpeg from 'jpeg-js';
|
|
||||||
import { PNG } from 'pngjs';
|
|
||||||
import * as dom from '../dom';
|
|
||||||
import { ScreenshotterDelegate } from '../screenshotter';
|
|
||||||
import * as types from '../types';
|
|
||||||
import { TargetSession } from './Connection';
|
|
||||||
|
|
||||||
export class WKScreenshotDelegate implements ScreenshotterDelegate {
|
|
||||||
private _session: TargetSession;
|
|
||||||
|
|
||||||
setSession(session: TargetSession) {
|
|
||||||
this._session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
getBoundingBox(handle: dom.ElementHandle<Node>): Promise<types.Rect | null> {
|
|
||||||
return handle.boundingBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
canCaptureOutsideViewport(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void> {
|
|
||||||
// TODO: line below crashes, sort it out.
|
|
||||||
this._session.send('Page.setDefaultBackgroundColorOverride', { color });
|
|
||||||
}
|
|
||||||
|
|
||||||
async screenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer> {
|
|
||||||
const rect = options.clip || { x: 0, y: 0, width: viewport.width, height: viewport.height };
|
|
||||||
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: options.fullPage ? 'Page' : 'Viewport' });
|
|
||||||
const prefix = 'data:image/png;base64,';
|
|
||||||
let buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64');
|
|
||||||
if (format === 'jpeg')
|
|
||||||
buffer = jpeg.encode(PNG.sync.read(buffer)).data;
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetViewport(oldSize: types.Size): Promise<void> {
|
|
||||||
await this._session.send('Emulation.setDeviceMetricsOverride', { ...oldSize, deviceScaleFactor: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user