diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 02059e7cb2..12f1d21d93 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -25,7 +25,7 @@ import zlib from 'zlib'; import type { HTTPCredentials } from '../../types/types'; import { TimeoutSettings } from '../common/timeoutSettings'; import { getUserAgent } from '../utils/userAgent'; -import { assert, constructURLBasedOnBaseURL, createGuid, monotonicTime } from '../utils'; +import { assert, constructURLBasedOnBaseURL, createGuid, eventsHelper, monotonicTime, type RegisteredListener } from '../utils'; import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle'; import { BrowserContext, verifyClientCertificates } from './browserContext'; import { CookieStore, domainMatches, parseRawCookie } from './cookieStore'; @@ -312,7 +312,7 @@ export abstract class APIRequestContext extends SdkObject { let securityDetails: har.SecurityDetails | undefined; - const cleanup: (() => void)[] = []; + const listeners: RegisteredListener[] = []; const request = requestConstructor(url, requestOptions as any, async response => { const responseAt = monotonicTime(); @@ -481,13 +481,13 @@ export abstract class APIRequestContext extends SdkObject { }); request.on('error', reject); - const disposeListener = () => { - reject(new Error('Request context disposed.')); - request.destroy(); - }; - this.on(APIRequestContext.Events.Dispose, disposeListener); - cleanup.push(() => this.off(APIRequestContext.Events.Dispose, disposeListener)); - request.on('close', () => cleanup.forEach(c => c())); + listeners.push( + eventsHelper.addEventListener(this, APIRequestContext.Events.Dispose, () => { + reject(new Error('Request context disposed.')); + request.destroy(); + }) + ); + request.on('close', () => eventsHelper.removeEventListeners(listeners)); request.on('socket', socket => { // happy eyeballs don't emit lookup and connect events, so we use our custom ones @@ -496,28 +496,24 @@ export abstract class APIRequestContext extends SdkObject { tcpConnectionAt = happyEyeBallsTimings.tcpConnectionAt; // non-happy-eyeballs sockets - const onLookup = () => { dnsLookupAt = monotonicTime(); }; - socket.once('lookup', onLookup); - cleanup.push(() => socket.removeListener('lookup', onLookup)); - const onConnect = () => { tcpConnectionAt = monotonicTime(); }; - socket.once('connect', onConnect); - cleanup.push(() => socket.removeListener('connect', onConnect)); - const onSecureConnect = () => { - tlsHandshakeAt = monotonicTime(); + listeners.push( + eventsHelper.addEventListener(socket, 'lookup', () => { dnsLookupAt = monotonicTime(); }), + eventsHelper.addEventListener(socket, 'connect', () => { tcpConnectionAt = monotonicTime(); }), + eventsHelper.addEventListener(socket, 'secureConnect', () => { + tlsHandshakeAt = monotonicTime(); - if (socket instanceof TLSSocket) { - const peerCertificate = socket.getPeerCertificate(); - securityDetails = { - protocol: socket.getProtocol() ?? undefined, - subjectName: peerCertificate.subject.CN, - validFrom: new Date(peerCertificate.valid_from).getTime() / 1000, - validTo: new Date(peerCertificate.valid_to).getTime() / 1000, - issuer: peerCertificate.issuer.CN - }; - } - }; - socket.once('secureConnect', onSecureConnect); - cleanup.push(() => socket.removeListener('secureConnect', onSecureConnect)); + if (socket instanceof TLSSocket) { + const peerCertificate = socket.getPeerCertificate(); + securityDetails = { + protocol: socket.getProtocol() ?? undefined, + subjectName: peerCertificate.subject.CN, + validFrom: new Date(peerCertificate.valid_from).getTime() / 1000, + validTo: new Date(peerCertificate.valid_to).getTime() / 1000, + issuer: peerCertificate.issuer.CN + }; + } + }), + ); serverIPAddress = socket.remoteAddress; serverPort = socket.remotePort;