From 95538e73e7267e29b7f533f822aa68936483ea46 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 30 Jun 2020 22:21:17 -0700 Subject: [PATCH] chore(rpc): move classes around, fix tests, respect dispatcher scopes (#2784) --- src/rpc/channels.ts | 1 - src/rpc/client.ts | 4 +- src/rpc/client/browser.ts | 7 ++- src/rpc/client/browserContext.ts | 2 +- src/rpc/client/browserServer.ts | 2 +- src/rpc/client/browserType.ts | 2 +- src/rpc/client/channelOwner.ts | 2 +- src/rpc/{ => client}/connection.ts | 34 +++++------ src/rpc/client/consoleMessage.ts | 2 +- src/rpc/client/dialog.ts | 2 +- src/rpc/client/download.ts | 2 +- src/rpc/client/elementHandle.ts | 2 +- src/rpc/client/frame.ts | 2 +- src/rpc/client/jsHandle.ts | 2 +- src/rpc/client/network.ts | 2 +- src/rpc/client/page.ts | 2 +- src/rpc/client/worker.ts | 2 +- src/rpc/server.ts | 9 +-- src/rpc/server/browserContextDispatcher.ts | 3 +- src/rpc/server/browserDispatcher.ts | 22 ++++---- src/rpc/server/browserServerDispatcher.ts | 2 +- src/rpc/server/browserTypeDispatcher.ts | 8 +-- src/rpc/server/consoleMessageDispatcher.ts | 2 +- src/rpc/server/dialogDispatcher.ts | 2 +- src/rpc/{ => server}/dispatcher.ts | 65 ++++++++++++++++++---- src/rpc/server/downloadDispatcher.ts | 2 +- src/rpc/server/elementHandlerDispatcher.ts | 2 +- src/rpc/server/frameDispatcher.ts | 2 +- src/rpc/server/jsHandleDispatcher.ts | 2 +- src/rpc/server/networkDispatchers.ts | 2 +- src/rpc/server/pageDispatcher.ts | 2 +- test/environments.js | 14 ++--- 32 files changed, 130 insertions(+), 81 deletions(-) rename src/rpc/{ => client}/connection.ts (88%) rename src/rpc/{ => server}/dispatcher.ts (66%) diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index 97a77d6773..49f18607f0 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -55,7 +55,6 @@ export interface BrowserChannel extends Channel { close(): Promise; newContext(params: { options?: types.BrowserContextOptions }): Promise; - newPage(params: { options?: types.BrowserContextOptions }): Promise; } export type BrowserInitializer = {}; diff --git a/src/rpc/client.ts b/src/rpc/client.ts index 64d6298546..77693effa6 100644 --- a/src/rpc/client.ts +++ b/src/rpc/client.ts @@ -16,7 +16,7 @@ import * as childProcess from 'child_process'; import * as path from 'path'; -import { Connection } from './connection'; +import { Connection } from './client/connection'; import { Transport } from './transport'; (async () => { @@ -24,7 +24,7 @@ import { Transport } from './transport'; const transport = new Transport(spawnedProcess.stdin, spawnedProcess.stdout); const connection = new Connection(); connection.onmessage = message => transport.send(message); - transport.onmessage = message => connection.send(message); + transport.onmessage = message => connection.dispatch(message); const chromium = await connection.waitForObjectWithKnownName('chromium'); const browser = await chromium.launch({ headless: false }); diff --git a/src/rpc/client/browser.ts b/src/rpc/client/browser.ts index 4070e62ab0..2c11f61fbd 100644 --- a/src/rpc/client/browser.ts +++ b/src/rpc/client/browser.ts @@ -19,12 +19,13 @@ import { BrowserChannel, BrowserInitializer } from '../channels'; import { BrowserContext } from './browserContext'; import { Page } from './page'; import { ChannelOwner } from './channelOwner'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { Events } from '../../events'; export class Browser extends ChannelOwner { readonly _contexts = new Set(); private _isConnected = true; + private _isClosedOrClosing = false; static from(browser: BrowserChannel): Browser { @@ -40,6 +41,7 @@ export class Browser extends ChannelOwner { channel.on('close', () => { this._isConnected = false; this.emit(Events.Browser.Disconnected); + this._isClosedOrClosing = true; }); } @@ -69,6 +71,9 @@ export class Browser extends ChannelOwner { } async close(): Promise { + if (this._isClosedOrClosing) + return; + this._isClosedOrClosing = true; await this._channel.close(); } } diff --git a/src/rpc/client/browserContext.ts b/src/rpc/client/browserContext.ts index e2982babee..9bd03b6a8f 100644 --- a/src/rpc/client/browserContext.ts +++ b/src/rpc/client/browserContext.ts @@ -23,7 +23,7 @@ import { BrowserContextChannel, BrowserContextInitializer } from '../channels'; import { ChannelOwner } from './channelOwner'; import { helper } from '../../helper'; import { Browser } from './browser'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { Events } from '../../events'; import { TimeoutSettings } from '../../timeoutSettings'; diff --git a/src/rpc/client/browserServer.ts b/src/rpc/client/browserServer.ts index 6cd52a8529..d699a58b85 100644 --- a/src/rpc/client/browserServer.ts +++ b/src/rpc/client/browserServer.ts @@ -16,7 +16,7 @@ import { ChildProcess } from 'child_process'; import { BrowserServerChannel, BrowserServerInitializer } from '../channels'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { ChannelOwner } from './channelOwner'; import { Events } from '../../events'; diff --git a/src/rpc/client/browserType.ts b/src/rpc/client/browserType.ts index c4996025ff..f0cd919f33 100644 --- a/src/rpc/client/browserType.ts +++ b/src/rpc/client/browserType.ts @@ -19,7 +19,7 @@ import { BrowserTypeChannel, BrowserTypeInitializer } from '../channels'; import { Browser } from './browser'; import { BrowserContext } from './browserContext'; import { ChannelOwner } from './channelOwner'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { BrowserServer } from './browserServer'; export class BrowserType extends ChannelOwner { diff --git a/src/rpc/client/channelOwner.ts b/src/rpc/client/channelOwner.ts index 5491ed60b5..efcfd239e7 100644 --- a/src/rpc/client/channelOwner.ts +++ b/src/rpc/client/channelOwner.ts @@ -16,7 +16,7 @@ import { EventEmitter } from 'events'; import { Channel } from '../channels'; -import { Connection } from '../connection'; +import { Connection } from './connection'; export abstract class ChannelOwner extends EventEmitter { readonly _channel: T; diff --git a/src/rpc/connection.ts b/src/rpc/client/connection.ts similarity index 88% rename from src/rpc/connection.ts rename to src/rpc/client/connection.ts index f6c3f0aa21..9d83b47b63 100644 --- a/src/rpc/connection.ts +++ b/src/rpc/client/connection.ts @@ -15,23 +15,23 @@ */ import { EventEmitter } from 'ws'; -import { Browser } from './client/browser'; -import { BrowserContext } from './client/browserContext'; -import { BrowserType } from './client/browserType'; -import { ChannelOwner } from './client/channelOwner'; -import { ElementHandle } from './client/elementHandle'; -import { Frame } from './client/frame'; -import { JSHandle } from './client/jsHandle'; -import { Request, Response, Route } from './client/network'; -import { Page, BindingCall } from './client/page'; -import { Worker } from './client/worker'; +import { Browser } from './browser'; +import { BrowserContext } from './browserContext'; +import { BrowserType } from './browserType'; +import { ChannelOwner } from './channelOwner'; +import { ElementHandle } from './elementHandle'; +import { Frame } from './frame'; +import { JSHandle } from './jsHandle'; +import { Request, Response, Route } from './network'; +import { Page, BindingCall } from './page'; +import { Worker } from './worker'; import debug = require('debug'); -import { Channel } from './channels'; -import { ConsoleMessage } from './client/consoleMessage'; -import { Dialog } from './client/dialog'; -import { Download } from './client/download'; -import { parseError } from './serializers'; -import { BrowserServer } from './client/browserServer'; +import { Channel } from '../channels'; +import { ConsoleMessage } from './consoleMessage'; +import { Dialog } from './dialog'; +import { Download } from './download'; +import { parseError } from '../serializers'; +import { BrowserServer } from './browserServer'; export class Connection { private _channels = new Map(); @@ -122,7 +122,7 @@ export class Connection { return new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject })); } - send(message: string) { + dispatch(message: string) { const parsedMessage = JSON.parse(message); const { id, guid, method, params, result, error } = parsedMessage; if (id) { diff --git a/src/rpc/client/consoleMessage.ts b/src/rpc/client/consoleMessage.ts index bb82f4ecd0..9055e65c10 100644 --- a/src/rpc/client/consoleMessage.ts +++ b/src/rpc/client/consoleMessage.ts @@ -19,7 +19,7 @@ import { ConsoleMessageLocation } from '../../types'; import { JSHandle } from './jsHandle'; import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels'; import { ChannelOwner } from './channelOwner'; -import { Connection } from '../connection'; +import { Connection } from './connection'; export class ConsoleMessage extends ChannelOwner { static from(request: ConsoleMessageChannel): ConsoleMessage { diff --git a/src/rpc/client/dialog.ts b/src/rpc/client/dialog.ts index bfdc99f213..760bff09b0 100644 --- a/src/rpc/client/dialog.ts +++ b/src/rpc/client/dialog.ts @@ -15,7 +15,7 @@ */ import { DialogChannel, DialogInitializer } from '../channels'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { ChannelOwner } from './channelOwner'; export class Dialog extends ChannelOwner { diff --git a/src/rpc/client/download.ts b/src/rpc/client/download.ts index f52f6ba4ed..04e1e7f816 100644 --- a/src/rpc/client/download.ts +++ b/src/rpc/client/download.ts @@ -16,7 +16,7 @@ import * as fs from 'fs'; import { DownloadChannel, DownloadInitializer } from '../channels'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { ChannelOwner } from './channelOwner'; import { Readable } from 'stream'; diff --git a/src/rpc/client/elementHandle.ts b/src/rpc/client/elementHandle.ts index f4a7b50257..61b0f3e7b3 100644 --- a/src/rpc/client/elementHandle.ts +++ b/src/rpc/client/elementHandle.ts @@ -18,7 +18,7 @@ import * as types from '../../types'; import { ElementHandleChannel, JSHandleInitializer } from '../channels'; import { Frame } from './frame'; import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle'; -import { Connection } from '../connection'; +import { Connection } from './connection'; export class ElementHandle extends JSHandle { readonly _elementChannel: ElementHandleChannel; diff --git a/src/rpc/client/frame.ts b/src/rpc/client/frame.ts index 7e34e23f5b..dc1ab1abc1 100644 --- a/src/rpc/client/frame.ts +++ b/src/rpc/client/frame.ts @@ -25,7 +25,7 @@ import { JSHandle, Func1, FuncOn, SmartHandle, serializeArgument, parseResult } import * as network from './network'; import { Response } from './network'; import { Page } from './page'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { normalizeFilePayloads } from '../serializers'; export type GotoOptions = types.NavigateOptions & { diff --git a/src/rpc/client/jsHandle.ts b/src/rpc/client/jsHandle.ts index 64538d98c6..a3a171ea81 100644 --- a/src/rpc/client/jsHandle.ts +++ b/src/rpc/client/jsHandle.ts @@ -17,7 +17,7 @@ import { JSHandleChannel, JSHandleInitializer } from '../channels'; import { ElementHandle } from './elementHandle'; import { ChannelOwner } from './channelOwner'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { serializeAsCallArgument, parseEvaluationResultValue } from '../../common/utilityScriptSerializers'; type NoHandles = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles } : Arg); diff --git a/src/rpc/client/network.ts b/src/rpc/client/network.ts index 5fcec44f59..729a182298 100644 --- a/src/rpc/client/network.ts +++ b/src/rpc/client/network.ts @@ -19,7 +19,7 @@ import * as types from '../../types'; import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels'; import { ChannelOwner } from './channelOwner'; import { Frame } from './frame'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { normalizeFulfillParameters } from '../serializers'; export type NetworkCookie = { diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index 016ba6c5b8..b6d4cccc20 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -22,7 +22,7 @@ import { assert, assertMaxArguments, helper, Listener } from '../../helper'; import { TimeoutSettings } from '../../timeoutSettings'; import * as types from '../../types'; import { BindingCallChannel, BindingCallInitializer, Channel, PageChannel, PageInitializer } from '../channels'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { parseError, serializeError } from '../serializers'; import { Accessibility } from './accessibility'; import { BrowserContext } from './browserContext'; diff --git a/src/rpc/client/worker.ts b/src/rpc/client/worker.ts index 920f08adc7..d929a3e527 100644 --- a/src/rpc/client/worker.ts +++ b/src/rpc/client/worker.ts @@ -17,7 +17,7 @@ import { Events } from '../../events'; import { assertMaxArguments } from '../../helper'; import { WorkerChannel, WorkerInitializer } from '../channels'; -import { Connection } from '../connection'; +import { Connection } from './connection'; import { ChannelOwner } from './channelOwner'; import { Func1, JSHandle, parseResult, serializeArgument, SmartHandle } from './jsHandle'; import { Page } from './page'; diff --git a/src/rpc/server.ts b/src/rpc/server.ts index 8250beac13..f1f8911a80 100644 --- a/src/rpc/server.ts +++ b/src/rpc/server.ts @@ -15,15 +15,16 @@ */ import { Transport } from './transport'; -import { DispatcherScope } from './dispatcher'; +import { DispatcherConnection } from './server/dispatcher'; import { Playwright } from '../server/playwright'; import { BrowserTypeDispatcher } from './server/browserTypeDispatcher'; -const dispatcherScope = new DispatcherScope(); +const dispatcherConnection = new DispatcherConnection(); const transport = new Transport(process.stdout, process.stdin); -transport.onmessage = message => dispatcherScope.send(message); -dispatcherScope.onmessage = message => transport.send(message); +transport.onmessage = message => dispatcherConnection.dispatch(message); +dispatcherConnection.onmessage = message => transport.send(message); +const dispatcherScope = dispatcherConnection.createScope(); const playwright = new Playwright(__dirname, require('../../browsers.json')['browsers']); new BrowserTypeDispatcher(dispatcherScope, playwright.chromium!); new BrowserTypeDispatcher(dispatcherScope, playwright.firefox!); diff --git a/src/rpc/server/browserContextDispatcher.ts b/src/rpc/server/browserContextDispatcher.ts index af6a499c82..b6fda0b4f5 100644 --- a/src/rpc/server/browserContextDispatcher.ts +++ b/src/rpc/server/browserContextDispatcher.ts @@ -17,7 +17,7 @@ import * as types from '../../types'; import { BrowserContextBase, BrowserContext } from '../../browserContext'; import { Events } from '../../events'; -import { Dispatcher, DispatcherScope, lookupNullableDispatcher, lookupDispatcher } from '../dispatcher'; +import { Dispatcher, DispatcherScope, lookupNullableDispatcher, lookupDispatcher } from './dispatcher'; import { PageDispatcher, BindingCallDispatcher } from './pageDispatcher'; import { PageChannel, BrowserContextChannel, BrowserContextInitializer } from '../channels'; import { RouteDispatcher, RequestDispatcher } from './networkDispatchers'; @@ -34,6 +34,7 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('page', PageDispatcher.from(this._scope, page))); context.on(Events.BrowserContext.Close, () => { this._dispatchEvent('close'); + scope.dispose(); }); } diff --git a/src/rpc/server/browserDispatcher.ts b/src/rpc/server/browserDispatcher.ts index a362b61270..d71bd929bf 100644 --- a/src/rpc/server/browserDispatcher.ts +++ b/src/rpc/server/browserDispatcher.ts @@ -14,27 +14,25 @@ * limitations under the License. */ -import { BrowserBase, Browser } from '../../browser'; +import { Browser, BrowserBase } from '../../browser'; import { BrowserContextBase } from '../../browserContext'; -import * as types from '../../types'; -import { BrowserContextDispatcher } from './browserContextDispatcher'; -import { BrowserChannel, BrowserContextChannel, PageChannel, BrowserInitializer } from '../channels'; -import { Dispatcher, DispatcherScope, lookupDispatcher } from '../dispatcher'; -import { PageDispatcher } from './pageDispatcher'; import { Events } from '../../events'; +import * as types from '../../types'; +import { BrowserChannel, BrowserContextChannel, BrowserInitializer } from '../channels'; +import { BrowserContextDispatcher } from './browserContextDispatcher'; +import { Dispatcher, DispatcherScope } from './dispatcher'; export class BrowserDispatcher extends Dispatcher implements BrowserChannel { constructor(scope: DispatcherScope, browser: BrowserBase) { super(scope, browser, 'browser', {}); - browser.on(Events.Browser.Disconnected, () => this._dispatchEvent('close')); + browser.on(Events.Browser.Disconnected, () => { + this._dispatchEvent('close'); + scope.dispose(); + }); } async newContext(params: { options?: types.BrowserContextOptions }): Promise { - return new BrowserContextDispatcher(this._scope, await this._object.newContext(params.options) as BrowserContextBase); - } - - async newPage(params: { options?: types.BrowserContextOptions }): Promise { - return lookupDispatcher(await this._object.newPage(params.options))!; + return new BrowserContextDispatcher(this._scope.createChild(), await this._object.newContext(params.options) as BrowserContextBase); } async close(): Promise { diff --git a/src/rpc/server/browserServerDispatcher.ts b/src/rpc/server/browserServerDispatcher.ts index fe2c90e1c1..2f59abc6d9 100644 --- a/src/rpc/server/browserServerDispatcher.ts +++ b/src/rpc/server/browserServerDispatcher.ts @@ -16,7 +16,7 @@ import { BrowserServer } from '../../server/browserServer'; import { BrowserServerChannel, BrowserServerInitializer } from '../channels'; -import { Dispatcher, DispatcherScope } from '../dispatcher'; +import { Dispatcher, DispatcherScope } from './dispatcher'; import { Events } from '../../events'; export class BrowserServerDispatcher extends Dispatcher implements BrowserServerChannel { diff --git a/src/rpc/server/browserTypeDispatcher.ts b/src/rpc/server/browserTypeDispatcher.ts index ea818faf0a..ff9444eba4 100644 --- a/src/rpc/server/browserTypeDispatcher.ts +++ b/src/rpc/server/browserTypeDispatcher.ts @@ -19,7 +19,7 @@ import { BrowserTypeBase, BrowserType } from '../../server/browserType'; import * as types from '../../types'; import { BrowserDispatcher } from './browserDispatcher'; import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer, BrowserServerChannel } from '../channels'; -import { Dispatcher, DispatcherScope } from '../dispatcher'; +import { Dispatcher, DispatcherScope } from './dispatcher'; import { BrowserContextBase } from '../../browserContext'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import { BrowserServerDispatcher } from './browserServerDispatcher'; @@ -34,12 +34,12 @@ export class BrowserTypeDispatcher extends Dispatcher { const browser = await this._object.launch(params.options || undefined); - return new BrowserDispatcher(this._scope, browser as BrowserBase); + return new BrowserDispatcher(this._scope.createChild(), browser as BrowserBase); } async launchPersistentContext(params: { userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions }): Promise { const browserContext = await this._object.launchPersistentContext(params.userDataDir, params.options); - return new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase); + return new BrowserContextDispatcher(this._scope.createChild(), browserContext as BrowserContextBase); } async launchServer(params: { options?: types.LaunchServerOptions }): Promise { @@ -48,6 +48,6 @@ export class BrowserTypeDispatcher extends Dispatcher { const browser = await this._object.connect(params.options); - return new BrowserDispatcher(this._scope, browser as BrowserBase); + return new BrowserDispatcher(this._scope.createChild(), browser as BrowserBase); } } diff --git a/src/rpc/server/consoleMessageDispatcher.ts b/src/rpc/server/consoleMessageDispatcher.ts index 0627652a43..cd42d5a6bc 100644 --- a/src/rpc/server/consoleMessageDispatcher.ts +++ b/src/rpc/server/consoleMessageDispatcher.ts @@ -16,7 +16,7 @@ import { ConsoleMessage } from '../../console'; import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels'; -import { Dispatcher, DispatcherScope } from '../dispatcher'; +import { Dispatcher, DispatcherScope } from './dispatcher'; import { createHandle } from './elementHandlerDispatcher'; export class ConsoleMessageDispatcher extends Dispatcher implements ConsoleMessageChannel { diff --git a/src/rpc/server/dialogDispatcher.ts b/src/rpc/server/dialogDispatcher.ts index 304d4898ff..a59a45dac5 100644 --- a/src/rpc/server/dialogDispatcher.ts +++ b/src/rpc/server/dialogDispatcher.ts @@ -16,7 +16,7 @@ import { Dialog } from '../../dialog'; import { DialogChannel, DialogInitializer } from '../channels'; -import { Dispatcher, DispatcherScope } from '../dispatcher'; +import { Dispatcher, DispatcherScope } from './dispatcher'; export class DialogDispatcher extends Dispatcher implements DialogChannel { constructor(scope: DispatcherScope, dialog: Dialog) { diff --git a/src/rpc/dispatcher.ts b/src/rpc/server/dispatcher.ts similarity index 66% rename from src/rpc/dispatcher.ts rename to src/rpc/server/dispatcher.ts index b2357a579a..0df1e64d0b 100644 --- a/src/rpc/dispatcher.ts +++ b/src/rpc/server/dispatcher.ts @@ -15,9 +15,9 @@ */ import { EventEmitter } from 'events'; -import { helper, debugAssert } from '../helper'; -import { Channel } from './channels'; -import { serializeError } from './serializers'; +import { helper, debugAssert } from '../../helper'; +import { Channel } from '../channels'; +import { serializeError } from '../serializers'; export const dispatcherSymbol = Symbol('dispatcher'); @@ -47,7 +47,7 @@ export class Dispatcher extends EventEmitter implements Chann this._guid = guid; this._object = object; this._scope = scope; - scope.dispatchers.set(this._guid, this); + scope.bind(this._guid, this); (object as any)[dispatcherSymbol] = this; this._scope.sendMessageToClient(this._guid, '__create__', { type, initializer }); } @@ -58,17 +58,62 @@ export class Dispatcher extends EventEmitter implements Chann } export class DispatcherScope { - readonly dispatchers = new Map>(); - onmessage = (message: string) => {}; + private _connection: DispatcherConnection; + private _dispatchers = new Map>(); + private _parent: DispatcherScope | undefined; + private _childScopes = new Set(); + + constructor(connection: DispatcherConnection, parent?: DispatcherScope) { + this._connection = connection; + this._parent = parent; + if (parent) + parent._childScopes.add(this); + } + + createChild(): DispatcherScope { + return new DispatcherScope(this._connection, this); + } + + bind(guid: string, arg: Dispatcher) { + this._dispatchers.set(guid, arg); + this._connection._dispatchers.set(guid, arg); + } + + dispose() { + for (const child of [...this._childScopes]) + child.dispose(); + this._childScopes.clear(); + for (const guid of this._dispatchers.keys()) + this._connection._dispatchers.delete(guid); + if (this._parent) + this._parent._childScopes.delete(this); + } async sendMessageToClient(guid: string, method: string, params: any): Promise { + this._connection._sendMessageToClient(guid, method, params); + } +} + +export class DispatcherConnection { + readonly _dispatchers = new Map>(); + onmessage = (message: string) => {}; + + async _sendMessageToClient(guid: string, method: string, params: any): Promise { this.onmessage(JSON.stringify({ guid, method, params: this._replaceDispatchersWithGuids(params) })); } - async send(message: string) { + createScope(): DispatcherScope { + return new DispatcherScope(this); + } + + async dispatch(message: string) { const parsedMessage = JSON.parse(message); const { id, guid, method, params } = parsedMessage; - const dispatcher = this.dispatchers.get(guid)!; + const dispatcher = this._dispatchers.get(guid); + if (!dispatcher) { + this.onmessage(JSON.stringify({ id, error: serializeError(new Error('Target browser or context has been closed')) })); + return; + } try { const result = await (dispatcher as any)[method](this._replaceGuidsWithDispatchers(params)); this.onmessage(JSON.stringify({ id, result: this._replaceDispatchersWithGuids(result) })); @@ -101,8 +146,8 @@ export class DispatcherScope { return payload; if (Array.isArray(payload)) return payload.map(p => this._replaceGuidsWithDispatchers(p)); - if (payload.guid && this.dispatchers.has(payload.guid)) - return this.dispatchers.get(payload.guid); + if (payload.guid && this._dispatchers.has(payload.guid)) + return this._dispatchers.get(payload.guid); // TODO: send base64 if (payload instanceof Buffer) return payload; diff --git a/src/rpc/server/downloadDispatcher.ts b/src/rpc/server/downloadDispatcher.ts index ac69e60cb4..97460678ba 100644 --- a/src/rpc/server/downloadDispatcher.ts +++ b/src/rpc/server/downloadDispatcher.ts @@ -16,7 +16,7 @@ import { Download } from '../../download'; import { DownloadChannel, DownloadInitializer } from '../channels'; -import { Dispatcher, DispatcherScope } from '../dispatcher'; +import { Dispatcher, DispatcherScope } from './dispatcher'; export class DownloadDispatcher extends Dispatcher implements DownloadChannel { constructor(scope: DispatcherScope, download: Download) { diff --git a/src/rpc/server/elementHandlerDispatcher.ts b/src/rpc/server/elementHandlerDispatcher.ts index 526d2e00ab..b2d6fb74a0 100644 --- a/src/rpc/server/elementHandlerDispatcher.ts +++ b/src/rpc/server/elementHandlerDispatcher.ts @@ -18,7 +18,7 @@ import { ElementHandle } from '../../dom'; import * as js from '../../javascript'; import * as types from '../../types'; import { ElementHandleChannel, FrameChannel } from '../channels'; -import { DispatcherScope, lookupNullableDispatcher } from '../dispatcher'; +import { DispatcherScope, lookupNullableDispatcher } from './dispatcher'; import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher'; import { FrameDispatcher } from './frameDispatcher'; diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index 81f7421f44..d2a5fa6021 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -17,7 +17,7 @@ import { Frame } from '../../frames'; import * as types from '../../types'; import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel } from '../channels'; -import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from '../dispatcher'; +import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { convertSelectOptionValues, ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { ResponseDispatcher } from './networkDispatchers'; diff --git a/src/rpc/server/jsHandleDispatcher.ts b/src/rpc/server/jsHandleDispatcher.ts index d8092786ae..c49c73f080 100644 --- a/src/rpc/server/jsHandleDispatcher.ts +++ b/src/rpc/server/jsHandleDispatcher.ts @@ -16,7 +16,7 @@ import * as js from '../../javascript'; import { JSHandleChannel, JSHandleInitializer } from '../channels'; -import { Dispatcher, DispatcherScope } from '../dispatcher'; +import { Dispatcher, DispatcherScope } from './dispatcher'; import { parseEvaluationResultValue, serializeAsCallArgument } from '../../common/utilityScriptSerializers'; import { createHandle } from './elementHandlerDispatcher'; diff --git a/src/rpc/server/networkDispatchers.ts b/src/rpc/server/networkDispatchers.ts index 50cd453753..f65f0f10e1 100644 --- a/src/rpc/server/networkDispatchers.ts +++ b/src/rpc/server/networkDispatchers.ts @@ -17,7 +17,7 @@ import { Request, Response, Route } from '../../network'; import * as types from '../../types'; import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary } from '../channels'; -import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from '../dispatcher'; +import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { FrameDispatcher } from './frameDispatcher'; export class RequestDispatcher extends Dispatcher implements RequestChannel { diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index 00b4a723e1..3b5eb04b55 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -21,7 +21,7 @@ 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 { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { parseError, serializeError } from '../serializers'; import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; import { DialogDispatcher } from './dialogDispatcher'; diff --git a/test/environments.js b/test/environments.js index 1e93e90ff1..71b2844e3f 100644 --- a/test/environments.js +++ b/test/environments.js @@ -20,8 +20,8 @@ const fs = require('fs'); const path = require('path'); const rm = require('rimraf').sync; const {TestServer} = require('../utils/testserver/'); -const { DispatcherScope } = require('../lib/rpc/dispatcher'); -const { Connection } = require('../lib/rpc/connection'); +const { DispatcherConnection } = require('../lib/rpc/server/dispatcher'); +const { Connection } = require('../lib/rpc/client/connection'); const { BrowserTypeDispatcher } = require('../lib/rpc/server/browserTypeDispatcher'); class ServerEnvironment { @@ -172,17 +172,17 @@ class BrowserTypeEnvironment { // Channel substitute let overridenBrowserType = this._browserType; if (process.env.PWCHANNEL) { - const dispatcherScope = new DispatcherScope(); + const dispatcherConnection = new DispatcherConnection(); const connection = new Connection(); - dispatcherScope.onmessage = async message => { - setImmediate(() => connection.send(message)); + dispatcherConnection.onmessage = async message => { + setImmediate(() => connection.dispatch(message)); }; connection.onmessage = async message => { - const result = await dispatcherScope.send(message); + const result = await dispatcherConnection.dispatch(message); await new Promise(f => setImmediate(f)); return result; }; - new BrowserTypeDispatcher(dispatcherScope, this._browserType); + new BrowserTypeDispatcher(dispatcherConnection.createScope(), this._browserType); overridenBrowserType = await connection.waitForObjectWithKnownName(this._browserType.name()); } state.browserType = overridenBrowserType;