Repository: puppeteer/puppeteer
File: packages/puppeteer-core/src/common/FrameManager.ts (TypeScript)

Code before:
    this.setupEventListeners(this.#client);
    client.once(CDPSessionEmittedEvents.Disconnected, () => {
      this.#onClientDisconnect().catch(debugError);
    });
  }

  /**
   * Called when the frame's client is disconnected. We don't know if the
   * disconnect means that the frame is removed or if it will be replaced by a
   * new frame. Therefore, we wait for a swap event.
   */
  async #onClientDisconnect() {
    const mainFrame = this._frameTree.getMainFrame();
    if (!mainFrame) {
      return;
    }
    const swapped = Deferred.create<void>({
      timeout: TIME_FOR_WAITING_FOR_SWAP,
      message: 'Frame was not swapped',
    });
    mainFrame.once(FrameEmittedEvents.FrameSwappedByActivation, () => {
      swapped.resolve();
    });
    try {
      await swapped.valueOrThrow();
      for (const child of mainFrame.childFrames()) {
        this.#removeFramesRecursively(child);
      }
    } catch (err) {
      this.#removeFramesRecursively(mainFrame);
    }
  }

  /**
   * When the main frame is replaced by another main frame,
   * we maintain the main frame object identity while updating
   * its frame tree and ID.
   */
  async swapFrameTree(client: CDPSession): Promise<void> {
    this.#onExecutionContextsCleared(this.#client);

    this.#client = client;
    assert(
      this.#client instanceof CDPSessionImpl,
      'CDPSession is not an instance of CDPSessionImpl.'
    );
    const frame = this._frameTree.getMainFrame();
    if (frame) {
      this._frameTree.removeFrame(frame);
      frame.updateId(this.#client._target()._targetId);
    }

Review comment (suggestion):
Small optimization would be to remove all the child frames before entering the `try/catch` 

Code after:
    this.setupEventListeners(this.#client);
    client.once(CDPSessionEmittedEvents.Disconnected, () => {
      this.#onClientDisconnect().catch(debugError);
    });
  }

  /**
   * Called when the frame's client is disconnected. We don't know if the
   * disconnect means that the frame is removed or if it will be replaced by a
   * new frame. Therefore, we wait for a swap event.
   */
  async #onClientDisconnect() {
    const mainFrame = this._frameTree.getMainFrame();
    if (!mainFrame) {
      return;
    }
    for (const child of mainFrame.childFrames()) {
      this.#removeFramesRecursively(child);
    }
    const swapped = Deferred.create<void>({
      timeout: TIME_FOR_WAITING_FOR_SWAP,
      message: 'Frame was not swapped',
    });
    mainFrame.once(FrameEmittedEvents.FrameSwappedByActivation, () => {
      swapped.resolve();
    });
    try {
      await swapped.valueOrThrow();
    } catch (err) {
      this.#removeFramesRecursively(mainFrame);
    }
  }

  /**
   * When the main frame is replaced by another main frame,
   * we maintain the main frame object identity while updating
   * its frame tree and ID.
   */
  async swapFrameTree(client: CDPSession): Promise<void> {
    this.#onExecutionContextsCleared(this.#client);

    this.#client = client;
    assert(
      this.#client instanceof CDPSessionImpl,
      'CDPSession is not an instance of CDPSessionImpl.'
    );
    const frame = this._frameTree.getMainFrame();
    if (frame) {
      this.#frameNavigatedReceived.add(this.#client._target()._targetId);
      this._frameTree.removeFrame(frame);
      frame.updateId(this.#client._target()._targetId);