From 884edbb3eed7956be675d4782ffb31855212dc53 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 26 Nov 2020 07:33:09 -0800 Subject: [PATCH] fix(channels): only send protocol methods to connection (#4493) When using 'domain' module, it calls various EventEmitter methods like 'listenerCount' that we do not expect. To avoid this problem in the future, we validate the method name before sending it over the protocol connection. --- src/client/channelOwner.ts | 40 ++++++++++++++++++++++---------------- src/client/connection.ts | 23 ++-------------------- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/client/channelOwner.ts b/src/client/channelOwner.ts index c943d22133..91e59deabf 100644 --- a/src/client/channelOwner.ts +++ b/src/client/channelOwner.ts @@ -20,6 +20,7 @@ import type { Connection } from './connection'; import type { Logger } from './types'; import { debugLogger } from '../utils/debugLogger'; import { rewriteErrorMessage } from '../utils/stackTrace'; +import { createScheme, Validator, ValidationError } from '../protocol/validator'; export abstract class ChannelOwner extends EventEmitter { private _connection: Connection; @@ -48,23 +49,14 @@ export abstract class ChannelOwner { - if (String(prop).startsWith('_')) - return obj[prop]; - if (prop === 'then') - return obj.then; - if (prop === 'emit') - return obj.emit; - if (prop === 'on') - return obj.on; - if (prop === 'once') - return obj.once; - if (prop === 'addEventListener') - return obj.addListener; - if (prop === 'removeEventListener') - return obj.removeListener; - if (prop === 'domain') // https://github.com/microsoft/playwright/issues/3848 - return obj.domain; - return (params: any) => this._connection.sendMessageToServer(this._type, guid, String(prop), params); + if (prop === 'debugScopeState') + return (params: any) => this._connection.sendMessageToServer(guid, prop, params); + if (typeof prop === 'string') { + const validator = scheme[paramsName(type, prop)]; + if (validator) + return (params: any) => this._connection.sendMessageToServer(guid, prop, validator(params, '')); + } + return obj[prop]; }, }); (this._channel as any)._object = this; @@ -121,3 +113,17 @@ function logApiCall(logger: Logger | undefined, message: string) { logger.log('api', 'info', message, [], { color: 'cyan' }); debugLogger.log('api', message); } + +function paramsName(type: string, method: string) { + return type + method[0].toUpperCase() + method.substring(1) + 'Params'; +} + +const tChannel = (name: string): Validator => { + return (arg: any, path: string) => { + if (arg._object instanceof ChannelOwner && (name === '*' || arg._object._type === name)) + return { guid: arg._object._guid }; + throw new ValidationError(`${path}: expected ${name}`); + }; +}; + +const scheme = createScheme(tChannel); diff --git a/src/client/connection.ts b/src/client/connection.ts index cc0e01dc64..3efc9bf7a7 100644 --- a/src/client/connection.ts +++ b/src/client/connection.ts @@ -35,7 +35,6 @@ import * as channels from '../protocol/channels'; import { ChromiumBrowser } from './chromiumBrowser'; import { ChromiumBrowserContext } from './chromiumBrowserContext'; import { Stream } from './stream'; -import { createScheme, Validator, ValidationError } from '../protocol/validator'; import { WebKitBrowser } from './webkitBrowser'; import { FirefoxBrowser } from './firefoxBrowser'; import { debugLogger } from '../utils/debugLogger'; @@ -70,13 +69,12 @@ export class Connection { return this._objects.get(guid)!; } - async sendMessageToServer(type: string, guid: string, method: string, params: any): Promise { + async sendMessageToServer(guid: string, method: string, params: any): Promise { const stackObject: any = {}; Error.captureStackTrace(stackObject); const stack = stackObject.stack.startsWith('Error') ? stackObject.stack.substring(5) : stackObject.stack; const id = ++this._lastId; - const validated = method === 'debugScopeState' ? params : validateParams(type, method, params); - const converted = { id, guid, method, params: validated }; + const converted = { id, guid, method, params }; // Do not include metadata in debug logs to avoid noise. debugLogger.log('channel:command', converted); this.onmessage({ ...converted, metadata: { stack } }); @@ -243,20 +241,3 @@ export class Connection { return result; } } - -const tChannel = (name: string): Validator => { - return (arg: any, path: string) => { - if (arg._object instanceof ChannelOwner && (name === '*' || arg._object._type === name)) - return { guid: arg._object._guid }; - throw new ValidationError(`${path}: expected ${name}`); - }; -}; - -const scheme = createScheme(tChannel); - -function validateParams(type: string, method: string, params: any): any { - const name = type + method[0].toUpperCase() + method.substring(1) + 'Params'; - if (!scheme[name]) - throw new ValidationError(`Unknown scheme for ${type}.${method}`); - return scheme[name](params, ''); -}