chore: unregister handlers when dispatcher goes (#15425)

This commit is contained in:
Pavel Feldman 2022-07-06 19:32:31 -08:00 коммит произвёл GitHub
Родитель b9e41df0a5
Коммит f71b620de7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 45 добавлений и 37 удалений

Просмотреть файл

@ -56,8 +56,8 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
}, true);
for (const webView of device.webViews())
this._dispatchEvent('webViewAdded', { webView });
device.on(AndroidDevice.Events.WebViewAdded, webView => 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<SocketBackend, channels.
constructor(scope: DispatcherScope, socket: SocketBackend) {
super(scope, socket, 'AndroidSocket', {}, true);
socket.on('data', (data: Buffer) => this._dispatchEvent('data', { data }));
socket.on('close', () => {
this.addObjectListener('data', (data: Buffer) => this._dispatchEvent('data', { data }));
this.addObjectListener('close', () => {
this._dispatchEvent('close');
this._dispose();
});

Просмотреть файл

@ -55,7 +55,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
const artifactDispatcher = new ArtifactDispatcher(scope, artifact);
this._dispatchEvent('video', { artifact: artifactDispatcher });
};
context.on(BrowserContext.Events.VideoStarted, onVideo);
this.addObjectListener(BrowserContext.Events.VideoStarted, onVideo);
for (const video of context._browser._idToVideo.values()) {
if (video.context === context)
onVideo(video.artifact);
@ -63,8 +63,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
for (const page of context.pages())
this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) });
context.on(BrowserContext.Events.Page, page => 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<BrowserContext, channel
if (context._browser.options.name === 'chromium') {
for (const page of (context as CRBrowserContext).backgroundPages())
this._dispatchEvent('backgroundPage', { page: new PageDispatcher(this._scope, page) });
context.on(CRBrowserContext.CREvents.BackgroundPage, page => 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,

Просмотреть файл

@ -31,7 +31,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
_type_Browser = true;
constructor(scope: DispatcherScope, browser: Browser) {
super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name }, true);
browser.on(Browser.Events.Disconnected, () => this._didClose());
this.addObjectListener(Browser.Events.Disconnected, () => this._didClose());
}
_didClose() {

Просмотреть файл

@ -28,7 +28,7 @@ export class CDPSessionDispatcher extends Dispatcher<CRSession, channels.CDPSess
crSession._eventListener = (method, params) => {
this._dispatchEvent('event', { method, params });
};
crSession.on(CRSessionEvents.Disconnected, () => this._dispose());
this.addObjectListener(CRSessionEvents.Disconnected, () => this._dispose());
}
async send(params: channels.CDPSessionSendParams): Promise<channels.CDPSessionSendResult> {

Просмотреть файл

@ -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<Type extends { guid: string }, ChannelType> extends Even
// Only "isScope" channel owners have registered dispatchers inside.
private _dispatchers = new Map<string, Dispatcher<any, any>>();
protected _disposed = false;
protected _eventListeners: RegisteredListener[] = [];
readonly _guid: string;
readonly _type: string;
@ -81,6 +84,10 @@ export class Dispatcher<Type extends { guid: string }, ChannelType> 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<T extends keyof channels.EventsTraits<ChannelType>>(method: T, params?: channels.EventsTraits<ChannelType>[T]) {
if (this._disposed) {
if (isUnderTest())
@ -95,6 +102,7 @@ export class Dispatcher<Type extends { guid: string }, ChannelType> extends Even
protected _dispose() {
assert(!this._disposed);
this._disposed = true;
eventsHelper.removeEventListeners(this._eventListeners);
// Clean up from parent and connection.
if (this._parent)

Просмотреть файл

@ -44,7 +44,7 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
super(scope, electronApplication, 'ElectronApplication', {
context: new BrowserContextDispatcher(scope, electronApplication.context())
}, true);
electronApplication.on(ElectronApplication.Events.Close, () => {
this.addObjectListener(ElectronApplication.Events.Close, () => {
this._dispatchEvent('close');
this._dispose();
});

Просмотреть файл

@ -51,13 +51,13 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel> 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 };

Просмотреть файл

@ -153,10 +153,10 @@ export class WebSocketDispatcher extends Dispatcher<WebSocket, channels.WebSocke
super(scope, webSocket, 'WebSocket', {
url: webSocket.url(),
});
webSocket.on(WebSocket.Events.FrameSent, (event: { opcode: number, data: string }) => 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', {}));
}
}

Просмотреть файл

@ -59,26 +59,26 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel> 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<ArtifactDispatcher>(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<ArtifactDispatcher>(artifact) }));
if (page._video)
this._dispatchEvent('video', { artifact: existingDispatcher<ArtifactDispatcher>(page._video) });
// Ensure client knows about all frames.
@ -299,7 +299,7 @@ export class WorkerDispatcher extends Dispatcher<Worker, channels.WorkerChannel>
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<channels.WorkerEvaluateExpressionResult> {

Просмотреть файл

@ -30,7 +30,7 @@ export class TracingDispatcher extends Dispatcher<Tracing, channels.TracingChann
constructor(scope: DispatcherScope, tracing: Tracing) {
super(scope, tracing, 'Tracing', {}, true);
tracing.on(Tracing.Events.Dispose, () => this._dispose());
this.addObjectListener(Tracing.Events.Dispose, () => this._dispose());
}
async tracingStart(params: channels.TracingTracingStartParams): Promise<channels.TracingTracingStartResult> {