Merge pull request #157171 from mjbvz/able-rooster

Try using AbortController for disposable dom listeners
This commit is contained in:
Alexandru Dima
2022-08-05 15:06:16 +02:00
committed by GitHub
3 changed files with 19 additions and 39 deletions

View File

@@ -29,40 +29,20 @@ export function isInDOM(node: Node | null): boolean {
return node?.isConnected ?? false;
}
class DomListener implements IDisposable {
private _handler: (e: any) => void;
private _node: EventTarget;
private readonly _type: string;
private readonly _options: boolean | AddEventListenerOptions;
constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) {
this._node = node;
this._type = type;
this._handler = handler;
this._options = (options || false);
this._node.addEventListener(this._type, this._handler, this._options);
}
public dispose(): void {
if (!this._handler) {
// Already disposed
return;
}
this._node.removeEventListener(this._type, this._handler, this._options);
// Prevent leakers from holding on to the dom or handler func
this._node = null!;
this._handler = null!;
}
}
export function addDisposableListener<K extends keyof GlobalEventHandlersEventMap>(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable;
export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable;
export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable;
export function addDisposableListener<K extends keyof GlobalEventHandlersEventMap>(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable;
export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable;
export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable {
return new DomListener(node, type, handler, useCaptureOrOptions);
let controller: AbortController | undefined = new AbortController();
const opts: AddEventListenerOptions = typeof useCaptureOrOptions === 'boolean'
? { capture: useCaptureOrOptions, signal: controller.signal }
: { signal: controller.signal, ...(useCaptureOrOptions ?? {}) };
node.addEventListener(type, handler, opts);
return toDisposable(() => {
controller?.abort();
controller = undefined;
});
}
export interface IAddStandardDisposableListenerSignature {
@@ -122,18 +102,16 @@ export function addDisposableGenericMouseUpListener(node: EventTarget, handler:
}
export function createEventEmitter<K extends keyof HTMLElementEventMap>(target: HTMLElement, type: K, options?: boolean | AddEventListenerOptions): event.Emitter<HTMLElementEventMap[K]> {
let domListener: DomListener | null = null;
let domListener: IDisposable | undefined = undefined;
const handler = (e: HTMLElementEventMap[K]) => result.fire(e);
const onFirstListenerAdd = () => {
if (!domListener) {
domListener = new DomListener(target, type, handler, options);
domListener = addDisposableListener(target, type, handler, options);
}
};
const onLastListenerRemove = () => {
if (domListener) {
domListener.dispose();
domListener = null;
}
domListener?.dispose();
domListener = undefined;
};
const result = new event.Emitter<HTMLElementEventMap[K]>({ onFirstListenerAdd, onLastListenerRemove });
return result;