diff --git a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts index 3208a3be22..283369937a 100644 --- a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts @@ -56,8 +56,8 @@ export class AndroidDeviceDispatcher extends Dispatcher this._dispatchEvent('webViewAdded', { webView })); - device.on(AndroidDevice.Events.WebViewRemoved, socketName => this._dispatchEvent('webViewRemoved', { socketName })); + this.addObjectListener(AndroidDevice.Events.WebViewAdded, webView => this._dispatchEvent('webViewAdded', { webView })); + this.addObjectListener(AndroidDevice.Events.WebViewRemoved, socketName => this._dispatchEvent('webViewRemoved', { socketName })); } async wait(params: channels.AndroidDeviceWaitParams) { @@ -179,8 +179,8 @@ export class AndroidSocketDispatcher extends Dispatcher this._dispatchEvent('data', { data })); - socket.on('close', () => { + this.addObjectListener('data', (data: Buffer) => this._dispatchEvent('data', { data })); + this.addObjectListener('close', () => { this._dispatchEvent('close'); this._dispose(); }); diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 501c4e7947..8c5bc108b7 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -55,7 +55,7 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) })); - context.on(BrowserContext.Events.Close, () => { + this.addObjectListener(BrowserContext.Events.Page, page => this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) })); + this.addObjectListener(BrowserContext.Events.Close, () => { this._dispatchEvent('close'); this._dispose(); }); @@ -72,28 +72,28 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('backgroundPage', { page: new PageDispatcher(this._scope, page) })); + this.addObjectListener(CRBrowserContext.CREvents.BackgroundPage, page => this._dispatchEvent('backgroundPage', { page: new PageDispatcher(this._scope, page) })); for (const serviceWorker of (context as CRBrowserContext).serviceWorkers()) this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) }); - context.on(CRBrowserContext.CREvents.ServiceWorker, serviceWorker => this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) })); + this.addObjectListener(CRBrowserContext.CREvents.ServiceWorker, serviceWorker => this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) })); } - context.on(BrowserContext.Events.Request, (request: Request) => { + this.addObjectListener(BrowserContext.Events.Request, (request: Request) => { return this._dispatchEvent('request', { request: RequestDispatcher.from(this._scope, request), page: PageDispatcher.fromNullable(this._scope, request.frame()?._page.initializedOrUndefined()) }); }); - context.on(BrowserContext.Events.Response, (response: Response) => this._dispatchEvent('response', { + this.addObjectListener(BrowserContext.Events.Response, (response: Response) => this._dispatchEvent('response', { response: ResponseDispatcher.from(this._scope, response), page: PageDispatcher.fromNullable(this._scope, response.frame()?._page.initializedOrUndefined()) })); - context.on(BrowserContext.Events.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', { + this.addObjectListener(BrowserContext.Events.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', { request: RequestDispatcher.from(this._scope, request), failureText: request._failureText || undefined, responseEndTiming: request._responseEndTiming, page: PageDispatcher.fromNullable(this._scope, request.frame()?._page.initializedOrUndefined()) })); - context.on(BrowserContext.Events.RequestFinished, ({ request, response }: { request: Request, response: Response | null }) => this._dispatchEvent('requestFinished', { + this.addObjectListener(BrowserContext.Events.RequestFinished, ({ request, response }: { request: Request, response: Response | null }) => this._dispatchEvent('requestFinished', { request: RequestDispatcher.from(scope, request), response: ResponseDispatcher.fromNullable(scope, response), responseEndTiming: request._responseEndTiming, diff --git a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts index 4c8b181d4a..f15a86a78a 100644 --- a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts @@ -31,7 +31,7 @@ export class BrowserDispatcher extends Dispatcher this._didClose()); + this.addObjectListener(Browser.Events.Disconnected, () => this._didClose()); } _didClose() { diff --git a/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts b/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts index 29007ceac0..9b61367933 100644 --- a/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts @@ -28,7 +28,7 @@ export class CDPSessionDispatcher extends Dispatcher { this._dispatchEvent('event', { method, params }); }; - crSession.on(CRSessionEvents.Disconnected, () => this._dispose()); + this.addObjectListener(CRSessionEvents.Disconnected, () => this._dispose()); } async send(params: channels.CDPSessionSendParams): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index 6448df227b..38f929e79a 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -24,6 +24,8 @@ import type { CallMetadata } from '../instrumentation'; import { SdkObject } from '../instrumentation'; import { rewriteErrorMessage } from '../../utils/stackTrace'; import type { PlaywrightDispatcher } from './playwrightDispatcher'; +import { eventsHelper } from '../..//utils/eventsHelper'; +import type { RegisteredListener } from '../..//utils/eventsHelper'; export const dispatcherSymbol = Symbol('dispatcher'); const metadataValidator = createMetadataValidator(); @@ -50,6 +52,7 @@ export class Dispatcher extends Even // Only "isScope" channel owners have registered dispatchers inside. private _dispatchers = new Map>(); protected _disposed = false; + protected _eventListeners: RegisteredListener[] = []; readonly _guid: string; readonly _type: string; @@ -81,6 +84,10 @@ export class Dispatcher extends Even this._connection.sendCreate(this._parent, type, guid, initializer, this._parent._object); } + addObjectListener(eventName: (string | symbol), handler: (...args: any[]) => void) { + this._eventListeners.push(eventsHelper.addEventListener(this._object as unknown as EventEmitter, eventName, handler)); + } + _dispatchEvent>(method: T, params?: channels.EventsTraits[T]) { if (this._disposed) { if (isUnderTest()) @@ -95,6 +102,7 @@ export class Dispatcher extends Even protected _dispose() { assert(!this._disposed); this._disposed = true; + eventsHelper.removeEventListeners(this._eventListeners); // Clean up from parent and connection. if (this._parent) diff --git a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts index a90bc6a0f9..5a99b4e859 100644 --- a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts @@ -44,7 +44,7 @@ export class ElectronApplicationDispatcher extends Dispatcher { + this.addObjectListener(ElectronApplication.Events.Close, () => { this._dispatchEvent('close'); this._dispose(); }); diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 4390dfa91f..c0e3381c7e 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -51,13 +51,13 @@ export class FrameDispatcher extends Dispatcher im loadStates: Array.from(frame._subtreeLifecycleEvents), }); this._frame = frame; - frame.on(Frame.Events.AddLifecycle, lifecycleEvent => { + this.addObjectListener(Frame.Events.AddLifecycle, lifecycleEvent => { this._dispatchEvent('loadstate', { add: lifecycleEvent }); }); - frame.on(Frame.Events.RemoveLifecycle, lifecycleEvent => { + this.addObjectListener(Frame.Events.RemoveLifecycle, lifecycleEvent => { this._dispatchEvent('loadstate', { remove: lifecycleEvent }); }); - frame.on(Frame.Events.InternalNavigation, (event: NavigationEvent) => { + this.addObjectListener(Frame.Events.InternalNavigation, (event: NavigationEvent) => { if (!event.isPublic) return; const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined }; diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index d9ac336e57..0dff9a50f6 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -153,10 +153,10 @@ export class WebSocketDispatcher extends Dispatcher this._dispatchEvent('frameSent', event)); - webSocket.on(WebSocket.Events.FrameReceived, (event: { opcode: number, data: string }) => this._dispatchEvent('frameReceived', event)); - webSocket.on(WebSocket.Events.SocketError, (error: string) => this._dispatchEvent('socketError', { error })); - webSocket.on(WebSocket.Events.Close, () => this._dispatchEvent('close', {})); + this.addObjectListener(WebSocket.Events.FrameSent, (event: { opcode: number, data: string }) => this._dispatchEvent('frameSent', event)); + this.addObjectListener(WebSocket.Events.FrameReceived, (event: { opcode: number, data: string }) => this._dispatchEvent('frameReceived', event)); + this.addObjectListener(WebSocket.Events.SocketError, (error: string) => this._dispatchEvent('socketError', { error })); + this.addObjectListener(WebSocket.Events.Close, () => this._dispatchEvent('close', {})); } } diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index a7a1304032..67014b7927 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -59,26 +59,26 @@ export class PageDispatcher extends Dispatcher imple opener: PageDispatcher.fromNullable(scope, page.opener()) }, true); this._page = page; - page.on(Page.Events.Close, () => { + this.addObjectListener(Page.Events.Close, () => { this._dispatchEvent('close'); this._dispose(); }); - page.on(Page.Events.Console, message => this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(this._scope, message) })); - page.on(Page.Events.Crash, () => this._dispatchEvent('crash')); - page.on(Page.Events.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this._scope, dialog) })); - page.on(Page.Events.Download, (download: Download) => { + this.addObjectListener(Page.Events.Console, message => this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(this._scope, message) })); + this.addObjectListener(Page.Events.Crash, () => this._dispatchEvent('crash')); + this.addObjectListener(Page.Events.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this._scope, dialog) })); + this.addObjectListener(Page.Events.Download, (download: Download) => { this._dispatchEvent('download', { url: download.url, suggestedFilename: download.suggestedFilename(), artifact: new ArtifactDispatcher(scope, download.artifact) }); }); - this._page.on(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', { + this.addObjectListener(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', { element: ElementHandleDispatcher.from(this._scope, fileChooser.element()), isMultiple: fileChooser.isMultiple() })); - page.on(Page.Events.FrameAttached, frame => this._onFrameAttached(frame)); - page.on(Page.Events.FrameDetached, frame => this._onFrameDetached(frame)); - page.on(Page.Events.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) })); - page.on(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this._scope, webSocket) })); - page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) })); - page.on(Page.Events.Video, (artifact: Artifact) => this._dispatchEvent('video', { artifact: existingDispatcher(artifact) })); + this.addObjectListener(Page.Events.FrameAttached, frame => this._onFrameAttached(frame)); + this.addObjectListener(Page.Events.FrameDetached, frame => this._onFrameDetached(frame)); + this.addObjectListener(Page.Events.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) })); + this.addObjectListener(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this._scope, webSocket) })); + this.addObjectListener(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) })); + this.addObjectListener(Page.Events.Video, (artifact: Artifact) => this._dispatchEvent('video', { artifact: existingDispatcher(artifact) })); if (page._video) this._dispatchEvent('video', { artifact: existingDispatcher(page._video) }); // Ensure client knows about all frames. @@ -299,7 +299,7 @@ export class WorkerDispatcher extends Dispatcher super(scope, worker, 'Worker', { url: worker.url() }); - worker.on(Worker.Events.Close, () => this._dispatchEvent('close')); + this.addObjectListener(Worker.Events.Close, () => this._dispatchEvent('close')); } async evaluateExpression(params: channels.WorkerEvaluateExpressionParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts index 3b7059df64..93830424af 100644 --- a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts @@ -30,7 +30,7 @@ export class TracingDispatcher extends Dispatcher this._dispose()); + this.addObjectListener(Tracing.Events.Dispose, () => this._dispose()); } async tracingStart(params: channels.TracingTracingStartParams): Promise {