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 net from 'net';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { createSocket } from '../utils/happy-eyeballs'; import { createSocket } from '../utils/happy-eyeballs';
import { assert, createGuid, } from '../utils'; import { assert, createGuid, ManagedEventEmitter, } from '../utils';
// https://tools.ietf.org/html/rfc1928 // 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)); return (host, port) => matchers.some(matcher => matcher(host, port));
} }
export class SocksProxy extends EventEmitter implements SocksConnectionClient { export class SocksProxy extends ManagedEventEmitter<{
static Events = { 'socksRequested': [SocksSocketRequestedPayload],
SocksRequested: 'socksRequested', 'socksData': [SocksSocketDataPayload],
SocksData: 'socksData', 'socksClosed': [SocksSocketClosedPayload],
SocksClosed: 'socksClosed', }> implements SocksConnectionClient {
};
private _server: net.Server; private _server: net.Server;
private _connections = new Map<string, SocksConnection>(); private _connections = new Map<string, SocksConnection>();
private _sockets = new Set<net.Socket>(); private _sockets = new Set<net.Socket>();
@ -456,7 +454,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
this._handleDirect(payload); this._handleDirect(payload);
return; return;
} }
this.emit(SocksProxy.Events.SocksRequested, payload); this.emit('socksRequested', payload);
} }
onSocketData(payload: SocksSocketDataPayload): void { onSocketData(payload: SocksSocketDataPayload): void {
@ -465,7 +463,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
direct.write(payload.data); direct.write(payload.data);
return; return;
} }
this.emit(SocksProxy.Events.SocksData, payload); this.emit('socksData', payload);
} }
onSocketClosed(payload: SocksSocketClosedPayload): void { onSocketClosed(payload: SocksSocketClosedPayload): void {
@ -475,7 +473,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
this._directSockets.delete(payload.uid); this._directSockets.delete(payload.uid);
return; return;
} }
this.emit(SocksProxy.Events.SocksClosed, payload); this.emit('socksClosed', payload);
} }
socketConnected({ uid, host, port }: SocksSocketConnectedPayload) { socketConnected({ uid, host, port }: SocksSocketConnectedPayload) {
@ -499,15 +497,13 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
} }
} }
export class SocksProxyHandler extends EventEmitter { export class SocksProxyHandler extends EventEmitter<{
static Events = { socksConnected: [SocksSocketConnectedPayload],
SocksConnected: 'socksConnected', socksData: [SocksSocketDataPayload],
SocksData: 'socksData', socksError: [SocksSocketErrorPayload],
SocksError: 'socksError', socksFailed: [SocksSocketFailedPayload],
SocksFailed: 'socksFailed', socksEnd: [SocksSocketEndPayload],
SocksEnd: 'socksEnd', }> {
};
private _sockets = new Map<string, net.Socket>(); private _sockets = new Map<string, net.Socket>();
private _patternMatcher: PatternMatcher = () => false; private _patternMatcher: PatternMatcher = () => false;
private _redirectPortForTest: number | undefined; private _redirectPortForTest: number | undefined;
@ -528,7 +524,7 @@ export class SocksProxyHandler extends EventEmitter {
if (!this._patternMatcher(host, port)) { if (!this._patternMatcher(host, port)) {
const payload: SocksSocketFailedPayload = { uid, errorCode: 'ERULESET' }; const payload: SocksSocketFailedPayload = { uid, errorCode: 'ERULESET' };
debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`); debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`);
this.emit(SocksProxyHandler.Events.SocksFailed, payload); this.emit('socksFailed', payload);
return; return;
} }
@ -540,18 +536,18 @@ export class SocksProxyHandler extends EventEmitter {
const socket = await createSocket(host, port); const socket = await createSocket(host, port);
socket.on('data', data => { socket.on('data', data => {
const payload: SocksSocketDataPayload = { uid, data }; const payload: SocksSocketDataPayload = { uid, data };
this.emit(SocksProxyHandler.Events.SocksData, payload); this.emit('socksData', payload);
}); });
socket.on('error', error => { socket.on('error', error => {
const payload: SocksSocketErrorPayload = { uid, error: error.message }; const payload: SocksSocketErrorPayload = { uid, error: error.message };
debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`); debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`);
this.emit(SocksProxyHandler.Events.SocksError, payload); this.emit('socksError', payload);
this._sockets.delete(uid); this._sockets.delete(uid);
}); });
socket.on('end', () => { socket.on('end', () => {
const payload: SocksSocketEndPayload = { uid }; const payload: SocksSocketEndPayload = { uid };
debugLogger.log('socks', `[${uid}] <= network socket closed`); debugLogger.log('socks', `[${uid}] <= network socket closed`);
this.emit(SocksProxyHandler.Events.SocksEnd, payload); this.emit('socksEnd', payload);
this._sockets.delete(uid); this._sockets.delete(uid);
}); });
const localAddress = socket.localAddress; const localAddress = socket.localAddress;
@ -559,11 +555,11 @@ export class SocksProxyHandler extends EventEmitter {
this._sockets.set(uid, socket); this._sockets.set(uid, socket);
const payload: SocksSocketConnectedPayload = { uid, host: localAddress!, port: localPort! }; const payload: SocksSocketConnectedPayload = { uid, host: localAddress!, port: localPort! };
debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`); debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`);
this.emit(SocksProxyHandler.Events.SocksConnected, payload); this.emit('socksConnected', payload);
} catch (error) { } catch (error) {
const payload: SocksSocketFailedPayload = { uid, errorCode: error.code }; const payload: SocksSocketFailedPayload = { uid, errorCode: error.code };
debugLogger.log('socks', `[${uid}] <= connect error ${payload.errorCode}`); 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 { DispatcherScope, Playwright } from '../server';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import { createPlaywright, DispatcherConnection, RootDispatcher, PlaywrightDispatcher } from '../server'; import { createPlaywright, DispatcherConnection, RootDispatcher, PlaywrightDispatcher } from '../server';
import { Browser } from '../server/browser'; import type { Browser } from '../server/browser';
import { serverSideCallMetadata } from '../server/instrumentation'; import { serverSideCallMetadata } from '../server/instrumentation';
import { SocksProxy } from '../common/socksProxy'; import { SocksProxy } from '../common/socksProxy';
import { assert, isUnderTest } from '../utils'; import { assert, isUnderTest } from '../utils';
import type { LaunchOptions } from '../server/types'; 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 { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher';
import { startProfiling, stopProfiling } from '../utils'; import { startProfiling, stopProfiling } from '../utils';
import { monotonicTime } from '../utils'; import { monotonicTime } from '../utils';
@ -123,7 +123,7 @@ export class PlaywrightConnection {
for (const browser of playwright.allBrowsers()) for (const browser of playwright.allBrowsers())
await browser.close({ reason: 'Connection terminated' }); 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. // Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Browser closed' }); this.close({ code: 1001, reason: 'Browser closed' });
}); });
@ -139,7 +139,7 @@ export class PlaywrightConnection {
this._preLaunched.socksProxy?.setPattern(this._options.socksProxyPattern); this._preLaunched.socksProxy?.setPattern(this._options.socksProxyPattern);
const browser = this._preLaunched.browser!; const browser = this._preLaunched.browser!;
browser.on(Browser.Events.Disconnected, () => { browser.on('disconnected', () => {
// Underlying browser did close for some reason - force disconnect the client. // Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Browser closed' }); this.close({ code: 1001, reason: 'Browser closed' });
}); });
@ -158,7 +158,7 @@ export class PlaywrightConnection {
debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`); debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`);
const playwright = this._preLaunched.playwright!; const playwright = this._preLaunched.playwright!;
const androidDevice = this._preLaunched.androidDevice!; const androidDevice = this._preLaunched.androidDevice!;
androidDevice.on(AndroidDevice.Events.Close, () => { androidDevice.on('close', () => {
// Underlying browser did close for some reason - force disconnect the client. // Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Android device disconnected' }); this.close({ code: 1001, reason: 'Android device disconnected' });
}); });
@ -202,7 +202,7 @@ export class PlaywrightConnection {
...this._options.launchOptions, ...this._options.launchOptions,
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS, 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. // Underlying browser did close for some reason - force disconnect the client.
this.close({ code: 1001, reason: 'Browser closed' }); this.close({ code: 1001, reason: 'Browser closed' });
}); });

