2020-06-25 16:05:36 -07:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2020-06-26 17:24:21 -07:00
|
|
|
import { BrowserContext } from '../../browserContext';
|
2020-06-25 16:05:36 -07:00
|
|
|
import { Events } from '../../events';
|
|
|
|
import { Frame } from '../../frames';
|
2020-06-26 17:24:21 -07:00
|
|
|
import { Request } from '../../network';
|
2020-06-30 10:55:11 -07:00
|
|
|
import { Page, Worker } from '../../page';
|
2020-06-25 16:05:36 -07:00
|
|
|
import * as types from '../../types';
|
2020-07-10 15:39:11 -07:00
|
|
|
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, PDFOptions } from '../channels';
|
2020-07-01 18:36:09 -07:00
|
|
|
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
|
2020-06-27 11:10:07 -07:00
|
|
|
import { parseError, serializeError } from '../serializers';
|
2020-06-26 17:24:21 -07:00
|
|
|
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
|
|
|
|
import { DialogDispatcher } from './dialogDispatcher';
|
|
|
|
import { DownloadDispatcher } from './downloadDispatcher';
|
2020-06-25 16:05:36 -07:00
|
|
|
import { FrameDispatcher } from './frameDispatcher';
|
2020-06-26 11:51:47 -07:00
|
|
|
import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers';
|
2020-06-30 10:55:11 -07:00
|
|
|
import { serializeResult, parseArgument } from './jsHandleDispatcher';
|
2020-06-30 21:30:39 -07:00
|
|
|
import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher';
|
2020-06-30 10:55:11 -07:00
|
|
|
import { FileChooser } from '../../fileChooser';
|
2020-07-09 15:33:01 -07:00
|
|
|
import { CRCoverage } from '../../chromium/crCoverage';
|
2020-06-25 16:05:36 -07:00
|
|
|
|
2020-06-26 17:24:21 -07:00
|
|
|
export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements PageChannel {
|
2020-06-25 16:05:36 -07:00
|
|
|
private _page: Page;
|
|
|
|
|
2020-07-01 18:36:09 -07:00
|
|
|
constructor(scope: DispatcherScope, page: Page) {
|
2020-07-13 15:26:09 -07:00
|
|
|
// TODO: theoretically, there could be more than one frame already.
|
|
|
|
// If we split pageCreated and pageReady, there should be no main frame during pageCreated.
|
2020-06-26 12:28:27 -07:00
|
|
|
super(scope, page, 'page', {
|
2020-06-25 16:05:36 -07:00
|
|
|
mainFrame: FrameDispatcher.from(scope, page.mainFrame()),
|
2020-07-10 13:15:39 -07:00
|
|
|
viewportSize: page.viewportSize(),
|
|
|
|
isClosed: page.isClosed()
|
2020-06-25 16:05:36 -07:00
|
|
|
});
|
|
|
|
this._page = page;
|
2020-06-26 11:51:47 -07:00
|
|
|
page.on(Events.Page.Close, () => this._dispatchEvent('close'));
|
2020-06-30 21:30:39 -07:00
|
|
|
page.on(Events.Page.Console, message => this._dispatchEvent('console', new ConsoleMessageDispatcher(this._scope, message)));
|
2020-06-26 21:22:03 -07:00
|
|
|
page.on(Events.Page.Crash, () => this._dispatchEvent('crash'));
|
|
|
|
page.on(Events.Page.DOMContentLoaded, () => this._dispatchEvent('domcontentloaded'));
|
2020-06-30 21:30:39 -07:00
|
|
|
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)));
|
2020-06-30 10:55:11 -07:00
|
|
|
page.on(Events.Page.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
|
2020-06-30 21:30:39 -07:00
|
|
|
element: new ElementHandleDispatcher(this._scope, fileChooser.element()),
|
2020-06-30 10:55:11 -07:00
|
|
|
isMultiple: fileChooser.isMultiple()
|
|
|
|
}));
|
2020-06-25 16:05:36 -07:00
|
|
|
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));
|
2020-06-26 21:22:03 -07:00
|
|
|
page.on(Events.Page.Load, () => this._dispatchEvent('load'));
|
2020-06-26 11:51:47 -07:00
|
|
|
page.on(Events.Page.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) }));
|
2020-07-01 18:36:09 -07:00
|
|
|
page.on(Events.Page.Popup, page => this._dispatchEvent('popup', lookupDispatcher<PageDispatcher>(page)));
|
2020-06-25 16:05:36 -07:00
|
|
|
page.on(Events.Page.Request, request => this._dispatchEvent('request', RequestDispatcher.from(this._scope, request)));
|
2020-06-26 11:51:47 -07:00
|
|
|
page.on(Events.Page.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', {
|
|
|
|
request: RequestDispatcher.from(this._scope, request),
|
|
|
|
failureText: request._failureText
|
|
|
|
}));
|
2020-06-30 21:30:39 -07:00
|
|
|
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)));
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async setDefaultNavigationTimeoutNoReply(params: { timeout: number }) {
|
|
|
|
this._page.setDefaultNavigationTimeout(params.timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
async setDefaultTimeoutNoReply(params: { timeout: number }) {
|
|
|
|
this._page.setDefaultTimeout(params.timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
async opener(): Promise<PageChannel | null> {
|
2020-06-30 21:30:39 -07:00
|
|
|
return lookupNullableDispatcher<PageDispatcher>(await this._page.opener());
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async exposeBinding(params: { name: string }): Promise<void> {
|
2020-06-26 22:38:21 -07:00
|
|
|
await this._page.exposeBinding(params.name, (source, ...args) => {
|
2020-06-26 11:51:47 -07:00
|
|
|
const bindingCall = new BindingCallDispatcher(this._scope, params.name, source, args);
|
|
|
|
this._dispatchEvent('bindingCall', bindingCall);
|
|
|
|
return bindingCall.promise();
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void> {
|
|
|
|
await this._page.setExtraHTTPHeaders(params.headers);
|
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async reload(params: types.NavigateOptions): Promise<ResponseChannel | null> {
|
|
|
|
return lookupNullableDispatcher<ResponseDispatcher>(await this._page.reload(params));
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async goBack(params: types.NavigateOptions): Promise<ResponseChannel | null> {
|
|
|
|
return lookupNullableDispatcher<ResponseDispatcher>(await this._page.goBack(params));
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async goForward(params: types.NavigateOptions): Promise<ResponseChannel | null> {
|
|
|
|
return lookupNullableDispatcher<ResponseDispatcher>(await this._page.goForward(params));
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async emulateMedia(params: { media?: 'screen' | 'print', colorScheme?: 'dark' | 'light' | 'no-preference' }): Promise<void> {
|
|
|
|
await this._page.emulateMedia(params);
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async setViewportSize(params: { viewportSize: types.Size }): Promise<void> {
|
|
|
|
await this._page.setViewportSize(params.viewportSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
async addInitScript(params: { source: string }): Promise<void> {
|
|
|
|
await this._page._addInitScriptExpression(params.source);
|
|
|
|
}
|
|
|
|
|
|
|
|
async setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void> {
|
2020-06-26 11:51:47 -07:00
|
|
|
if (!params.enabled) {
|
|
|
|
await this._page.unroute('**/*');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._page.route('**/*', (route, request) => {
|
2020-06-30 21:30:39 -07:00
|
|
|
this._dispatchEvent('route', { route: new RouteDispatcher(this._scope, route), request: RequestDispatcher.from(this._scope, request) });
|
2020-06-26 11:51:47 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-09 15:33:01 -07:00
|
|
|
async screenshot(params: types.ScreenshotOptions): Promise<Binary> {
|
2020-07-06 10:04:09 -07:00
|
|
|
return (await this._page.screenshot(params)).toString('base64');
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async close(params: { runBeforeUnload?: boolean }): Promise<void> {
|
|
|
|
await this._page.close(params);
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async setFileChooserInterceptedNoReply(params: { intercepted: boolean }) {
|
|
|
|
}
|
|
|
|
|
|
|
|
async title() {
|
|
|
|
return await this._page.title();
|
|
|
|
}
|
|
|
|
|
2020-06-25 18:01:18 -07:00
|
|
|
async keyboardDown(params: { key: string }): Promise<void> {
|
|
|
|
await this._page.keyboard.down(params.key);
|
|
|
|
}
|
|
|
|
|
|
|
|
async keyboardUp(params: { key: string }): Promise<void> {
|
|
|
|
await this._page.keyboard.up(params.key);
|
|
|
|
}
|
|
|
|
|
|
|
|
async keyboardInsertText(params: { text: string }): Promise<void> {
|
|
|
|
await this._page.keyboard.insertText(params.text);
|
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async keyboardType(params: { text: string, delay?: number }): Promise<void> {
|
|
|
|
await this._page.keyboard.type(params.text, params);
|
2020-06-25 18:01:18 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async keyboardPress(params: { key: string, delay?: number }): Promise<void> {
|
|
|
|
await this._page.keyboard.press(params.key, params);
|
2020-06-25 18:01:18 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async mouseMove(params: { x: number, y: number, steps?: number }): Promise<void> {
|
|
|
|
await this._page.mouse.move(params.x, params.y, params);
|
2020-06-25 18:01:18 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async mouseDown(params: { button?: types.MouseButton, clickCount?: number }): Promise<void> {
|
|
|
|
await this._page.mouse.down(params);
|
2020-06-25 18:01:18 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async mouseUp(params: { button?: types.MouseButton, clickCount?: number }): Promise<void> {
|
|
|
|
await this._page.mouse.up(params);
|
2020-06-25 18:01:18 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async mouseClick(params: { x: number, y: number, delay?: number, button?: types.MouseButton, clickCount?: number }): Promise<void> {
|
|
|
|
await this._page.mouse.click(params.x, params.y, params);
|
2020-06-25 18:01:18 -07:00
|
|
|
}
|
|
|
|
|
2020-07-06 10:04:09 -07:00
|
|
|
async accessibilitySnapshot(params: { interestingOnly?: boolean, root?: ElementHandleChannel }): Promise<types.SerializedAXNode | null> {
|
2020-06-25 18:01:18 -07:00
|
|
|
return await this._page.accessibility.snapshot({
|
2020-07-06 10:04:09 -07:00
|
|
|
interestingOnly: params.interestingOnly,
|
|
|
|
root: params.root ? (params.root as ElementHandleDispatcher)._elementHandle : undefined
|
2020-06-25 18:01:18 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-10 15:39:11 -07:00
|
|
|
async pdf(params: PDFOptions): Promise<Binary> {
|
2020-06-30 10:55:11 -07:00
|
|
|
if (!this._page.pdf)
|
|
|
|
throw new Error('PDF generation is only supported for Headless Chromium');
|
2020-07-06 10:04:09 -07:00
|
|
|
const binary = await this._page.pdf(params);
|
2020-06-30 10:55:11 -07:00
|
|
|
return binary.toString('base64');
|
|
|
|
}
|
|
|
|
|
2020-07-09 15:33:01 -07:00
|
|
|
async crStartJSCoverage(params: types.JSCoverageOptions): Promise<void> {
|
|
|
|
const coverage = this._page.coverage as CRCoverage;
|
|
|
|
await coverage.startJSCoverage(params);
|
|
|
|
}
|
|
|
|
|
|
|
|
async crStopJSCoverage(): Promise<types.JSCoverageEntry[]> {
|
|
|
|
const coverage = this._page.coverage as CRCoverage;
|
|
|
|
return await coverage.stopJSCoverage();
|
|
|
|
}
|
|
|
|
|
|
|
|
async crStartCSSCoverage(params: types.CSSCoverageOptions): Promise<void> {
|
|
|
|
const coverage = this._page.coverage as CRCoverage;
|
|
|
|
await coverage.startCSSCoverage(params);
|
|
|
|
}
|
|
|
|
|
|
|
|
async crStopCSSCoverage(): Promise<types.CSSCoverageEntry[]> {
|
|
|
|
const coverage = this._page.coverage as CRCoverage;
|
|
|
|
return await coverage.stopCSSCoverage();
|
|
|
|
}
|
|
|
|
|
2020-06-25 16:05:36 -07:00
|
|
|
_onFrameAttached(frame: Frame) {
|
|
|
|
this._dispatchEvent('frameAttached', FrameDispatcher.from(this._scope, frame));
|
|
|
|
}
|
|
|
|
|
|
|
|
_onFrameNavigated(frame: Frame) {
|
2020-06-30 21:30:39 -07:00
|
|
|
this._dispatchEvent('frameNavigated', { frame: lookupDispatcher<FrameDispatcher>(frame), url: frame.url(), name: frame.name() });
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
_onFrameDetached(frame: Frame) {
|
2020-06-30 21:30:39 -07:00
|
|
|
this._dispatchEvent('frameDetached', lookupDispatcher<FrameDispatcher>(frame));
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
}
|
2020-06-26 11:51:47 -07:00
|
|
|
|
|
|
|
|
2020-06-30 10:55:11 -07:00
|
|
|
export class WorkerDispatcher extends Dispatcher<Worker, WorkerInitializer> 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<any> {
|
|
|
|
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<JSHandleChannel> {
|
2020-06-30 21:30:39 -07:00
|
|
|
return createHandle(this._scope, await this._object._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg)));
|
2020-06-30 10:55:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 17:24:21 -07:00
|
|
|
export class BindingCallDispatcher extends Dispatcher<{}, BindingCallInitializer> implements BindingCallChannel {
|
2020-06-26 11:51:47 -07:00
|
|
|
private _resolve: ((arg: any) => void) | undefined;
|
|
|
|
private _reject: ((error: any) => void) | undefined;
|
|
|
|
private _promise: Promise<any>;
|
|
|
|
|
|
|
|
constructor(scope: DispatcherScope, name: string, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) {
|
2020-06-26 12:28:27 -07:00
|
|
|
super(scope, {}, 'bindingCall', {
|
2020-06-30 21:30:39 -07:00
|
|
|
frame: lookupDispatcher<FrameDispatcher>(source.frame),
|
2020-06-26 12:28:27 -07:00
|
|
|
name,
|
2020-06-26 11:51:47 -07:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|