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 { 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
return this._domWorld ? this._domWorld.delegate.frame : null;
|
||||
return this._domWorld ? this._domWorld.frame : null;
|
||||
}
|
||||
|
||||
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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