/** * 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 { BrowserContext } from '../../browserContext'; import { Events } from '../../events'; import { Frame } from '../../frames'; import { Request } from '../../network'; import { Page, Worker } from '../../page'; import * as types from '../../types'; import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary } from '../channels'; import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { parseError, serializeError } from '../serializers'; import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; import { DialogDispatcher } from './dialogDispatcher'; import { DownloadDispatcher } from './downloadDispatcher'; import { FrameDispatcher } from './frameDispatcher'; import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers'; import { serializeResult, parseArgument } from './jsHandleDispatcher'; import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher'; import { FileChooser } from '../../fileChooser'; export class PageDispatcher extends Dispatcher implements PageChannel { private _page: Page; static from(scope: DispatcherScope, page: Page): PageDispatcher { const result = existingDispatcher(page); return result || new PageDispatcher(scope, page); } static fromNullable(scope: DispatcherScope, request: Page | null): PageDispatcher | null { return request ? PageDispatcher.from(scope, request) : null; } private constructor(scope: DispatcherScope, page: Page) { super(scope, page, 'page', { mainFrame: FrameDispatcher.from(scope, page.mainFrame()), viewportSize: page.viewportSize() }); this._page = page; page.on(Events.Page.Close, () => this._dispatchEvent('close')); page.on(Events.Page.Console, message => this._dispatchEvent('console', new ConsoleMessageDispatcher(this._scope, message))); page.on(Events.Page.Crash, () => this._dispatchEvent('crash')); page.on(Events.Page.DOMContentLoaded, () => this._dispatchEvent('domcontentloaded')); page.on(Events.Page.Dialog, dialog => this._dispatchEvent('dialog', new DialogDispatcher(this._scope, dialog))); page.on(Events.Page.Download, dialog => this._dispatchEvent('download', new DownloadDispatcher(this._scope, dialog))); page.on(Events.Page.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', { element: new ElementHandleDispatcher(this._scope, fileChooser.element()), isMultiple: fileChooser.isMultiple() })); page.on(Events.Page.FrameAttached, frame => this._onFrameAttached(frame)); page.on(Events.Page.FrameDetached, frame => this._onFrameDetached(frame)); page.on(Events.Page.FrameNavigated, frame => this._onFrameNavigated(frame)); page.on(Events.Page.Load, () => this._dispatchEvent('load')); page.on(Events.Page.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) })); page.on(Events.Page.Popup, page => this._dispatchEvent('popup', PageDispatcher.from(this._scope, page))); page.on(Events.Page.Request, request => this._dispatchEvent('request', RequestDispatcher.from(this._scope, request))); page.on(Events.Page.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', { request: RequestDispatcher.from(this._scope, request), failureText: request._failureText })); page.on(Events.Page.RequestFinished, request => this._dispatchEvent('requestFinished', RequestDispatcher.from(scope, request))); page.on(Events.Page.Response, response => this._dispatchEvent('response', new ResponseDispatcher(this._scope, response))); page.on(Events.Page.Worker, worker => this._dispatchEvent('worker', new WorkerDispatcher(this._scope, worker))); } async setDefaultNavigationTimeoutNoReply(params: { timeout: number }) { this._page.setDefaultNavigationTimeout(params.timeout); } async setDefaultTimeoutNoReply(params: { timeout: number }) { this._page.setDefaultTimeout(params.timeout); } async opener(): Promise { return lookupNullableDispatcher(await this._page.opener()); } async exposeBinding(params: { name: string }): Promise { await this._page.exposeBinding(params.name, (source, ...args) => { const bindingCall = new BindingCallDispatcher(this._scope, params.name, source, args); this._dispatchEvent('bindingCall', bindingCall); return bindingCall.promise(); }); } async setExtraHTTPHeaders(params: { headers: types.Headers }): Promise { await this._page.setExtraHTTPHeaders(params.headers); } async reload(params: { options?: types.NavigateOptions }): Promise { return lookupNullableDispatcher(await this._page.reload(params.options)); } async goBack(params: { options?: types.NavigateOptions }): Promise { return lookupNullableDispatcher(await this._page.goBack(params.options)); } async goForward(params: { options?: types.NavigateOptions }): Promise { return lookupNullableDispatcher(await this._page.goForward(params.options)); } async emulateMedia(params: { options: { media?: 'screen' | 'print', colorScheme?: 'dark' | 'light' | 'no-preference' } }): Promise { await this._page.emulateMedia(params.options); } async setViewportSize(params: { viewportSize: types.Size }): Promise { await this._page.setViewportSize(params.viewportSize); } async addInitScript(params: { source: string }): Promise { await this._page._addInitScriptExpression(params.source); } async setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise { if (!params.enabled) { await this._page.unroute('**/*'); return; } this._page.route('**/*', (route, request) => { this._dispatchEvent('route', { route: new RouteDispatcher(this._scope, route), request: RequestDispatcher.from(this._scope, request) }); }); } async screenshot(params: { options?: types.ScreenshotOptions }): Promise { return (await this._page.screenshot(params.options)).toString('base64'); } async close(params: { options?: { runBeforeUnload?: boolean } }): Promise { await this._page.close(params.options); } async setFileChooserInterceptedNoReply(params: { intercepted: boolean }) { } async title() { return await this._page.title(); } async keyboardDown(params: { key: string }): Promise { await this._page.keyboard.down(params.key); } async keyboardUp(params: { key: string }): Promise { await this._page.keyboard.up(params.key); } async keyboardInsertText(params: { text: string }): Promise { await this._page.keyboard.insertText(params.text); } async keyboardType(params: { text: string, options?: { delay?: number } }): Promise { await this._page.keyboard.type(params.text, params.options); } async keyboardPress(params: { key: string, options?: { delay?: number } }): Promise { await this._page.keyboard.press(params.key, params.options); } async mouseMove(params: { x: number, y: number, options?: { steps?: number } }): Promise { await this._page.mouse.move(params.x, params.y, params.options); } async mouseDown(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise { await this._page.mouse.down(params.options); } async mouseUp(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise { await this._page.mouse.up(params.options); } async mouseClick(params: { x: number, y: number, options?: { delay?: number, button?: types.MouseButton, clickCount?: number } }): Promise { await this._page.mouse.click(params.x, params.y, params.options); } async accessibilitySnapshot(params: { options: { interestingOnly?: boolean, root?: ElementHandleChannel } }): Promise { return await this._page.accessibility.snapshot({ interestingOnly: params.options.interestingOnly, root: params.options.root ? params.options.root._object : undefined }); } async pdf(params: { options?: types.PDFOptions }): Promise { if (!this._page.pdf) throw new Error('PDF generation is only supported for Headless Chromium'); const binary = await this._page.pdf(params.options); return binary.toString('base64'); } _onFrameAttached(frame: Frame) { this._dispatchEvent('frameAttached', FrameDispatcher.from(this._scope, frame)); } _onFrameNavigated(frame: Frame) { this._dispatchEvent('frameNavigated', { frame: lookupDispatcher(frame), url: frame.url(), name: frame.name() }); } _onFrameDetached(frame: Frame) { this._dispatchEvent('frameDetached', lookupDispatcher(frame)); } } export class WorkerDispatcher extends Dispatcher implements WorkerChannel { constructor(scope: DispatcherScope, worker: Worker) { super(scope, worker, 'worker', { url: worker.url() }); worker.on(Events.Worker.Close, () => this._dispatchEvent('close')); } async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise { return serializeResult(await this._object._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))); } async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise { return createHandle(this._scope, await this._object._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))); } } export class BindingCallDispatcher extends Dispatcher<{}, BindingCallInitializer> implements BindingCallChannel { private _resolve: ((arg: any) => void) | undefined; private _reject: ((error: any) => void) | undefined; private _promise: Promise; constructor(scope: DispatcherScope, name: string, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) { super(scope, {}, 'bindingCall', { frame: lookupDispatcher(source.frame), name, args }); this._promise = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); } promise() { return this._promise; } resolve(params: { result: any }) { this._resolve!(params.result); } reject(params: { error: types.Error }) { this._reject!(parseError(params.error)); } }