chore(rpc): support downloads, dialogs, persistent context (#2733)

This commit is contained in:
Pavel Feldman 2020-06-26 17:24:21 -07:00 committed by GitHub
parent b54303a386
commit 6393407a6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 351 additions and 85 deletions

View File

@ -33,6 +33,7 @@ export type BrowserTypeInitializer = {
name: string
};
export interface BrowserChannel extends Channel {
close(): Promise<void>;
newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel>;
@ -40,6 +41,7 @@ export interface BrowserChannel extends Channel {
}
export type BrowserInitializer = {};
export interface BrowserContextChannel extends Channel {
addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise<void>;
addInitScript(params: { source: string }): Promise<void>;
@ -59,17 +61,23 @@ export interface BrowserContextChannel extends Channel {
setOffline(params: { offline: boolean }): Promise<void>;
waitForEvent(params: { event: string }): Promise<any>;
}
export type BrowserContextInitializer = {};
export type BrowserContextInitializer = {
pages: PageChannel[]
};
export interface PageChannel extends Channel {
on(event: 'bindingCall', callback: (params: BindingCallChannel) => void): this;
on(event: 'close', callback: () => void): this;
on(event: 'console', callback: (params: ConsoleMessageChannel) => void): this;
on(event: 'dialog', callback: (params: DialogChannel) => void): this;
on(event: 'download', callback: (params: DownloadChannel) => void): this;
on(event: 'frameAttached', callback: (params: FrameChannel) => void): this;
on(event: 'frameDetached', callback: (params: FrameChannel) => void): this;
on(event: 'frameNavigated', callback: (params: { frame: FrameChannel, url: string, name: string }) => void): this;
on(event: 'frameNavigated', callback: (params: { frame: FrameChannel, url: string, name: string }) => void): this;
on(event: 'pageError', callback: (params: { error: types.Error }) => void): this;
on(event: 'popup', callback: (params: PageChannel) => void): this;
on(event: 'request', callback: (params: RequestChannel) => void): this;
on(event: 'requestFailed', callback: (params: { request: RequestChannel, failureText: string | null }) => void): this;
on(event: 'requestFinished', callback: (params: RequestChannel) => void): this;
@ -112,6 +120,7 @@ export type PageInitializer = {
viewportSize: types.Size | null
};
export interface FrameChannel extends Channel {
$$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
@ -153,6 +162,7 @@ export type FrameInitializer = {
parentFrame: FrameChannel | null
};
export interface JSHandleChannel extends Channel {
dispose(): Promise<void>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any>;
@ -164,6 +174,7 @@ export type JSHandleInitializer = {
preview: string,
};
export interface ElementHandleChannel extends JSHandleChannel {
$$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
@ -193,6 +204,7 @@ export interface ElementHandleChannel extends JSHandleChannel {
uncheck(params: { options?: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
}
export interface RequestChannel extends Channel {
response(): Promise<ResponseChannel | null>;
}
@ -207,6 +219,7 @@ export type RequestInitializer = {
redirectedFrom: RequestChannel | null,
};
export interface RouteChannel extends Channel {
abort(params: { errorCode: string }): Promise<void>;
continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise<void>;
@ -216,6 +229,7 @@ export type RouteInitializer = {
request: RequestChannel,
};
export interface ResponseChannel extends Channel {
body(): Promise<Buffer>;
finished(): Promise<Error | null>;
@ -228,6 +242,7 @@ export type ResponseInitializer = {
headers: types.Headers,
};
export interface ConsoleMessageChannel extends Channel {
}
export type ConsoleMessageInitializer = {
@ -237,6 +252,7 @@ export type ConsoleMessageInitializer = {
location: types.ConsoleMessageLocation,
};
export interface BindingCallChannel extends Channel {
reject(params: { error: types.Error }): void;
resolve(params: { result: any }): void;
@ -246,3 +262,25 @@ export type BindingCallInitializer = {
name: string,
args: any[]
};
export interface DialogChannel extends Channel {
accept(params: { promptText?: string }): Promise<void>;
dismiss(): Promise<void>;
}
export type DialogInitializer = {
type: string,
message: string,
defaultValue: string,
};
export interface DownloadChannel extends Channel {
path(): Promise<string>;
failure(): Promise<string | null>;
delete(): Promise<void>;
}
export type DownloadInitializer = {
url: string,
suggestedFilename: string,
};

View File

@ -50,7 +50,10 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
}
async newPage(options?: types.BrowserContextOptions): Promise<Page> {
return Page.from(await this._channel.newPage({ options }));
const context = await this.newContext(options);
const page = await context.newPage();
page._ownedContext = context;
return page;
}
isConnected(): boolean {

View File

@ -42,6 +42,11 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
constructor(connection: Connection, channel: BrowserContextChannel, initializer: BrowserContextInitializer) {
super(connection, channel, initializer);
initializer.pages.map(p => {
const page = Page.from(p);
this._pages.add(page);
page._browserContext = this;
});
channel.on('page', page => this._onPage(Page.from(page)));
}
@ -162,7 +167,8 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
async close(): Promise<void> {
await this._channel.close();
this._browser!._contexts.delete(this);
if (this._browser)
this._browser._contexts.delete(this);
this.emit(Events.BrowserContext.Close);
}
}

49
src/rpc/client/dialog.ts Normal file
View File

@ -0,0 +1,49 @@
/**
* 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 { DialogChannel, DialogInitializer } from '../channels';
import { Connection } from '../connection';
import { ChannelOwner } from './channelOwner';
export class Dialog extends ChannelOwner<DialogChannel, DialogInitializer> {
static from(request: DialogChannel): Dialog {
return request._object;
}
constructor(connection: Connection, channel: DialogChannel, initializer: DialogInitializer) {
super(connection, channel, initializer);
}
type(): string {
return this._initializer.type;
}
message(): string {
return this._initializer.message;
}
defaultValue(): string {
return this._initializer.defaultValue;
}
async accept(promptText: string | undefined) {
await this._channel.accept({ promptText });
}
async dismiss() {
await this._channel.dismiss();
}
}

View File

@ -0,0 +1,56 @@
/**
* 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 fs from 'fs';
import { DownloadChannel, DownloadInitializer } from '../channels';
import { Connection } from '../connection';
import { ChannelOwner } from './channelOwner';
import { Readable } from 'stream';
export class Download extends ChannelOwner<DownloadChannel, DownloadInitializer> {
static from(request: DownloadChannel): Download {
return request._object;
}
constructor(connection: Connection, channel: DownloadChannel, initializer: DownloadInitializer) {
super(connection, channel, initializer);
}
url(): string {
return this._initializer.url;
}
suggestedFilename(): string {
return this._initializer.suggestedFilename;
}
async path(): Promise<string | null> {
return this._channel.path();
}
async failure(): Promise<string | null> {
return this._channel.failure();
}
async createReadStream(): Promise<Readable | null> {
const fileName = await this.path();
return fileName ? fs.createReadStream(fileName) : null;
}
async delete(): Promise<void> {
return this._channel.delete();
}
}

View File

@ -37,7 +37,7 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
_parentFrame: Frame | null = null;
_url = '';
_name = '';
private _detached = false;
_detached = false;
_childFrames = new Set<Frame>();
_page: Page | undefined;

View File

@ -30,11 +30,15 @@ import { Connection } from '../connection';
import { Keyboard, Mouse } from './input';
import { Accessibility } from './accessibility';
import { ConsoleMessage } from './consoleMessage';
import { Dialog } from './dialog';
import { Download } from './download';
export class Page extends ChannelOwner<PageChannel, PageInitializer> {
readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined;
_browserContext: BrowserContext | undefined;
_ownedContext: BrowserContext | undefined;
private _mainFrame: Frame;
private _frames = new Set<Frame>();
private _workers: Worker[] = [];
@ -69,10 +73,13 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
this._channel.on('bindingCall', bindingCall => this._onBinding(BindingCall.from(bindingCall)));
this._channel.on('close', () => this._onClose());
this._channel.on('console', message => this.emit(Events.Page.Console, ConsoleMessage.from(message)));
this._channel.on('dialog', dialog => this.emit(Events.Page.Dialog, Dialog.from(dialog)));
this._channel.on('download', download => this.emit(Events.Page.Download, Download.from(download)));
this._channel.on('frameAttached', frame => this._onFrameAttached(Frame.from(frame)));
this._channel.on('frameDetached', frame => this._onFrameDetached(Frame.from(frame)));
this._channel.on('frameNavigated', ({ frame, url, name }) => this._onFrameNavigated(Frame.from(frame), url, name));
this._channel.on('pageError', ({ error }) => this.emit(Events.Page.PageError, parseError(error)));
this._channel.on('popup', popup => this.emit(Events.Page.Popup, Page.from(popup)));
this._channel.on('request', request => this.emit(Events.Page.Request, Request.from(request)));
this._channel.on('requestFailed', ({ request, failureText }) => this._onRequestFailed(Request.from(request), failureText));
this._channel.on('requestFinished', request => this.emit(Events.Page.RequestFinished, Request.from(request)));
@ -95,6 +102,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
private _onFrameDetached(frame: Frame) {
this._frames.delete(frame);
frame._detached = true;
if (frame._parentFrame)
frame._parentFrame._childFrames.delete(frame);
this.emit(Events.Page.FrameDetached, frame);
@ -329,6 +337,8 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
async close(options: { runBeforeUnload?: boolean } = {runBeforeUnload: undefined}) {
await this._channel.close({ options });
if (this._ownedContext)
await this._ownedContext.close();
}
isClosed(): boolean {

View File

@ -27,6 +27,8 @@ import { Page, BindingCall } from './client/page';
import debug = require('debug');
import { Channel } from './channels';
import { ConsoleMessage } from './client/consoleMessage';
import { Dialog } from './client/dialog';
import { Download } from './client/download';
export class Connection {
private _channels = new Map<string, Channel>();
@ -41,21 +43,39 @@ export class Connection {
let result: ChannelOwner<any, any>;
initializer = this._replaceGuidsWithChannels(initializer);
switch (type) {
case 'browserType':
result = new BrowserType(this, channel, initializer);
case 'bindingCall':
result = new BindingCall(this, channel, initializer);
break;
case 'browser':
result = new Browser(this, channel, initializer);
break;
case 'browserType':
result = new BrowserType(this, channel, initializer);
break;
case 'context':
result = new BrowserContext(this, channel, initializer);
break;
case 'page':
result = new Page(this, channel, initializer);
case 'consoleMessage':
result = new ConsoleMessage(this, channel, initializer);
break;
case 'dialog':
result = new Dialog(this, channel, initializer);
break;
case 'download':
result = new Download(this, channel, initializer);
break;
case 'elementHandle':
result = new ElementHandle(this, channel, initializer);
break;
case 'frame':
result = new Frame(this, channel, initializer);
break;
case 'jsHandle':
result = new JSHandle(this, channel, initializer);
break;
case 'page':
result = new Page(this, channel, initializer);
break;
case 'request':
result = new Request(this, channel, initializer);
break;
@ -65,18 +85,6 @@ export class Connection {
case 'route':
result = new Route(this, channel, initializer);
break;
case 'bindingCall':
result = new BindingCall(this, channel, initializer);
break;
case 'jsHandle':
result = new JSHandle(this, channel, initializer);
break;
case 'elementHandle':
result = new ElementHandle(this, channel, initializer);
break;
case 'consoleMessage':
result = new ConsoleMessage(this, channel, initializer);
break;
default:
throw new Error('Missing type ' + type);
}
@ -150,8 +158,12 @@ export class Connection {
// TODO: send base64
if (payload instanceof Buffer)
return payload;
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceChannelsWithGuids(v)]));
if (typeof payload === 'object') {
const result: any = {};
for (const key of Object.keys(payload))
result[key] = this._replaceChannelsWithGuids(payload[key]);
return result;
}
return payload;
}
@ -165,8 +177,12 @@ export class Connection {
// TODO: send base64
if (payload instanceof Buffer)
return payload;
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceGuidsWithChannels(v)]));
if (typeof payload === 'object') {
const result: any = {};
for (const key of Object.keys(payload))
result[key] = this._replaceGuidsWithChannels(payload[key]);
return result;
}
return payload;
}
}

View File

@ -18,30 +18,30 @@ import { EventEmitter } from 'events';
import { helper } from '../helper';
import { Channel } from './channels';
export class Dispatcher<Initializer> extends EventEmitter implements Channel {
export class Dispatcher<Type, Initializer> extends EventEmitter implements Channel {
readonly _guid: string;
readonly _type: string;
protected _scope: DispatcherScope;
_object: any;
constructor(scope: DispatcherScope, object: any, type: string, initializer: Initializer, guid = type + '@' + helper.guid()) {
constructor(scope: DispatcherScope, object: Type, type: string, initializer: Initializer, guid = type + '@' + helper.guid()) {
super();
this._type = type;
this._guid = guid;
this._object = object;
this._scope = scope;
scope.dispatchers.set(this._guid, this);
object[scope.dispatcherSymbol] = this;
(object as any)[scope.dispatcherSymbol] = this;
this._scope.sendMessageToClient(this._guid, '__create__', { type, initializer });
}
_dispatchEvent(method: string, params: Dispatcher<any> | any = {}) {
_dispatchEvent(method: string, params: Dispatcher<any, any> | any = {}) {
this._scope.sendMessageToClient(this._guid, method, params);
}
}
export class DispatcherScope {
readonly dispatchers = new Map<string, Dispatcher<any>>();
readonly dispatchers = new Map<string, Dispatcher<any, any>>();
readonly dispatcherSymbol = Symbol('dispatcher');
sendMessageToClientTransport = (message: any) => {};
@ -65,8 +65,12 @@ export class DispatcherScope {
// TODO: send base64
if (payload instanceof Buffer)
return payload;
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceDispatchersWithGuids(v)]));
if (typeof payload === 'object') {
const result: any = {};
for (const key of Object.keys(payload))
result[key] = this._replaceDispatchersWithGuids(payload[key]);
return result;
}
return payload;
}
@ -80,8 +84,12 @@ export class DispatcherScope {
// TODO: send base64
if (payload instanceof Buffer)
return payload;
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceGuidsWithDispatchers(v)]));
if (typeof payload === 'object') {
const result: any = {};
for (const key of Object.keys(payload))
result[key] = this._replaceGuidsWithDispatchers(payload[key]);
return result;
}
return payload;
}
}

View File

@ -23,9 +23,8 @@ import { PageChannel, BrowserContextChannel, BrowserContextInitializer } from '.
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
import { Page } from '../../page';
export class BrowserContextDispatcher extends Dispatcher<BrowserContextInitializer> implements BrowserContextChannel {
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, BrowserContextInitializer> implements BrowserContextChannel {
private _context: BrowserContextBase;
static from(scope: DispatcherScope, browserContext: BrowserContext): BrowserContextDispatcher {
if ((browserContext as any)[scope.dispatcherSymbol])
return (browserContext as any)[scope.dispatcherSymbol];
@ -33,7 +32,9 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContextInitializ
}
constructor(scope: DispatcherScope, context: BrowserContextBase) {
super(scope, context, 'context', {});
super(scope, context, 'context', {
pages: context.pages().map(p => PageDispatcher.from(scope, p))
});
this._context = context;
context.on(Events.BrowserContext.Page, page => this._dispatchEvent('page', PageDispatcher.from(this._scope, page)));
context.on(Events.BrowserContext.Close, () => {

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { BrowserBase } from '../../browser';
import { BrowserBase, Browser } from '../../browser';
import { BrowserContextBase } from '../../browserContext';
import * as types from '../../types';
import { BrowserContextDispatcher } from './browserContextDispatcher';
@ -22,9 +22,7 @@ import { BrowserChannel, BrowserContextChannel, PageChannel, BrowserInitializer
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { PageDispatcher } from './pageDispatcher';
export class BrowserDispatcher extends Dispatcher<BrowserInitializer> implements BrowserChannel {
private _browser: BrowserBase;
export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel {
static from(scope: DispatcherScope, browser: BrowserBase): BrowserDispatcher {
if ((browser as any)[scope.dispatcherSymbol])
return (browser as any)[scope.dispatcherSymbol];
@ -39,18 +37,17 @@ export class BrowserDispatcher extends Dispatcher<BrowserInitializer> implements
constructor(scope: DispatcherScope, browser: BrowserBase) {
super(scope, browser, 'browser', {});
this._browser = browser;
}
async newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel> {
return BrowserContextDispatcher.from(this._scope, await this._browser.newContext(params.options) as BrowserContextBase);
return BrowserContextDispatcher.from(this._scope, await this._object.newContext(params.options) as BrowserContextBase);
}
async newPage(params: { options?: types.BrowserContextOptions }): Promise<PageChannel> {
return PageDispatcher.from(this._scope, await this._browser.newPage(params.options));
return PageDispatcher.from(this._scope, await this._object.newPage(params.options));
}
async close(): Promise<void> {
await this._browser.close();
await this._object.close();
}
}

View File

@ -15,7 +15,7 @@
*/
import { BrowserBase } from '../../browser';
import { BrowserTypeBase } from '../../server/browserType';
import { BrowserTypeBase, BrowserType } from '../../server/browserType';
import * as types from '../../types';
import { BrowserDispatcher } from './browserDispatcher';
import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer } from '../channels';
@ -23,9 +23,7 @@ import { Dispatcher, DispatcherScope } from '../dispatcher';
import { BrowserContextBase } from '../../browserContext';
import { BrowserContextDispatcher } from './browserContextDispatcher';
export class BrowserTypeDispatcher extends Dispatcher<BrowserTypeInitializer> implements BrowserTypeChannel {
private _browserType: BrowserTypeBase;
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel {
static from(scope: DispatcherScope, browserType: BrowserTypeBase): BrowserTypeDispatcher {
if ((browserType as any)[scope.dispatcherSymbol])
return (browserType as any)[scope.dispatcherSymbol];
@ -37,21 +35,20 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserTypeInitializer> im
executablePath: browserType.executablePath(),
name: browserType.name()
}, browserType.name());
this._browserType = browserType;
}
async launch(params: { options?: types.LaunchOptions }): Promise<BrowserChannel> {
const browser = await this._browserType.launch(params.options || undefined);
const browser = await this._object.launch(params.options || undefined);
return BrowserDispatcher.from(this._scope, browser as BrowserBase);
}
async launchPersistentContext(params: { userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions }): Promise<BrowserContextChannel> {
const browserContext = await this._browserType.launchPersistentContext(params.userDataDir, params.options);
const browserContext = await this._object.launchPersistentContext(params.userDataDir, params.options);
return BrowserContextDispatcher.from(this._scope, browserContext as BrowserContextBase);
}
async connect(params: { options: types.ConnectOptions }): Promise<BrowserChannel> {
const browser = await this._browserType.connect(params.options);
const browser = await this._object.connect(params.options);
return BrowserDispatcher.from(this._scope, browser as BrowserBase);
}
}

View File

@ -19,7 +19,7 @@ import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessageInitializer> implements ConsoleMessageChannel {
export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessage, ConsoleMessageInitializer> implements ConsoleMessageChannel {
static from(scope: DispatcherScope, message: ConsoleMessage): ConsoleMessageDispatcher {
if ((message as any)[scope.dispatcherSymbol])
return (message as any)[scope.dispatcherSymbol];

View File

@ -0,0 +1,43 @@
/**
* 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 { Dialog } from '../../dialog';
import { DialogChannel, DialogInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
export class DialogDispatcher extends Dispatcher<Dialog, DialogInitializer> implements DialogChannel {
static from(scope: DispatcherScope, dialog: Dialog): DialogDispatcher {
if ((dialog as any)[scope.dispatcherSymbol])
return (dialog as any)[scope.dispatcherSymbol];
return new DialogDispatcher(scope, dialog);
}
constructor(scope: DispatcherScope, dialog: Dialog) {
super(scope, dialog, 'dialog', {
type: dialog.type(),
message: dialog.message(),
defaultValue: dialog.defaultValue(),
});
}
async accept(params: { promptText?: string }): Promise<void> {
await this._object.accept(params.promptText);
}
async dismiss(): Promise<void> {
await this._object.dismiss();
}
}

View File

@ -0,0 +1,46 @@
/**
* 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 { Download } from '../../download';
import { DownloadChannel, DownloadInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
export class DownloadDispatcher extends Dispatcher<Download, DownloadInitializer> implements DownloadChannel {
static from(scope: DispatcherScope, Download: Download): DownloadDispatcher {
if ((Download as any)[scope.dispatcherSymbol])
return (Download as any)[scope.dispatcherSymbol];
return new DownloadDispatcher(scope, Download);
}
constructor(scope: DispatcherScope, download: Download) {
super(scope, download, 'download', {
url: download.url(),
suggestedFilename: download.suggestedFilename(),
});
}
async path(): Promise<string> {
return this._object.path();
}
async failure(): Promise<string | null> {
return this._object.failure();
}
async delete(): Promise<void> {
await this._object.delete();
}
}

View File

@ -22,7 +22,7 @@ import { ElementHandleDispatcher, convertSelectOptionValues } from './elementHan
import { JSHandleDispatcher } from './jsHandleDispatcher';
import { ResponseDispatcher } from './networkDispatchers';
export class FrameDispatcher extends Dispatcher<FrameInitializer> implements FrameChannel {
export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> implements FrameChannel {
private _frame: Frame;
static from(scope: DispatcherScope, frame: Frame): FrameDispatcher {

View File

@ -19,27 +19,25 @@ import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { convertArg } from './frameDispatcher';
export class JSHandleDispatcher extends Dispatcher<JSHandleInitializer> implements JSHandleChannel {
readonly _jsHandle: js.JSHandle<any>;
export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitializer> implements JSHandleChannel {
constructor(scope: DispatcherScope, jsHandle: js.JSHandle) {
super(scope, jsHandle, jsHandle.asElement() ? 'elementHandle' : 'jsHandle', {
preview: jsHandle.toString(),
});
this._jsHandle = jsHandle;
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._jsHandle._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, convertArg(this._scope, params.arg));
return this._object._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, convertArg(this._scope, params.arg));
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel> {
const jsHandle = await this._jsHandle._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, convertArg(this._scope, params.arg));
const jsHandle = await this._object._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, convertArg(this._scope, params.arg));
return new JSHandleDispatcher(this._scope, jsHandle);
}
async getPropertyList(): Promise<{ name: string, value: JSHandleChannel }[]> {
const map = await this._jsHandle.getProperties();
const map = await this._object.getProperties();
const result = [];
for (const [name, value] of map)
result.push({ name, value: new JSHandleDispatcher(this._scope, value) });
@ -47,10 +45,10 @@ export class JSHandleDispatcher extends Dispatcher<JSHandleInitializer> implemen
}
async jsonValue(): Promise<any> {
return this._jsHandle.jsonValue();
return this._object.jsonValue();
}
async dispose() {
await this._jsHandle.dispose();
await this._object.dispose();
}
}

View File

@ -20,9 +20,7 @@ import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, Req
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { FrameDispatcher } from './frameDispatcher';
export class RequestDispatcher extends Dispatcher<RequestInitializer> implements RequestChannel {
private _request: Request;
export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel {
static from(scope: DispatcherScope, request: Request): RequestDispatcher {
if ((request as any)[scope.dispatcherSymbol])
return (request as any)[scope.dispatcherSymbol];
@ -44,16 +42,14 @@ export class RequestDispatcher extends Dispatcher<RequestInitializer> implements
isNavigationRequest: request.isNavigationRequest(),
redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()),
});
this._request = request;
}
async response(): Promise<ResponseChannel | null> {
return ResponseDispatcher.fromNullable(this._scope, await this._request.response());
return ResponseDispatcher.fromNullable(this._scope, await this._object.response());
}
}
export class ResponseDispatcher extends Dispatcher<ResponseInitializer> implements ResponseChannel {
private _response: Response;
export class ResponseDispatcher extends Dispatcher<Response, ResponseInitializer> implements ResponseChannel {
static from(scope: DispatcherScope, response: Response): ResponseDispatcher {
if ((response as any)[scope.dispatcherSymbol])
@ -73,20 +69,18 @@ export class ResponseDispatcher extends Dispatcher<ResponseInitializer> implemen
statusText: response.statusText(),
headers: response.headers(),
});
this._response = response;
}
async finished(): Promise<Error | null> {
return await this._response.finished();
return await this._object.finished();
}
async body(): Promise<Buffer> {
return await this._response.body();
return await this._object.body();
}
}
export class RouteDispatcher extends Dispatcher<RouteInitializer> implements RouteChannel {
private _route: Route;
export class RouteDispatcher extends Dispatcher<Route, RouteInitializer> implements RouteChannel {
static from(scope: DispatcherScope, route: Route): RouteDispatcher {
if ((route as any)[scope.dispatcherSymbol])
@ -102,18 +96,17 @@ export class RouteDispatcher extends Dispatcher<RouteInitializer> implements Rou
super(scope, route, 'route', {
request: RequestDispatcher.from(scope, route.request())
});
this._route = route;
}
async continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise<void> {
await this._route.continue(params.overrides);
await this._object.continue(params.overrides);
}
async fulfill(params: { response: types.FulfillResponse & { path?: string } }): Promise<void> {
await this._route.fulfill(params.response);
await this._object.fulfill(params.response);
}
async abort(params: { errorCode: string }): Promise<void> {
await this._route.abort(params.errorCode);
await this._object.abort(params.errorCode);
}
}

View File

@ -14,20 +14,22 @@
* limitations under the License.
*/
import { BrowserContext } from '../../browserContext';
import { Events } from '../../events';
import { Request } from '../../network';
import { Frame } from '../../frames';
import { parseError, serializeError } from '../../helper';
import { Request } from '../../network';
import { Page } from '../../page';
import * as types from '../../types';
import { ElementHandleChannel, PageChannel, ResponseChannel, BindingCallChannel, PageInitializer, BindingCallInitializer } from '../channels';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
import { DialogDispatcher } from './dialogDispatcher';
import { DownloadDispatcher } from './downloadDispatcher';
import { FrameDispatcher } from './frameDispatcher';
import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
import { BrowserContext } from '../../browserContext';
import { serializeError, parseError } from '../../helper';
export class PageDispatcher extends Dispatcher<PageInitializer> implements PageChannel {
export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements PageChannel {
private _page: Page;
static from(scope: DispatcherScope, page: Page): PageDispatcher {
@ -50,10 +52,13 @@ export class PageDispatcher extends Dispatcher<PageInitializer> implements PageC
this._page = page;
page.on(Events.Page.Close, () => this._dispatchEvent('close'));
page.on(Events.Page.Console, message => this._dispatchEvent('console', ConsoleMessageDispatcher.from(this._scope, message)));
page.on(Events.Page.Dialog, dialog => this._dispatchEvent('dialog', DialogDispatcher.from(this._scope, dialog)));
page.on(Events.Page.Download, dialog => this._dispatchEvent('download', DownloadDispatcher.from(this._scope, dialog)));
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.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),
@ -193,7 +198,7 @@ export class PageDispatcher extends Dispatcher<PageInitializer> implements PageC
}
export class BindingCallDispatcher extends Dispatcher<BindingCallInitializer> implements BindingCallChannel {
export class BindingCallDispatcher extends Dispatcher<{}, BindingCallInitializer> implements BindingCallChannel {
private _resolve: ((arg: any) => void) | undefined;
private _reject: ((error: any) => void) | undefined;
private _promise: Promise<any>;