chore: merge DOMWorldDelegate and PageDelegate (#228)

This commit is contained in:
Dmitry Gozman 2019-12-12 17:51:05 -08:00 committed by Pavel Feldman
parent 3b202fb4b8
commit 39fa313535
9 changed files with 228 additions and 394 deletions

View file

@ -23,7 +23,6 @@ import * as js from '../javascript';
import * as network from '../network';
import { CDPSession } from './Connection';
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
import { DOMWorldDelegate } from './JSHandle';
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
import { Page } from '../page';
import { Protocol } from './protocol';
@ -353,7 +352,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
this._isolatedWorlds.add(contextPayload.name);
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._client, contextPayload));
if (frame)
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
context._domWorld = new dom.DOMWorld(context, frame);
if (frame) {
if (contextPayload.auxData && !!contextPayload.auxData.isDefault)
frame._contextCreated('main', context);
@ -454,7 +453,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
const frame = this.frame(event.frameId);
const utilityWorld = await frame._utilityDOMWorld();
const handle = await (utilityWorld.delegate as DOMWorldDelegate).adoptBackendNodeId(event.backendNodeId, utilityWorld);
const handle = await this.adoptBackendNodeId(event.backendNodeId, utilityWorld);
this._page._onFileChooserOpened(handle);
}
@ -567,6 +566,73 @@ export class FrameManager extends EventEmitter implements PageDelegate {
async resetViewport(): Promise<void> {
await this._client.send('Emulation.setDeviceMetricsOverride', { mobile: false, width: 0, height: 0, deviceScaleFactor: 0 });
}
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: toRemoteObject(handle).objectId
});
if (typeof nodeInfo.node.frameId !== 'string')
return null;
return this.frame(nodeInfo.node.frameId);
}
isElementHandle(remoteObject: any): boolean {
return (remoteObject as Protocol.Runtime.RemoteObject).subtype === 'node';
}
async getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const result = await this._client.send('DOM.getBoxModel', {
objectId: toRemoteObject(handle).objectId
}).catch(debugError);
if (!result)
return null;
const quad = result.model.border;
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
return {x, y, width, height};
}
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._client.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId
}).catch(debugError);
if (!result)
return null;
return result.quads.map(quad => [
{ x: quad[0], y: quad[1] },
{ x: quad[2], y: quad[3] },
{ x: quad[4], y: quad[5] },
{ x: quad[6], y: quad[7] }
]);
}
async layoutViewport(): Promise<{ width: number, height: number }> {
const layoutMetrics = await this._client.send('Page.getLayoutMetrics');
return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight };
}
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
await handle.evaluate(input.setFileInputFunction, files);
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: toRemoteObject(handle).objectId,
});
return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise<dom.ElementHandle<T>>;
}
async adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.DOMWorld): Promise<dom.ElementHandle> {
const result = await this._client.send('DOM.resolveNode', {
backendNodeId,
executionContextId: (to.context._delegate as ExecutionContextDelegate)._contextId,
}).catch(debugError);
if (!result)
throw new Error('Unable to adopt element handle from a different document');
return to.context._createHandle(result.object).asElement()!;
}
}
function assertNoLegacyNavigationOptions(options: frames.NavigateOptions) {
@ -574,3 +640,7 @@ function assertNoLegacyNavigationOptions(options: frames.NavigateOptions) {
assert((options as any)['networkIdleInflight'] === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
assert((options as any).waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
}
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject {
return handle._remoteObject as Protocol.Runtime.RemoteObject;
}

View file

@ -1,122 +0,0 @@
/**
* Copyright 2019 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 { debugError } from '../helper';
import * as dom from '../dom';
import * as input from '../input';
import * as types from '../types';
import * as frames from '../frames';
import { CDPSession } from './Connection';
import { FrameManager } from './FrameManager';
import { Protocol } from './protocol';
import { ExecutionContextDelegate } from './ExecutionContext';
export class DOMWorldDelegate implements dom.DOMWorldDelegate {
readonly keyboard: input.Keyboard;
readonly mouse: input.Mouse;
readonly frame: frames.Frame;
private _client: CDPSession;
private _frameManager: FrameManager;
constructor(frameManager: FrameManager, frame: frames.Frame) {
this.keyboard = frameManager.page().keyboard;
this.mouse = frameManager.page().mouse;
this.frame = frame;
this._client = frameManager._client;
this._frameManager = frameManager;
}
async contentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: toRemoteObject(handle).objectId
});
if (typeof nodeInfo.node.frameId !== 'string')
return null;
return this._frameManager.frame(nodeInfo.node.frameId);
}
isJavascriptEnabled(): boolean {
return !!this._frameManager.page()._state.javascriptEnabled;
}
isElement(remoteObject: any): boolean {
return (remoteObject as Protocol.Runtime.RemoteObject).subtype === 'node';
}
async boundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const result = await this._client.send('DOM.getBoxModel', {
objectId: toRemoteObject(handle).objectId
}).catch(debugError);
if (!result)
return null;
const quad = result.model.border;
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
return {x, y, width, height};
}
async contentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._client.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId
}).catch(debugError);
if (!result)
return null;
return result.quads.map(quad => [
{ x: quad[0], y: quad[1] },
{ x: quad[2], y: quad[3] },
{ x: quad[4], y: quad[5] },
{ x: quad[6], y: quad[7] }
]);
}
async layoutViewport(): Promise<{ width: number, height: number }> {
const layoutMetrics = await this._client.send('Page.getLayoutMetrics');
return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight };
}
screenshot(handle: dom.ElementHandle, options?: types.ElementScreenshotOptions): Promise<Buffer> {
const page = this._frameManager.page();
return page._screenshotter.screenshotElement(handle, options);
}
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
await handle.evaluate(input.setFileInputFunction, files);
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: toRemoteObject(handle).objectId,
});
return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise<dom.ElementHandle<T>>;
}
async adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.DOMWorld): Promise<dom.ElementHandle> {
const result = await this._client.send('DOM.resolveNode', {
backendNodeId,
executionContextId: (to.context._delegate as ExecutionContextDelegate)._contextId,
}).catch(debugError);
if (!result)
throw new Error('Unable to adopt element handle from a different document');
return to.context._createHandle(result.object).asElement()!;
}
}
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject {
return handle._remoteObject as Protocol.Runtime.RemoteObject;
}

View file

@ -12,37 +12,22 @@ import * as zsSelectorEngineSource from './generated/zsSelectorEngineSource';
import { assert, helper, debugError } from './helper';
import Injected from './injected/injected';
export interface DOMWorldDelegate {
keyboard: input.Keyboard;
mouse: input.Mouse;
frame: frames.Frame;
isJavascriptEnabled(): boolean;
isElement(remoteObject: any): boolean;
contentFrame(handle: ElementHandle): Promise<frames.Frame | null>;
contentQuads(handle: ElementHandle): Promise<types.Quad[] | null>;
layoutViewport(): Promise<{ width: number, height: number }>;
boundingBox(handle: ElementHandle): Promise<types.Rect | null>;
screenshot(handle: ElementHandle, options?: types.ScreenshotOptions): Promise<string | Buffer>;
setInputFiles(handle: ElementHandle, files: input.FilePayload[]): Promise<void>;
adoptElementHandle<T extends Node>(handle: ElementHandle<T>, to: DOMWorld): Promise<ElementHandle<T>>;
}
type ScopedSelector = types.Selector & { scope?: ElementHandle };
type ResolvedSelector = { scope?: ElementHandle, selector: string, visible?: boolean, disposeScope?: boolean };
export class DOMWorld {
readonly context: js.ExecutionContext;
readonly delegate: DOMWorldDelegate;
readonly frame: frames.Frame;
private _injectedPromise?: Promise<js.JSHandle>;
constructor(context: js.ExecutionContext, delegate: DOMWorldDelegate) {
constructor(context: js.ExecutionContext, frame: frames.Frame) {
this.context = context;
this.delegate = delegate;
this.frame = frame;
}
createHandle(remoteObject: any): ElementHandle | null {
if (this.delegate.isElement(remoteObject))
if (this.frame._page._delegate.isElementHandle(remoteObject))
return new ElementHandle(this.context, remoteObject);
return null;
}
@ -62,7 +47,7 @@ export class DOMWorld {
async adoptElementHandle<T extends Node>(handle: ElementHandle<T>): Promise<ElementHandle<T>> {
assert(handle.executionContext() !== this.context, 'Should not adopt to the same context');
return this.delegate.adoptElementHandle(handle, this);
return this.frame._page._delegate.adoptElementHandle(handle, this);
}
async resolveSelector(selector: string | ScopedSelector): Promise<ResolvedSelector> {
@ -159,7 +144,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async contentFrame(): Promise<frames.Frame | null> {
return this._world.delegate.contentFrame(this);
return this._world.frame._page._delegate.getContentFrame(this);
}
async _scrollIntoViewIfNeeded() {
@ -190,7 +175,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
}
return false;
}, this._world.delegate.isJavascriptEnabled());
}, !!this._world.frame._page._state.javascriptEnabled);
if (error)
throw new Error(error);
}
@ -237,8 +222,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
};
const [quads, metrics] = await Promise.all([
this._world.delegate.contentQuads(this),
this._world.delegate.layoutViewport(),
this._world.frame._page._delegate.getContentQuads(this),
this._world.frame._page._delegate.layoutViewport(),
]);
if (!quads || !quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
@ -275,7 +260,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
point.x += border.x;
point.y += border.y;
}
const metrics = await this._world.delegate.layoutViewport();
const metrics = await this._world.frame._page._delegate.layoutViewport();
// Give 20 extra pixels to avoid any issues on viewport edge.
let scrollX = 0;
if (point.x < 20)
@ -294,26 +279,26 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const point = await this._ensurePointerActionPoint(options ? options.relativePoint : undefined);
let restoreModifiers: input.Modifier[] | undefined;
if (options && options.modifiers)
restoreModifiers = await this._world.delegate.keyboard._ensureModifiers(options.modifiers);
restoreModifiers = await this._world.frame._page.keyboard._ensureModifiers(options.modifiers);
await action(point);
if (restoreModifiers)
await this._world.delegate.keyboard._ensureModifiers(restoreModifiers);
await this._world.frame._page.keyboard._ensureModifiers(restoreModifiers);
}
hover(options?: input.PointerActionOptions): Promise<void> {
return this._performPointerAction(point => this._world.delegate.mouse.move(point.x, point.y), options);
return this._performPointerAction(point => this._world.frame._page.mouse.move(point.x, point.y), options);
}
click(options?: input.ClickOptions): Promise<void> {
return this._performPointerAction(point => this._world.delegate.mouse.click(point.x, point.y, options), options);
return this._performPointerAction(point => this._world.frame._page.mouse.click(point.x, point.y, options), options);
}
dblclick(options?: input.MultiClickOptions): Promise<void> {
return this._performPointerAction(point => this._world.delegate.mouse.dblclick(point.x, point.y, options), options);
return this._performPointerAction(point => this._world.frame._page.mouse.dblclick(point.x, point.y, options), options);
}
tripleclick(options?: input.MultiClickOptions): Promise<void> {
return this._performPointerAction(point => this._world.delegate.mouse.tripleclick(point.x, point.y, options), options);
return this._performPointerAction(point => this._world.frame._page.mouse.tripleclick(point.x, point.y, options), options);
}
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
@ -336,7 +321,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const error = await this.evaluate(input.fillFunction);
if (error)
throw new Error(error);
await this._world.delegate.keyboard.sendCharacters(value);
await this._world.frame._page.keyboard.sendCharacters(value);
}
async setInputFiles(...files: (string|input.FilePayload)[]) {
@ -347,7 +332,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return input.multiple;
});
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
await this._world.delegate.setInputFiles(this, await input.loadFiles(files));
await this._world.frame._page._delegate.setInputFiles(this, await input.loadFiles(files));
}
async focus() {
@ -363,20 +348,20 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
async type(text: string, options: { delay: (number | undefined); } | undefined) {
await this.focus();
await this._world.delegate.keyboard.type(text, options);
await this._world.frame._page.keyboard.type(text, options);
}
async press(key: string, options: { delay?: number; text?: string; } | undefined) {
await this.focus();
await this._world.delegate.keyboard.press(key, options);
await this._world.frame._page.keyboard.press(key, options);
}
async boundingBox(): Promise<types.Rect | null> {
return this._world.delegate.boundingBox(this);
return this._world.frame._page._delegate.getBoundingBox(this);
}
async screenshot(options?: types.ElementScreenshotOptions): Promise<string | Buffer> {
return this._world.delegate.screenshot(this, options);
return this._world.frame._page._screenshotter.screenshotElement(this, options);
}
private _scopedSelector(selector: string | types.Selector): string | ScopedSelector {

View file

@ -24,7 +24,6 @@ import { JugglerSession } from './Connection';
import { ExecutionContextDelegate } from './ExecutionContext';
import { Page, PageDelegate } from '../page';
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
import { DOMWorldDelegate } from './JSHandle';
import { Events } from '../events';
import * as dialog from '../dialog';
import { Protocol } from './protocol';
@ -112,7 +111,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
const frame = this._frames.get(frameId) || null;
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, executionContextId));
if (frame) {
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
context._domWorld = new dom.DOMWorld(context, frame);
frame._contextCreated('main', context);
frame._contextCreated('utility', context);
}
@ -425,6 +424,62 @@ export class FrameManager extends EventEmitter implements PageDelegate {
async resetViewport(): Promise<void> {
await this._session.send('Page.setViewport', { viewport: null });
}
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const { frameId } = await this._session.send('Page.contentFrame', {
frameId: this._frameData(handle._world.frame).frameId,
objectId: toRemoteObject(handle).objectId,
});
if (!frameId)
return null;
return this.frame(frameId);
}
isElementHandle(remoteObject: any): boolean {
return remoteObject.subtype === 'node';
}
async getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const quads = await this.getContentQuads(handle);
if (!quads || !quads.length)
return null;
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (const quad of quads) {
for (const point of quad) {
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
}
}
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('Page.getContentQuads', {
frameId: this._frameData(handle._world.frame).frameId,
objectId: toRemoteObject(handle).objectId,
}).catch(debugError);
if (!result)
return null;
return result.quads.map(quad => [ quad.p1, quad.p2, quad.p3, quad.p4 ]);
}
async layoutViewport(): Promise<{ width: number, height: number }> {
return this._page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
}
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
await handle.evaluate(input.setFileInputFunction, files);
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
assert(false, 'Multiple isolated worlds are not implemented');
return handle;
}
}
export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {
@ -436,3 +491,7 @@ export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.Lif
}
return waitUntil;
}
function toRemoteObject(handle: dom.ElementHandle): Protocol.RemoteObject {
return handle._remoteObject;
}

View file

@ -1,114 +0,0 @@
/**
* Copyright 2019 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, debugError } from '../helper';
import * as dom from '../dom';
import * as input from '../input';
import * as types from '../types';
import * as frames from '../frames';
import { JugglerSession } from './Connection';
import { FrameManager } from './FrameManager';
import { Protocol } from './protocol';
export class DOMWorldDelegate implements dom.DOMWorldDelegate {
readonly keyboard: input.Keyboard;
readonly mouse: input.Mouse;
readonly frame: frames.Frame;
private _session: JugglerSession;
private _frameManager: FrameManager;
private _frameId: string;
constructor(frameManager: FrameManager, frame: frames.Frame) {
this.keyboard = frameManager._page.keyboard;
this.mouse = frameManager._page.mouse;
this.frame = frame;
this._session = frameManager._session;
this._frameManager = frameManager;
this._frameId = frameManager._frameData(frame).frameId;
}
async contentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const {frameId} = await this._session.send('Page.contentFrame', {
frameId: this._frameId,
objectId: toRemoteObject(handle).objectId,
});
if (!frameId)
return null;
const frame = this._frameManager.frame(frameId);
return frame;
}
isJavascriptEnabled(): boolean {
return !!this._frameManager._page._state.javascriptEnabled;
}
isElement(remoteObject: any): boolean {
return remoteObject.subtype === 'node';
}
async boundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const quads = await this.contentQuads(handle);
if (!quads || !quads.length)
return null;
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (const quad of quads) {
for (const point of quad) {
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
}
}
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
async contentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('Page.getContentQuads', {
frameId: this._frameId,
objectId: toRemoteObject(handle).objectId,
}).catch(debugError);
if (!result)
return null;
return result.quads.map(quad => [ quad.p1, quad.p2, quad.p3, quad.p4 ]);
}
async layoutViewport(): Promise<{ width: number, height: number }> {
return this._frameManager._page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
}
async screenshot(handle: dom.ElementHandle, options?: types.ElementScreenshotOptions): Promise<Buffer> {
const page = this._frameManager._page;
return page._screenshotter.screenshotElement(handle, options);
}
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
await handle.evaluate(input.setFileInputFunction, files);
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
assert(false, 'Multiple isolated worlds are not implemented');
return handle;
}
}
function toRemoteObject(handle: dom.ElementHandle): Protocol.RemoteObject {
return handle._remoteObject;
}

View file

@ -22,7 +22,7 @@ export class ExecutionContext {
}
frame(): frames.Frame | null {
return this._domWorld ? this._domWorld.delegate.frame : null;
return this._domWorld ? this._domWorld.frame : null;
}
evaluate: types.Evaluate = (pageFunction, ...args) => {

View file

@ -62,6 +62,14 @@ export interface PageDelegate {
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>;
isElementHandle(remoteObject: any): boolean;
adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>>;
getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null>;
getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null>;
layoutViewport(): Promise<{ width: number, height: number }>;
setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void>;
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
}
type PageState = {

View file

@ -27,7 +27,6 @@ import { ExecutionContextDelegate, EVALUATION_SCRIPT_URL } from './ExecutionCont
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
import { Page, PageDelegate } from '../page';
import { Protocol } from './protocol';
import { DOMWorldDelegate } from './JSHandle';
import * as dialog from '../dialog';
import { Browser } from './Browser';
import { BrowserContext } from '../browserContext';
@ -292,7 +291,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
return;
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, contextPayload));
if (frame) {
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
context._domWorld = new dom.DOMWorld(context, frame);
if (contextPayload.isPageContext)
frame._contextCreated('main', context);
else if (contextPayload.name === UTILITY_WORLD_NAME)
@ -543,4 +542,66 @@ export class FrameManager extends EventEmitter implements PageDelegate {
}
this._page.emit(Events.Page.RequestFailed, request);
}
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
throw new Error('contentFrame() is not implemented');
}
isElementHandle(remoteObject: any): boolean {
return (remoteObject as Protocol.Runtime.RemoteObject).subtype === 'node';
}
async getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const quads = await this.getContentQuads(handle);
if (!quads || !quads.length)
return null;
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (const quad of quads) {
for (const point of quad) {
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
}
}
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId
}).catch(debugError);
if (!result)
return null;
return result.quads.map(quad => [
{ x: quad[0], y: quad[1] },
{ x: quad[2], y: quad[3] },
{ x: quad[4], y: quad[5] },
{ x: quad[6], y: quad[7] }
]);
}
async layoutViewport(): Promise<{ width: number, height: number }> {
return this._page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
}
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
const objectId = toRemoteObject(handle).objectId;
await this._session.send('DOM.setInputFiles', { objectId, files });
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
const result = await this._session.send('DOM.resolveNode', {
objectId: toRemoteObject(handle).objectId,
executionContextId: (to.context._delegate as ExecutionContextDelegate)._contextId
});
return to.context._createHandle(result.object) as dom.ElementHandle<T>;
}
}
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject {
return handle._remoteObject as Protocol.Runtime.RemoteObject;
}

View file

@ -1,113 +0,0 @@
/**
* Copyright 2019 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 * as dom from '../dom';
import * as frames from '../frames';
import { debugError } from '../helper';
import * as input from '../input';
import * as types from '../types';
import { TargetSession } from './Connection';
import { ExecutionContextDelegate } from './ExecutionContext';
import { FrameManager } from './FrameManager';
import { Protocol } from './protocol';
export class DOMWorldDelegate implements dom.DOMWorldDelegate {
readonly keyboard: input.Keyboard;
readonly mouse: input.Mouse;
readonly frame: frames.Frame;
private _client: TargetSession;
private _frameManager: FrameManager;
constructor(frameManager: FrameManager, frame: frames.Frame) {
this.keyboard = frameManager.page().keyboard;
this.mouse = frameManager.page().mouse;
this.frame = frame;
this._client = frameManager._session;
this._frameManager = frameManager;
}
async contentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
throw new Error('contentFrame() is not implemented');
}
isJavascriptEnabled(): boolean {
return !!this._frameManager.page()._state.javascriptEnabled;
}
isElement(remoteObject: any): boolean {
return (remoteObject as Protocol.Runtime.RemoteObject).subtype === 'node';
}
async boundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const quads = await this.contentQuads(handle);
if (!quads || !quads.length)
return null;
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (const quad of quads) {
for (const point of quad) {
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
}
}
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
async contentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._client.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId
}).catch(debugError);
if (!result)
return null;
return result.quads.map(quad => [
{ x: quad[0], y: quad[1] },
{ x: quad[2], y: quad[3] },
{ x: quad[4], y: quad[5] },
{ x: quad[6], y: quad[7] }
]);
}
async layoutViewport(): Promise<{ width: number, height: number }> {
return this._frameManager._page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
}
screenshot(handle: dom.ElementHandle, options?: types.ElementScreenshotOptions): Promise<string | Buffer> {
const page = this._frameManager._page;
return page._screenshotter.screenshotElement(handle, options);
}
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
const objectId = toRemoteObject(handle).objectId;
await this._client.send('DOM.setInputFiles', { objectId, files });
}
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
const result = await this._client.send('DOM.resolveNode', {
objectId: toRemoteObject(handle).objectId,
executionContextId: (to.context._delegate as ExecutionContextDelegate)._contextId
});
return to.context._createHandle(result.object) as dom.ElementHandle<T>;
}
}
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject {
return handle._remoteObject as Protocol.Runtime.RemoteObject;
}