From 39fa313535df7ddd3dce283bbc65cace00192988 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 12 Dec 2019 17:51:05 -0800 Subject: [PATCH] chore: merge DOMWorldDelegate and PageDelegate (#228) --- src/chromium/FrameManager.ts | 76 +++++++++++++++++++++- src/chromium/JSHandle.ts | 122 ----------------------------------- src/dom.ts | 59 +++++++---------- src/firefox/FrameManager.ts | 63 +++++++++++++++++- src/firefox/JSHandle.ts | 114 -------------------------------- src/javascript.ts | 2 +- src/page.ts | 8 +++ src/webkit/FrameManager.ts | 65 ++++++++++++++++++- src/webkit/JSHandle.ts | 113 -------------------------------- 9 files changed, 228 insertions(+), 394 deletions(-) delete mode 100644 src/chromium/JSHandle.ts delete mode 100644 src/firefox/JSHandle.ts delete mode 100644 src/webkit/JSHandle.ts diff --git a/src/chromium/FrameManager.ts b/src/chromium/FrameManager.ts index 72ec9869b8..2f2d8540b8 100644 --- a/src/chromium/FrameManager.ts +++ b/src/chromium/FrameManager.ts @@ -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 { await this._client.send('Emulation.setDeviceMetricsOverride', { mobile: false, width: 0, height: 0, deviceScaleFactor: 0 }); } + + async getContentFrame(handle: dom.ElementHandle): Promise { + 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 { + 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 { + 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 { + await handle.evaluate(input.setFileInputFunction, files); + } + + async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { + const nodeInfo = await this._client.send('DOM.describeNode', { + objectId: toRemoteObject(handle).objectId, + }); + return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise>; + } + + async adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.DOMWorld): Promise { + 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; +} diff --git a/src/chromium/JSHandle.ts b/src/chromium/JSHandle.ts deleted file mode 100644 index 30220b6224..0000000000 --- a/src/chromium/JSHandle.ts +++ /dev/null @@ -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 { - 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 { - 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 { - 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 { - const page = this._frameManager.page(); - return page._screenshotter.screenshotElement(handle, options); - } - - async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise { - await handle.evaluate(input.setFileInputFunction, files); - } - - async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { - const nodeInfo = await this._client.send('DOM.describeNode', { - objectId: toRemoteObject(handle).objectId, - }); - return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise>; - } - - async adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.DOMWorld): Promise { - 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; -} diff --git a/src/dom.ts b/src/dom.ts index 8f1ca5285f..d81585ba73 100644 --- a/src/dom.ts +++ b/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; - contentQuads(handle: ElementHandle): Promise; - layoutViewport(): Promise<{ width: number, height: number }>; - boundingBox(handle: ElementHandle): Promise; - screenshot(handle: ElementHandle, options?: types.ScreenshotOptions): Promise; - setInputFiles(handle: ElementHandle, files: input.FilePayload[]): Promise; - adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise>; -} - 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; - 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(handle: ElementHandle): Promise> { 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 { @@ -159,7 +144,7 @@ export class ElementHandle extends js.JSHandle { } async contentFrame(): Promise { - return this._world.delegate.contentFrame(this); + return this._world.frame._page._delegate.getContentFrame(this); } async _scrollIntoViewIfNeeded() { @@ -190,7 +175,7 @@ export class ElementHandle extends js.JSHandle { 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 extends js.JSHandle { }; 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 extends js.JSHandle { 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 extends js.JSHandle { 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 { - 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 { - 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 { - 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 { - 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 { @@ -336,7 +321,7 @@ export class ElementHandle extends js.JSHandle { 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 extends js.JSHandle { 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 extends js.JSHandle { 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 { - return this._world.delegate.boundingBox(this); + return this._world.frame._page._delegate.getBoundingBox(this); } async screenshot(options?: types.ElementScreenshotOptions): Promise { - return this._world.delegate.screenshot(this, options); + return this._world.frame._page._screenshotter.screenshotElement(this, options); } private _scopedSelector(selector: string | types.Selector): string | ScopedSelector { diff --git a/src/firefox/FrameManager.ts b/src/firefox/FrameManager.ts index 7d051379fa..5a22a07235 100644 --- a/src/firefox/FrameManager.ts +++ b/src/firefox/FrameManager.ts @@ -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 { await this._session.send('Page.setViewport', { viewport: null }); } + + async getContentFrame(handle: dom.ElementHandle): Promise { + 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 { + 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 { + 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 { + await handle.evaluate(input.setFileInputFunction, files); + } + + async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { + 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; +} diff --git a/src/firefox/JSHandle.ts b/src/firefox/JSHandle.ts deleted file mode 100644 index c76f05e402..0000000000 --- a/src/firefox/JSHandle.ts +++ /dev/null @@ -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 { - 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 { - 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 { - 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 { - const page = this._frameManager._page; - return page._screenshotter.screenshotElement(handle, options); - } - - async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise { - await handle.evaluate(input.setFileInputFunction, files); - } - - async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { - assert(false, 'Multiple isolated worlds are not implemented'); - return handle; - } -} - -function toRemoteObject(handle: dom.ElementHandle): Protocol.RemoteObject { - return handle._remoteObject; -} - diff --git a/src/javascript.ts b/src/javascript.ts index fe5eb5c3eb..9a559ca6f7 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -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) => { diff --git a/src/page.ts b/src/page.ts index 6bcc085aa9..d59cb0634c 100644 --- a/src/page.ts +++ b/src/page.ts @@ -62,6 +62,14 @@ export interface PageDelegate { setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise; takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise; resetViewport(oldSize: types.Size): Promise; + + isElementHandle(remoteObject: any): boolean; + adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise>; + getContentFrame(handle: dom.ElementHandle): Promise; + getContentQuads(handle: dom.ElementHandle): Promise; + layoutViewport(): Promise<{ width: number, height: number }>; + setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise; + getBoundingBox(handle: dom.ElementHandle): Promise; } type PageState = { diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index c938c8a0bc..01181b2203 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -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 { + 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 { + 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 { + 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 { + const objectId = toRemoteObject(handle).objectId; + await this._session.send('DOM.setInputFiles', { objectId, files }); + } + + async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { + 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; + } +} + +function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject { + return handle._remoteObject as Protocol.Runtime.RemoteObject; } diff --git a/src/webkit/JSHandle.ts b/src/webkit/JSHandle.ts deleted file mode 100644 index 250bab91e8..0000000000 --- a/src/webkit/JSHandle.ts +++ /dev/null @@ -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 { - 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 { - 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 { - 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 { - const page = this._frameManager._page; - return page._screenshotter.screenshotElement(handle, options); - } - - async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise { - const objectId = toRemoteObject(handle).objectId; - await this._client.send('DOM.setInputFiles', { objectId, files }); - } - - async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { - 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; - } -} - -function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject { - return handle._remoteObject as Protocol.Runtime.RemoteObject; -}