/** * 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'; 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._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: any = {}): Promise { const clip = await this._session.send('Page.getBoundingBox', { frameId: this._frameId, objectId: toRemoteObject(handle).objectId, }); if (!clip) throw new Error('Node is either not visible or not an HTMLElement'); assert(clip.width, 'Node has 0 width.'); assert(clip.height, 'Node has 0 height.'); await handle._scrollIntoViewIfNeeded(); return await this._frameManager._page.screenshot(Object.assign({}, options, { clip: { x: clip.x, y: clip.y, width: clip.width, height: clip.height, }, })); } 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): any { return handle._remoteObject; }