tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

EventEmitter.ts (5383B)


      1 /**
      2 * @license
      3 * Copyright 2022 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import mitt, {type Emitter} from '../../third_party/mitt/mitt.js';
      8 import {disposeSymbol} from '../util/disposable.js';
      9 
     10 /**
     11 * @public
     12 */
     13 export type EventType = string | symbol;
     14 
     15 /**
     16 * @public
     17 */
     18 export type Handler<T = unknown> = (event: T) => void;
     19 
     20 /**
     21 * @public
     22 */
     23 export interface CommonEventEmitter<Events extends Record<EventType, unknown>> {
     24  on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): this;
     25  off<Key extends keyof Events>(
     26    type: Key,
     27    handler?: Handler<Events[Key]>,
     28  ): this;
     29  emit<Key extends keyof Events>(type: Key, event: Events[Key]): boolean;
     30  once<Key extends keyof Events>(
     31    type: Key,
     32    handler: Handler<Events[Key]>,
     33  ): this;
     34  listenerCount(event: keyof Events): number;
     35 
     36  removeAllListeners(event?: keyof Events): this;
     37 }
     38 
     39 /**
     40 * @public
     41 */
     42 export type EventsWithWildcard<Events extends Record<EventType, unknown>> =
     43  Events & {
     44    '*': Events[keyof Events];
     45  };
     46 
     47 /**
     48 * The EventEmitter class that many Puppeteer classes extend.
     49 *
     50 * @remarks
     51 *
     52 * This allows you to listen to events that Puppeteer classes fire and act
     53 * accordingly. Therefore you'll mostly use {@link EventEmitter.on | on} and
     54 * {@link EventEmitter.off | off} to bind
     55 * and unbind to event listeners.
     56 *
     57 * @public
     58 */
     59 export class EventEmitter<Events extends Record<EventType, unknown>>
     60  implements CommonEventEmitter<EventsWithWildcard<Events>>
     61 {
     62  #emitter: Emitter<EventsWithWildcard<Events>> | EventEmitter<Events>;
     63  #handlers = new Map<keyof Events | '*', Array<Handler<any>>>();
     64 
     65  /**
     66   * If you pass an emitter, the returned emitter will wrap the passed emitter.
     67   *
     68   * @internal
     69   */
     70  constructor(
     71    emitter: Emitter<EventsWithWildcard<Events>> | EventEmitter<Events> = mitt(
     72      new Map(),
     73    ),
     74  ) {
     75    this.#emitter = emitter;
     76  }
     77 
     78  /**
     79   * Bind an event listener to fire when an event occurs.
     80   * @param type - the event type you'd like to listen to. Can be a string or symbol.
     81   * @param handler - the function to be called when the event occurs.
     82   * @returns `this` to enable you to chain method calls.
     83   */
     84  on<Key extends keyof EventsWithWildcard<Events>>(
     85    type: Key,
     86    handler: Handler<EventsWithWildcard<Events>[Key]>,
     87  ): this {
     88    const handlers = this.#handlers.get(type);
     89    if (handlers === undefined) {
     90      this.#handlers.set(type, [handler]);
     91    } else {
     92      handlers.push(handler);
     93    }
     94 
     95    this.#emitter.on(type, handler);
     96    return this;
     97  }
     98 
     99  /**
    100   * Remove an event listener from firing.
    101   * @param type - the event type you'd like to stop listening to.
    102   * @param handler - the function that should be removed.
    103   * @returns `this` to enable you to chain method calls.
    104   */
    105  off<Key extends keyof EventsWithWildcard<Events>>(
    106    type: Key,
    107    handler?: Handler<EventsWithWildcard<Events>[Key]>,
    108  ): this {
    109    const handlers = this.#handlers.get(type) ?? [];
    110    if (handler === undefined) {
    111      for (const handler of handlers) {
    112        this.#emitter.off(type, handler);
    113      }
    114      this.#handlers.delete(type);
    115      return this;
    116    }
    117    const index = handlers.lastIndexOf(handler);
    118    if (index > -1) {
    119      this.#emitter.off(type, ...handlers.splice(index, 1));
    120    }
    121    return this;
    122  }
    123 
    124  /**
    125   * Emit an event and call any associated listeners.
    126   *
    127   * @param type - the event you'd like to emit
    128   * @param eventData - any data you'd like to emit with the event
    129   * @returns `true` if there are any listeners, `false` if there are not.
    130   */
    131  emit<Key extends keyof EventsWithWildcard<Events>>(
    132    type: Key,
    133    event: EventsWithWildcard<Events>[Key],
    134  ): boolean {
    135    this.#emitter.emit(type, event);
    136    return this.listenerCount(type) > 0;
    137  }
    138 
    139  /**
    140   * Like `on` but the listener will only be fired once and then it will be removed.
    141   * @param type - the event you'd like to listen to
    142   * @param handler - the handler function to run when the event occurs
    143   * @returns `this` to enable you to chain method calls.
    144   */
    145  once<Key extends keyof EventsWithWildcard<Events>>(
    146    type: Key,
    147    handler: Handler<EventsWithWildcard<Events>[Key]>,
    148  ): this {
    149    const onceHandler: Handler<EventsWithWildcard<Events>[Key]> = eventData => {
    150      handler(eventData);
    151      this.off(type, onceHandler);
    152    };
    153 
    154    return this.on(type, onceHandler);
    155  }
    156 
    157  /**
    158   * Gets the number of listeners for a given event.
    159   *
    160   * @param type - the event to get the listener count for
    161   * @returns the number of listeners bound to the given event
    162   */
    163  listenerCount(type: keyof EventsWithWildcard<Events>): number {
    164    return this.#handlers.get(type)?.length || 0;
    165  }
    166 
    167  /**
    168   * Removes all listeners. If given an event argument, it will remove only
    169   * listeners for that event.
    170   *
    171   * @param type - the event to remove listeners for.
    172   * @returns `this` to enable you to chain method calls.
    173   */
    174  removeAllListeners(type?: keyof EventsWithWildcard<Events>): this {
    175    if (type !== undefined) {
    176      return this.off(type);
    177    }
    178    this[disposeSymbol]();
    179    return this;
    180  }
    181 
    182  /**
    183   * @internal
    184   */
    185  [disposeSymbol](): void {
    186    for (const [type, handlers] of this.#handlers) {
    187      for (const handler of handlers) {
    188        this.#emitter.off(type, handler);
    189      }
    190    }
    191    this.#handlers.clear();
    192  }
    193 }