chore: move to generic EventEmitters

This commit is contained in:
Max Schmitt 2024-07-13 18:21:42 +02:00
parent 1b4d9003c6
commit 7f3c57cc4c
56 changed files with 571 additions and 558 deletions

View file

@ -19,7 +19,7 @@ import type { AddressInfo } from 'net';
import net from 'net';
import { debugLogger } from '../utils/debugLogger';
import { createSocket } from '../utils/happy-eyeballs';
import { assert, createGuid, } from '../utils';
import { assert, createGuid, ManagedEventEmitter, } from '../utils';
// https://tools.ietf.org/html/rfc1928
@ -366,13 +366,11 @@ export function parsePattern(pattern: string | undefined): PatternMatcher {
return (host, port) => matchers.some(matcher => matcher(host, port));
}
export class SocksProxy extends EventEmitter implements SocksConnectionClient {
static Events = {
SocksRequested: 'socksRequested',
SocksData: 'socksData',
SocksClosed: 'socksClosed',
};
export class SocksProxy extends ManagedEventEmitter<{
'socksRequested': [SocksSocketRequestedPayload],
'socksData': [SocksSocketDataPayload],
'socksClosed': [SocksSocketClosedPayload],
}> implements SocksConnectionClient {
private _server: net.Server;
private _connections = new Map<string, SocksConnection>();
private _sockets = new Set<net.Socket>();
@ -456,7 +454,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
this._handleDirect(payload);
return;
}
this.emit(SocksProxy.Events.SocksRequested, payload);
this.emit('socksRequested', payload);
}
onSocketData(payload: SocksSocketDataPayload): void {
@ -465,7 +463,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
direct.write(payload.data);
return;
}
this.emit(SocksProxy.Events.SocksData, payload);
this.emit('socksData', payload);
}
onSocketClosed(payload: SocksSocketClosedPayload): void {
@ -475,7 +473,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
this._directSockets.delete(payload.uid);
return;
}
this.emit(SocksProxy.Events.SocksClosed, payload);
this.emit('socksClosed', payload);
}
socketConnected({ uid, host, port }: SocksSocketConnectedPayload) {
@ -499,15 +497,13 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
}
}
export class SocksProxyHandler extends EventEmitter {
static Events = {
SocksConnected: 'socksConnected',
SocksData: 'socksData',
SocksError: 'socksError',
SocksFailed: 'socksFailed',
SocksEnd: 'socksEnd',
};
export class SocksProxyHandler extends EventEmitter<{
socksConnected: [SocksSocketConnectedPayload],
socksData: [SocksSocketDataPayload],
socksError: [SocksSocketErrorPayload],
socksFailed: [SocksSocketFailedPayload],
socksEnd: [SocksSocketEndPayload],
}> {
private _sockets = new Map<string, net.Socket>();
private _patternMatcher: PatternMatcher = () => false;
private _redirectPortForTest: number | undefined;
@ -528,7 +524,7 @@ export class SocksProxyHandler extends EventEmitter {
if (!this._patternMatcher(host, port)) {
const payload: SocksSocketFailedPayload = { uid, errorCode: 'ERULESET' };
debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`);
this.emit(SocksProxyHandler.Events.SocksFailed, payload);
this.emit('socksFailed', payload);
return;
}
@ -540,18 +536,18 @@ export class SocksProxyHandler extends EventEmitter {
const socket = await createSocket(host, port);
socket.on('data', data => {
const payload: SocksSocketDataPayload = { uid, data };
this.emit(SocksProxyHandler.Events.SocksData, payload);
this.emit('socksData', payload);
});
socket.on('error', error => {
const payload: SocksSocketErrorPayload = { uid, error: error.message };
debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`);
this.emit(SocksProxyHandler.Events.SocksError, payload);
this.emit('socksError', payload);
this._sockets.delete(uid);
});
socket.on('end', () => {
const payload: SocksSocketEndPayload = { uid };
debugLogger.log('socks', `[${uid}] <= network socket closed`);
this.emit(SocksProxyHandler.Events.SocksEnd, payload);
this.emit('socksEnd', payload);
this._sockets.delete(uid);
});
const localAddress = socket.localAddress;
@ -559,11 +555,11 @@ export class SocksProxyHandler extends EventEmitter {
this._sockets.set(uid, socket);
const payload: SocksSocketConnectedPayload = { uid, host: localAddress!, port: localPort! };
debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`);
this.emit(SocksProxyHandler.Events.SocksConnected, payload);
this.emit('socksConnected', payload);
} catch (error) {
const payload: SocksSocketFailedPayload = { uid, errorCode: error.code };
debugLogger.log('socks', `[${uid}] <= connect error ${payload.errorCode}`);
this.emit(SocksProxyHandler.Events.SocksFailed, payload);
this.emit('socksFailed', payload);
}
}

View file

@ -18,12 +18,12 @@ import type { WebSocket } from '../utilsBundle';
import type { DispatcherScope, Playwright } from '../server';
import type * as channels from '@protocol/channels';
import { createPlaywright, DispatcherConnection, RootDispatcher, PlaywrightDispatcher } from '../server';
import { Browser } from '../server/browser';
import type { Browser } from '../server/browser';
import { serverSideCallMetadata } from '../server/instrumentation';
import { SocksProxy } from '../common/socksProxy';
import { assert, isUnderTest } from '../utils';
import type { LaunchOptions } from '../server/types';
import { AndroidDevice } from '../server/android/android';
import type { AndroidDevice } from '../server/android/android';
import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher';
import { startProfiling, stopProfiling } from '../utils';
import { monotonicTime } from '../utils';
@ -123,7 +123,7 @@ export class PlaywrightConnection {
for (const browser of playwright.allBrowsers())
await browser.close({ reason: 'Connection terminated' });
});
browser.on(Browser.Events.Disconnected, () => {
browser.on('disconnected', () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Browser closed' });
});
@ -139,7 +139,7 @@ export class PlaywrightConnection {
this._preLaunched.socksProxy?.setPattern(this._options.socksProxyPattern);
const browser = this._preLaunched.browser!;
browser.on(Browser.Events.Disconnected, () => {
browser.on('disconnected', () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Browser closed' });
});
@ -158,7 +158,7 @@ export class PlaywrightConnection {
debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`);
const playwright = this._preLaunched.playwright!;
const androidDevice = this._preLaunched.androidDevice!;
androidDevice.on(AndroidDevice.Events.Close, () => {
androidDevice.on('close', () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Android device disconnected' });
});
@ -202,7 +202,7 @@ export class PlaywrightConnection {
...this._options.launchOptions,
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS,
});
browser.on(Browser.Events.Disconnected, () => {
browser.on('disconnected', () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Browser closed' });
});

View file

@ -21,6 +21,7 @@ import os from 'os';
import path from 'path';
import type * as stream from 'stream';
import { wsReceiver, wsSender } from '../../utilsBundle';
import type { ManagedEventEmitter } from '../../utils';
import { createGuid, makeWaitForNextTask, isUnderTest, getPackageManagerExecCommand } from '../../utils';
import { removeFolders } from '../../utils/fileUtils';
import type { BrowserOptions, BrowserProcess } from '../browser';
@ -53,7 +54,13 @@ export interface DeviceBackend {
open(command: string): Promise<SocketBackend>;
}
export interface SocketBackend extends EventEmitter {
export type SocketBackendEvents = {
data: [Buffer],
close: []
error: [Error]
};
export interface SocketBackend extends ManagedEventEmitter<SocketBackendEvents> {
guid: string;
write(data: Buffer): Promise<void>;
close(): void;
@ -96,7 +103,11 @@ export class Android extends SdkObject {
}
}
export class AndroidDevice extends SdkObject {
export class AndroidDevice extends SdkObject<{
webViewAdded: [channels.AndroidWebView]
webViewRemoved: [string]
close: []
}> {
readonly _backend: DeviceBackend;
readonly model: string;
readonly serial: string;
@ -108,12 +119,6 @@ export class AndroidDevice extends SdkObject {
readonly _timeoutSettings: TimeoutSettings;
private _webViews = new Map<string, channels.AndroidWebView>();
static Events = {
WebViewAdded: 'webViewAdded',
WebViewRemoved: 'webViewRemoved',
Close: 'close',
};
private _browserConnections = new Set<AndroidBrowser>();
readonly _android: Android;
private _isClosed = false;
@ -256,7 +261,7 @@ export class AndroidDevice extends SdkObject {
}
await this._backend.close();
this._android._deviceClosed(this);
this.emit(AndroidDevice.Events.Close);
this.emit('close');
}
async launchBrowser(pkg: string = 'com.android.chrome', options: channels.AndroidDeviceLaunchBrowserParams): Promise<BrowserContext> {
@ -420,12 +425,12 @@ export class AndroidDevice extends SdkObject {
const webView = { pid, pkg, socketName };
this._webViews.set(socketName, webView);
this.emit(AndroidDevice.Events.WebViewAdded, webView);
this.emit('webViewAdded', webView);
}
for (const p of this._webViews.keys()) {
if (!socketNames.has(p)) {
this._webViews.delete(p);
this.emit(AndroidDevice.Events.WebViewRemoved, p);
this.emit('webViewRemoved', p);
}
}
}

View file

@ -17,9 +17,8 @@
import { debug } from '../../utilsBundle';
import type * as channels from '@protocol/channels';
import * as net from 'net';
import { EventEmitter } from 'events';
import type { Backend, DeviceBackend, SocketBackend } from './android';
import { assert, createGuid } from '../../utils';
import type { Backend, DeviceBackend, SocketBackend, SocketBackendEvents } from './android';
import { assert, createGuid, ManagedEventEmitter } from '../../utils';
export class AdbBackend implements Backend {
async devices(options: channels.AndroidDevicesOptions = {}): Promise<DeviceBackend[]> {
@ -112,7 +111,7 @@ function encodeMessage(message: string): Buffer {
return Buffer.from(lenHex + message);
}
class BufferedSocketWrapper extends EventEmitter implements SocketBackend {
class BufferedSocketWrapper extends ManagedEventEmitter<SocketBackendEvents> implements SocketBackend {
readonly guid = createGuid();
private _socket: net.Socket;
private _buffer = Buffer.from([]);

View file

@ -52,13 +52,10 @@ export type BrowserOptions = {
originalLaunchOptions: types.LaunchOptions;
};
export abstract class Browser extends SdkObject {
static Events = {
BeforeClose: 'beforeClose',
Disconnected: 'disconnected',
};
export abstract class Browser extends SdkObject<{
beforeClose: []
disconnected: []
}> {
readonly options: BrowserOptions;
private _downloads = new Map<string, Download>();
_defaultContext: BrowserContext | null = null;
@ -140,8 +137,8 @@ export abstract class Browser extends SdkObject {
pageOrError.then(page => {
if (page instanceof Page) {
page._video = artifact;
page.emitOnContext(BrowserContext.Events.VideoStarted, artifact);
page.emit(Page.Events.Video, artifact);
page.emitOnContext('videostarted', artifact);
page.emit('video', artifact);
}
});
}
@ -158,7 +155,7 @@ export abstract class Browser extends SdkObject {
_didClose() {
if (this._needsBeforeCloseEvent)
this.emit(Browser.Events.BeforeClose);
this.emit('beforeClose');
else
this.beforeCloseFinished();
}
@ -168,7 +165,7 @@ export abstract class Browser extends SdkObject {
context._browserClosed();
if (this._defaultContext)
this._defaultContext._browserClosed();
this.emit(Browser.Events.Disconnected);
this.emit('disconnected');
this.instrumentation.onBrowserClose(this);
}
@ -180,7 +177,7 @@ export abstract class Browser extends SdkObject {
await this.options.browserProcess.close();
}
if (this.isConnected())
await new Promise(x => this.once(Browser.Events.Disconnected, x));
await new Promise<void>(x => this.once('disconnected', x));
}
async killForTests() {

View file

@ -25,7 +25,7 @@ import type * as frames from './frames';
import { helper } from './helper';
import * as network from './network';
import { InitScript } from './page';
import type { PageDelegate } from './page';
import type { PageDelegate, Worker } from './page';
import { Page, PageBinding } from './page';
import type { Progress, ProgressController } from './progress';
import type { Selectors } from './selectors';
@ -44,27 +44,33 @@ import { BrowserContextAPIRequestContext } from './fetch';
import type { Artifact } from './artifact';
import { Clock } from './clock';
import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
import type { ConsoleMessage } from './console';
import type { Dialog } from './dialog';
export abstract class BrowserContext extends SdkObject {
static Events = {
Console: 'console',
Close: 'close',
Dialog: 'dialog',
Page: 'page',
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: 'pageerror',
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished',
RequestAborted: 'requestaborted',
RequestFulfilled: 'requestfulfilled',
RequestContinued: 'requestcontinued',
BeforeClose: 'beforeclose',
VideoStarted: 'videostarted',
};
export type BrowserContextEvents = {
console: [ConsoleMessage]
close: []
dialog: [Dialog]
page: [Page]
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
pageerror: [Error, Page]
request: [network.Request]
response: [network.Response]
requestfailed: [network.Request]
requestfinished: [{ request: network.Request, response: network.Response | null}]
requestaborted: [network.Request]
requestfulfilled: [network.Request]
requestcontinued: [network.Request]
beforeclose: []
videostarted: [Artifact]
// Chromium only
backgroundpage: [Page]
serviceworker: [Worker]
};
export abstract class BrowserContext extends SdkObject<BrowserContextEvents> {
readonly _timeoutSettings = new TimeoutSettings();
readonly _pageBindings = new Map<string, PageBinding>();
readonly _activeProgressControllers = new Set<ProgressController>();
@ -136,7 +142,7 @@ export abstract class BrowserContext extends SdkObject {
// When paused, show inspector.
if (this._debugger.isPaused())
Recorder.showInspector(this);
this._debugger.on(Debugger.Events.PausedStateChanged, () => {
this._debugger.on('pausedstatechanged', () => {
Recorder.showInspector(this);
});
@ -252,7 +258,7 @@ export abstract class BrowserContext extends SdkObject {
if (this._isPersistentContext)
this.onClosePersistent();
this._closePromiseFulfill!(new Error('Context closed'));
this.emit(BrowserContext.Events.Close);
this.emit('close');
}
// BrowserContext methods.
@ -359,7 +365,7 @@ export abstract class BrowserContext extends SdkObject {
async _loadDefaultContextAsIs(progress: Progress): Promise<Page[]> {
if (!this.pages().length) {
const waitForEvent = helper.waitForEvent(progress, this, BrowserContext.Events.Page);
const waitForEvent = helper.waitForEvent(progress, this, 'page');
progress.cleanupWhenAborted(() => waitForEvent.dispose);
const page = (await waitForEvent.promise) as Page;
if (page._pageIsError)
@ -443,7 +449,7 @@ export abstract class BrowserContext extends SdkObject {
if (this._closedStatus === 'open') {
if (options.reason)
this._closeReason = options.reason;
this.emit(BrowserContext.Events.BeforeClose);
this.emit('beforeclose');
this._closedStatus = 'closing';
for (const harRecorder of this._harRecorders.values())
@ -615,10 +621,10 @@ export abstract class BrowserContext extends SdkObject {
async extendInjectedScript(source: string, arg?: any) {
const installInFrame = (frame: frames.Frame) => frame.extendInjectedScript(source, arg).catch(() => {});
const installInPage = (page: Page) => {
page.on(Page.Events.InternalFrameNavigatedToNewDocument, installInFrame);
page.on('internalframenavigatedtonewdocument', installInFrame);
return Promise.all(page.frames().map(installInFrame));
};
this.on(BrowserContext.Events.Page, installInPage);
this.on('page', installInPage);
return Promise.all(this.pages().map(installInPage));
}

View file

@ -28,7 +28,6 @@ import type { ConnectionTransport, ProtocolRequest } from '../transport';
import { WebSocketTransport } from '../transport';
import { CRDevTools } from './crDevTools';
import type { BrowserOptions, BrowserProcess } from '../browser';
import { Browser } from '../browser';
import type * as types from '../types';
import type * as channels from '@protocol/channels';
import type { HTTPRequestParams } from '../../utils/network';
@ -121,7 +120,7 @@ export class Chromium extends BrowserType {
progress.throwIfAborted();
const browser = await CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions);
browser._isCollocatedWithServer = false;
browser.on(Browser.Events.Disconnected, doCleanup);
browser.on('disconnected', doCleanup);
return browser;
}

View file

@ -192,7 +192,7 @@ export class CRBrowser extends Browser {
if (targetInfo.type === 'service_worker') {
const serviceWorker = new CRServiceWorker(context, session, targetInfo.url);
this._serviceWorkers.set(targetInfo.targetId, serviceWorker);
context.emit(CRBrowserContext.CREvents.ServiceWorker, serviceWorker);
context.emit('serviceworker', serviceWorker);
return;
}
@ -333,11 +333,6 @@ export class CRBrowser extends Browser {
}
export class CRBrowserContext extends BrowserContext {
static CREvents = {
BackgroundPage: 'backgroundpage',
ServiceWorker: 'serviceworker',
};
declare readonly _browser: CRBrowser;
constructor(browser: CRBrowser, browserContextId: string | undefined, options: channels.BrowserNewContextParams) {

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { type RegisteredListener, assert, eventsHelper } from '../../utils';
import { ManagedEventEmitter, type RegisteredListener, assert, eventsHelper } from '../../utils';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { Protocol } from './protocol';
import { EventEmitter } from 'events';
@ -97,7 +97,7 @@ export class CRConnection extends EventEmitter {
type SessionEventListener = (method: string, params?: Object) => void;
export class CRSession extends EventEmitter {
export class CRSession extends ManagedEventEmitter<{ [K in keyof Protocol.Events]: [Protocol.Events[K]] }> {
private readonly _connection: CRConnection;
private _eventListener?: SessionEventListener;
private readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError }>();
@ -105,11 +105,6 @@ export class CRSession extends EventEmitter {
private readonly _parentSession: CRSession | null;
private _crashed: boolean = false;
private _closed = false;
override on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(connection: CRConnection, parentSession: CRSession | null, sessionId: string, eventListener?: SessionEventListener) {
super();
@ -169,6 +164,7 @@ export class CRSession extends EventEmitter {
Promise.resolve().then(() => {
if (this._eventListener)
this._eventListener(object.method!, object.params);
// @ts-ignore
this.emit(object.method!, object.params);
});
}
@ -199,12 +195,10 @@ export class CRSession extends EventEmitter {
}
}
export class CDPSession extends EventEmitter {
static Events = {
Event: 'event',
Closed: 'close',
};
export class CDPSession extends ManagedEventEmitter<{
event: [{ method: string, params: Object | undefined }]
closed: [];
}> {
readonly guid: string;
private _session: CRSession;
private _listeners: RegisteredListener[] = [];
@ -212,8 +206,8 @@ export class CDPSession extends EventEmitter {
constructor(parentSession: CRSession, sessionId: string) {
super();
this.guid = `cdp-session@${sessionId}`;
this._session = parentSession.createChildSession(sessionId, (method, params) => this.emit(CDPSession.Events.Event, { method, params }));
this._listeners = [eventsHelper.addEventListener(parentSession, 'Target.detachedFromTarget', (event: Protocol.Target.detachedFromTargetPayload) => {
this._session = parentSession.createChildSession(sessionId, (method, params) => this.emit('event', { method, params }));
this._listeners = [parentSession.addManagedListener('Target.detachedFromTarget', (event: Protocol.Target.detachedFromTargetPayload) => {
if (event.sessionId === sessionId)
this._onClose();
})];
@ -235,6 +229,6 @@ export class CDPSession extends EventEmitter {
private _onClose() {
eventsHelper.removeEventListeners(this._listeners);
this._session.dispose();
this.emit(CDPSession.Events.Closed);
this.emit('closed');
}
}

View file

@ -78,9 +78,9 @@ class JSCoverage {
this._scriptIds.clear();
this._scriptSources.clear();
this._eventListeners = [
eventsHelper.addEventListener(this._client, 'Debugger.scriptParsed', this._onScriptParsed.bind(this)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
eventsHelper.addEventListener(this._client, 'Debugger.paused', this._onDebuggerPaused.bind(this)),
this._client.addManagedListener('Debugger.scriptParsed', this._onScriptParsed.bind(this)),
this._client.addManagedListener('Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
this._client.addManagedListener('Debugger.paused', this._onDebuggerPaused.bind(this)),
];
await Promise.all([
this._client.send('Profiler.enable'),
@ -164,8 +164,8 @@ class CSSCoverage {
this._stylesheetURLs.clear();
this._stylesheetSources.clear();
this._eventListeners = [
eventsHelper.addEventListener(this._client, 'CSS.styleSheetAdded', this._onStyleSheet.bind(this)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
this._client.addManagedListener('CSS.styleSheetAdded', this._onStyleSheet.bind(this)),
this._client.addManagedListener('Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
];
await Promise.all([
this._client.send('DOM.enable'),

View file

@ -60,25 +60,25 @@ export class CRNetworkManager {
async addSession(session: CRSession, workerFrame?: frames.Frame, isMain?: boolean) {
const sessionInfo: SessionInfo = { session, isMain, workerFrame, eventListeners: [] };
sessionInfo.eventListeners = [
eventsHelper.addEventListener(session, 'Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Fetch.authRequired', this._onAuthRequired.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)),
eventsHelper.addEventListener(session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)),
eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)),
eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo)),
session.addManagedListener('Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)),
session.addManagedListener('Fetch.authRequired', this._onAuthRequired.bind(this, sessionInfo)),
session.addManagedListener('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)),
session.addManagedListener('Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)),
session.addManagedListener('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)),
session.addManagedListener('Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)),
session.addManagedListener('Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)),
session.addManagedListener('Network.loadingFinished', this._onLoadingFinished.bind(this, sessionInfo)),
session.addManagedListener('Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo)),
];
if (this._page) {
sessionInfo.eventListeners.push(...[
eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page!._frameManager.onWebSocketCreated(e.requestId, e.url)),
eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!._frameManager.onWebSocketRequest(e.requestId)),
eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page!._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page!._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page!._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)),
eventsHelper.addEventListener(session, 'Network.webSocketClosed', e => this._page!._frameManager.webSocketClosed(e.requestId)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameError', e => this._page!._frameManager.webSocketError(e.requestId, e.errorMessage)),
session.addManagedListener('Network.webSocketCreated', e => this._page!._frameManager.onWebSocketCreated(e.requestId, e.url)),
session.addManagedListener('Network.webSocketWillSendHandshakeRequest', e => this._page!._frameManager.onWebSocketRequest(e.requestId)),
session.addManagedListener('Network.webSocketHandshakeResponseReceived', e => this._page!._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)),
session.addManagedListener('Network.webSocketFrameSent', e => e.response.payloadData && this._page!._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)),
session.addManagedListener('Network.webSocketFrameReceived', e => e.response.payloadData && this._page!._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)),
session.addManagedListener('Network.webSocketClosed', e => this._page!._frameManager.webSocketClosed(e.requestId)),
session.addManagedListener('Network.webSocketFrameError', e => this._page!._frameManager.webSocketError(e.requestId, e.errorMessage)),
]);
}
this._sessions.set(session, sessionInfo);

View file

@ -32,7 +32,7 @@ import type { Progress } from '../progress';
import type * as types from '../types';
import type * as channels from '@protocol/channels';
import { getAccessibilityTree } from './crAccessibility';
import { CRBrowserContext } from './crBrowser';
import type { CRBrowserContext } from './crBrowser';
import type { CRSession } from './crConnection';
import { CRCoverage } from './crCoverage';
import { DragManager } from './crDragDrop';
@ -44,7 +44,6 @@ import { exceptionToError, releaseObject, toConsoleMessageLocation } from './crP
import { platformToFontFamilies } from './defaultFontFamilies';
import type { Protocol } from './protocol';
import { VideoRecorder } from './videoRecorder';
import { BrowserContext } from '../browserContext';
import { TargetClosedError } from '../errors';
import { isSessionClosedError } from '../protocolError';
@ -131,7 +130,7 @@ export class CRPage implements PageDelegate {
}
private _reportAsNew(error?: Error) {
this._page.reportAsNew(error, this._isBackgroundPage ? CRBrowserContext.CREvents.BackgroundPage : BrowserContext.Events.Page);
this._page.reportAsNew(error, this._isBackgroundPage ? 'backgroundpage' : 'page');
}
private async _forAllFrameSessions(cb: (frame: FrameSession) => Promise<any>) {
@ -444,30 +443,30 @@ class FrameSession {
private _addRendererListeners() {
this._eventListeners.push(...[
eventsHelper.addEventListener(this._client, 'Log.entryAdded', event => this._onLogEntryAdded(event)),
eventsHelper.addEventListener(this._client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
eventsHelper.addEventListener(this._client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
eventsHelper.addEventListener(this._client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId, event.reason)),
eventsHelper.addEventListener(this._client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
eventsHelper.addEventListener(this._client, 'Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
eventsHelper.addEventListener(this._client, 'Page.javascriptDialogOpening', event => this._onDialog(event)),
eventsHelper.addEventListener(this._client, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
eventsHelper.addEventListener(this._client, 'Runtime.bindingCalled', event => this._onBindingCalled(event)),
eventsHelper.addEventListener(this._client, 'Runtime.consoleAPICalled', event => this._onConsoleAPI(event)),
eventsHelper.addEventListener(this._client, 'Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()),
eventsHelper.addEventListener(this._client, 'Target.attachedToTarget', event => this._onAttachedToTarget(event)),
eventsHelper.addEventListener(this._client, 'Target.detachedFromTarget', event => this._onDetachedFromTarget(event)),
this._client.addManagedListener('Log.entryAdded', event => this._onLogEntryAdded(event)),
this._client.addManagedListener('Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
this._client.addManagedListener('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
this._client.addManagedListener('Page.frameDetached', event => this._onFrameDetached(event.frameId, event.reason)),
this._client.addManagedListener('Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
this._client.addManagedListener('Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
this._client.addManagedListener('Page.javascriptDialogOpening', event => this._onDialog(event)),
this._client.addManagedListener('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
this._client.addManagedListener('Runtime.bindingCalled', event => this._onBindingCalled(event)),
this._client.addManagedListener('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)),
this._client.addManagedListener('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)),
this._client.addManagedListener('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
this._client.addManagedListener('Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)),
this._client.addManagedListener('Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()),
this._client.addManagedListener('Target.attachedToTarget', event => this._onAttachedToTarget(event)),
this._client.addManagedListener('Target.detachedFromTarget', event => this._onDetachedFromTarget(event)),
]);
}
private _addBrowserListeners() {
this._eventListeners.push(...[
eventsHelper.addEventListener(this._client, 'Inspector.targetCrashed', event => this._onTargetCrashed()),
eventsHelper.addEventListener(this._client, 'Page.screencastFrame', event => this._onScreencastFrame(event)),
eventsHelper.addEventListener(this._client, 'Page.windowOpen', event => this._onWindowOpen(event)),
this._client.addManagedListener('Inspector.targetCrashed', event => this._onTargetCrashed()),
this._client.addManagedListener('Page.screencastFrame', event => this._onScreencastFrame(event)),
this._client.addManagedListener('Page.windowOpen', event => this._onWindowOpen(event)),
]);
}
@ -531,11 +530,11 @@ class FrameSession {
// hence we are going to get more lifecycle updates after the actual navigation has
// started (even if the target url is about:blank).
lifecycleEventsEnabled.catch(e => {}).then(() => {
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
this._eventListeners.push(this._client.addManagedListener('Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
});
} else {
this._firstNonInitialNavigationCommittedFulfill();
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
this._eventListeners.push(this._client.addManagedListener('Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
}
}),
this._client.send('Log.enable', {}),
@ -770,7 +769,7 @@ class FrameSession {
const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o));
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
});
session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, exceptionToError(exception.exceptionDetails), this._page));
session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized('pageerror', exceptionToError(exception.exceptionDetails), this._page));
}
_onDetachedFromTarget(event: Protocol.Target.detachedFromTargetPayload) {
@ -865,7 +864,7 @@ class FrameSession {
_onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) {
if (!this._page._frameManager.frame(this._targetId))
return; // Our frame/subtree may be gone already.
this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog(
this._page.emitOnContext('dialog', new dialog.Dialog(
this._page,
event.type,
event.message,
@ -876,7 +875,7 @@ class FrameSession {
}
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, exceptionToError(exceptionDetails), this._page);
this._page.emitOnContextOnceInitialized('pageerror', exceptionToError(exceptionDetails), this._page);
}
async _onTargetCrashed() {
@ -929,7 +928,7 @@ class FrameSession {
this._client.send('Page.screencastFrameAck', { sessionId: payload.sessionId }).catch(() => {});
});
const buffer = Buffer.from(payload.data, 'base64');
this._page.emit(Page.Events.ScreencastFrame, {
this._page.emit('screencastframe', {
buffer,
timestamp: payload.metadata.timestamp,
width: payload.metadata.deviceWidth,
@ -947,7 +946,7 @@ class FrameSession {
async _startVideoRecording(options: types.PageScreencastOptions) {
const screencastId = this._screencastId;
assert(screencastId);
this._page.once(Page.Events.Close, () => this._stopVideoRecording().catch(() => {}));
this._page.once('close', () => this._stopVideoRecording().catch(() => {}));
const gotFirstFrame = new Promise(f => this._client.once('Page.screencastFrame', f));
await this._startScreencast(this._videoRecorder, {
format: 'jpeg',

View file

@ -19,7 +19,6 @@ import type { CRSession } from './crConnection';
import { CRExecutionContext } from './crExecutionContext';
import { CRNetworkManager } from './crNetworkManager';
import * as network from '../network';
import { BrowserContext } from '../browserContext';
export class CRServiceWorker extends Worker {
readonly _browserContext: CRBrowserContext;
@ -87,19 +86,19 @@ export class CRServiceWorker extends Worker {
}
reportRequestFinished(request: network.Request, response: network.Response | null) {
this._browserContext.emit(BrowserContext.Events.RequestFinished, { request, response });
this._browserContext.emit('requestfinished', { request, response });
}
requestFailed(request: network.Request, _canceled: boolean) {
this._browserContext.emit(BrowserContext.Events.RequestFailed, request);
this._browserContext.emit('requestfailed', request);
}
requestReceivedResponse(response: network.Response) {
this._browserContext.emit(BrowserContext.Events.Response, response);
this._browserContext.emit('response', response);
}
requestStarted(request: network.Request, route?: network.RouteDelegate) {
this._browserContext.emit(BrowserContext.Events.Request, request);
this._browserContext.emit('request', request);
if (route) {
const r = new network.Route(request, route);
if (this._browserContext._requestInterceptor?.(r, request))

View file

@ -16,7 +16,7 @@
import type { ChildProcess } from 'child_process';
import { assert, monotonicTime } from '../../utils';
import { Page } from '../page';
import type { Page } from '../page';
import { launchProcess } from '../../utils/processLauncher';
import type { Progress } from '../progress';
import { ProgressController } from '../progress';
@ -53,7 +53,7 @@ export class VideoRecorder {
private constructor(page: Page, ffmpegPath: string, progress: Progress) {
this._progress = progress;
this._ffmpegPath = ffmpegPath;
page.on(Page.Events.ScreencastFrame, frame => this.writeFrame(frame.buffer, frame.timestamp));
page.on('screencastframe', frame => this.writeFrame(frame.buffer, frame.timestamp!));
}
private async _launch(options: types.PageScreencastOptions) {

View file

@ -28,15 +28,13 @@ import type { Language } from '../utils/isomorphic/locatorGenerators';
const internalMetadata = serverSideCallMetadata();
export class DebugController extends SdkObject {
static Events = {
StateChanged: 'stateChanged',
InspectRequested: 'inspectRequested',
SourceChanged: 'sourceChanged',
Paused: 'paused',
SetModeRequested: 'setModeRequested',
};
export class DebugController extends SdkObject<{
stateChanged: [{ pageCount: number }]
inspectRequested: [{ selector: string, locator: string }]
sourceChanged: [{ text: string, header: string | undefined, footer: string | undefined, actions: string[] | undefined }]
paused: [{ paused: boolean }]
setModeRequested: [{ mode: Mode }]
}> {
private _autoCloseTimer: NodeJS.Timeout | undefined;
// TODO: remove in 1.27
private _autoCloseAllowed = false;
@ -193,7 +191,7 @@ export class DebugController extends SdkObject {
pageCount += context.pages().length;
}
}
this.emit(DebugController.Events.StateChanged, { pageCount });
this.emit('stateChanged', { pageCount });
}
private async _allRecorders(): Promise<Recorder[]> {
@ -226,20 +224,20 @@ class InspectingRecorderApp extends EmptyRecorderApp {
override async setSelector(selector: string): Promise<void> {
const locator: string = asLocator(this._debugController._sdkLanguage, selector);
this._debugController.emit(DebugController.Events.InspectRequested, { selector, locator });
this._debugController.emit('inspectRequested', { selector, locator });
}
override async setSources(sources: Source[]): Promise<void> {
const source = sources.find(s => s.id === this._debugController._codegenId);
const { text, header, footer, actions } = source || { text: '' };
this._debugController.emit(DebugController.Events.SourceChanged, { text, header, footer, actions });
this._debugController.emit('sourceChanged', { text, header, footer, actions });
}
override async setPaused(paused: boolean) {
this._debugController.emit(DebugController.Events.Paused, { paused });
this._debugController.emit('paused', { paused });
}
override async setMode(mode: Mode) {
this._debugController.emit(DebugController.Events.SetModeRequested, { mode });
this._debugController.emit('setModeRequested', { mode });
}
}

View file

@ -16,21 +16,20 @@
import { EventEmitter } from 'events';
import { debugMode, isUnderTest, monotonicTime } from '../utils';
import { BrowserContext } from './browserContext';
import type { BrowserContext } from './browserContext';
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
import { commandsWithTracingSnapshots, pausesBeforeInputActions, slowMoActions } from '../protocol/debug';
const symbol = Symbol('Debugger');
export class Debugger extends EventEmitter implements InstrumentationListener {
export class Debugger extends EventEmitter<{
pausedstatechanged: []
}> implements InstrumentationListener {
private _pauseOnNextStatement = false;
private _pausedCallsMetadata = new Map<CallMetadata, { resolve: () => void, sdkObject: SdkObject }>();
private _enabled: boolean;
private _context: BrowserContext;
static Events = {
PausedStateChanged: 'pausedstatechanged'
};
private _muted = false;
private _slowMo: number | undefined;
@ -42,7 +41,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
if (this._enabled)
this.pauseOnNextStatement();
context.instrumentation.addListener(this, context);
this._context.once(BrowserContext.Events.Close, () => {
this._context.once('close', () => {
this._context.instrumentation.removeListener(this);
});
this._slowMo = this._context._browser.options.slowMo;
@ -83,7 +82,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
const result = new Promise<void>(resolve => {
this._pausedCallsMetadata.set(metadata, { resolve, sdkObject });
});
this.emit(Debugger.Events.PausedStateChanged);
this.emit('pausedstatechanged');
return result;
}
@ -98,7 +97,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
resolve();
}
this._pausedCallsMetadata.clear();
this.emit(Debugger.Events.PausedStateChanged);
this.emit('pausedstatechanged');
}
pauseOnNextStatement() {

View file

@ -17,7 +17,7 @@
import type { RootDispatcher } from './dispatcher';
import { Dispatcher, existingDispatcher } from './dispatcher';
import type { Android, SocketBackend } from '../android/android';
import { AndroidDevice } from '../android/android';
import type { AndroidDevice } from '../android/android';
import type * as channels from '@protocol/channels';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import type { CallMetadata } from '../instrumentation';
@ -56,9 +56,9 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
});
for (const webView of device.webViews())
this._dispatchEvent('webViewAdded', { webView });
this.addObjectListener(AndroidDevice.Events.WebViewAdded, webView => this._dispatchEvent('webViewAdded', { webView }));
this.addObjectListener(AndroidDevice.Events.WebViewRemoved, socketName => this._dispatchEvent('webViewRemoved', { socketName }));
this.addObjectListener(AndroidDevice.Events.Close, socketName => this._dispatchEvent('close'));
this.addObjectListener('webViewAdded', webView => this._dispatchEvent('webViewAdded', { webView }));
this.addObjectListener('webViewRemoved', socketName => this._dispatchEvent('webViewRemoved', { socketName }));
this.addObjectListener('close', () => this._dispatchEvent('close'));
}
async wait(params: channels.AndroidDeviceWaitParams) {

View file

@ -14,14 +14,14 @@
* limitations under the License.
*/
import { BrowserContext } from '../browserContext';
import type { BrowserContext } from '../browserContext';
import { Dispatcher, existingDispatcher } from './dispatcher';
import type { DispatcherScope } from './dispatcher';
import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher';
import type { FrameDispatcher } from './frameDispatcher';
import type * as channels from '@protocol/channels';
import { RouteDispatcher, RequestDispatcher, ResponseDispatcher, APIRequestContextDispatcher } from './networkDispatchers';
import { CRBrowserContext } from '../chromium/crBrowser';
import type { CRBrowserContext } from '../chromium/crBrowser';
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { Recorder } from '../recorder';
import type { CallMetadata } from '../instrumentation';
@ -71,7 +71,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
const artifactDispatcher = ArtifactDispatcher.from(parentScope, artifact);
this._dispatchEvent('video', { artifact: artifactDispatcher });
};
this.addObjectListener(BrowserContext.Events.VideoStarted, onVideo);
this.addObjectListener('videostarted', onVideo);
for (const video of context._browser._idToVideo.values()) {
if (video.context === context)
onVideo(video.artifact);
@ -79,17 +79,17 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
for (const page of context.pages())
this._dispatchEvent('page', { page: PageDispatcher.from(this, page) });
this.addObjectListener(BrowserContext.Events.Page, page => {
this.addObjectListener('page', page => {
this._dispatchEvent('page', { page: PageDispatcher.from(this, page) });
});
this.addObjectListener(BrowserContext.Events.Close, () => {
this.addObjectListener('close', () => {
this._dispatchEvent('close');
this._dispose();
});
this.addObjectListener(BrowserContext.Events.PageError, (error: Error, page: Page) => {
this.addObjectListener('pageerror', (error: Error, page: Page) => {
this._dispatchEvent('pageError', { error: serializeError(error), page: PageDispatcher.from(this, page) });
});
this.addObjectListener(BrowserContext.Events.Console, (message: ConsoleMessage) => {
this.addObjectListener('console', (message: ConsoleMessage) => {
const page = message.page()!;
if (this._shouldDispatchEvent(page, 'console')) {
const pageDispatcher = PageDispatcher.from(this, page);
@ -102,7 +102,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
});
}
});
this.addObjectListener(BrowserContext.Events.Dialog, (dialog: Dialog) => {
this.addObjectListener('dialog', (dialog: Dialog) => {
if (this._shouldDispatchEvent(dialog.page(), 'dialog'))
this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this, dialog) });
else
@ -112,12 +112,12 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
if (context._browser.options.name === 'chromium') {
for (const page of (context as CRBrowserContext).backgroundPages())
this._dispatchEvent('backgroundPage', { page: PageDispatcher.from(this, page) });
this.addObjectListener(CRBrowserContext.CREvents.BackgroundPage, page => this._dispatchEvent('backgroundPage', { page: PageDispatcher.from(this, page) }));
this.addObjectListener('backgroundpage', page => this._dispatchEvent('backgroundPage', { page: PageDispatcher.from(this, page) }));
for (const serviceWorker of (context as CRBrowserContext).serviceWorkers())
this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this, serviceWorker) });
this.addObjectListener(CRBrowserContext.CREvents.ServiceWorker, serviceWorker => this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this, serviceWorker) }));
this.addObjectListener('serviceworker', serviceWorker => this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this, serviceWorker) }));
}
this.addObjectListener(BrowserContext.Events.Request, (request: Request) => {
this.addObjectListener('request', (request: Request) => {
// Create dispatcher, if:
// - There are listeners to the requests.
// - We are redirected from a reported request so that redirectedTo was updated on client.
@ -132,7 +132,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
page: PageDispatcher.fromNullable(this, request.frame()?._page.initializedOrUndefined())
});
});
this.addObjectListener(BrowserContext.Events.Response, (response: Response) => {
this.addObjectListener('response', (response: Response) => {
const requestDispatcher = existingDispatcher<RequestDispatcher>(response.request());
if (!requestDispatcher && !this._shouldDispatchNetworkEvent(response.request(), 'response'))
return;
@ -141,7 +141,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
page: PageDispatcher.fromNullable(this, response.frame()?._page.initializedOrUndefined())
});
});
this.addObjectListener(BrowserContext.Events.RequestFailed, (request: Request) => {
this.addObjectListener('requestfailed', (request: Request) => {
const requestDispatcher = existingDispatcher<RequestDispatcher>(request);
if (!requestDispatcher && !this._shouldDispatchNetworkEvent(request, 'requestFailed'))
return;
@ -152,7 +152,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
page: PageDispatcher.fromNullable(this, request.frame()?._page.initializedOrUndefined())
});
});
this.addObjectListener(BrowserContext.Events.RequestFinished, ({ request, response }: { request: Request, response: Response | null }) => {
this.addObjectListener('requestfinished', ({ request, response }: { request: Request, response: Response | null }) => {
const requestDispatcher = existingDispatcher<RequestDispatcher>(request);
if (!requestDispatcher && !this._shouldDispatchNetworkEvent(request, 'requestFinished'))
return;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Browser } from '../browser';
import type { Browser } from '../browser';
import type * as channels from '@protocol/channels';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
@ -24,7 +24,7 @@ import { Dispatcher } from './dispatcher';
import type { CRBrowser } from '../chromium/crBrowser';
import type { PageDispatcher } from './pageDispatcher';
import type { CallMetadata } from '../instrumentation';
import { BrowserContext } from '../browserContext';
import type { BrowserContext } from '../browserContext';
import { Selectors } from '../selectors';
import type { BrowserTypeDispatcher } from './browserTypeDispatcher';
import { ArtifactDispatcher } from './artifactDispatcher';
@ -35,8 +35,8 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
constructor(scope: BrowserTypeDispatcher, browser: Browser) {
super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name });
browser.setNeedsBeforeCloseEvent(true);
this.addObjectListener(Browser.Events.BeforeClose, () => this._dispatchEvent('beforeClose'));
this.addObjectListener(Browser.Events.Disconnected, () => this._didClose());
this.addObjectListener('beforeClose', () => this._dispatchEvent('beforeClose'));
this.addObjectListener('disconnected', () => this._didClose());
}
override _onDispose() {
@ -109,7 +109,7 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
constructor(scope: RootDispatcher, browser: Browser) {
super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name });
this.addObjectListener(Browser.Events.BeforeClose, () => this.emit('beforeClose'));
this.addObjectListener('beforeClose', () => this.emit('beforeClose'));
// When we have a remotely-connected browser, each client gets a fresh Selector instance,
// so that two clients do not interfere between each other.
this.selectors = new Selectors();
@ -121,7 +121,7 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
const context = await this._object.newContext(metadata, params);
this._contexts.add(context);
context.setSelectors(this.selectors);
context.on(BrowserContext.Events.Close, () => this._contexts.delete(context));
context.on('close', () => this._contexts.delete(context));
return { context: new BrowserContextDispatcher(this, context) };
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { CDPSession } from '../chromium/crConnection';
import type { CDPSession } from '../chromium/crConnection';
import type * as channels from '@protocol/channels';
import { Dispatcher } from './dispatcher';
import type { BrowserDispatcher } from './browserDispatcher';
@ -26,8 +26,8 @@ export class CDPSessionDispatcher extends Dispatcher<CDPSession, channels.CDPSes
constructor(scope: BrowserDispatcher | BrowserContextDispatcher, cdpSession: CDPSession) {
super(scope, cdpSession, 'CDPSession', {});
this.addObjectListener(CDPSession.Events.Event, ({ method, params }) => this._dispatchEvent('event', { method, params }));
this.addObjectListener(CDPSession.Events.Closed, () => this._dispose());
this.addObjectListener('event', ({ method, params }) => this._dispatchEvent('event', { method, params }));
this.addObjectListener('closed', () => this._dispose());
}
async send(params: channels.CDPSessionSendParams): Promise<channels.CDPSessionSendResult> {

View file

@ -17,7 +17,7 @@
import type * as channels from '@protocol/channels';
import { eventsHelper } from '../../utils';
import type { RegisteredListener } from '../../utils/eventsHelper';
import { DebugController } from '../debugController';
import type { DebugController } from '../debugController';
import type { DispatcherConnection, RootDispatcher } from './dispatcher';
import { Dispatcher } from './dispatcher';
@ -29,19 +29,19 @@ export class DebugControllerDispatcher extends Dispatcher<DebugController, chann
super(connection, debugController, 'DebugController', {});
this._type_DebugController = true;
this._listeners = [
eventsHelper.addEventListener(this._object, DebugController.Events.StateChanged, params => {
this._object.addManagedListener('stateChanged', params => {
this._dispatchEvent('stateChanged', params);
}),
eventsHelper.addEventListener(this._object, DebugController.Events.InspectRequested, ({ selector, locator }) => {
this._object.addManagedListener('inspectRequested', ({ selector, locator }) => {
this._dispatchEvent('inspectRequested', { selector, locator });
}),
eventsHelper.addEventListener(this._object, DebugController.Events.SourceChanged, ({ text, header, footer, actions }) => {
this._object.addManagedListener('sourceChanged', ({ text, header, footer, actions }) => {
this._dispatchEvent('sourceChanged', ({ text, header, footer, actions }));
}),
eventsHelper.addEventListener(this._object, DebugController.Events.Paused, ({ paused }) => {
this._object.addManagedListener('paused', ({ paused }) => {
this._dispatchEvent('paused', ({ paused }));
}),
eventsHelper.addEventListener(this._object, DebugController.Events.SetModeRequested, ({ mode }) => {
this._object.addManagedListener('setModeRequested', ({ mode }) => {
this._dispatchEvent('setModeRequested', ({ mode }));
}),
];

View file

@ -23,7 +23,7 @@ import type { CallMetadata } from '../instrumentation';
import { SdkObject } from '../instrumentation';
import type { PlaywrightDispatcher } from './playwrightDispatcher';
import { eventsHelper } from '../..//utils/eventsHelper';
import type { RegisteredListener } from '../..//utils/eventsHelper';
import type { ManagedEventEmitter, RegisteredListener } from '../..//utils/eventsHelper';
import { isProtocolError } from '../protocolError';
export const dispatcherSymbol = Symbol('dispatcher');
@ -44,7 +44,7 @@ function maxDispatchersForBucket(gcBucket: string) {
}[gcBucket] ?? 10000;
}
export class Dispatcher<Type extends { guid: string }, ChannelType, ParentScopeType extends DispatcherScope> extends EventEmitter implements channels.Channel {
export class Dispatcher<Type extends (ManagedEventEmitter<any> & { guid: string }) | { guid: string; _events?: void }, ChannelType, ParentScopeType extends DispatcherScope> extends EventEmitter implements channels.Channel {
private _connection: DispatcherConnection;
// Parent is always "isScope".
private _parent: ParentScopeType | undefined;
@ -88,8 +88,12 @@ export class Dispatcher<Type extends { guid: string }, ChannelType, ParentScopeT
return this._parent!;
}
addObjectListener(eventName: (string | symbol), handler: (...args: any[]) => void) {
this._eventListeners.push(eventsHelper.addEventListener(this._object as unknown as EventEmitter, eventName, handler));
addObjectListener<K extends keyof Type['_events']>(
eventName: K,
handler: (...args: Type['_events'][K]) => void
) {
// @ts-ignore
this._eventListeners.push(eventsHelper.addEventListener(this._object, eventName, handler));
}
adopt(child: DispatcherScope) {

View file

@ -17,7 +17,7 @@
import type { RootDispatcher } from './dispatcher';
import { Dispatcher } from './dispatcher';
import type { Electron } from '../electron/electron';
import { ElectronApplication } from '../electron/electron';
import type { ElectronApplication } from '../electron/electron';
import type * as channels from '@protocol/channels';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import type { PageDispatcher } from './pageDispatcher';
@ -47,11 +47,11 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
super(scope, electronApplication, 'ElectronApplication', {
context: new BrowserContextDispatcher(scope, electronApplication.context())
});
this.addObjectListener(ElectronApplication.Events.Close, () => {
this.addObjectListener('close', () => {
this._dispatchEvent('close');
this._dispose();
});
this.addObjectListener(ElectronApplication.Events.Console, (message: ConsoleMessage) => {
this.addObjectListener('console', (message: ConsoleMessage) => {
if (!this._subscriptions.has('console'))
return;
this._dispatchEvent('console', {

View file

@ -15,7 +15,7 @@
*/
import type { NavigationEvent } from '../frames';
import { Frame } from '../frames';
import type { Frame } from '../frames';
import type * as channels from '@protocol/channels';
import { Dispatcher, existingDispatcher } from './dispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
@ -58,13 +58,13 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
}, gcBucket);
this._browserContextDispatcher = scope;
this._frame = frame;
this.addObjectListener(Frame.Events.AddLifecycle, lifecycleEvent => {
this.addObjectListener('addlifecycle', lifecycleEvent => {
this._dispatchEvent('loadstate', { add: lifecycleEvent });
});
this.addObjectListener(Frame.Events.RemoveLifecycle, lifecycleEvent => {
this.addObjectListener('removelifecycle', lifecycleEvent => {
this._dispatchEvent('loadstate', { remove: lifecycleEvent });
});
this.addObjectListener(Frame.Events.InternalNavigation, (event: NavigationEvent) => {
this.addObjectListener('internalnavigation', (event: NavigationEvent) => {
if (!event.isPublic)
return;
const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined };

View file

@ -18,7 +18,7 @@ import type * as channels from '@protocol/channels';
import type { APIRequestContext } from '../fetch';
import type { CallMetadata } from '../instrumentation';
import type { Request, Response, Route } from '../network';
import { WebSocket } from '../network';
import type { WebSocket } from '../network';
import type { RootDispatcher } from './dispatcher';
import { Dispatcher, existingDispatcher } from './dispatcher';
import { TracingDispatcher } from './tracingDispatcher';
@ -164,10 +164,10 @@ export class WebSocketDispatcher extends Dispatcher<WebSocket, channels.WebSocke
super(scope, webSocket, 'WebSocket', {
url: webSocket.url(),
});
this.addObjectListener(WebSocket.Events.FrameSent, (event: { opcode: number, data: string }) => this._dispatchEvent('frameSent', event));
this.addObjectListener(WebSocket.Events.FrameReceived, (event: { opcode: number, data: string }) => this._dispatchEvent('frameReceived', event));
this.addObjectListener(WebSocket.Events.SocketError, (error: string) => this._dispatchEvent('socketError', { error }));
this.addObjectListener(WebSocket.Events.Close, () => this._dispatchEvent('close', {}));
this.addObjectListener('framesent', (event: { opcode: number, data: string }) => this._dispatchEvent('frameSent', event));
this.addObjectListener('framereceived', (event: { opcode: number, data: string }) => this._dispatchEvent('frameReceived', event));
this.addObjectListener('socketerror', (error: string) => this._dispatchEvent('socketError', { error }));
this.addObjectListener('close', () => this._dispatchEvent('close', {}));
}
}

View file

@ -16,7 +16,8 @@
import type { BrowserContext } from '../browserContext';
import type { Frame } from '../frames';
import { Page, Worker } from '../page';
import type { Worker } from '../page';
import type { Page } from '../page';
import type * as channels from '@protocol/channels';
import { Dispatcher, existingDispatcher } from './dispatcher';
import { parseError } from '../errors';
@ -70,25 +71,25 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
this.adopt(mainFrame);
this._page = page;
this.addObjectListener(Page.Events.Close, () => {
this.addObjectListener('close', () => {
this._dispatchEvent('close');
this._dispose();
});
this.addObjectListener(Page.Events.Crash, () => this._dispatchEvent('crash'));
this.addObjectListener(Page.Events.Download, (download: Download) => {
this.addObjectListener('crash', () => this._dispatchEvent('crash'));
this.addObjectListener('download', (download: Download) => {
// Artifact can outlive the page, so bind to the context scope.
this._dispatchEvent('download', { url: download.url, suggestedFilename: download.suggestedFilename(), artifact: ArtifactDispatcher.from(parentScope, download.artifact) });
});
this.addObjectListener(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
this.addObjectListener('filechooser', (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
element: ElementHandleDispatcher.from(mainFrame, fileChooser.element()),
isMultiple: fileChooser.isMultiple()
}));
this.addObjectListener(Page.Events.FrameAttached, frame => this._onFrameAttached(frame));
this.addObjectListener(Page.Events.FrameDetached, frame => this._onFrameDetached(frame));
this.addObjectListener(Page.Events.LocatorHandlerTriggered, (uid: number) => this._dispatchEvent('locatorHandlerTriggered', { uid }));
this.addObjectListener(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this, webSocket) }));
this.addObjectListener(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this, worker) }));
this.addObjectListener(Page.Events.Video, (artifact: Artifact) => this._dispatchEvent('video', { artifact: ArtifactDispatcher.from(parentScope, artifact) }));
this.addObjectListener('frameattached', frame => this._onFrameAttached(frame));
this.addObjectListener('framedetached', frame => this._onFrameDetached(frame));
this.addObjectListener('locatorhandlertriggered', (uid: number) => this._dispatchEvent('locatorHandlerTriggered', { uid }));
this.addObjectListener('websocket', webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this, webSocket) }));
this.addObjectListener('worker', worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this, worker) }));
this.addObjectListener('video', (artifact: Artifact) => this._dispatchEvent('video', { artifact: ArtifactDispatcher.from(parentScope, artifact) }));
if (page._video)
this._dispatchEvent('video', { artifact: ArtifactDispatcher.from(this.parentScope(), page._video) });
// Ensure client knows about all frames.
@ -338,7 +339,7 @@ export class WorkerDispatcher extends Dispatcher<Worker, channels.WorkerChannel,
super(scope, worker, 'Worker', {
url: worker.url()
});
this.addObjectListener(Worker.Events.Close, () => this._dispatchEvent('close'));
this.addObjectListener('close', () => this._dispatchEvent('close'));
}
async evaluateExpression(params: channels.WorkerEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.WorkerEvaluateExpressionResult> {

View file

@ -19,7 +19,7 @@ import type { Browser } from '../browser';
import { GlobalAPIRequestContext } from '../fetch';
import type { Playwright } from '../playwright';
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../../common/socksProxy';
import { SocksProxy } from '../../common/socksProxy';
import type { SocksProxy } from '../../common/socksProxy';
import { AndroidDispatcher } from './androidDispatcher';
import { BrowserTypeDispatcher } from './browserTypeDispatcher';
import type { RootDispatcher } from './dispatcher';
@ -79,9 +79,9 @@ class SocksSupportDispatcher extends Dispatcher<{ guid: string }, channels.Socks
this._type_SocksSupport = true;
this._socksProxy = socksProxy;
this._socksListeners = [
eventsHelper.addEventListener(socksProxy, SocksProxy.Events.SocksRequested, (payload: SocksSocketRequestedPayload) => this._dispatchEvent('socksRequested', payload)),
eventsHelper.addEventListener(socksProxy, SocksProxy.Events.SocksData, (payload: SocksSocketDataPayload) => this._dispatchEvent('socksData', payload)),
eventsHelper.addEventListener(socksProxy, SocksProxy.Events.SocksClosed, (payload: SocksSocketClosedPayload) => this._dispatchEvent('socksClosed', payload)),
socksProxy.addManagedListener('socksRequested', (payload: SocksSocketRequestedPayload) => this._dispatchEvent('socksRequested', payload)),
socksProxy.addManagedListener('socksData', (payload: SocksSocketDataPayload) => this._dispatchEvent('socksData', payload)),
socksProxy.addManagedListener('socksClosed', (payload: SocksSocketClosedPayload) => this._dispatchEvent('socksClosed', payload)),
];
}

View file

@ -15,7 +15,7 @@
*/
import path from 'path';
import { Page } from './page';
import type { Page } from './page';
import { assert } from '../utils';
import { Artifact } from './artifact';
@ -35,13 +35,13 @@ export class Download {
this._suggestedFilename = suggestedFilename;
page._browserContext._downloads.add(this);
if (suggestedFilename !== undefined)
this._page.emit(Page.Events.Download, this);
this._page.emit('download', this);
}
_filenameSuggested(suggestedFilename: string) {
assert(this._suggestedFilename === undefined);
this._suggestedFilename = suggestedFilename;
this._page.emit(Page.Events.Download, this);
this._page.emit('download', this);
}
suggestedFilename(): string {

View file

@ -49,12 +49,10 @@ import { ConsoleMessage } from '../console';
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
export class ElectronApplication extends SdkObject {
static Events = {
Close: 'close',
Console: 'console',
};
export class ElectronApplication extends SdkObject<{
close: [],
console: [ConsoleMessage]
}> {
private _browserContext: CRBrowserContext;
private _nodeConnection: CRConnection;
private _nodeSession: CRSession;
@ -83,7 +81,7 @@ export class ElectronApplication extends SdkObject {
this._nodeElectronHandlePromise.resolve(new js.JSHandle(this._nodeExecutionContext!, 'object', 'ElectronModule', remoteObject.objectId!));
});
this._nodeSession.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
const appClosePromise = new Promise(f => this.once(ElectronApplication.Events.Close, f));
const appClosePromise = new Promise<void>(f => this.once('close', f));
this._browserContext.setCustomCloseHandler(async () => {
await this._browserContext.stopVideoRecording();
const electronHandle = await this._nodeElectronHandlePromise;
@ -114,7 +112,7 @@ export class ElectronApplication extends SdkObject {
return;
const args = event.args.map(arg => this._nodeExecutionContext!.createHandle(arg));
const message = new ConsoleMessage(null, event.type, undefined, args, toConsoleMessageLocation(event.stackTrace));
this.emit(ElectronApplication.Events.Console, message);
this.emit('console', message);
}
async initialize() {
@ -226,7 +224,7 @@ export class Electron extends SdkObject {
handleSIGINT: true,
handleSIGTERM: true,
handleSIGHUP: true,
onExit: () => app?.emit(ElectronApplication.Events.Close),
onExit: () => app?.emit('close'),
});
// All waitForLines must be started immediately.

View file

@ -28,7 +28,8 @@ import { TimeoutSettings } from '../common/timeoutSettings';
import { getUserAgent } from '../utils/userAgent';
import { assert, createGuid, monotonicTime } from '../utils';
import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle';
import { BrowserContext, verifyClientCertificates } from './browserContext';
import type { BrowserContext } from './browserContext';
import { verifyClientCertificates } from './browserContext';
import { CookieStore, domainMatches } from './cookieStore';
import { MultipartFormData } from './formData';
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '../utils/happy-eyeballs';
@ -82,14 +83,11 @@ type SendRequestOptions = https.RequestOptions & {
__testHookLookup?: (hostname: string) => LookupAddress[]
};
export abstract class APIRequestContext extends SdkObject {
static Events = {
Dispose: 'dispose',
Request: 'request',
RequestFinished: 'requestfinished',
};
export abstract class APIRequestContext extends SdkObject<{
dispose: []
request: [APIRequestEvent]
requestfinished: [APIRequestFinishedEvent]
}> {
readonly fetchResponses: Map<string, Buffer> = new Map();
readonly fetchLog: Map<string, string[]> = new Map();
protected static allInstances: Set<APIRequestContext> = new Set();
@ -114,7 +112,7 @@ export abstract class APIRequestContext extends SdkObject {
APIRequestContext.allInstances.delete(this);
this.fetchResponses.clear();
this.fetchLog.clear();
this.emit(APIRequestContext.Events.Dispose);
this.emit('dispose');
}
disposeResponse(fetchUid: string) {
@ -289,7 +287,7 @@ export abstract class APIRequestContext extends SdkObject {
cookies: requestCookies,
postData
};
this.emit(APIRequestContext.Events.Request, requestEvent);
this.emit('request', requestEvent);
return new Promise((fulfill, reject) => {
const requestConstructor: ((url: URL, options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void) => http.ClientRequest)
@ -309,7 +307,7 @@ export abstract class APIRequestContext extends SdkObject {
cookies,
body
};
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
this.emit('requestfinished', requestFinishedEvent);
};
progress.log(`${response.statusCode} ${response.statusMessage}`);
for (const [name, value] of Object.entries(response.headers))
@ -447,8 +445,8 @@ export abstract class APIRequestContext extends SdkObject {
reject(new Error('Request context disposed.'));
request.destroy();
};
this.on(APIRequestContext.Events.Dispose, disposeListener);
request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
this.on('dispose', disposeListener);
request.on('close', () => this.off('dispose', disposeListener));
progress.log(`${options.method} ${url.toString()}`);
if (options.headers) {
@ -508,7 +506,7 @@ export class BrowserContextAPIRequestContext extends APIRequestContext {
constructor(context: BrowserContext) {
super(context);
this._context = context;
context.once(BrowserContext.Events.Close, () => this._disposeImpl());
context.once('close', () => this._disposeImpl());
}
override tracing() {

View file

@ -25,7 +25,7 @@ import type { InitScript, Page, PageBinding, PageDelegate } from '../page';
import type { ConnectionTransport } from '../transport';
import type * as types from '../types';
import type * as channels from '@protocol/channels';
import { ConnectionEvents, FFConnection, type FFSession } from './ffConnection';
import { FFConnection, type FFSession } from './ffConnection';
import { FFPage } from './ffPage';
import type { Protocol } from './protocol';
import type { SdkObject } from '../instrumentation';
@ -69,7 +69,7 @@ export class FFBrowser extends Browser {
this.session = connection.rootSession;
this._ffPages = new Map();
this._contexts = new Map();
this._connection.on(ConnectionEvents.Disconnected, () => this._onDisconnect());
this._connection.on('disconnected', () => this._onDisconnect());
this.session.on('Browser.attachedToTarget', this._onAttachedToTarget.bind(this));
this.session.on('Browser.detachedFromTarget', this._onDetachedFromTarget.bind(this));
this.session.on('Browser.downloadCreated', this._onDownloadCreated.bind(this));

View file

@ -15,7 +15,6 @@
* limitations under the License.
*/
import { EventEmitter } from 'events';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { Protocol } from './protocol';
import type { RecentLogsCollector } from '../../utils/debugLogger';
@ -23,16 +22,15 @@ import { debugLogger } from '../../utils/debugLogger';
import type { ProtocolLogger } from '../types';
import { helper } from '../helper';
import { ProtocolError } from '../protocolError';
export const ConnectionEvents = {
Disconnected: Symbol('Disconnected'),
};
import { ManagedEventEmitter } from '../../utils';
// FFPlaywright uses this special id to issue Browser.close command which we
// should ignore.
export const kBrowserCloseMessageId = -9999;
export class FFConnection extends EventEmitter {
export class FFConnection extends ManagedEventEmitter<{ [K in keyof Protocol.Events]: [Protocol.Events[K]] } & {
'disconnected': [],
}> {
private _lastId: number;
private _transport: ConnectionTransport;
private readonly _protocolLogger: ProtocolLogger;
@ -83,7 +81,7 @@ export class FFConnection extends EventEmitter {
this._transport.onclose = undefined;
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs(), reason);
this.rootSession.dispose();
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
Promise.resolve().then(() => this.emit('disconnected'));
}
close() {
@ -98,18 +96,13 @@ export class FFConnection extends EventEmitter {
}
}
export class FFSession extends EventEmitter {
export class FFSession extends ManagedEventEmitter<{ [K in keyof Protocol.Events]: [Protocol.Events[K]] }> {
_connection: FFConnection;
_disposed = false;
private _callbacks: Map<number, { resolve: Function, reject: Function, error: ProtocolError }>;
private _sessionId: string;
private _rawSend: (message: any) => void;
private _crashed: boolean = false;
override on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(connection: FFConnection, sessionId: string, rawSend: (message: any) => void) {
super();
@ -161,6 +154,7 @@ export class FFSession extends EventEmitter {
}
}
} else {
// @ts-expect-error
Promise.resolve().then(() => this.emit(object.method!, object.params));
}
}

View file

@ -38,10 +38,10 @@ export class FFNetworkManager {
this._page = page;
this._eventListeners = [
eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)),
eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)),
eventsHelper.addEventListener(session, 'Network.requestFinished', this._onRequestFinished.bind(this)),
eventsHelper.addEventListener(session, 'Network.requestFailed', this._onRequestFailed.bind(this)),
session.addManagedListener('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)),
session.addManagedListener('Network.responseReceived', this._onResponseReceived.bind(this)),
session.addManagedListener('Network.requestFinished', this._onRequestFinished.bind(this)),
session.addManagedListener('Network.requestFailed', this._onRequestFailed.bind(this)),
];
}

View file

@ -35,7 +35,6 @@ import type { Progress } from '../progress';
import { splitErrorMessage } from '../../utils/stackTrace';
import { debugLogger } from '../../utils/debugLogger';
import { ManualPromise } from '../../utils/manualPromise';
import { BrowserContext } from '../browserContext';
import { TargetClosedError } from '../errors';
export const UTILITY_WORLD_NAME = '__playwright_utility_world__';
@ -70,36 +69,36 @@ export class FFPage implements PageDelegate {
this._page = new Page(this, browserContext);
this.rawMouse.setPage(this._page);
this._networkManager = new FFNetworkManager(session, this._page);
this._page.on(Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame));
this._page.on('framedetached', frame => this._removeContextsForFrame(frame));
// TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
this._eventListeners = [
eventsHelper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
eventsHelper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)),
eventsHelper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)),
eventsHelper.addEventListener(this._session, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.linkClicked', event => this._onLinkClicked(event.phase)),
eventsHelper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)),
eventsHelper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', this._onFileChooserOpened.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.workerCreated', this._onWorkerCreated.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.workerDestroyed', this._onWorkerDestroyed.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.crashed', this._onCrashed.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.videoRecordingStarted', this._onVideoRecordingStarted.bind(this)),
this._session.addManagedListener('Page.eventFired', this._onEventFired.bind(this)),
this._session.addManagedListener('Page.frameAttached', this._onFrameAttached.bind(this)),
this._session.addManagedListener('Page.frameDetached', this._onFrameDetached.bind(this)),
this._session.addManagedListener('Page.navigationAborted', this._onNavigationAborted.bind(this)),
this._session.addManagedListener('Page.navigationCommitted', this._onNavigationCommitted.bind(this)),
this._session.addManagedListener('Page.navigationStarted', this._onNavigationStarted.bind(this)),
this._session.addManagedListener('Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
this._session.addManagedListener('Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)),
this._session.addManagedListener('Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)),
this._session.addManagedListener('Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)),
this._session.addManagedListener('Page.linkClicked', event => this._onLinkClicked(event.phase)),
this._session.addManagedListener('Page.uncaughtError', this._onUncaughtError.bind(this)),
this._session.addManagedListener('Runtime.console', this._onConsole.bind(this)),
this._session.addManagedListener('Page.dialogOpened', this._onDialogOpened.bind(this)),
this._session.addManagedListener('Page.bindingCalled', this._onBindingCalled.bind(this)),
this._session.addManagedListener('Page.fileChooserOpened', this._onFileChooserOpened.bind(this)),
this._session.addManagedListener('Page.workerCreated', this._onWorkerCreated.bind(this)),
this._session.addManagedListener('Page.workerDestroyed', this._onWorkerDestroyed.bind(this)),
this._session.addManagedListener('Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)),
this._session.addManagedListener('Page.crashed', this._onCrashed.bind(this)),
this._session.addManagedListener('Page.videoRecordingStarted', this._onVideoRecordingStarted.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketCreated', this._onWebSocketCreated.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketClosed', this._onWebSocketClosed.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this)),
this._session.addManagedListener('Page.webSocketCreated', this._onWebSocketCreated.bind(this)),
this._session.addManagedListener('Page.webSocketClosed', this._onWebSocketClosed.bind(this)),
this._session.addManagedListener('Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)),
this._session.addManagedListener('Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)),
this._session.addManagedListener('Page.screencastFrame', this._onScreencastFrame.bind(this)),
];
this._session.once('Page.ready', async () => {
@ -244,7 +243,7 @@ export class FFPage implements PageDelegate {
const error = new Error(message);
error.stack = params.message + '\n' + params.stack.split('\n').filter(Boolean).map(a => a.replace(/([^@]*)@(.*)/, ' at $1 ($2)')).join('\n');
error.name = name;
this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, error, this._page);
this._page.emitOnContextOnceInitialized('pageerror', error, this._page);
}
_onConsole(payload: Protocol.Runtime.consolePayload) {
@ -257,7 +256,7 @@ export class FFPage implements PageDelegate {
}
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog(
this._page.emitOnContext('dialog', new dialog.Dialog(
this._page,
params.type,
params.message,
@ -519,7 +518,7 @@ export class FFPage implements PageDelegate {
});
const buffer = Buffer.from(event.data, 'base64');
this._page.emit(Page.Events.ScreencastFrame, {
this._page.emit('screencastframe', {
buffer,
width: event.deviceWidth,
height: event.deviceHeight,

View file

@ -24,9 +24,9 @@ import { eventsHelper } from '../utils/eventsHelper';
import * as js from './javascript';
import * as network from './network';
import type { Dialog } from './dialog';
import { Page } from './page';
import type { Page } from './page';
import * as types from './types';
import { BrowserContext } from './browserContext';
import type { BrowserContext } from './browserContext';
import type { Progress } from './progress';
import { ProgressController } from './progress';
import { LongStandingScope, assert, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime } from '../utils';
@ -156,7 +156,7 @@ export class FrameManager {
assert(!this._frames.has(frameId));
const frame = new Frame(this._page, frameId, parentFrame);
this._frames.set(frameId, frame);
this._page.emit(Page.Events.FrameAttached, frame);
this._page.emit('frameattached', frame);
return frame;
}
}
@ -297,7 +297,7 @@ export class FrameManager {
route.continue(request, { isFallback: true }).catch(() => {});
return;
}
this._page.emitOnContext(BrowserContext.Events.Request, request);
this._page.emitOnContext('request', request);
if (route) {
const r = new network.Route(request, route);
if (this._page._serverRequestInterceptor?.(r, request))
@ -313,14 +313,14 @@ export class FrameManager {
requestReceivedResponse(response: network.Response) {
if (response.request()._isFavicon)
return;
this._page.emitOnContext(BrowserContext.Events.Response, response);
this._page.emitOnContext('response', response);
}
reportRequestFinished(request: network.Request, response: network.Response | null) {
this._inflightRequestFinished(request);
if (request._isFavicon)
return;
this._page.emitOnContext(BrowserContext.Events.RequestFinished, { request, response });
this._page.emitOnContext('requestfinished', { request, response });
}
requestFailed(request: network.Request, canceled: boolean) {
@ -334,7 +334,7 @@ export class FrameManager {
}
if (request._isFavicon)
return;
this._page.emitOnContext(BrowserContext.Events.RequestFailed, request);
this._page.emitOnContext('requestfailed', request);
}
dialogDidOpen(dialog: Dialog) {
@ -370,7 +370,7 @@ export class FrameManager {
frame._onDetached();
this._frames.delete(frame._id);
if (!this._page.isClosed())
this._page.emit(Page.Events.FrameDetached, frame);
this._page.emit('framedetached', frame);
}
private _inflightRequestFinished(request: network.Request) {
@ -420,7 +420,7 @@ export class FrameManager {
onWebSocketRequest(requestId: string) {
const ws = this._webSockets.get(requestId);
if (ws && ws.markAsNotified())
this._page.emit(Page.Events.WebSocket, ws);
this._page.emit('websocket', ws);
}
onWebSocketResponse(requestId: string, status: number, statusText: string) {
@ -457,17 +457,15 @@ export class FrameManager {
}
private _fireInternalFrameNavigation(frame: Frame, event: NavigationEvent) {
frame.emit(Frame.Events.InternalNavigation, event);
frame.emit('internalnavigation', event);
}
}
export class Frame extends SdkObject {
static Events = {
InternalNavigation: 'internalnavigation',
AddLifecycle: 'addlifecycle',
RemoveLifecycle: 'removelifecycle',
};
export class Frame extends SdkObject<{
internalnavigation: [NavigationEvent]
addlifecycle: [types.LifecycleEvent]
removelifecycle: [types.LifecycleEvent]
}> {
_id: string;
_firedLifecycleEvents = new Set<types.LifecycleEvent>();
private _firedNetworkIdleSelf = false;
@ -517,7 +515,7 @@ export class Frame extends SdkObject {
if (this._firedLifecycleEvents.has(event))
return;
this._firedLifecycleEvents.add(event);
this.emit(Frame.Events.AddLifecycle, event);
this.emit('addlifecycle', event);
if (this === this._page.mainFrame() && this._url !== 'about:blank')
debugLogger.log('api', ` "${event}" event fired`);
this._page.mainFrame()._recalculateNetworkIdle();
@ -525,7 +523,7 @@ export class Frame extends SdkObject {
_onClearLifecycle() {
for (const event of this._firedLifecycleEvents)
this.emit(Frame.Events.RemoveLifecycle, event);
this.emit('removelifecycle', event);
this._firedLifecycleEvents.clear();
// Keep the current navigation request if any.
this._inflightRequests = new Set(Array.from(this._inflightRequests).filter(request => request === this._currentDocument.request));
@ -600,7 +598,7 @@ export class Frame extends SdkObject {
}
if (isNetworkIdle && !this._firedLifecycleEvents.has('networkidle')) {
this._firedLifecycleEvents.add('networkidle');
this.emit(Frame.Events.AddLifecycle, 'networkidle');
this.emit('addlifecycle', 'networkidle');
if (this === this._page.mainFrame() && this._url !== 'about:blank')
debugLogger.log('api', ` "networkidle" event fired`);
}
@ -608,7 +606,7 @@ export class Frame extends SdkObject {
// Usually, networkidle is fired once and not removed after that.
// However, when we clear them right before a new commit, this is allowed for a particular frame.
this._firedLifecycleEvents.delete('networkidle');
this.emit(Frame.Events.RemoveLifecycle, 'networkidle');
this.emit('removelifecycle', 'networkidle');
}
}
@ -661,13 +659,13 @@ export class Frame extends SdkObject {
}
url = helper.completeUserURL(url);
const sameDocument = helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, (e: NavigationEvent) => !e.newDocument);
const sameDocument = helper.waitForEvent(progress, this, 'internalnavigation', (e: NavigationEvent) => !e.newDocument);
const navigateResult = await this._page._delegate.navigateFrame(this, url, referer);
let event: NavigationEvent;
if (navigateResult.newDocumentId) {
sameDocument.dispose();
event = await helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, (event: NavigationEvent) => {
event = await helper.waitForEvent(progress, this, 'internalnavigation', (event: NavigationEvent) => {
// We are interested either in this specific document, or any other document that
// did commit and replaced the expected document.
return event.newDocument && (event.newDocument.documentId === navigateResult.newDocumentId || !event.error);
@ -685,7 +683,7 @@ export class Frame extends SdkObject {
}
if (!this._firedLifecycleEvents.has(waitUntil))
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
await helper.waitForEvent(progress, this, 'addlifecycle', (e: types.LifecycleEvent) => e === waitUntil).promise;
const request = event.newDocument ? event.newDocument.request : undefined;
const response = request ? request._finalRequest().response() : null;
@ -696,7 +694,7 @@ export class Frame extends SdkObject {
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
progress.log(`waiting for navigation until "${waitUntil}"`);
const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, (event: NavigationEvent) => {
const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this, 'internalnavigation', (event: NavigationEvent) => {
// Any failed navigation results in a rejection.
if (event.error)
return true;
@ -709,7 +707,7 @@ export class Frame extends SdkObject {
throw navigationEvent.error;
if (!this._firedLifecycleEvents.has(waitUntil))
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
await helper.waitForEvent(progress, this, 'addlifecycle', (e: types.LifecycleEvent) => e === waitUntil).promise;
const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined;
return request ? request._finalRequest().response() : null;
@ -718,7 +716,7 @@ export class Frame extends SdkObject {
async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
const waitUntil = verifyLifecycle('state', state);
if (!this._firedLifecycleEvents.has(waitUntil))
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
await helper.waitForEvent(progress, this, 'addlifecycle', (e: types.LifecycleEvent) => e === waitUntil).promise;
}
async frameElement(): Promise<dom.ElementHandle> {
@ -1034,7 +1032,7 @@ export class Frame extends SdkObject {
let cspMessage: ConsoleMessage | undefined;
const actionPromise = func().then(r => result = r).catch(e => error = e);
const errorPromise = new Promise<void>(resolve => {
listeners.push(eventsHelper.addEventListener(this._page._browserContext, BrowserContext.Events.Console, (message: ConsoleMessage) => {
listeners.push(this._page._browserContext.addManagedListener('console', (message: ConsoleMessage) => {
if (message.page() !== this._page || message.type() !== 'error')
return;
if (message.text().includes('Content-Security-Policy') || message.text().includes('Content Security Policy')) {
@ -1725,7 +1723,7 @@ class SignalBarrier {
if (frame.parentFrame())
return;
this.retain();
const waiter = helper.waitForEvent(null, frame, Frame.Events.InternalNavigation, (e: NavigationEvent) => {
const waiter = helper.waitForEvent(null, frame, 'internalnavigation', (e: NavigationEvent) => {
if (!e.isPublic)
return false;
if (!e.error && this._progress)

View file

@ -29,7 +29,6 @@ import { mime } from '../../utilsBundle';
import { ManualPromise } from '../../utils/manualPromise';
import { getPlaywrightVersion } from '../../utils/userAgent';
import { urlMatches } from '../../utils/network';
import { Frame } from '../frames';
import type { HeadersArray, LifecycleEvent } from '../types';
import { isTextualMimeType } from '../../utils/isomorphic/mimeType';
@ -93,19 +92,19 @@ export class HarTracer {
this._started = true;
const apiRequest = this._context instanceof APIRequestContext ? this._context : this._context.fetchRequest;
this._eventListeners = [
eventsHelper.addEventListener(apiRequest, APIRequestContext.Events.Request, (event: APIRequestEvent) => this._onAPIRequest(event)),
eventsHelper.addEventListener(apiRequest, APIRequestContext.Events.RequestFinished, (event: APIRequestFinishedEvent) => this._onAPIRequestFinished(event)),
apiRequest.addManagedListener('request', (event: APIRequestEvent) => this._onAPIRequest(event)),
apiRequest.addManagedListener('requestfinished', (event: APIRequestFinishedEvent) => this._onAPIRequestFinished(event)),
];
if (this._context instanceof BrowserContext) {
this._eventListeners.push(
eventsHelper.addEventListener(this._context, BrowserContext.Events.Page, (page: Page) => this._createPageEntryIfNeeded(page)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.Request, (request: network.Request) => this._onRequest(request)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFinished, ({ request, response }) => this._onRequestFinished(request, response).catch(() => {})),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFailed, request => this._onRequestFailed(request)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.Response, (response: network.Response) => this._onResponse(response)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestAborted, request => this._onRequestAborted(request)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFulfilled, request => this._onRequestFulfilled(request)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestContinued, request => this._onRequestContinued(request)),
this._context.addManagedListener('page', (page: Page) => this._createPageEntryIfNeeded(page)),
this._context.addManagedListener('request', (request: network.Request) => this._onRequest(request)),
this._context.addManagedListener('requestfinished', ({ request, response }) => this._onRequestFinished(request, response).catch(() => {})),
this._context.addManagedListener('requestfailed', request => this._onRequestFailed(request)),
this._context.addManagedListener('response', (response: network.Response) => this._onResponse(response)),
this._context.addManagedListener('requestaborted', request => this._onRequestAborted(request)),
this._context.addManagedListener('requestfulfilled', request => this._onRequestFulfilled(request)),
this._context.addManagedListener('requestcontinued', request => this._onRequestContinued(request)),
);
}
}
@ -139,7 +138,7 @@ export class HarTracer {
};
(pageEntry as any)[startedDateSymbol] = date;
page.mainFrame().on(Frame.Events.AddLifecycle, (event: LifecycleEvent) => {
page.mainFrame().on('addlifecycle', (event: LifecycleEvent) => {
if (event === 'load')
this._onLoad(page, pageEntry!);
if (event === 'domcontentloaded')

View file

@ -15,11 +15,10 @@
* limitations under the License.
*/
import type { EventEmitter } from 'events';
import type * as types from './types';
import type { Progress } from './progress';
import { debugLogger } from '../utils/debugLogger';
import type { RegisteredListener } from '../utils/eventsHelper';
import type { ManagedEventEmitter, RegisteredListener } from '../utils/eventsHelper';
import { eventsHelper } from '../utils/eventsHelper';
const MAX_LOG_LENGTH = process.env.MAX_LOG_LENGTH ? +process.env.MAX_LOG_LENGTH : Infinity;
@ -53,10 +52,10 @@ class Helper {
return null;
}
static waitForEvent(progress: Progress | null, emitter: EventEmitter, event: string | symbol, predicate?: Function): { promise: Promise<any>, dispose: () => void } {
static waitForEvent<T extends ManagedEventEmitter<any>, K extends keyof T['_events']>(progress: Progress | null, emitter: T, event: K, predicate?: Function): { promise: Promise<any>, dispose: () => void } {
const listeners: RegisteredListener[] = [];
const promise = new Promise((resolve, reject) => {
listeners.push(eventsHelper.addEventListener(emitter, event, eventArg => {
listeners.push(emitter.addManagedListener(event, eventArg => {
try {
if (predicate && !predicate(eventArg))
return;

View file

@ -14,8 +14,7 @@
* limitations under the License.
*/
import { EventEmitter } from 'events';
import { createGuid } from '../utils';
import { createGuid, ManagedEventEmitter } from '../utils';
import type { APIRequestContext } from './fetch';
import type { Browser } from './browser';
import type { BrowserContext } from './browserContext';
@ -37,7 +36,7 @@ export type Attribution = {
import type { CallMetadata } from '@protocol/callMetadata';
export type { CallMetadata } from '@protocol/callMetadata';
export class SdkObject extends EventEmitter {
export class SdkObject<Events extends Record<string, any[]> = any> extends ManagedEventEmitter<Events> {
guid: string;
attribution: Attribution;
instrumentation: Instrumentation;

View file

@ -25,7 +25,6 @@ import { SdkObject } from './instrumentation';
import type { HeadersArray, NameValue } from '../common/types';
import { APIRequestContext } from './fetch';
import type { NormalizedContinueOverrides } from './types';
import { BrowserContext } from './browserContext';
export function filterCookies(cookies: channels.NetworkCookie[], urls: string[]): channels.NetworkCookie[] {
const parsedURLs = urls.map(s => new URL(s));
@ -257,7 +256,7 @@ export class Route extends SdkObject {
async abort(errorCode: string = 'failed') {
this._startHandling();
this._request._context.emit(BrowserContext.Events.RequestAborted, this._request);
this._request._context.emit('requestaborted', this._request);
await this._delegate.abort(errorCode);
this._endHandling();
}
@ -285,7 +284,7 @@ export class Route extends SdkObject {
}
const headers = [...(overrides.headers || [])];
this._maybeAddCorsHeaders(headers);
this._request._context.emit(BrowserContext.Events.RequestFulfilled, this._request);
this._request._context.emit('requestfulfilled', this._request);
await this._delegate.fulfill({
status: overrides.status || 200,
headers,
@ -323,7 +322,7 @@ export class Route extends SdkObject {
}
this._request._setOverrides(overrides);
if (!overrides.isFallback)
this._request._context.emit(BrowserContext.Events.RequestContinued, this._request);
this._request._context.emit('requestcontinued', this._request);
await this._delegate.continue(this._request, overrides);
this._endHandling();
}
@ -562,17 +561,15 @@ export class Response extends SdkObject {
}
}
export class WebSocket extends SdkObject {
export class WebSocket extends SdkObject<{
close: []
socketerror: [string]
framereceived: [{ opcode: number, data: string }]
framesent: [{ opcode: number, data: string }]
}> {
private _url: string;
private _notified = false;
static Events = {
Close: 'close',
SocketError: 'socketerror',
FrameReceived: 'framereceived',
FrameSent: 'framesent',
};
constructor(parent: SdkObject, url: string) {
super(parent, 'ws');
this._url = url;
@ -593,19 +590,19 @@ export class WebSocket extends SdkObject {
}
frameSent(opcode: number, data: string) {
this.emit(WebSocket.Events.FrameSent, { opcode, data });
this.emit('framesent', { opcode, data });
}
frameReceived(opcode: number, data: string) {
this.emit(WebSocket.Events.FrameReceived, { opcode, data });
this.emit('framereceived', { opcode, data });
}
error(errorMessage: string) {
this.emit(WebSocket.Events.SocketError, errorMessage);
this.emit('socketerror', errorMessage);
}
closed() {
this.emit(WebSocket.Events.Close);
this.emit('close');
}
}

View file

@ -25,7 +25,8 @@ import type { ScreenshotOptions } from './screenshotter';
import { Screenshotter, validateScreenshotOptions } from './screenshotter';
import { TimeoutSettings } from '../common/timeoutSettings';
import type * as types from './types';
import { BrowserContext } from './browserContext';
import type { BrowserContextEvents } from './browserContext';
import type { BrowserContext } from './browserContext';
import { ConsoleMessage } from './console';
import * as accessibility from './accessibility';
import { FileChooser } from './fileChooser';
@ -45,6 +46,7 @@ import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSe
import type { SerializedValue } from './isomorphic/utilityScriptSerializers';
import { TargetClosedError } from './errors';
import { asLocator } from '../utils/isomorphic/locatorGenerators';
import type { Download } from './download';
export interface PageDelegate {
readonly rawMouse: input.RawMouse;
@ -121,26 +123,24 @@ type ExpectScreenshotOptions = ImageComparatorOptions & ScreenshotOptions & {
},
};
export class Page extends SdkObject {
static Events = {
Close: 'close',
Crash: 'crash',
Download: 'download',
FileChooser: 'filechooser',
FrameAttached: 'frameattached',
FrameDetached: 'framedetached',
InternalFrameNavigatedToNewDocument: 'internalframenavigatedtonewdocument',
LocatorHandlerTriggered: 'locatorhandlertriggered',
ScreencastFrame: 'screencastframe',
Video: 'video',
WebSocket: 'websocket',
Worker: 'worker',
};
export class Page extends SdkObject<{
close: []
crash: []
download: [Download]
filechooser: [FileChooser]
frameattached: [frames.Frame]
framedetached: [frames.Frame]
internalframenavigatedtonewdocument: [frames.Frame]
locatorhandlertriggered: [number]
screencastframe: [{ buffer: Buffer, width: number, height: number, timestamp?: number }]
video: [Artifact]
websocket: [network.WebSocket]
worker: [Worker]
}> {
private _closedState: 'open' | 'closing' | 'closed' = 'open';
private _closedPromise = new ManualPromise<void>();
private _initialized = false;
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
private _eventsToEmitAfterInitialized: { event: string, args: any[] }[] = [];
private _crashed = false;
readonly openScope = new LongStandingScope();
readonly _browserContext: BrowserContext;
@ -202,7 +202,7 @@ export class Page extends SdkObject {
this._opener = openerPage;
}
reportAsNew(error: Error | undefined = undefined, contextEvent: string = BrowserContext.Events.Page) {
reportAsNew(error: Error | undefined = undefined, contextEvent: keyof BrowserContext['_events'] = 'page') {
if (error) {
// Initialization error could have happened because of
// context/browser closure. Just ignore the page.
@ -214,6 +214,7 @@ export class Page extends SdkObject {
this.emitOnContext(contextEvent, this);
for (const { event, args } of this._eventsToEmitAfterInitialized)
// @ts-ignore
this._browserContext.emit(event, ...args);
this._eventsToEmitAfterInitialized = [];
@ -221,7 +222,7 @@ export class Page extends SdkObject {
// in that case we fire another Close event to ensure that each reported Page will have
// corresponding Close event after it is reported on the context.
if (this.isClosed())
this.emit(Page.Events.Close);
this.emit('close');
else
this.instrumentation.onPageOpen(this);
}
@ -230,13 +231,14 @@ export class Page extends SdkObject {
return this._initialized ? this : undefined;
}
emitOnContext(event: string | symbol, ...args: any[]) {
emitOnContext<K extends keyof BrowserContextEvents>(event: K, ...args: BrowserContextEvents[K]) {
if (this._isServerSideOnly)
return;
// @ts-ignore
this._browserContext.emit(event, ...args);
}
emitOnContextOnceInitialized(event: string | symbol, ...args: any[]) {
emitOnContextOnceInitialized<K extends keyof BrowserContextEvents>(event: K, ...args: BrowserContextEvents[K]) {
if (this._isServerSideOnly)
return;
// Some events, like console messages, may come before page is ready.
@ -244,6 +246,7 @@ export class Page extends SdkObject {
// and dispatch it to the client later, either on the live Page,
// or on the "errored" Page.
if (this._initialized)
// @ts-ignore
this._browserContext.emit(event, ...args);
else
this._eventsToEmitAfterInitialized.push({ event, args });
@ -280,7 +283,7 @@ export class Page extends SdkObject {
this._frameThrottler.dispose();
assert(this._closedState !== 'closed', 'Page closed twice');
this._closedState = 'closed';
this.emit(Page.Events.Close);
this.emit('close');
this._closedPromise.resolve();
this.instrumentation.onPageClose(this);
this.openScope.close(new TargetClosedError());
@ -289,7 +292,7 @@ export class Page extends SdkObject {
_didCrash() {
this._frameManager.dispose();
this._frameThrottler.dispose();
this.emit(Page.Events.Crash);
this.emit('crash');
this._crashed = true;
this.instrumentation.onPageClose(this);
this.openScope.close(new Error('Page crashed'));
@ -303,12 +306,12 @@ export class Page extends SdkObject {
// Frame/context may be gone during async processing. Do not throw.
return;
}
if (!this.listenerCount(Page.Events.FileChooser)) {
if (!this.listenerCount('filechooser')) {
handle.dispose();
return;
}
const fileChooser = new FileChooser(this, handle, multiple);
this.emit(Page.Events.FileChooser, fileChooser);
this.emit('filechooser', fileChooser);
}
context(): BrowserContext {
@ -375,7 +378,7 @@ export class Page extends SdkObject {
args.forEach(arg => arg.dispose());
return;
}
this.emitOnContextOnceInitialized(BrowserContext.Events.Console, message);
this.emitOnContextOnceInitialized('console', message);
}
async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
@ -460,7 +463,7 @@ export class Page extends SdkObject {
if (!handler.resolved) {
if (await this.mainFrame().isVisibleInternal(handler.selector, { strict: true })) {
handler.resolved = new ManualPromise();
this.emit(Page.Events.LocatorHandlerTriggered, uid);
this.emit('locatorhandlertriggered', uid);
}
}
if (handler.resolved) {
@ -693,7 +696,7 @@ export class Page extends SdkObject {
_addWorker(workerId: string, worker: Worker) {
this._workers.set(workerId, worker);
this.emit(Page.Events.Worker, worker);
this.emit('worker', worker);
}
_removeWorker(workerId: string) {
@ -721,7 +724,7 @@ export class Page extends SdkObject {
}
frameNavigatedToNewDocument(frame: frames.Frame) {
this.emit(Page.Events.InternalFrameNavigatedToNewDocument, frame);
this.emit('internalframenavigatedtonewdocument', frame);
const origin = frame.origin();
if (origin)
this._browserContext.addVisitedOrigin(origin);
@ -769,11 +772,9 @@ export class Page extends SdkObject {
}
}
export class Worker extends SdkObject {
static Events = {
Close: 'close',
};
export class Worker extends SdkObject<{
close: [Worker]
}> {
private _url: string;
private _executionContextPromise: Promise<js.ExecutionContext>;
private _executionContextCallback: (value: js.ExecutionContext) => void;
@ -799,7 +800,7 @@ export class Worker extends SdkObject {
didClose() {
if (this._existingExecutionContext)
this._existingExecutionContext.contextDestroyed('Worker was closed');
this.emit(Worker.Events.Close, this);
this.emit('close', this);
this.openScope.close(new Error('Worker closed'));
}

View file

@ -20,9 +20,9 @@ import type * as channels from '@protocol/channels';
import type { ActionInContext } from './recorder/codeGenerator';
import { CodeGenerator } from './recorder/codeGenerator';
import { toClickOptions, toModifiers } from './recorder/utils';
import { Page } from './page';
import { Frame } from './frames';
import { BrowserContext } from './browserContext';
import type { Page } from './page';
import type { Frame } from './frames';
import type { BrowserContext } from './browserContext';
import { JavaLanguageGenerator } from './recorder/java';
import { JavaScriptLanguageGenerator } from './recorder/javascript';
import { JsonlLanguageGenerator } from './recorder/jsonl';
@ -38,7 +38,7 @@ import type { Point } from '../common/types';
import type { CallLog, CallLogStatus, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
import { createGuid, isUnderTest, monotonicTime } from '../utils';
import { metadataToCallLog } from './recorder/recorderUtils';
import { Debugger } from './debugger';
import type { Debugger } from './debugger';
import { EventEmitter } from 'events';
import { raceAgainstDeadline } from '../utils/timeoutRunner';
import type { Language, LanguageGenerator } from './recorder/language';
@ -156,12 +156,12 @@ export class Recorder implements InstrumentationListener {
this._pushAllSources()
]);
this._context.once(BrowserContext.Events.Close, () => {
this._context.once('close', () => {
this._contextRecorder.dispose();
this._context.instrumentation.removeListener(this);
recorderApp.close().catch(() => {});
});
this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], primaryFileName: string }) => {
this._contextRecorder.on('change', data => {
this._recorderSources = data.sources;
this._pushAllSources();
this._recorderApp?.setFileIfNeeded(data.primaryFileName);
@ -224,7 +224,7 @@ export class Recorder implements InstrumentationListener {
if (this._debugger.isPaused())
this._pausedStateChanged();
this._debugger.on(Debugger.Events.PausedStateChanged, () => this._pausedStateChanged());
this._debugger.on('pausedstatechanged', () => this._pausedStateChanged());
(this._context as any).recorderAppForTest = recorderApp;
}
@ -370,11 +370,9 @@ export class Recorder implements InstrumentationListener {
}
}
class ContextRecorder extends EventEmitter {
static Events = {
Change: 'change'
};
class ContextRecorder extends EventEmitter<{
change: [{ sources: Source[], primaryFileName: string }]
}> {
private _generator: CodeGenerator;
private _pageAliases = new Map<Page, string>();
private _lastPopupOrdinal = 0;
@ -417,12 +415,12 @@ class ContextRecorder extends EventEmitter {
if (languageGenerator === this._orderedLanguages[0])
this._throttledOutputFile?.setContent(source.text);
}
this.emit(ContextRecorder.Events.Change, {
this.emit('change', {
sources: this._recorderSources,
primaryFileName: this._orderedLanguages[0].id
});
});
context.on(BrowserContext.Events.BeforeClose, () => {
context.on('beforeclose', () => {
this._throttledOutputFile?.flush();
});
this._listeners.push(eventsHelper.addEventListener(process, 'exit', () => {
@ -463,10 +461,10 @@ class ContextRecorder extends EventEmitter {
}
async install() {
this._context.on(BrowserContext.Events.Page, (page: Page) => this._onPage(page));
this._context.on('page', (page: Page) => this._onPage(page));
for (const page of this._context.pages())
this._onPage(page);
this._context.on(BrowserContext.Events.Dialog, (dialog: Dialog) => this._onDialog(dialog.page()));
this._context.on('dialog', (dialog: Dialog) => this._onDialog(dialog.page()));
// Input actions that potentially lead to navigation are intercepted on the page and are
// performed by the Playwright.
@ -505,11 +503,11 @@ class ContextRecorder extends EventEmitter {
});
this._pageAliases.delete(page);
});
frame.on(Frame.Events.InternalNavigation, event => {
frame.on('internalnavigation', event => {
if (event.isPublic)
this._onFrameNavigated(frame, page);
});
page.on(Page.Events.Download, () => this._onDownload(page));
page.on('download', () => this._onDownload(page));
const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : '';
const pageAlias = 'page' + suffix;
this._pageAliases.set(page, pageAlias);

View file

@ -139,7 +139,7 @@ export class ClientCertificatesProxy {
) {
this._socksProxy = new SocksProxy();
this._socksProxy.setPattern('*');
this._socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => {
this._socksProxy.addListener('socksRequested', async (payload: SocksSocketRequestedPayload) => {
try {
const connection = new SocksProxyConnection(this, payload.uid, payload.host, payload.port);
await connection.connect();
@ -148,10 +148,10 @@ export class ClientCertificatesProxy {
this._socksProxy.socketFailed({ uid: payload.uid, errorCode: error.code });
}
});
this._socksProxy.addListener(SocksProxy.Events.SocksData, async (payload: SocksSocketDataPayload) => {
this._socksProxy.addListener('socksData', async (payload: SocksSocketDataPayload) => {
this._connections.get(payload.uid)?.onData(payload.data);
});
this._socksProxy.addListener(SocksProxy.Events.SocksClosed, (payload: SocksSocketClosedPayload) => {
this._socksProxy.addListener('socksClosed', (payload: SocksSocketClosedPayload) => {
this._connections.get(payload.uid)?.onClose();
this._connections.delete(payload.uid);
});

View file

@ -47,11 +47,11 @@ export class SocksInterceptor {
};
},
}) as channels.SocksSupportChannel & EventEmitter;
this._handler.on(socks.SocksProxyHandler.Events.SocksConnected, (payload: socks.SocksSocketConnectedPayload) => this._channel.socksConnected(payload));
this._handler.on(socks.SocksProxyHandler.Events.SocksData, (payload: socks.SocksSocketDataPayload) => this._channel.socksData(payload));
this._handler.on(socks.SocksProxyHandler.Events.SocksError, (payload: socks.SocksSocketErrorPayload) => this._channel.socksError(payload));
this._handler.on(socks.SocksProxyHandler.Events.SocksFailed, (payload: socks.SocksSocketFailedPayload) => this._channel.socksFailed(payload));
this._handler.on(socks.SocksProxyHandler.Events.SocksEnd, (payload: socks.SocksSocketEndPayload) => this._channel.socksEnd(payload));
this._handler.on('socksConnected', (payload: socks.SocksSocketConnectedPayload) => this._channel.socksConnected(payload));
this._handler.on('socksData', (payload: socks.SocksSocketDataPayload) => this._channel.socksData(payload));
this._handler.on('socksError', (payload: socks.SocksSocketErrorPayload) => this._channel.socksError(payload));
this._handler.on('socksFailed', (payload: socks.SocksSocketFailedPayload) => this._channel.socksFailed(payload));
this._handler.on('socksEnd', (payload: socks.SocksSocketEndPayload) => this._channel.socksEnd(payload));
this._channel.on('socksRequested', payload => this._handler.socketRequested(payload));
this._channel.on('socksClosed', payload => this._handler.socketClosed(payload));
this._channel.on('socksData', payload => this._handler.sendSocketData(payload));

View file

@ -14,8 +14,8 @@
* limitations under the License.
*/
import { BrowserContext } from '../../browserContext';
import { Page } from '../../page';
import type { BrowserContext } from '../../browserContext';
import type { Page } from '../../page';
import type { RegisteredListener } from '../../../utils/eventsHelper';
import { eventsHelper } from '../../../utils/eventsHelper';
import { debugLogger } from '../../../utils/debugLogger';
@ -83,7 +83,7 @@ export class Snapshotter {
for (const page of this._context.pages())
this._onPage(page);
this._eventListeners = [
eventsHelper.addEventListener(this._context, BrowserContext.Events.Page, this._onPage.bind(this)),
this._context.addManagedListener('page', this._onPage.bind(this)),
];
const { javaScriptEnabled } = this._context._options;
@ -160,7 +160,7 @@ export class Snapshotter {
// Annotate frame hierarchy so that snapshots could include frame ids.
for (const frame of page.frames())
this._annotateFrameHierarchy(frame);
this._eventListeners.push(eventsHelper.addEventListener(page, Page.Events.FrameAttached, frame => this._annotateFrameHierarchy(frame)));
this._eventListeners.push(page.addManagedListener('frameattached', frame => this._annotateFrameHierarchy(frame)));
}
private async _annotateFrameHierarchy(frame: Frame) {

View file

@ -27,7 +27,7 @@ import type { ElementHandle } from '../../dom';
import type { APIRequestContext } from '../../fetch';
import type { CallMetadata, InstrumentationListener } from '../../instrumentation';
import { SdkObject } from '../../instrumentation';
import { Page } from '../../page';
import type { Page } from '../../page';
import type * as har from '@trace/har';
import type { HarTracerDelegate } from '../../har/harTracer';
import { HarTracer } from '../../har/harTracer';
@ -182,10 +182,12 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
this._fs.appendFile(this._state.traceFile, JSON.stringify(event) + '\n');
this._context.instrumentation.addListener(this, this._context);
this._eventListeners.push(
eventsHelper.addEventListener(this._context, BrowserContext.Events.Console, this._onConsoleMessage.bind(this)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.PageError, this._onPageError.bind(this)),
);
if (this._context instanceof BrowserContext) {
this._eventListeners.push(
this._context.addManagedListener('console', this._onConsoleMessage.bind(this)),
this._context.addManagedListener('pageerror', this._onPageError.bind(this)),
);
}
if (this._state.options.screenshots)
this._startScreencast();
if (this._state.options.snapshots)
@ -199,7 +201,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
for (const page of this._context.pages())
this._startScreencastInPage(page);
this._screencastListeners.push(
eventsHelper.addEventListener(this._context, BrowserContext.Events.Page, this._startScreencastInPage.bind(this)),
this._context.addManagedListener('page', this._startScreencastInPage.bind(this)),
);
}
@ -463,7 +465,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
page.setScreencastOptions(kScreencastOptions);
const prefix = page.guid;
this._screencastListeners.push(
eventsHelper.addEventListener(page, Page.Events.ScreencastFrame, params => {
page.addManagedListener('screencastframe', params => {
const suffix = params.timestamp || Date.now();
const sha1 = `${prefix}-${suffix}.jpeg`;
const event: trace.ScreencastFrameTraceEvent = {

View file

@ -20,7 +20,6 @@ import { Browser } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
import type { RegisteredListener } from '../../utils/eventsHelper';
import { assert } from '../../utils';
import { eventsHelper } from '../../utils/eventsHelper';
import * as network from '../network';
import type { InitScript, Page, PageBinding, PageDelegate } from '../page';
import type { ConnectionTransport } from '../transport';
@ -64,15 +63,15 @@ export class WKBrowser extends Browser {
this._connection = new WKConnection(transport, this._onDisconnect.bind(this), options.protocolLogger, options.browserLogsCollector);
this._browserSession = this._connection.browserSession;
this._eventListeners = [
eventsHelper.addEventListener(this._browserSession, 'Playwright.pageProxyCreated', this._onPageProxyCreated.bind(this)),
eventsHelper.addEventListener(this._browserSession, 'Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)),
eventsHelper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)),
eventsHelper.addEventListener(this._browserSession, 'Playwright.windowOpen', event => this._onWindowOpen(event)),
eventsHelper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)),
eventsHelper.addEventListener(this._browserSession, 'Playwright.downloadFilenameSuggested', this._onDownloadFilenameSuggested.bind(this)),
eventsHelper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)),
eventsHelper.addEventListener(this._browserSession, 'Playwright.screencastFinished', this._onScreencastFinished.bind(this)),
eventsHelper.addEventListener(this._browserSession, kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)),
this._browserSession.addManagedListener('Playwright.pageProxyCreated', this._onPageProxyCreated.bind(this)),
this._browserSession.addManagedListener('Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)),
this._browserSession.addManagedListener('Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)),
this._browserSession.addManagedListener('Playwright.windowOpen', event => this._onWindowOpen(event)),
this._browserSession.addManagedListener('Playwright.downloadCreated', this._onDownloadCreated.bind(this)),
this._browserSession.addManagedListener('Playwright.downloadFilenameSuggested', this._onDownloadFilenameSuggested.bind(this)),
this._browserSession.addManagedListener('Playwright.downloadFinished', this._onDownloadFinished.bind(this)),
this._browserSession.addManagedListener('Playwright.screencastFinished', this._onScreencastFinished.bind(this)),
this._browserSession.addManagedListener(kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)),
];
}

View file

@ -15,8 +15,7 @@
* limitations under the License.
*/
import { EventEmitter } from 'events';
import { assert } from '../../utils';
import { assert, ManagedEventEmitter } from '../../utils';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { Protocol } from './protocol';
import type { RecentLogsCollector } from '../../utils/debugLogger';
@ -97,7 +96,11 @@ export class WKConnection {
}
}
export class WKSession extends EventEmitter {
export class WKSession extends ManagedEventEmitter<{
[K in keyof Protocol.Events]: [Protocol.Events[K]]
} & {
[kPageProxyMessageReceived]: [PageProxyMessageReceivedPayload]
}> {
connection: WKConnection;
readonly sessionId: string;
@ -106,12 +109,6 @@ export class WKSession extends EventEmitter {
private readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError }>();
private _crashed: boolean = false;
override on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(connection: WKConnection, sessionId: string, rawSend: (message: any) => void) {
super();
this.setMaxListeners(0);

View file

@ -45,7 +45,6 @@ import { WKProvisionalPage } from './wkProvisionalPage';
import { WKWorkers } from './wkWorkers';
import { debugLogger } from '../../utils/debugLogger';
import { ManualPromise } from '../../utils/manualPromise';
import { BrowserContext } from '../browserContext';
import { TargetClosedError } from '../errors';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
@ -92,13 +91,13 @@ export class WKPage implements PageDelegate {
this._workers = new WKWorkers(this._page);
this._session = undefined as any as WKSession;
this._browserContext = browserContext;
this._page.on(Page.Events.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false));
this._page.on('framedetached', (frame: frames.Frame) => this._removeContextsForFrame(frame, false));
this._eventListeners = [
eventsHelper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
eventsHelper.addEventListener(this._pageProxySession, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
eventsHelper.addEventListener(this._pageProxySession, 'Target.dispatchMessageFromTarget', this._onDispatchMessageFromTarget.bind(this)),
eventsHelper.addEventListener(this._pageProxySession, 'Target.didCommitProvisionalTarget', this._onDidCommitProvisionalTarget.bind(this)),
eventsHelper.addEventListener(this._pageProxySession, 'Screencast.screencastFrame', this._onScreencastFrame.bind(this)),
this._pageProxySession.addManagedListener('Target.targetCreated', this._onTargetCreated.bind(this)),
this._pageProxySession.addManagedListener('Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
this._pageProxySession.addManagedListener('Target.dispatchMessageFromTarget', this._onDispatchMessageFromTarget.bind(this)),
this._pageProxySession.addManagedListener('Target.didCommitProvisionalTarget', this._onDidCommitProvisionalTarget.bind(this)),
this._pageProxySession.addManagedListener('Screencast.screencastFrame', this._onScreencastFrame.bind(this)),
];
this._firstNonInitialNavigationCommittedPromise = new Promise((f, r) => {
this._firstNonInitialNavigationCommittedFulfill = f;
@ -373,33 +372,33 @@ export class WKPage implements PageDelegate {
private _addSessionListeners() {
this._sessionListeners = [
eventsHelper.addEventListener(this._session, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
eventsHelper.addEventListener(this._session, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
eventsHelper.addEventListener(this._session, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
eventsHelper.addEventListener(this._session, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
eventsHelper.addEventListener(this._session, 'Page.willCheckNavigationPolicy', event => this._onWillCheckNavigationPolicy(event.frameId)),
eventsHelper.addEventListener(this._session, 'Page.didCheckNavigationPolicy', event => this._onDidCheckNavigationPolicy(event.frameId, event.cancel)),
eventsHelper.addEventListener(this._session, 'Page.frameScheduledNavigation', event => this._onFrameScheduledNavigation(event.frameId, event.delay, event.targetIsCurrentFrame)),
eventsHelper.addEventListener(this._session, 'Page.loadEventFired', event => this._page._frameManager.frameLifecycleEvent(event.frameId, 'load')),
eventsHelper.addEventListener(this._session, 'Page.domContentEventFired', event => this._page._frameManager.frameLifecycleEvent(event.frameId, 'domcontentloaded')),
eventsHelper.addEventListener(this._session, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
eventsHelper.addEventListener(this._session, 'Runtime.bindingCalled', event => this._onBindingCalled(event.contextId, event.argument)),
eventsHelper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
eventsHelper.addEventListener(this._session, 'Console.messageRepeatCountUpdated', event => this._onConsoleRepeatCountUpdated(event)),
eventsHelper.addEventListener(this._pageProxySession, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
eventsHelper.addEventListener(this._session, 'Network.requestWillBeSent', e => this._onRequestWillBeSent(this._session, e)),
eventsHelper.addEventListener(this._session, 'Network.requestIntercepted', e => this._onRequestIntercepted(this._session, e)),
eventsHelper.addEventListener(this._session, 'Network.responseReceived', e => this._onResponseReceived(this._session, e)),
eventsHelper.addEventListener(this._session, 'Network.loadingFinished', e => this._onLoadingFinished(e)),
eventsHelper.addEventListener(this._session, 'Network.loadingFailed', e => this._onLoadingFailed(this._session, e)),
eventsHelper.addEventListener(this._session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)),
eventsHelper.addEventListener(this._session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId)),
eventsHelper.addEventListener(this._session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)),
eventsHelper.addEventListener(this._session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)),
eventsHelper.addEventListener(this._session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)),
eventsHelper.addEventListener(this._session, 'Network.webSocketClosed', e => this._page._frameManager.webSocketClosed(e.requestId)),
eventsHelper.addEventListener(this._session, 'Network.webSocketFrameError', e => this._page._frameManager.webSocketError(e.requestId, e.errorMessage)),
this._session.addManagedListener('Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
this._session.addManagedListener('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
this._session.addManagedListener('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
this._session.addManagedListener('Page.frameDetached', event => this._onFrameDetached(event.frameId)),
this._session.addManagedListener('Page.willCheckNavigationPolicy', event => this._onWillCheckNavigationPolicy(event.frameId)),
this._session.addManagedListener('Page.didCheckNavigationPolicy', event => this._onDidCheckNavigationPolicy(event.frameId, event.cancel)),
this._session.addManagedListener('Page.frameScheduledNavigation', event => this._onFrameScheduledNavigation(event.frameId, event.delay, event.targetIsCurrentFrame)),
this._session.addManagedListener('Page.loadEventFired', event => this._page._frameManager.frameLifecycleEvent(event.frameId, 'load')),
this._session.addManagedListener('Page.domContentEventFired', event => this._page._frameManager.frameLifecycleEvent(event.frameId, 'domcontentloaded')),
this._session.addManagedListener('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
this._session.addManagedListener('Runtime.bindingCalled', event => this._onBindingCalled(event.contextId, event.argument)),
this._session.addManagedListener('Console.messageAdded', event => this._onConsoleMessage(event)),
this._session.addManagedListener('Console.messageRepeatCountUpdated', event => this._onConsoleRepeatCountUpdated(event)),
this._pageProxySession.addManagedListener('Dialog.javascriptDialogOpening', event => this._onDialog(event)),
this._session.addManagedListener('Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
this._session.addManagedListener('Network.requestWillBeSent', e => this._onRequestWillBeSent(this._session, e)),
this._session.addManagedListener('Network.requestIntercepted', e => this._onRequestIntercepted(this._session, e)),
this._session.addManagedListener('Network.responseReceived', e => this._onResponseReceived(this._session, e)),
this._session.addManagedListener('Network.loadingFinished', e => this._onLoadingFinished(e)),
this._session.addManagedListener('Network.loadingFailed', e => this._onLoadingFailed(this._session, e)),
this._session.addManagedListener('Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)),
this._session.addManagedListener('Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId)),
this._session.addManagedListener('Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)),
this._session.addManagedListener('Network.webSocketFrameSent', e => e.response.payloadData && this._page._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)),
this._session.addManagedListener('Network.webSocketFrameReceived', e => e.response.payloadData && this._page._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)),
this._session.addManagedListener('Network.webSocketClosed', e => this._page._frameManager.webSocketClosed(e.requestId)),
this._session.addManagedListener('Network.webSocketFrameError', e => this._page._frameManager.webSocketError(e.requestId, e.errorMessage)),
];
}
private async _updateState<T extends keyof Protocol.CommandParameters>(
@ -451,7 +450,7 @@ export class WKPage implements PageDelegate {
}
private _handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {
this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId || null);
this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId);
this._onFrameNavigated(frameTree.frame, true);
this._page._frameManager.frameLifecycleEvent(frameTree.frame.id, 'domcontentloaded');
this._page._frameManager.frameLifecycleEvent(frameTree.frame.id, 'load');
@ -462,7 +461,7 @@ export class WKPage implements PageDelegate {
this._handleFrameTree(child);
}
_onFrameAttached(frameId: string, parentFrameId: string | null): frames.Frame {
_onFrameAttached(frameId: string, parentFrameId: string | undefined): frames.Frame {
return this._page._frameManager.frameAttached(frameId, parentFrameId);
}
@ -552,7 +551,7 @@ export class WKPage implements PageDelegate {
error.stack = stack;
error.name = name;
this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, error, this._page);
this._page.emitOnContextOnceInitialized('pageerror', error, this._page);
return;
}
@ -606,7 +605,7 @@ export class WKPage implements PageDelegate {
}
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog(
this._page.emitOnContext('dialog', new dialog.Dialog(
this._page,
event.type as dialog.DialogType,
event.message,
@ -937,7 +936,7 @@ export class WKPage implements PageDelegate {
this._pageProxySession.send('Screencast.screencastFrameAck', { generation }).catch(e => debugLogger.log('error', e));
});
const buffer = Buffer.from(event.data, 'base64');
this._page.emit(Page.Events.ScreencastFrame, {
this._page.emit('screencastframe', {
buffer,
width: event.deviceWidth,
height: event.deviceHeight,

View file

@ -43,11 +43,11 @@ export class WKProvisionalPage {
const wkPage = this._wkPage;
this._sessionListeners = [
eventsHelper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => wkPage._onRequestWillBeSent(session, e))),
eventsHelper.addEventListener(session, 'Network.requestIntercepted', overrideFrameId(e => wkPage._onRequestIntercepted(session, e))),
eventsHelper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => wkPage._onResponseReceived(session, e))),
eventsHelper.addEventListener(session, 'Network.loadingFinished', overrideFrameId(e => wkPage._onLoadingFinished(e))),
eventsHelper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(session, e))),
session.addManagedListener('Network.requestWillBeSent', overrideFrameId(e => wkPage._onRequestWillBeSent(session, e))),
session.addManagedListener('Network.requestIntercepted', overrideFrameId(e => wkPage._onRequestIntercepted(session, e))),
session.addManagedListener('Network.responseReceived', overrideFrameId(e => wkPage._onResponseReceived(session, e))),
session.addManagedListener('Network.loadingFinished', overrideFrameId(e => wkPage._onLoadingFinished(e))),
session.addManagedListener('Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(session, e))),
];
this.initializationPromise = this._wkPage._initializeSession(session, true, ({ frameTree }) => this._handleFrameTree(frameTree));
@ -59,7 +59,7 @@ export class WKProvisionalPage {
commit() {
assert(this._mainFrameId);
this._wkPage._onFrameAttached(this._mainFrameId, null);
this._wkPage._onFrameAttached(this._mainFrameId, undefined);
}
private _handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {

View file

@ -36,7 +36,7 @@ export class WKWorkers {
eventsHelper.removeEventListeners(this._sessionListeners);
this.clear();
this._sessionListeners = [
eventsHelper.addEventListener(session, 'Worker.workerCreated', (event: Protocol.Worker.workerCreatedPayload) => {
session.addManagedListener('Worker.workerCreated', (event: Protocol.Worker.workerCreatedPayload) => {
const worker = new Worker(this._page, event.url);
const workerSession = new WKSession(session.connection, event.workerId, (message: any) => {
session.send('Worker.sendMessageToWorker', {
@ -59,13 +59,13 @@ export class WKWorkers {
this._page._removeWorker(event.workerId);
});
}),
eventsHelper.addEventListener(session, 'Worker.dispatchMessageFromWorker', (event: Protocol.Worker.dispatchMessageFromWorkerPayload) => {
session.addManagedListener('Worker.dispatchMessageFromWorker', (event: Protocol.Worker.dispatchMessageFromWorkerPayload) => {
const workerSession = this._workerSessions.get(event.workerId)!;
if (!workerSession)
return;
workerSession.dispatchMessage(JSON.parse(event.message));
}),
eventsHelper.addEventListener(session, 'Worker.workerTerminated', (event: Protocol.Worker.workerTerminatedPayload) => {
session.addManagedListener('Worker.workerTerminated', (event: Protocol.Worker.workerTerminatedPayload) => {
const workerSession = this._workerSessions.get(event.workerId)!;
if (!workerSession)
return;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
import type { EventEmitter } from 'events';
import { EventEmitter } from 'events';
export type RegisteredListener = {
emitter: EventEmitter;
@ -24,6 +24,8 @@ export type RegisteredListener = {
};
class EventsHelper {
// Used for normal EventEmitter instances, in order to benefit from
// auto-completion, use ManagedEventEmitter.addManagedListener instead
static addEventListener(
emitter: EventEmitter,
eventName: (string | symbol),
@ -43,4 +45,28 @@ class EventsHelper {
}
}
// From node_modules/@types/node/events.d.ts
type DefaultEventMap = [never];
type Listener<K, T, F> = T extends DefaultEventMap ? F : (
K extends keyof T ? (
T[K] extends unknown[] ? (...args: T[K]) => void : never
)
: never
);
type Listener1<K, T> = Listener<K, T, (...args: any[]) => void>;
type Key<K, T> = T extends DefaultEventMap ? string | symbol : K | keyof T;
export class ManagedEventEmitter<T extends Record<keyof T, any[]>> extends EventEmitter<T> {
public _events!: T;
addManagedListener<K>(eventName: Key<K, T>, listener: Listener1<K, T>): RegisteredListener {
this.on(eventName, listener);
return {
emitter: this as EventEmitter,
eventName: eventName as string,
handler: listener
};
}
}
export const eventsHelper = EventsHelper;

View file

@ -504,13 +504,13 @@ class JobDispatcher {
worker.runTestGroup(runPayload);
this._listeners = [
eventsHelper.addEventListener(worker, 'testBegin', this._onTestBegin.bind(this)),
eventsHelper.addEventListener(worker, 'testEnd', this._onTestEnd.bind(this)),
eventsHelper.addEventListener(worker, 'stepBegin', this._onStepBegin.bind(this)),
eventsHelper.addEventListener(worker, 'stepEnd', this._onStepEnd.bind(this)),
eventsHelper.addEventListener(worker, 'attach', this._onAttach.bind(this)),
eventsHelper.addEventListener(worker, 'done', this._onDone.bind(this)),
eventsHelper.addEventListener(worker, 'exit', this.onExit.bind(this)),
worker.addManagedListener('testBegin', this._onTestBegin.bind(this)),
worker.addManagedListener('testEnd', this._onTestEnd.bind(this)),
worker.addManagedListener('stepBegin', this._onStepBegin.bind(this)),
worker.addManagedListener('stepEnd', this._onStepEnd.bind(this)),
worker.addManagedListener('attach', this._onAttach.bind(this)),
worker.addManagedListener('done', this._onDone.bind(this)),
worker.addManagedListener('exit', this.onExit.bind(this)),
];
}

View file

@ -15,13 +15,13 @@
*/
import child_process from 'child_process';
import { EventEmitter } from 'events';
import { debug } from 'playwright-core/lib/utilsBundle';
import type { EnvProducedPayload, ProcessInitParams } from '../common/ipc';
import type { ProtocolResponse } from '../common/process';
import { execArgvWithExperimentalLoaderOptions } from '../transform/esmUtils';
import { assert } from 'playwright-core/lib/utils';
import { assert, ManagedEventEmitter } from 'playwright-core/lib/utils';
import { esmLoaderRegistered } from '../common/esmLoaderHost';
import type { WorkerHostEvents } from './workerHost';
export type ProcessExitData = {
unexpectedly: boolean;
@ -29,7 +29,7 @@ export type ProcessExitData = {
signal: NodeJS.Signals | null;
};
export class ProcessHost extends EventEmitter {
export class ProcessHost extends ManagedEventEmitter<WorkerHostEvents> {
private process: child_process.ChildProcess | undefined;
private _didSendStop = false;
private _processDidExit = false;
@ -69,7 +69,7 @@ export class ProcessHost extends EventEmitter {
this._processDidExit = true;
await this.onExit();
this._didExitAndRanOnExit = true;
this.emit('exit', { unexpectedly: !this._didSendStop, code, signal } as ProcessExitData);
this.emit('exit', { unexpectedly: !this._didSendStop, code, signal });
});
this.process.on('error', e => {}); // do not yell at a send to dead process.
this.process.on('message', (message: any) => {
@ -91,6 +91,7 @@ export class ProcessHost extends EventEmitter {
resolve(result);
}
} else {
// @ts-ignore
this.emit(method!, params);
}
} else {
@ -148,7 +149,7 @@ export class ProcessHost extends EventEmitter {
this._didSendStop = true;
}
if (!this._didExitAndRanOnExit)
await new Promise(f => this.once('exit', f));
await new Promise<any>(f => this.once('exit', f));
}
didSendStop() {

View file

@ -18,13 +18,29 @@ import fs from 'fs';
import path from 'path';
import type { TestGroup } from './testGroups';
import { stdioChunkToParams } from '../common/ipc';
import type { RunPayload, SerializedConfig, WorkerInitParams } from '../common/ipc';
import type { AttachmentPayload, DonePayload, RunPayload, SerializedConfig, StepBeginPayload, StepEndPayload, TeardownErrorsPayload, TestBeginPayload, TestEndPayload, TestOutputPayload, WorkerInitParams } from '../common/ipc';
import type { ProcessExitData } from './processHost';
import { ProcessHost } from './processHost';
import { artifactsFolderName } from '../isomorphic/folders';
import { removeFolders } from 'playwright-core/lib/utils';
let lastWorkerIndex = 0;
export type WorkerHostEvents = {
stdOut: [TestOutputPayload]
stdErr: [TestOutputPayload]
testBegin: [TestBeginPayload]
testEnd: [TestEndPayload]
stepBegin: [StepBeginPayload]
stepEnd: [StepEndPayload]
attach: [AttachmentPayload]
done: [DonePayload]
teardownErrors: [TeardownErrorsPayload]
exit: [ProcessExitData]
ready: []
};
export class WorkerHost extends ProcessHost {
readonly parallelIndex: number;
readonly workerIndex: number;

View file

@ -18,9 +18,9 @@ import type { Fixtures } from '@playwright/test';
import path from 'path';
import { TestServer } from './testserver';
import { TestProxy } from './proxy';
import type { SocksSocketRequestedPayload } from '../../packages/playwright-core/src/common/socksProxy';
import { SocksProxy } from '../../packages/playwright-core/lib/common/socksProxy';
import type { SocksProxy } from '../../packages/playwright-core/src/common/socksProxy';
const SocksProxyImpl = require('../../packages/playwright-core/lib/common/socksProxy').SocksProxy as any as typeof SocksProxy;
export type ServerWorkerOptions = {
loopback?: string;
@ -100,16 +100,16 @@ export class MockSocksServer {
private _socksProxy: SocksProxy;
constructor() {
this._socksProxy = new SocksProxy();
this._socksProxy = new SocksProxyImpl();
this._socksProxy.setPattern('*');
this._socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => {
this._socksProxy.addListener('socksRequested', async payload => {
this._socksProxy.socketConnected({
uid: payload.uid,
host: '127.0.0.1',
port: 0,
});
});
this._socksProxy.addListener(SocksProxy.Events.SocksData, async (payload: SocksSocketRequestedPayload) => {
this._socksProxy.addListener('socksData', async payload => {
const body = '<html><title>Served by the SOCKS proxy</title></html>';
const data = Buffer.from([
'HTTP/1.1 200 OK',

View file

@ -17,7 +17,9 @@
import { playwrightTest as it, expect } from '../config/browserTest';
import net from 'net';
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../../packages/playwright-core/src/common/socksProxy';
import { SocksProxy } from '../../packages/playwright-core/lib/common/socksProxy';
import type { SocksProxy } from '../../packages/playwright-core/src/common/socksProxy';
const SocksProxyImpl = require('../../packages/playwright-core/lib/common/socksProxy').SocksProxy as any as typeof SocksProxy;
it.skip(({ mode }) => mode.startsWith('service'));
@ -291,9 +293,9 @@ it('should use proxy with emulated user agent', async ({ browserType }) => {
async function setupSocksForwardingServer(port: number, forwardPort: number) {
const connections = new Map<string, net.Socket>();
const socksProxy = new SocksProxy();
const socksProxy = new SocksProxyImpl();
socksProxy.setPattern('*');
socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => {
socksProxy.addListener('socksRequested', async (payload: SocksSocketRequestedPayload) => {
if (!['127.0.0.1', 'fake-localhost-127-0-0-1.nip.io'].includes(payload.host) || payload.port !== 1337) {
socksProxy.sendSocketError({ uid: payload.uid, error: 'ECONNREFUSED' });
return;
@ -309,10 +311,10 @@ async function setupSocksForwardingServer(port: number, forwardPort: number) {
socksProxy.socketConnected({ uid: payload.uid, host: target.localAddress, port: target.localPort });
});
});
socksProxy.addListener(SocksProxy.Events.SocksData, async (payload: SocksSocketDataPayload) => {
socksProxy.addListener('socksData', async (payload: SocksSocketDataPayload) => {
connections.get(payload.uid)?.write(payload.data);
});
socksProxy.addListener(SocksProxy.Events.SocksClosed, (payload: SocksSocketClosedPayload) => {
socksProxy.addListener('socksClosed', (payload: SocksSocketClosedPayload) => {
connections.get(payload.uid)?.destroy();
connections.delete(payload.uid);
});