chore: merge DOMWorldDelegate and PageDelegate (#228)
This commit is contained in:
parent
3b202fb4b8
commit
39fa313535
|
|
@ -23,7 +23,6 @@ import * as js from '../javascript';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
|
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
|
||||||
import { DOMWorldDelegate } from './JSHandle';
|
|
||||||
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
@ -353,7 +352,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
this._isolatedWorlds.add(contextPayload.name);
|
this._isolatedWorlds.add(contextPayload.name);
|
||||||
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._client, contextPayload));
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._client, contextPayload));
|
||||||
if (frame)
|
if (frame)
|
||||||
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
|
context._domWorld = new dom.DOMWorld(context, frame);
|
||||||
if (frame) {
|
if (frame) {
|
||||||
if (contextPayload.auxData && !!contextPayload.auxData.isDefault)
|
if (contextPayload.auxData && !!contextPayload.auxData.isDefault)
|
||||||
frame._contextCreated('main', context);
|
frame._contextCreated('main', context);
|
||||||
|
|
@ -454,7 +453,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
|
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
|
||||||
const frame = this.frame(event.frameId);
|
const frame = this.frame(event.frameId);
|
||||||
const utilityWorld = await frame._utilityDOMWorld();
|
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);
|
this._page._onFileChooserOpened(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -567,6 +566,73 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
async resetViewport(): Promise<void> {
|
async resetViewport(): Promise<void> {
|
||||||
await this._client.send('Emulation.setDeviceMetricsOverride', { mobile: false, width: 0, height: 0, deviceScaleFactor: 0 });
|
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) {
|
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)['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');
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
59
src/dom.ts
59
src/dom.ts
|
|
@ -12,37 +12,22 @@ import * as zsSelectorEngineSource from './generated/zsSelectorEngineSource';
|
||||||
import { assert, helper, debugError } from './helper';
|
import { assert, helper, debugError } from './helper';
|
||||||
import Injected from './injected/injected';
|
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 ScopedSelector = types.Selector & { scope?: ElementHandle };
|
||||||
type ResolvedSelector = { scope?: ElementHandle, selector: string, visible?: boolean, disposeScope?: boolean };
|
type ResolvedSelector = { scope?: ElementHandle, selector: string, visible?: boolean, disposeScope?: boolean };
|
||||||
|
|
||||||
export class DOMWorld {
|
export class DOMWorld {
|
||||||
readonly context: js.ExecutionContext;
|
readonly context: js.ExecutionContext;
|
||||||
readonly delegate: DOMWorldDelegate;
|
readonly frame: frames.Frame;
|
||||||
|
|
||||||
private _injectedPromise?: Promise<js.JSHandle>;
|
private _injectedPromise?: Promise<js.JSHandle>;
|
||||||
|
|
||||||
constructor(context: js.ExecutionContext, delegate: DOMWorldDelegate) {
|
constructor(context: js.ExecutionContext, frame: frames.Frame) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.delegate = delegate;
|
this.frame = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
createHandle(remoteObject: any): ElementHandle | null {
|
createHandle(remoteObject: any): ElementHandle | null {
|
||||||
if (this.delegate.isElement(remoteObject))
|
if (this.frame._page._delegate.isElementHandle(remoteObject))
|
||||||
return new ElementHandle(this.context, remoteObject);
|
return new ElementHandle(this.context, remoteObject);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +47,7 @@ export class DOMWorld {
|
||||||
|
|
||||||
async adoptElementHandle<T extends Node>(handle: ElementHandle<T>): Promise<ElementHandle<T>> {
|
async adoptElementHandle<T extends Node>(handle: ElementHandle<T>): Promise<ElementHandle<T>> {
|
||||||
assert(handle.executionContext() !== this.context, 'Should not adopt to the same context');
|
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> {
|
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> {
|
async contentFrame(): Promise<frames.Frame | null> {
|
||||||
return this._world.delegate.contentFrame(this);
|
return this._world.frame._page._delegate.getContentFrame(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _scrollIntoViewIfNeeded() {
|
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'});
|
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}, this._world.delegate.isJavascriptEnabled());
|
}, !!this._world.frame._page._state.javascriptEnabled);
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(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([
|
const [quads, metrics] = await Promise.all([
|
||||||
this._world.delegate.contentQuads(this),
|
this._world.frame._page._delegate.getContentQuads(this),
|
||||||
this._world.delegate.layoutViewport(),
|
this._world.frame._page._delegate.layoutViewport(),
|
||||||
]);
|
]);
|
||||||
if (!quads || !quads.length)
|
if (!quads || !quads.length)
|
||||||
throw new Error('Node is either not visible or not an HTMLElement');
|
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.x += border.x;
|
||||||
point.y += border.y;
|
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.
|
// Give 20 extra pixels to avoid any issues on viewport edge.
|
||||||
let scrollX = 0;
|
let scrollX = 0;
|
||||||
if (point.x < 20)
|
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);
|
const point = await this._ensurePointerActionPoint(options ? options.relativePoint : undefined);
|
||||||
let restoreModifiers: input.Modifier[] | undefined;
|
let restoreModifiers: input.Modifier[] | undefined;
|
||||||
if (options && options.modifiers)
|
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);
|
await action(point);
|
||||||
if (restoreModifiers)
|
if (restoreModifiers)
|
||||||
await this._world.delegate.keyboard._ensureModifiers(restoreModifiers);
|
await this._world.frame._page.keyboard._ensureModifiers(restoreModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
hover(options?: input.PointerActionOptions): Promise<void> {
|
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> {
|
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> {
|
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> {
|
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[]> {
|
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);
|
const error = await this.evaluate(input.fillFunction);
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(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)[]) {
|
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
||||||
|
|
@ -347,7 +332,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
return input.multiple;
|
return input.multiple;
|
||||||
});
|
});
|
||||||
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
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() {
|
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) {
|
async type(text: string, options: { delay: (number | undefined); } | undefined) {
|
||||||
await this.focus();
|
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) {
|
async press(key: string, options: { delay?: number; text?: string; } | undefined) {
|
||||||
await this.focus();
|
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> {
|
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> {
|
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 {
|
private _scopedSelector(selector: string | types.Selector): string | ScopedSelector {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { JugglerSession } from './Connection';
|
||||||
import { ExecutionContextDelegate } from './ExecutionContext';
|
import { ExecutionContextDelegate } from './ExecutionContext';
|
||||||
import { Page, PageDelegate } from '../page';
|
import { Page, PageDelegate } from '../page';
|
||||||
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||||
import { DOMWorldDelegate } from './JSHandle';
|
|
||||||
import { Events } from '../events';
|
import { Events } from '../events';
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
@ -112,7 +111,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
const frame = this._frames.get(frameId) || null;
|
const frame = this._frames.get(frameId) || null;
|
||||||
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, executionContextId));
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, executionContextId));
|
||||||
if (frame) {
|
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('main', context);
|
||||||
frame._contextCreated('utility', context);
|
frame._contextCreated('utility', context);
|
||||||
}
|
}
|
||||||
|
|
@ -425,6 +424,62 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
async resetViewport(): Promise<void> {
|
async resetViewport(): Promise<void> {
|
||||||
await this._session.send('Page.setViewport', { viewport: null });
|
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[] {
|
export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {
|
||||||
|
|
@ -436,3 +491,7 @@ export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.Lif
|
||||||
}
|
}
|
||||||
return waitUntil;
|
return waitUntil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toRemoteObject(handle: dom.ElementHandle): Protocol.RemoteObject {
|
||||||
|
return handle._remoteObject;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ export class ExecutionContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
frame(): frames.Frame | null {
|
frame(): frames.Frame | null {
|
||||||
return this._domWorld ? this._domWorld.delegate.frame : null;
|
return this._domWorld ? this._domWorld.frame : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,14 @@ export interface PageDelegate {
|
||||||
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
||||||
takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer>;
|
takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer>;
|
||||||
resetViewport(oldSize: types.Size): Promise<void>;
|
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 = {
|
type PageState = {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import { ExecutionContextDelegate, EVALUATION_SCRIPT_URL } from './ExecutionCont
|
||||||
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||||
import { Page, PageDelegate } from '../page';
|
import { Page, PageDelegate } from '../page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { DOMWorldDelegate } from './JSHandle';
|
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import { Browser } from './Browser';
|
import { Browser } from './Browser';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
|
|
@ -292,7 +291,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||||
return;
|
return;
|
||||||
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, contextPayload));
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, contextPayload));
|
||||||
if (frame) {
|
if (frame) {
|
||||||
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
|
context._domWorld = new dom.DOMWorld(context, frame);
|
||||||
if (contextPayload.isPageContext)
|
if (contextPayload.isPageContext)
|
||||||
frame._contextCreated('main', context);
|
frame._contextCreated('main', context);
|
||||||
else if (contextPayload.name === UTILITY_WORLD_NAME)
|
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);
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue