diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index 9480ca08e0..e576346ec5 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -33,6 +33,7 @@ export type BrowserTypeInitializer = { name: string }; + export interface BrowserChannel extends Channel { close(): Promise; newContext(params: { options?: types.BrowserContextOptions }): Promise; @@ -40,6 +41,7 @@ export interface BrowserChannel extends Channel { } export type BrowserInitializer = {}; + export interface BrowserContextChannel extends Channel { addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise; addInitScript(params: { source: string }): Promise; @@ -59,17 +61,23 @@ export interface BrowserContextChannel extends Channel { setOffline(params: { offline: boolean }): Promise; waitForEvent(params: { event: string }): Promise; } -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; $eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise; @@ -153,6 +162,7 @@ export type FrameInitializer = { parentFrame: FrameChannel | null }; + export interface JSHandleChannel extends Channel { dispose(): Promise; evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise; @@ -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; $eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise; @@ -193,6 +204,7 @@ export interface ElementHandleChannel extends JSHandleChannel { uncheck(params: { options?: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise; } + export interface RequestChannel extends Channel { response(): Promise; } @@ -207,6 +219,7 @@ export type RequestInitializer = { redirectedFrom: RequestChannel | null, }; + export interface RouteChannel extends Channel { abort(params: { errorCode: string }): Promise; continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise; @@ -216,6 +229,7 @@ export type RouteInitializer = { request: RequestChannel, }; + export interface ResponseChannel extends Channel { body(): Promise; finished(): Promise; @@ -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; + dismiss(): Promise; +} +export type DialogInitializer = { + type: string, + message: string, + defaultValue: string, +}; + + +export interface DownloadChannel extends Channel { + path(): Promise; + failure(): Promise; + delete(): Promise; +} +export type DownloadInitializer = { + url: string, + suggestedFilename: string, +}; diff --git a/src/rpc/client/browser.ts b/src/rpc/client/browser.ts index 898d25c3cf..d16e32316f 100644 --- a/src/rpc/client/browser.ts +++ b/src/rpc/client/browser.ts @@ -50,7 +50,10 @@ export class Browser extends ChannelOwner { } async newPage(options?: types.BrowserContextOptions): Promise { - 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 { diff --git a/src/rpc/client/browserContext.ts b/src/rpc/client/browserContext.ts index d24e5675e7..856841f4b1 100644 --- a/src/rpc/client/browserContext.ts +++ b/src/rpc/client/browserContext.ts @@ -42,6 +42,11 @@ export class BrowserContext extends ChannelOwner { + 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 { await this._channel.close(); - this._browser!._contexts.delete(this); + if (this._browser) + this._browser._contexts.delete(this); this.emit(Events.BrowserContext.Close); } } diff --git a/src/rpc/client/dialog.ts b/src/rpc/client/dialog.ts new file mode 100644 index 0000000000..bfdc99f213 --- /dev/null +++ b/src/rpc/client/dialog.ts @@ -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 { + 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(); + } +} diff --git a/src/rpc/client/download.ts b/src/rpc/client/download.ts new file mode 100644 index 0000000000..f52f6ba4ed --- /dev/null +++ b/src/rpc/client/download.ts @@ -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 { + 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 { + return this._channel.path(); + } + + async failure(): Promise { + return this._channel.failure(); + } + + async createReadStream(): Promise { + const fileName = await this.path(); + return fileName ? fs.createReadStream(fileName) : null; + } + + async delete(): Promise { + return this._channel.delete(); + } +} diff --git a/src/rpc/client/frame.ts b/src/rpc/client/frame.ts index 2031319432..167c79040c 100644 --- a/src/rpc/client/frame.ts +++ b/src/rpc/client/frame.ts @@ -37,7 +37,7 @@ export class Frame extends ChannelOwner { _parentFrame: Frame | null = null; _url = ''; _name = ''; - private _detached = false; + _detached = false; _childFrames = new Set(); _page: Page | undefined; diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index 6e88d02a7a..1a7830ce67 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -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 { readonly pdf: ((options?: types.PDFOptions) => Promise) | undefined; _browserContext: BrowserContext | undefined; + _ownedContext: BrowserContext | undefined; + private _mainFrame: Frame; private _frames = new Set(); private _workers: Worker[] = []; @@ -69,10 +73,13 @@ export class Page extends ChannelOwner { 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 { 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 { async close(options: { runBeforeUnload?: boolean } = {runBeforeUnload: undefined}) { await this._channel.close({ options }); + if (this._ownedContext) + await this._ownedContext.close(); } isClosed(): boolean { diff --git a/src/rpc/connection.ts b/src/rpc/connection.ts index a3b5c18873..cab2bb2187 100644 --- a/src/rpc/connection.ts +++ b/src/rpc/connection.ts @@ -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(); @@ -41,21 +43,39 @@ export class Connection { let result: ChannelOwner; 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; } } diff --git a/src/rpc/dispatcher.ts b/src/rpc/dispatcher.ts index 1c5b450a49..8aad1880e2 100644 --- a/src/rpc/dispatcher.ts +++ b/src/rpc/dispatcher.ts @@ -18,30 +18,30 @@ import { EventEmitter } from 'events'; import { helper } from '../helper'; import { Channel } from './channels'; -export class Dispatcher extends EventEmitter implements Channel { +export class Dispatcher 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 = {}) { + _dispatchEvent(method: string, params: Dispatcher | any = {}) { this._scope.sendMessageToClient(this._guid, method, params); } } export class DispatcherScope { - readonly dispatchers = new Map>(); + readonly dispatchers = new Map>(); 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; } } diff --git a/src/rpc/server/browserContextDispatcher.ts b/src/rpc/server/browserContextDispatcher.ts index 5a5c45d5cb..a1587e1ebb 100644 --- a/src/rpc/server/browserContextDispatcher.ts +++ b/src/rpc/server/browserContextDispatcher.ts @@ -23,9 +23,8 @@ import { PageChannel, BrowserContextChannel, BrowserContextInitializer } from '. import { RouteDispatcher, RequestDispatcher } from './networkDispatchers'; import { Page } from '../../page'; -export class BrowserContextDispatcher extends Dispatcher implements BrowserContextChannel { +export class BrowserContextDispatcher extends Dispatcher 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 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, () => { diff --git a/src/rpc/server/browserDispatcher.ts b/src/rpc/server/browserDispatcher.ts index 53ccaf3a18..75a1bc2664 100644 --- a/src/rpc/server/browserDispatcher.ts +++ b/src/rpc/server/browserDispatcher.ts @@ -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 implements BrowserChannel { - private _browser: BrowserBase; - +export class BrowserDispatcher extends Dispatcher 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 implements constructor(scope: DispatcherScope, browser: BrowserBase) { super(scope, browser, 'browser', {}); - this._browser = browser; } async newContext(params: { options?: types.BrowserContextOptions }): Promise { - 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 { - 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 { - await this._browser.close(); + await this._object.close(); } } diff --git a/src/rpc/server/browserTypeDispatcher.ts b/src/rpc/server/browserTypeDispatcher.ts index 0e29bdba02..ce484a98f8 100644 --- a/src/rpc/server/browserTypeDispatcher.ts +++ b/src/rpc/server/browserTypeDispatcher.ts @@ -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 implements BrowserTypeChannel { - private _browserType: BrowserTypeBase; - +export class BrowserTypeDispatcher extends Dispatcher 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 im executablePath: browserType.executablePath(), name: browserType.name() }, browserType.name()); - this._browserType = browserType; } async launch(params: { options?: types.LaunchOptions }): Promise { - 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 { - 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 { - const browser = await this._browserType.connect(params.options); + const browser = await this._object.connect(params.options); return BrowserDispatcher.from(this._scope, browser as BrowserBase); } } diff --git a/src/rpc/server/consoleMessageDispatcher.ts b/src/rpc/server/consoleMessageDispatcher.ts index 88a630f729..0a3156cec7 100644 --- a/src/rpc/server/consoleMessageDispatcher.ts +++ b/src/rpc/server/consoleMessageDispatcher.ts @@ -19,7 +19,7 @@ import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels'; import { Dispatcher, DispatcherScope } from '../dispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher'; -export class ConsoleMessageDispatcher extends Dispatcher implements ConsoleMessageChannel { +export class ConsoleMessageDispatcher extends Dispatcher implements ConsoleMessageChannel { static from(scope: DispatcherScope, message: ConsoleMessage): ConsoleMessageDispatcher { if ((message as any)[scope.dispatcherSymbol]) return (message as any)[scope.dispatcherSymbol]; diff --git a/src/rpc/server/dialogDispatcher.ts b/src/rpc/server/dialogDispatcher.ts new file mode 100644 index 0000000000..8ae9001fad --- /dev/null +++ b/src/rpc/server/dialogDispatcher.ts @@ -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 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 { + await this._object.accept(params.promptText); + } + + async dismiss(): Promise { + await this._object.dismiss(); + } +} diff --git a/src/rpc/server/downloadDispatcher.ts b/src/rpc/server/downloadDispatcher.ts new file mode 100644 index 0000000000..a313b8c9d6 --- /dev/null +++ b/src/rpc/server/downloadDispatcher.ts @@ -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 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 { + return this._object.path(); + } + + async failure(): Promise { + return this._object.failure(); + } + + async delete(): Promise { + await this._object.delete(); + } +} diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index 4104eeffef..a659c06c78 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -22,7 +22,7 @@ import { ElementHandleDispatcher, convertSelectOptionValues } from './elementHan import { JSHandleDispatcher } from './jsHandleDispatcher'; import { ResponseDispatcher } from './networkDispatchers'; -export class FrameDispatcher extends Dispatcher implements FrameChannel { +export class FrameDispatcher extends Dispatcher implements FrameChannel { private _frame: Frame; static from(scope: DispatcherScope, frame: Frame): FrameDispatcher { diff --git a/src/rpc/server/jsHandleDispatcher.ts b/src/rpc/server/jsHandleDispatcher.ts index e6175dba06..403322c7c7 100644 --- a/src/rpc/server/jsHandleDispatcher.ts +++ b/src/rpc/server/jsHandleDispatcher.ts @@ -19,27 +19,25 @@ import { JSHandleChannel, JSHandleInitializer } from '../channels'; import { Dispatcher, DispatcherScope } from '../dispatcher'; import { convertArg } from './frameDispatcher'; -export class JSHandleDispatcher extends Dispatcher implements JSHandleChannel { - readonly _jsHandle: js.JSHandle; +export class JSHandleDispatcher extends Dispatcher 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 { - 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 { - 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 implemen } async jsonValue(): Promise { - return this._jsHandle.jsonValue(); + return this._object.jsonValue(); } async dispose() { - await this._jsHandle.dispose(); + await this._object.dispose(); } } diff --git a/src/rpc/server/networkDispatchers.ts b/src/rpc/server/networkDispatchers.ts index fcb7a067c5..278e5ae531 100644 --- a/src/rpc/server/networkDispatchers.ts +++ b/src/rpc/server/networkDispatchers.ts @@ -20,9 +20,7 @@ import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, Req import { Dispatcher, DispatcherScope } from '../dispatcher'; import { FrameDispatcher } from './frameDispatcher'; -export class RequestDispatcher extends Dispatcher implements RequestChannel { - private _request: Request; - +export class RequestDispatcher extends Dispatcher 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 implements isNavigationRequest: request.isNavigationRequest(), redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()), }); - this._request = request; } async response(): Promise { - return ResponseDispatcher.fromNullable(this._scope, await this._request.response()); + return ResponseDispatcher.fromNullable(this._scope, await this._object.response()); } } -export class ResponseDispatcher extends Dispatcher implements ResponseChannel { - private _response: Response; +export class ResponseDispatcher extends Dispatcher implements ResponseChannel { static from(scope: DispatcherScope, response: Response): ResponseDispatcher { if ((response as any)[scope.dispatcherSymbol]) @@ -73,20 +69,18 @@ export class ResponseDispatcher extends Dispatcher implemen statusText: response.statusText(), headers: response.headers(), }); - this._response = response; } async finished(): Promise { - return await this._response.finished(); + return await this._object.finished(); } async body(): Promise { - return await this._response.body(); + return await this._object.body(); } } -export class RouteDispatcher extends Dispatcher implements RouteChannel { - private _route: Route; +export class RouteDispatcher extends Dispatcher implements RouteChannel { static from(scope: DispatcherScope, route: Route): RouteDispatcher { if ((route as any)[scope.dispatcherSymbol]) @@ -102,18 +96,17 @@ export class RouteDispatcher extends Dispatcher 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 { - await this._route.continue(params.overrides); + await this._object.continue(params.overrides); } async fulfill(params: { response: types.FulfillResponse & { path?: string } }): Promise { - await this._route.fulfill(params.response); + await this._object.fulfill(params.response); } async abort(params: { errorCode: string }): Promise { - await this._route.abort(params.errorCode); + await this._object.abort(params.errorCode); } } diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index 84f9ba1477..629c50c2e8 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -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 implements PageChannel { +export class PageDispatcher extends Dispatcher implements PageChannel { private _page: Page; static from(scope: DispatcherScope, page: Page): PageDispatcher { @@ -50,10 +52,13 @@ export class PageDispatcher extends Dispatcher 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 implements PageC } -export class BindingCallDispatcher extends Dispatcher 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;