View file

@ -21,6 +21,7 @@ import os from 'os';
import path from 'path'; import path from 'path';
import type * as stream from 'stream'; import type * as stream from 'stream';
import { wsReceiver, wsSender } from '../../utilsBundle'; import { wsReceiver, wsSender } from '../../utilsBundle';
import type { ManagedEventEmitter } from '../../utils';
import { createGuid, makeWaitForNextTask, isUnderTest, getPackageManagerExecCommand } from '../../utils'; import { createGuid, makeWaitForNextTask, isUnderTest, getPackageManagerExecCommand } from '../../utils';
import { removeFolders } from '../../utils/fileUtils'; import { removeFolders } from '../../utils/fileUtils';
import type { BrowserOptions, BrowserProcess } from '../browser'; import type { BrowserOptions, BrowserProcess } from '../browser';
@ -53,7 +54,13 @@ export interface DeviceBackend {
open(command: string): Promise<SocketBackend>; 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; guid: string;
write(data: Buffer): Promise<void>; write(data: Buffer): Promise<void>;
close(): 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 _backend: DeviceBackend;
readonly model: string; readonly model: string;
readonly serial: string; readonly serial: string;
@ -108,12 +119,6 @@ export class AndroidDevice extends SdkObject {
readonly _timeoutSettings: TimeoutSettings; readonly _timeoutSettings: TimeoutSettings;
private _webViews = new Map<string, channels.AndroidWebView>(); private _webViews = new Map<string, channels.AndroidWebView>();
static Events = {
WebViewAdded: 'webViewAdded',
WebViewRemoved: 'webViewRemoved',
Close: 'close',
};
private _browserConnections = new Set<AndroidBrowser>(); private _browserConnections = new Set<AndroidBrowser>();
readonly _android: Android; readonly _android: Android;
private _isClosed = false; private _isClosed = false;
@ -256,7 +261,7 @@ export class AndroidDevice extends SdkObject {
} }
await this._backend.close(); await this._backend.close();
this._android._deviceClosed(this); this._android._deviceClosed(this);
this.emit(AndroidDevice.Events.Close); this.emit('close');
} }
async launchBrowser(pkg: string = 'com.android.chrome', options: channels.AndroidDeviceLaunchBrowserParams): Promise<BrowserContext> { 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 }; const webView = { pid, pkg, socketName };
this._webViews.set(socketName, webView); this._webViews.set(socketName, webView);
this.emit(AndroidDevice.Events.WebViewAdded, webView); this.emit('webViewAdded', webView);
} }
for (const p of this._webViews.keys()) { for (const p of this._webViews.keys()) {
if (!socketNames.has(p)) { if (!socketNames.has(p)) {
this._webViews.delete(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 { debug } from '../../utilsBundle';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import * as net from 'net'; import * as net from 'net';
import { EventEmitter } from 'events'; import type { Backend, DeviceBackend, SocketBackend, SocketBackendEvents } from './android';
import type { Backend, DeviceBackend, SocketBackend } from './android'; import { assert, createGuid, ManagedEventEmitter } from '../../utils';
import { assert, createGuid } from '../../utils';
export class AdbBackend implements Backend { export class AdbBackend implements Backend {
async devices(options: channels.AndroidDevicesOptions = {}): Promise<DeviceBackend[]> { async devices(options: channels.AndroidDevicesOptions = {}): Promise<DeviceBackend[]> {
@ -112,7 +111,7 @@ function encodeMessage(message: string): Buffer {
return Buffer.from(lenHex + message); return Buffer.from(lenHex + message);
} }
class BufferedSocketWrapper extends EventEmitter implements SocketBackend { class BufferedSocketWrapper extends ManagedEventEmitter<SocketBackendEvents> implements SocketBackend {
readonly guid = createGuid(); readonly guid = createGuid();
private _socket: net.Socket; private _socket: net.Socket;
private _buffer = Buffer.from([]); private _buffer = Buffer.from([]);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -60,25 +60,25 @@ export class CRNetworkManager {
async addSession(session: CRSession, workerFrame?: frames.Frame, isMain?: boolean) { async addSession(session: CRSession, workerFrame?: frames.Frame, isMain?: boolean) {
const sessionInfo: SessionInfo = { session, isMain, workerFrame, eventListeners: [] }; const sessionInfo: SessionInfo = { session, isMain, workerFrame, eventListeners: [] };
sessionInfo.eventListeners = [ sessionInfo.eventListeners = [
eventsHelper.addEventListener(session, 'Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)), session.addManagedListener('Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Fetch.authRequired', this._onAuthRequired.bind(this, sessionInfo)), session.addManagedListener('Fetch.authRequired', this._onAuthRequired.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)), session.addManagedListener('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)), session.addManagedListener('Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)),
eventsHelper.addEventListener(session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)), session.addManagedListener('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)),
eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)), session.addManagedListener('Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)), session.addManagedListener('Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)),
eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this, sessionInfo)), session.addManagedListener('Network.loadingFinished', this._onLoadingFinished.bind(this, sessionInfo)),
eventsHelper.addEventListener(session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo)), session.addManagedListener('Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo)),
]; ];
if (this._page) { if (this._page) {
sessionInfo.eventListeners.push(...[ sessionInfo.eventListeners.push(...[
eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page!._frameManager.onWebSocketCreated(e.requestId, e.url)), session.addManagedListener('Network.webSocketCreated', e => this._page!._frameManager.onWebSocketCreated(e.requestId, e.url)),
eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!._frameManager.onWebSocketRequest(e.requestId)), session.addManagedListener('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)), session.addManagedListener('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)), session.addManagedListener('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)), session.addManagedListener('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)), session.addManagedListener('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.webSocketFrameError', e => this._page!._frameManager.webSocketError(e.requestId, e.errorMessage)),
]); ]);
} }
this._sessions.set(session, sessionInfo); 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 types from '../types';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import { getAccessibilityTree } from './crAccessibility'; import { getAccessibilityTree } from './crAccessibility';
import { CRBrowserContext } from './crBrowser'; import type { CRBrowserContext } from './crBrowser';
import type { CRSession } from './crConnection'; import type { CRSession } from './crConnection';
import { CRCoverage } from './crCoverage'; import { CRCoverage } from './crCoverage';
import { DragManager } from './crDragDrop'; import { DragManager } from './crDragDrop';
@ -44,7 +44,6 @@ import { exceptionToError, releaseObject, toConsoleMessageLocation } from './crP
import { platformToFontFamilies } from './defaultFontFamilies'; import { platformToFontFamilies } from './defaultFontFamilies';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import { VideoRecorder } from './videoRecorder'; import { VideoRecorder } from './videoRecorder';
import { BrowserContext } from '../browserContext';
import { TargetClosedError } from '../errors'; import { TargetClosedError } from '../errors';
import { isSessionClosedError } from '../protocolError'; import { isSessionClosedError } from '../protocolError';
@ -131,7 +130,7 @@ export class CRPage implements PageDelegate {
} }
private _reportAsNew(error?: Error) { 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>) { private async _forAllFrameSessions(cb: (frame: FrameSession) => Promise<any>) {
@ -444,30 +443,30 @@ class FrameSession {
private _addRendererListeners() { private _addRendererListeners() {
this._eventListeners.push(...[ this._eventListeners.push(...[
eventsHelper.addEventListener(this._client, 'Log.entryAdded', event => this._onLogEntryAdded(event)), this._client.addManagedListener('Log.entryAdded', event => this._onLogEntryAdded(event)),
eventsHelper.addEventListener(this._client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)), this._client.addManagedListener('Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
eventsHelper.addEventListener(this._client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)), this._client.addManagedListener('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
eventsHelper.addEventListener(this._client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId, event.reason)), this._client.addManagedListener('Page.frameDetached', event => this._onFrameDetached(event.frameId, event.reason)),
eventsHelper.addEventListener(this._client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)), this._client.addManagedListener('Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
eventsHelper.addEventListener(this._client, 'Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)), this._client.addManagedListener('Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
eventsHelper.addEventListener(this._client, 'Page.javascriptDialogOpening', event => this._onDialog(event)), this._client.addManagedListener('Page.javascriptDialogOpening', event => this._onDialog(event)),
eventsHelper.addEventListener(this._client, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)), this._client.addManagedListener('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
eventsHelper.addEventListener(this._client, 'Runtime.bindingCalled', event => this._onBindingCalled(event)), this._client.addManagedListener('Runtime.bindingCalled', event => this._onBindingCalled(event)),
eventsHelper.addEventListener(this._client, 'Runtime.consoleAPICalled', event => this._onConsoleAPI(event)), this._client.addManagedListener('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)),
eventsHelper.addEventListener(this._client, 'Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)), this._client.addManagedListener('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)), this._client.addManagedListener('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)), this._client.addManagedListener('Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)),
eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()), this._client.addManagedListener('Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()),
eventsHelper.addEventListener(this._client, 'Target.attachedToTarget', event => this._onAttachedToTarget(event)), this._client.addManagedListener('Target.attachedToTarget', event => this._onAttachedToTarget(event)),
eventsHelper.addEventListener(this._client, 'Target.detachedFromTarget', event => this._onDetachedFromTarget(event)), this._client.addManagedListener('Target.detachedFromTarget', event => this._onDetachedFromTarget(event)),
]); ]);
} }
private _addBrowserListeners() { private _addBrowserListeners() {
this._eventListeners.push(...[ this._eventListeners.push(...[
eventsHelper.addEventListener(this._client, 'Inspector.targetCrashed', event => this._onTargetCrashed()), this._client.addManagedListener('Inspector.targetCrashed', event => this._onTargetCrashed()),
eventsHelper.addEventListener(this._client, 'Page.screencastFrame', event => this._onScreencastFrame(event)), this._client.addManagedListener('Page.screencastFrame', event => this._onScreencastFrame(event)),
eventsHelper.addEventListener(this._client, 'Page.windowOpen', event => this._onWindowOpen(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 // hence we are going to get more lifecycle updates after the actual navigation has
// started (even if the target url is about:blank). // started (even if the target url is about:blank).
lifecycleEventsEnabled.catch(e => {}).then(() => { 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 { } else {
this._firstNonInitialNavigationCommittedFulfill(); 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', {}), this._client.send('Log.enable', {}),
@ -770,7 +769,7 @@ class FrameSession {
const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o)); const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o));
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace)); 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) { _onDetachedFromTarget(event: Protocol.Target.detachedFromTargetPayload) {
@ -865,7 +864,7 @@ class FrameSession {
_onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) { _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) {
if (!this._page._frameManager.frame(this._targetId)) if (!this._page._frameManager.frame(this._targetId))
return; // Our frame/subtree may be gone already. 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, this._page,
event.type, event.type,
event.message, event.message,
@ -876,7 +875,7 @@ class FrameSession {
} }
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) { _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() { async _onTargetCrashed() {
@ -929,7 +928,7 @@ class FrameSession {
this._client.send('Page.screencastFrameAck', { sessionId: payload.sessionId }).catch(() => {}); this._client.send('Page.screencastFrameAck', { sessionId: payload.sessionId }).catch(() => {});
}); });
const buffer = Buffer.from(payload.data, 'base64'); const buffer = Buffer.from(payload.data, 'base64');
this._page.emit(Page.Events.ScreencastFrame, { this._page.emit('screencastframe', {
buffer, buffer,
timestamp: payload.metadata.timestamp, timestamp: payload.metadata.timestamp,
width: payload.metadata.deviceWidth, width: payload.metadata.deviceWidth,
@ -947,7 +946,7 @@ class FrameSession {
async _startVideoRecording(options: types.PageScreencastOptions) { async _startVideoRecording(options: types.PageScreencastOptions) {
const screencastId = this._screencastId; const screencastId = this._screencastId;
assert(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)); const gotFirstFrame = new Promise(f => this._client.once('Page.screencastFrame', f));
await this._startScreencast(this._videoRecorder, { await this._startScreencast(this._videoRecorder, {
format: 'jpeg', format: 'jpeg',

View file

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

View file

@ -16,7 +16,7 @@
import type { ChildProcess } from 'child_process'; import type { ChildProcess } from 'child_process';
import { assert, monotonicTime } from '../../utils'; import { assert, monotonicTime } from '../../utils';
import { Page } from '../page'; import type { Page } from '../page';
import { launchProcess } from '../../utils/processLauncher'; import { launchProcess } from '../../utils/processLauncher';
import type { Progress } from '../progress'; import type { Progress } from '../progress';
import { ProgressController } from '../progress'; import { ProgressController } from '../progress';
@ -53,7 +53,7 @@ export class VideoRecorder {
private constructor(page: Page, ffmpegPath: string, progress: Progress) { private constructor(page: Page, ffmpegPath: string, progress: Progress) {
this._progress = progress; this._progress = progress;
this._ffmpegPath = ffmpegPath; 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) { private async _launch(options: types.PageScreencastOptions) {

View file

@ -28,15 +28,13 @@ import type { Language } from '../utils/isomorphic/locatorGenerators';
const internalMetadata = serverSideCallMetadata(); const internalMetadata = serverSideCallMetadata();
export class DebugController extends SdkObject { export class DebugController extends SdkObject<{
static Events = { stateChanged: [{ pageCount: number }]
StateChanged: 'stateChanged', inspectRequested: [{ selector: string, locator: string }]
InspectRequested: 'inspectRequested', sourceChanged: [{ text: string, header: string | undefined, footer: string | undefined, actions: string[] | undefined }]
SourceChanged: 'sourceChanged', paused: [{ paused: boolean }]
Paused: 'paused', setModeRequested: [{ mode: Mode }]
SetModeRequested: 'setModeRequested', }> {
};
private _autoCloseTimer: NodeJS.Timeout | undefined; private _autoCloseTimer: NodeJS.Timeout | undefined;
// TODO: remove in 1.27 // TODO: remove in 1.27
private _autoCloseAllowed = false; private _autoCloseAllowed = false;
@ -193,7 +191,7 @@ export class DebugController extends SdkObject {
pageCount += context.pages().length; pageCount += context.pages().length;
} }
} }
this.emit(DebugController.Events.StateChanged, { pageCount }); this.emit('stateChanged', { pageCount });
} }
private async _allRecorders(): Promise<Recorder[]> { private async _allRecorders(): Promise<Recorder[]> {
@ -226,20 +224,20 @@ class InspectingRecorderApp extends EmptyRecorderApp {
override async setSelector(selector: string): Promise<void> { override async setSelector(selector: string): Promise<void> {
const locator: string = asLocator(this._debugController._sdkLanguage, selector); 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> { override async setSources(sources: Source[]): Promise<void> {
const source = sources.find(s => s.id === this._debugController._codegenId); const source = sources.find(s => s.id === this._debugController._codegenId);
const { text, header, footer, actions } = source || { text: '' }; 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) { override async setPaused(paused: boolean) {
this._debugController.emit(DebugController.Events.Paused, { paused }); this._debugController.emit('paused', { paused });
} }
override async setMode(mode: Mode) { 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 { EventEmitter } from 'events';
import { debugMode, isUnderTest, monotonicTime } from '../utils'; import { debugMode, isUnderTest, monotonicTime } from '../utils';
import { BrowserContext } from './browserContext'; import type { BrowserContext } from './browserContext';
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation'; import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
import { commandsWithTracingSnapshots, pausesBeforeInputActions, slowMoActions } from '../protocol/debug'; import { commandsWithTracingSnapshots, pausesBeforeInputActions, slowMoActions } from '../protocol/debug';
const symbol = Symbol('Debugger'); const symbol = Symbol('Debugger');
export class Debugger extends EventEmitter implements InstrumentationListener { export class Debugger extends EventEmitter<{
pausedstatechanged: []
}> implements InstrumentationListener {
private _pauseOnNextStatement = false; private _pauseOnNextStatement = false;
private _pausedCallsMetadata = new Map<CallMetadata, { resolve: () => void, sdkObject: SdkObject }>(); private _pausedCallsMetadata = new Map<CallMetadata, { resolve: () => void, sdkObject: SdkObject }>();
private _enabled: boolean; private _enabled: boolean;
private _context: BrowserContext; private _context: BrowserContext;
static Events = {
PausedStateChanged: 'pausedstatechanged'
};
private _muted = false; private _muted = false;
private _slowMo: number | undefined; private _slowMo: number | undefined;
@ -42,7 +41,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
if (this._enabled) if (this._enabled)
this.pauseOnNextStatement(); this.pauseOnNextStatement();
context.instrumentation.addListener(this, context); context.instrumentation.addListener(this, context);
this._context.once(BrowserContext.Events.Close, () => { this._context.once('close', () => {
this._context.instrumentation.removeListener(this); this._context.instrumentation.removeListener(this);
}); });
this._slowMo = this._context._browser.options.slowMo; this._slowMo = this._context._browser.options.slowMo;
@ -83,7 +82,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
const result = new Promise<void>(resolve => { const result = new Promise<void>(resolve => {
this._pausedCallsMetadata.set(metadata, { resolve, sdkObject }); this._pausedCallsMetadata.set(metadata, { resolve, sdkObject });
}); });
this.emit(Debugger.Events.PausedStateChanged); this.emit('pausedstatechanged');
return result; return result;
} }
@ -98,7 +97,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
resolve(); resolve();
} }
this._pausedCallsMetadata.clear(); this._pausedCallsMetadata.clear();
this.emit(Debugger.Events.PausedStateChanged); this.emit('pausedstatechanged');
} }
pauseOnNextStatement() { pauseOnNextStatement() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,7 +15,7 @@
*/ */
import type { NavigationEvent } from '../frames'; import type { NavigationEvent } from '../frames';
import { Frame } from '../frames'; import type { Frame } from '../frames';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import { Dispatcher, existingDispatcher } from './dispatcher'; import { Dispatcher, existingDispatcher } from './dispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
@ -58,13 +58,13 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
}, gcBucket); }, gcBucket);
this._browserContextDispatcher = scope; this._browserContextDispatcher = scope;
this._frame = frame; this._frame = frame;
this.addObjectListener(Frame.Events.AddLifecycle, lifecycleEvent => { this.addObjectListener('addlifecycle', lifecycleEvent => {
this._dispatchEvent('loadstate', { add: lifecycleEvent }); this._dispatchEvent('loadstate', { add: lifecycleEvent });
}); });
this.addObjectListener(Frame.Events.RemoveLifecycle, lifecycleEvent => { this.addObjectListener('removelifecycle', lifecycleEvent => {
this._dispatchEvent('loadstate', { remove: lifecycleEvent }); this._dispatchEvent('loadstate', { remove: lifecycleEvent });
}); });
this.addObjectListener(Frame.Events.InternalNavigation, (event: NavigationEvent) => { this.addObjectListener('internalnavigation', (event: NavigationEvent) => {
if (!event.isPublic) if (!event.isPublic)
return; return;
const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined }; 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 { APIRequestContext } from '../fetch';
import type { CallMetadata } from '../instrumentation'; import type { CallMetadata } from '../instrumentation';
import type { Request, Response, Route } from '../network'; import type { Request, Response, Route } from '../network';
import { WebSocket } from '../network'; import type { WebSocket } from '../network';
import type { RootDispatcher } from './dispatcher'; import type { RootDispatcher } from './dispatcher';
import { Dispatcher, existingDispatcher } from './dispatcher'; import { Dispatcher, existingDispatcher } from './dispatcher';
import { TracingDispatcher } from './tracingDispatcher'; import { TracingDispatcher } from './tracingDispatcher';
@ -164,10 +164,10 @@ export class WebSocketDispatcher extends Dispatcher<WebSocket, channels.WebSocke
super(scope, webSocket, 'WebSocket', { super(scope, webSocket, 'WebSocket', {
url: webSocket.url(), url: webSocket.url(),
}); });
this.addObjectListener(WebSocket.Events.FrameSent, (event: { opcode: number, data: string }) => this._dispatchEvent('frameSent', event)); this.addObjectListener('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('framereceived', (event: { opcode: number, data: string }) => this._dispatchEvent('frameReceived', event));
this.addObjectListener(WebSocket.Events.SocketError, (error: string) => this._dispatchEvent('socketError', { error })); this.addObjectListener('socketerror', (error: string) => this._dispatchEvent('socketError', { error }));
this.addObjectListener(WebSocket.Events.Close, () => this._dispatchEvent('close', {})); this.addObjectListener('close', () => this._dispatchEvent('close', {}));
} }
} }

View file

@ -16,7 +16,8 @@
import type { BrowserContext } from '../browserContext'; import type { BrowserContext } from '../browserContext';
import type { Frame } from '../frames'; 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 type * as channels from '@protocol/channels';
import { Dispatcher, existingDispatcher } from './dispatcher'; import { Dispatcher, existingDispatcher } from './dispatcher';
import { parseError } from '../errors'; import { parseError } from '../errors';
@ -70,25 +71,25 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
this.adopt(mainFrame); this.adopt(mainFrame);
this._page = page; this._page = page;
this.addObjectListener(Page.Events.Close, () => { this.addObjectListener('close', () => {
this._dispatchEvent('close'); this._dispatchEvent('close');
this._dispose(); this._dispose();
}); });
this.addObjectListener(Page.Events.Crash, () => this._dispatchEvent('crash')); this.addObjectListener('crash', () => this._dispatchEvent('crash'));
this.addObjectListener(Page.Events.Download, (download: Download) => { this.addObjectListener('download', (download: Download) => {
// Artifact can outlive the page, so bind to the context scope. // 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._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()), element: ElementHandleDispatcher.from(mainFrame, fileChooser.element()),
isMultiple: fileChooser.isMultiple() isMultiple: fileChooser.isMultiple()
})); }));
this.addObjectListener(Page.Events.FrameAttached, frame => this._onFrameAttached(frame)); this.addObjectListener('frameattached', frame => this._onFrameAttached(frame));
this.addObjectListener(Page.Events.FrameDetached, frame => this._onFrameDetached(frame)); this.addObjectListener('framedetached', frame => this._onFrameDetached(frame));
this.addObjectListener(Page.Events.LocatorHandlerTriggered, (uid: number) => this._dispatchEvent('locatorHandlerTriggered', { uid })); this.addObjectListener('locatorhandlertriggered', (uid: number) => this._dispatchEvent('locatorHandlerTriggered', { uid }));
this.addObjectListener(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this, webSocket) })); this.addObjectListener('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('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('video', (artifact: Artifact) => this._dispatchEvent('video', { artifact: ArtifactDispatcher.from(parentScope, artifact) }));
if (page._video) if (page._video)
this._dispatchEvent('video', { artifact: ArtifactDispatcher.from(this.parentScope(), page._video) }); this._dispatchEvent('video', { artifact: ArtifactDispatcher.from(this.parentScope(), page._video) });
// Ensure client knows about all frames. // Ensure client knows about all frames.
@ -338,7 +339,7 @@ export class WorkerDispatcher extends Dispatcher<Worker, channels.WorkerChannel,
super(scope, worker, 'Worker', { super(scope, worker, 'Worker', {
url: worker.url() 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> { 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 { GlobalAPIRequestContext } from '../fetch';
import type { Playwright } from '../playwright'; import type { Playwright } from '../playwright';
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../../common/socksProxy'; import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../../common/socksProxy';
import { SocksProxy } from '../../common/socksProxy'; import type { SocksProxy } from '../../common/socksProxy';
import { AndroidDispatcher } from './androidDispatcher'; import { AndroidDispatcher } from './androidDispatcher';
import { BrowserTypeDispatcher } from './browserTypeDispatcher'; import { BrowserTypeDispatcher } from './browserTypeDispatcher';
import type { RootDispatcher } from './dispatcher'; import type { RootDispatcher } from './dispatcher';
@ -79,9 +79,9 @@ class SocksSupportDispatcher extends Dispatcher<{ guid: string }, channels.Socks
this._type_SocksSupport = true; this._type_SocksSupport = true;
this._socksProxy = socksProxy; this._socksProxy = socksProxy;
this._socksListeners = [ this._socksListeners = [
eventsHelper.addEventListener(socksProxy, SocksProxy.Events.SocksRequested, (payload: SocksSocketRequestedPayload) => this._dispatchEvent('socksRequested', payload)), socksProxy.addManagedListener('socksRequested', (payload: SocksSocketRequestedPayload) => this._dispatchEvent('socksRequested', payload)),
eventsHelper.addEventListener(socksProxy, SocksProxy.Events.SocksData, (payload: SocksSocketDataPayload) => this._dispatchEvent('socksData', payload)), socksProxy.addManagedListener('socksData', (payload: SocksSocketDataPayload) => this._dispatchEvent('socksData', payload)),
eventsHelper.addEventListener(socksProxy, SocksProxy.Events.SocksClosed, (payload: SocksSocketClosedPayload) => this._dispatchEvent('socksClosed', payload)), socksProxy.addManagedListener('socksClosed', (payload: SocksSocketClosedPayload) => this._dispatchEvent('socksClosed', payload)),
]; ];
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,6 @@ import { SdkObject } from './instrumentation';
import type { HeadersArray, NameValue } from '../common/types'; import type { HeadersArray, NameValue } from '../common/types';
import { APIRequestContext } from './fetch'; import { APIRequestContext } from './fetch';
import type { NormalizedContinueOverrides } from './types'; import type { NormalizedContinueOverrides } from './types';
import { BrowserContext } from './browserContext';
export function filterCookies(cookies: channels.NetworkCookie[], urls: string[]): channels.NetworkCookie[] { export function filterCookies(cookies: channels.NetworkCookie[], urls: string[]): channels.NetworkCookie[] {
const parsedURLs = urls.map(s => new URL(s)); const parsedURLs = urls.map(s => new URL(s));
@ -257,7 +256,7 @@ export class Route extends SdkObject {
async abort(errorCode: string = 'failed') { async abort(errorCode: string = 'failed') {
this._startHandling(); this._startHandling();
this._request._context.emit(BrowserContext.Events.RequestAborted, this._request); this._request._context.emit('requestaborted', this._request);
await this._delegate.abort(errorCode); await this._delegate.abort(errorCode);
this._endHandling(); this._endHandling();
} }
@ -285,7 +284,7 @@ export class Route extends SdkObject {
} }
const headers = [...(overrides.headers || [])]; const headers = [...(overrides.headers || [])];
this._maybeAddCorsHeaders(headers); this._maybeAddCorsHeaders(headers);
this._request._context.emit(BrowserContext.Events.RequestFulfilled, this._request); this._request._context.emit('requestfulfilled', this._request);
await this._delegate.fulfill({ await this._delegate.fulfill({
status: overrides.status || 200, status: overrides.status || 200,
headers, headers,
@ -323,7 +322,7 @@ export class Route extends SdkObject {
} }
this._request._setOverrides(overrides); this._request._setOverrides(overrides);
if (!overrides.isFallback) 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); await this._delegate.continue(this._request, overrides);
this._endHandling(); 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 _url: string;
private _notified = false; private _notified = false;
static Events = {
Close: 'close',
SocketError: 'socketerror',
FrameReceived: 'framereceived',
FrameSent: 'framesent',
};
constructor(parent: SdkObject, url: string) { constructor(parent: SdkObject, url: string) {
super(parent, 'ws'); super(parent, 'ws');
this._url = url; this._url = url;
@ -593,19 +590,19 @@ export class WebSocket extends SdkObject {
} }
frameSent(opcode: number, data: string) { frameSent(opcode: number, data: string) {
this.emit(WebSocket.Events.FrameSent, { opcode, data }); this.emit('framesent', { opcode, data });
} }
frameReceived(opcode: number, data: string) { frameReceived(opcode: number, data: string) {
this.emit(WebSocket.Events.FrameReceived, { opcode, data }); this.emit('framereceived', { opcode, data });
} }
error(errorMessage: string) { error(errorMessage: string) {
this.emit(WebSocket.Events.SocketError, errorMessage); this.emit('socketerror', errorMessage);
} }
closed() { 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 { Screenshotter, validateScreenshotOptions } from './screenshotter';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../common/timeoutSettings';
import type * as types from './types'; 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 { ConsoleMessage } from './console';
import * as accessibility from './accessibility'; import * as accessibility from './accessibility';
import { FileChooser } from './fileChooser'; import { FileChooser } from './fileChooser';
@ -45,6 +46,7 @@ import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSe
import type { SerializedValue } from './isomorphic/utilityScriptSerializers'; import type { SerializedValue } from './isomorphic/utilityScriptSerializers';
import { TargetClosedError } from './errors'; import { TargetClosedError } from './errors';
import { asLocator } from '../utils/isomorphic/locatorGenerators'; import { asLocator } from '../utils/isomorphic/locatorGenerators';
import type { Download } from './download';
export interface PageDelegate { export interface PageDelegate {
readonly rawMouse: input.RawMouse; readonly rawMouse: input.RawMouse;
@ -121,26 +123,24 @@ type ExpectScreenshotOptions = ImageComparatorOptions & ScreenshotOptions & {
}, },
}; };
export class Page extends SdkObject { export class Page extends SdkObject<{
static Events = { close: []
Close: 'close', crash: []
Crash: 'crash', download: [Download]
Download: 'download', filechooser: [FileChooser]
FileChooser: 'filechooser', frameattached: [frames.Frame]
FrameAttached: 'frameattached', framedetached: [frames.Frame]
FrameDetached: 'framedetached', internalframenavigatedtonewdocument: [frames.Frame]
InternalFrameNavigatedToNewDocument: 'internalframenavigatedtonewdocument', locatorhandlertriggered: [number]
LocatorHandlerTriggered: 'locatorhandlertriggered', screencastframe: [{ buffer: Buffer, width: number, height: number, timestamp?: number }]
ScreencastFrame: 'screencastframe', video: [Artifact]
Video: 'video', websocket: [network.WebSocket]
WebSocket: 'websocket', worker: [Worker]
Worker: 'worker', }> {
};
private _closedState: 'open' | 'closing' | 'closed' = 'open'; private _closedState: 'open' | 'closing' | 'closed' = 'open';
private _closedPromise = new ManualPromise<void>(); private _closedPromise = new ManualPromise<void>();
private _initialized = false; private _initialized = false;
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = []; private _eventsToEmitAfterInitialized: { event: string, args: any[] }[] = [];
private _crashed = false; private _crashed = false;
readonly openScope = new LongStandingScope(); readonly openScope = new LongStandingScope();
readonly _browserContext: BrowserContext; readonly _browserContext: BrowserContext;
@ -202,7 +202,7 @@ export class Page extends SdkObject {
this._opener = openerPage; 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) { if (error) {
// Initialization error could have happened because of // Initialization error could have happened because of
// context/browser closure. Just ignore the page. // context/browser closure. Just ignore the page.
@ -214,6 +214,7 @@ export class Page extends SdkObject {
this.emitOnContext(contextEvent, this); this.emitOnContext(contextEvent, this);
for (const { event, args } of this._eventsToEmitAfterInitialized) for (const { event, args } of this._eventsToEmitAfterInitialized)
// @ts-ignore
this._browserContext.emit(event, ...args); this._browserContext.emit(event, ...args);
this._eventsToEmitAfterInitialized = []; 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 // 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. // corresponding Close event after it is reported on the context.
if (this.isClosed()) if (this.isClosed())
this.emit(Page.Events.Close); this.emit('close');
else else
this.instrumentation.onPageOpen(this); this.instrumentation.onPageOpen(this);
} }
@ -230,13 +231,14 @@ export class Page extends SdkObject {
return this._initialized ? this : undefined; return this._initialized ? this : undefined;
} }
emitOnContext(event: string | symbol, ...args: any[]) { emitOnContext<K extends keyof BrowserContextEvents>(event: K, ...args: BrowserContextEvents[K]) {
if (this._isServerSideOnly) if (this._isServerSideOnly)
return; return;
// @ts-ignore
this._browserContext.emit(event, ...args); this._browserContext.emit(event, ...args);
} }
emitOnContextOnceInitialized(event: string | symbol, ...args: any[]) { emitOnContextOnceInitialized<K extends keyof BrowserContextEvents>(event: K, ...args: BrowserContextEvents[K]) {
if (this._isServerSideOnly) if (this._isServerSideOnly)
return; return;
// Some events, like console messages, may come before page is ready. // 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, // and dispatch it to the client later, either on the live Page,
// or on the "errored" Page. // or on the "errored" Page.
if (this._initialized) if (this._initialized)
// @ts-ignore
this._browserContext.emit(event, ...args); this._browserContext.emit(event, ...args);
else else
this._eventsToEmitAfterInitialized.push({ event, args }); this._eventsToEmitAfterInitialized.push({ event, args });
@ -280,7 +283,7 @@ export class Page extends SdkObject {
this._frameThrottler.dispose(); this._frameThrottler.dispose();
assert(this._closedState !== 'closed', 'Page closed twice'); assert(this._closedState !== 'closed', 'Page closed twice');
this._closedState = 'closed'; this._closedState = 'closed';
this.emit(Page.Events.Close); this.emit('close');
this._closedPromise.resolve(); this._closedPromise.resolve();
this.instrumentation.onPageClose(this); this.instrumentation.onPageClose(this);
this.openScope.close(new TargetClosedError()); this.openScope.close(new TargetClosedError());
@ -289,7 +292,7 @@ export class Page extends SdkObject {
_didCrash() { _didCrash() {
this._frameManager.dispose(); this._frameManager.dispose();
this._frameThrottler.dispose(); this._frameThrottler.dispose();
this.emit(Page.Events.Crash); this.emit('crash');
this._crashed = true; this._crashed = true;
this.instrumentation.onPageClose(this); this.instrumentation.onPageClose(this);
this.openScope.close(new Error('Page crashed')); 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. // Frame/context may be gone during async processing. Do not throw.
return; return;
} }
if (!this.listenerCount(Page.Events.FileChooser)) { if (!this.listenerCount('filechooser')) {
handle.dispose(); handle.dispose();
return; return;
} }
const fileChooser = new FileChooser(this, handle, multiple); const fileChooser = new FileChooser(this, handle, multiple);
this.emit(Page.Events.FileChooser, fileChooser); this.emit('filechooser', fileChooser);
} }
context(): BrowserContext { context(): BrowserContext {
@ -375,7 +378,7 @@ export class Page extends SdkObject {
args.forEach(arg => arg.dispose()); args.forEach(arg => arg.dispose());
return; return;
} }
this.emitOnContextOnceInitialized(BrowserContext.Events.Console, message); this.emitOnContextOnceInitialized('console', message);
} }
async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> { async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
@ -460,7 +463,7 @@ export class Page extends SdkObject {
if (!handler.resolved) { if (!handler.resolved) {
if (await this.mainFrame().isVisibleInternal(handler.selector, { strict: true })) { if (await this.mainFrame().isVisibleInternal(handler.selector, { strict: true })) {
handler.resolved = new ManualPromise(); handler.resolved = new ManualPromise();
this.emit(Page.Events.LocatorHandlerTriggered, uid); this.emit('locatorhandlertriggered', uid);
} }
} }
if (handler.resolved) { if (handler.resolved) {
@ -693,7 +696,7 @@ export class Page extends SdkObject {
_addWorker(workerId: string, worker: Worker) { _addWorker(workerId: string, worker: Worker) {
this._workers.set(workerId, worker); this._workers.set(workerId, worker);
this.emit(Page.Events.Worker, worker); this.emit('worker', worker);
} }
_removeWorker(workerId: string) { _removeWorker(workerId: string) {
@ -721,7 +724,7 @@ export class Page extends SdkObject {
} }
frameNavigatedToNewDocument(frame: frames.Frame) { frameNavigatedToNewDocument(frame: frames.Frame) {
this.emit(Page.Events.InternalFrameNavigatedToNewDocument, frame); this.emit('internalframenavigatedtonewdocument', frame);
const origin = frame.origin(); const origin = frame.origin();
if (origin) if (origin)
this._browserContext.addVisitedOrigin(origin); this._browserContext.addVisitedOrigin(origin);
@ -769,11 +772,9 @@ export class Page extends SdkObject {
} }
} }
export class Worker extends SdkObject { export class Worker extends SdkObject<{
static Events = { close: [Worker]
Close: 'close', }> {
};
private _url: string; private _url: string;
private _executionContextPromise: Promise<js.ExecutionContext>; private _executionContextPromise: Promise<js.ExecutionContext>;
private _executionContextCallback: (value: js.ExecutionContext) => void; private _executionContextCallback: (value: js.ExecutionContext) => void;
@ -799,7 +800,7 @@ export class Worker extends SdkObject {
didClose() { didClose() {
if (this._existingExecutionContext) if (this._existingExecutionContext)
this._existingExecutionContext.contextDestroyed('Worker was closed'); this._existingExecutionContext.contextDestroyed('Worker was closed');
this.emit(Worker.Events.Close, this); this.emit('close', this);
this.openScope.close(new Error('Worker closed')); 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 type { ActionInContext } from './recorder/codeGenerator';
import { CodeGenerator } from './recorder/codeGenerator'; import { CodeGenerator } from './recorder/codeGenerator';
import { toClickOptions, toModifiers } from './recorder/utils'; import { toClickOptions, toModifiers } from './recorder/utils';
import { Page } from './page'; import type { Page } from './page';
import { Frame } from './frames'; import type { Frame } from './frames';
import { BrowserContext } from './browserContext'; import type { BrowserContext } from './browserContext';
import { JavaLanguageGenerator } from './recorder/java'; import { JavaLanguageGenerator } from './recorder/java';
import { JavaScriptLanguageGenerator } from './recorder/javascript'; import { JavaScriptLanguageGenerator } from './recorder/javascript';
import { JsonlLanguageGenerator } from './recorder/jsonl'; 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 type { CallLog, CallLogStatus, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
import { createGuid, isUnderTest, monotonicTime } from '../utils'; import { createGuid, isUnderTest, monotonicTime } from '../utils';
import { metadataToCallLog } from './recorder/recorderUtils'; import { metadataToCallLog } from './recorder/recorderUtils';
import { Debugger } from './debugger'; import type { Debugger } from './debugger';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { raceAgainstDeadline } from '../utils/timeoutRunner'; import { raceAgainstDeadline } from '../utils/timeoutRunner';
import type { Language, LanguageGenerator } from './recorder/language'; import type { Language, LanguageGenerator } from './recorder/language';
@ -156,12 +156,12 @@ export class Recorder implements InstrumentationListener {
this._pushAllSources() this._pushAllSources()
]); ]);
this._context.once(BrowserContext.Events.Close, () => { this._context.once('close', () => {
this._contextRecorder.dispose(); this._contextRecorder.dispose();
this._context.instrumentation.removeListener(this); this._context.instrumentation.removeListener(this);
recorderApp.close().catch(() => {}); recorderApp.close().catch(() => {});
}); });
this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], primaryFileName: string }) => { this._contextRecorder.on('change', data => {
this._recorderSources = data.sources; this._recorderSources = data.sources;
this._pushAllSources(); this._pushAllSources();
this._recorderApp?.setFileIfNeeded(data.primaryFileName); this._recorderApp?.setFileIfNeeded(data.primaryFileName);
@ -224,7 +224,7 @@ export class Recorder implements InstrumentationListener {
if (this._debugger.isPaused()) if (this._debugger.isPaused())
this._pausedStateChanged(); this._pausedStateChanged();
this._debugger.on(Debugger.Events.PausedStateChanged, () => this._pausedStateChanged()); this._debugger.on('pausedstatechanged', () => this._pausedStateChanged());
(this._context as any).recorderAppForTest = recorderApp; (this._context as any).recorderAppForTest = recorderApp;
} }
@ -370,11 +370,9 @@ export class Recorder implements InstrumentationListener {
} }
} }
class ContextRecorder extends EventEmitter { class ContextRecorder extends EventEmitter<{
static Events = { change: [{ sources: Source[], primaryFileName: string }]
Change: 'change' }> {
};
private _generator: CodeGenerator; private _generator: CodeGenerator;
private _pageAliases = new Map<Page, string>(); private _pageAliases = new Map<Page, string>();
private _lastPopupOrdinal = 0; private _lastPopupOrdinal = 0;
@ -417,12 +415,12 @@ class ContextRecorder extends EventEmitter {
if (languageGenerator === this._orderedLanguages[0]) if (languageGenerator === this._orderedLanguages[0])
this._throttledOutputFile?.setContent(source.text); this._throttledOutputFile?.setContent(source.text);
} }
this.emit(ContextRecorder.Events.Change, { this.emit('change', {
sources: this._recorderSources, sources: this._recorderSources,
primaryFileName: this._orderedLanguages[0].id primaryFileName: this._orderedLanguages[0].id
}); });
}); });
context.on(BrowserContext.Events.BeforeClose, () => { context.on('beforeclose', () => {
this._throttledOutputFile?.flush(); this._throttledOutputFile?.flush();
}); });
this._listeners.push(eventsHelper.addEventListener(process, 'exit', () => { this._listeners.push(eventsHelper.addEventListener(process, 'exit', () => {
@ -463,10 +461,10 @@ class ContextRecorder extends EventEmitter {
} }
async install() { 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()) for (const page of this._context.pages())
this._onPage(page); 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 // Input actions that potentially lead to navigation are intercepted on the page and are
// performed by the Playwright. // performed by the Playwright.
@ -505,11 +503,11 @@ class ContextRecorder extends EventEmitter {
}); });
this._pageAliases.delete(page); this._pageAliases.delete(page);
}); });
frame.on(Frame.Events.InternalNavigation, event => { frame.on('internalnavigation', event => {
if (event.isPublic) if (event.isPublic)
this._onFrameNavigated(frame, page); 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 suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : '';
const pageAlias = 'page' + suffix; const pageAlias = 'page' + suffix;
this._pageAliases.set(page, pageAlias); this._pageAliases.set(page, pageAlias);

View file

@ -139,7 +139,7 @@ export class ClientCertificatesProxy {
) { ) {
this._socksProxy = new SocksProxy(); this._socksProxy = new SocksProxy();
this._socksProxy.setPattern('*'); this._socksProxy.setPattern('*');
this._socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => { this._socksProxy.addListener('socksRequested', async (payload: SocksSocketRequestedPayload) => {
try { try {
const connection = new SocksProxyConnection(this, payload.uid, payload.host, payload.port); const connection = new SocksProxyConnection(this, payload.uid, payload.host, payload.port);
await connection.connect(); await connection.connect();
@ -148,10 +148,10 @@ export class ClientCertificatesProxy {
this._socksProxy.socketFailed({ uid: payload.uid, errorCode: error.code }); 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._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.get(payload.uid)?.onClose();
this._connections.delete(payload.uid); this._connections.delete(payload.uid);
}); });

View file

@ -47,11 +47,11 @@ export class SocksInterceptor {
}; };
}, },
}) as channels.SocksSupportChannel & EventEmitter; }) as channels.SocksSupportChannel & EventEmitter;
this._handler.on(socks.SocksProxyHandler.Events.SocksConnected, (payload: socks.SocksSocketConnectedPayload) => this._channel.socksConnected(payload)); this._handler.on('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('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('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('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('socksEnd', (payload: socks.SocksSocketEndPayload) => this._channel.socksEnd(payload));
this._channel.on('socksRequested', payload => this._handler.socketRequested(payload)); this._channel.on('socksRequested', payload => this._handler.socketRequested(payload));
this._channel.on('socksClosed', payload => this._handler.socketClosed(payload)); this._channel.on('socksClosed', payload => this._handler.socketClosed(payload));
this._channel.on('socksData', payload => this._handler.sendSocketData(payload)); this._channel.on('socksData', payload => this._handler.sendSocketData(payload));

View file

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

View file

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

View file

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

View file

@ -15,8 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { EventEmitter } from 'events'; import { assert, ManagedEventEmitter } from '../../utils';
import { assert } from '../../utils';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import type { RecentLogsCollector } from '../../utils/debugLogger'; 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; connection: WKConnection;
readonly sessionId: string; 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 readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError }>();
private _crashed: boolean = false; 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) { constructor(connection: WKConnection, sessionId: string, rawSend: (message: any) => void) {
super(); super();
this.setMaxListeners(0); this.setMaxListeners(0);

View file

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

View file

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

View file

@ -36,7 +36,7 @@ export class WKWorkers {
eventsHelper.removeEventListeners(this._sessionListeners); eventsHelper.removeEventListeners(this._sessionListeners);
this.clear(); this.clear();
this._sessionListeners = [ 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 worker = new Worker(this._page, event.url);
const workerSession = new WKSession(session.connection, event.workerId, (message: any) => { const workerSession = new WKSession(session.connection, event.workerId, (message: any) => {
session.send('Worker.sendMessageToWorker', { session.send('Worker.sendMessageToWorker', {
@ -59,13 +59,13 @@ export class WKWorkers {
this._page._removeWorker(event.workerId); 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)!; const workerSession = this._workerSessions.get(event.workerId)!;
if (!workerSession) if (!workerSession)
return; return;
workerSession.dispatchMessage(JSON.parse(event.message)); 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)!; const workerSession = this._workerSessions.get(event.workerId)!;
if (!workerSession) if (!workerSession)
return; return;

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import type { EventEmitter } from 'events'; import { EventEmitter } from 'events';
export type RegisteredListener = { export type RegisteredListener = {
emitter: EventEmitter; emitter: EventEmitter;
@ -24,6 +24,8 @@ export type RegisteredListener = {
}; };
class EventsHelper { class EventsHelper {
// Used for normal EventEmitter instances, in order to benefit from
// auto-completion, use ManagedEventEmitter.addManagedListener instead
static addEventListener( static addEventListener(
emitter: EventEmitter, emitter: EventEmitter,
eventName: (string | symbol), 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; export const eventsHelper = EventsHelper;

View file

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

View file

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

View file

@ -18,13 +18,29 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import type { TestGroup } from './testGroups'; import type { TestGroup } from './testGroups';
import { stdioChunkToParams } from '../common/ipc'; 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 { ProcessHost } from './processHost';
import { artifactsFolderName } from '../isomorphic/folders'; import { artifactsFolderName } from '../isomorphic/folders';
import { removeFolders } from 'playwright-core/lib/utils'; import { removeFolders } from 'playwright-core/lib/utils';
let lastWorkerIndex = 0; 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 { export class WorkerHost extends ProcessHost {
readonly parallelIndex: number; readonly parallelIndex: number;
readonly workerIndex: number; readonly workerIndex: number;

View file

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

View file

@ -17,7 +17,9 @@
import { playwrightTest as it, expect } from '../config/browserTest'; import { playwrightTest as it, expect } from '../config/browserTest';
import net from 'net'; import net from 'net';
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../../packages/playwright-core/src/common/socksProxy'; 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')); 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) { async function setupSocksForwardingServer(port: number, forwardPort: number) {
const connections = new Map<string, net.Socket>(); const connections = new Map<string, net.Socket>();
const socksProxy = new SocksProxy(); const socksProxy = new SocksProxyImpl();
socksProxy.setPattern('*'); 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) { 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' }); socksProxy.sendSocketError({ uid: payload.uid, error: 'ECONNREFUSED' });
return; return;
@ -309,10 +311,10 @@ async function setupSocksForwardingServer(port: number, forwardPort: number) {
socksProxy.socketConnected({ uid: payload.uid, host: target.localAddress, port: target.localPort }); 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); 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.get(payload.uid)?.destroy();
connections.delete(payload.uid); connections.delete(payload.uid);
}); });