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.
This commit is contained in:
parent
7d90f5ef25
commit
884edbb3ee
|
|
@ -20,6 +20,7 @@ import type { Connection } from './connection';
|
||||||
import type { Logger } from './types';
|
import type { Logger } from './types';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
|
import { createScheme, Validator, ValidationError } from '../protocol/validator';
|
||||||
|
|
||||||
export abstract class ChannelOwner<T extends channels.Channel = channels.Channel, Initializer = {}> extends EventEmitter {
|
export abstract class ChannelOwner<T extends channels.Channel = channels.Channel, Initializer = {}> extends EventEmitter {
|
||||||
private _connection: Connection;
|
private _connection: Connection;
|
||||||
|
|
@ -48,23 +49,14 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
const base = new EventEmitter();
|
const base = new EventEmitter();
|
||||||
this._channel = new Proxy(base, {
|
this._channel = new Proxy(base, {
|
||||||
get: (obj: any, prop) => {
|
get: (obj: any, prop) => {
|
||||||
if (String(prop).startsWith('_'))
|
if (prop === 'debugScopeState')
|
||||||
return obj[prop];
|
return (params: any) => this._connection.sendMessageToServer(guid, prop, params);
|
||||||
if (prop === 'then')
|
if (typeof prop === 'string') {
|
||||||
return obj.then;
|
const validator = scheme[paramsName(type, prop)];
|
||||||
if (prop === 'emit')
|
if (validator)
|
||||||
return obj.emit;
|
return (params: any) => this._connection.sendMessageToServer(guid, prop, validator(params, ''));
|
||||||
if (prop === 'on')
|
}
|
||||||
return obj.on;
|
return obj[prop];
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
(this._channel as any)._object = this;
|
(this._channel as any)._object = this;
|
||||||
|
|
@ -121,3 +113,17 @@ function logApiCall(logger: Logger | undefined, message: string) {
|
||||||
logger.log('api', 'info', message, [], { color: 'cyan' });
|
logger.log('api', 'info', message, [], { color: 'cyan' });
|
||||||
debugLogger.log('api', message);
|
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);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ import * as channels from '../protocol/channels';
|
||||||
import { ChromiumBrowser } from './chromiumBrowser';
|
import { ChromiumBrowser } from './chromiumBrowser';
|
||||||
import { ChromiumBrowserContext } from './chromiumBrowserContext';
|
import { ChromiumBrowserContext } from './chromiumBrowserContext';
|
||||||
import { Stream } from './stream';
|
import { Stream } from './stream';
|
||||||
import { createScheme, Validator, ValidationError } from '../protocol/validator';
|
|
||||||
import { WebKitBrowser } from './webkitBrowser';
|
import { WebKitBrowser } from './webkitBrowser';
|
||||||
import { FirefoxBrowser } from './firefoxBrowser';
|
import { FirefoxBrowser } from './firefoxBrowser';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
|
|
@ -70,13 +69,12 @@ export class Connection {
|
||||||
return this._objects.get(guid)!;
|
return this._objects.get(guid)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessageToServer(type: string, guid: string, method: string, params: any): Promise<any> {
|
async sendMessageToServer(guid: string, method: string, params: any): Promise<any> {
|
||||||
const stackObject: any = {};
|
const stackObject: any = {};
|
||||||
Error.captureStackTrace(stackObject);
|
Error.captureStackTrace(stackObject);
|
||||||
const stack = stackObject.stack.startsWith('Error') ? stackObject.stack.substring(5) : stackObject.stack;
|
const stack = stackObject.stack.startsWith('Error') ? stackObject.stack.substring(5) : stackObject.stack;
|
||||||
const id = ++this._lastId;
|
const id = ++this._lastId;
|
||||||
const validated = method === 'debugScopeState' ? params : validateParams(type, method, params);
|
const converted = { id, guid, method, params };
|
||||||
const converted = { id, guid, method, params: validated };
|
|
||||||
// Do not include metadata in debug logs to avoid noise.
|
// Do not include metadata in debug logs to avoid noise.
|
||||||
debugLogger.log('channel:command', converted);
|
debugLogger.log('channel:command', converted);
|
||||||
this.onmessage({ ...converted, metadata: { stack } });
|
this.onmessage({ ...converted, metadata: { stack } });
|
||||||
|
|
@ -243,20 +241,3 @@ export class Connection {
|
||||||
return result;
|
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, '');
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue