tor-browser

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

FrameTree.ts (2893B)


      1 /**
      2 * @license
      3 * Copyright 2022 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import type {Frame} from '../api/Frame.js';
      8 import {Deferred} from '../util/Deferred.js';
      9 
     10 /**
     11 * Keeps track of the page frame tree and it's is managed by
     12 * {@link FrameManager}. FrameTree uses frame IDs to reference frame and it
     13 * means that referenced frames might not be in the tree anymore. Thus, the tree
     14 * structure is eventually consistent.
     15 * @internal
     16 */
     17 export class FrameTree<FrameType extends Frame> {
     18  #frames = new Map<string, FrameType>();
     19  // frameID -> parentFrameID
     20  #parentIds = new Map<string, string>();
     21  // frameID -> childFrameIDs
     22  #childIds = new Map<string, Set<string>>();
     23  #mainFrame?: FrameType;
     24  #isMainFrameStale = false;
     25  #waitRequests = new Map<string, Set<Deferred<FrameType>>>();
     26 
     27  getMainFrame(): FrameType | undefined {
     28    return this.#mainFrame;
     29  }
     30 
     31  getById(frameId: string): FrameType | undefined {
     32    return this.#frames.get(frameId);
     33  }
     34 
     35  /**
     36   * Returns a promise that is resolved once the frame with
     37   * the given ID is added to the tree.
     38   */
     39  waitForFrame(frameId: string): Promise<FrameType> {
     40    const frame = this.getById(frameId);
     41    if (frame) {
     42      return Promise.resolve(frame);
     43    }
     44    const deferred = Deferred.create<FrameType>();
     45    const callbacks =
     46      this.#waitRequests.get(frameId) || new Set<Deferred<FrameType>>();
     47    callbacks.add(deferred);
     48    return deferred.valueOrThrow();
     49  }
     50 
     51  frames(): FrameType[] {
     52    return Array.from(this.#frames.values());
     53  }
     54 
     55  addFrame(frame: FrameType): void {
     56    this.#frames.set(frame._id, frame);
     57    if (frame._parentId) {
     58      this.#parentIds.set(frame._id, frame._parentId);
     59      if (!this.#childIds.has(frame._parentId)) {
     60        this.#childIds.set(frame._parentId, new Set());
     61      }
     62      this.#childIds.get(frame._parentId)!.add(frame._id);
     63    } else if (!this.#mainFrame || this.#isMainFrameStale) {
     64      this.#mainFrame = frame;
     65      this.#isMainFrameStale = false;
     66    }
     67    this.#waitRequests.get(frame._id)?.forEach(request => {
     68      return request.resolve(frame);
     69    });
     70  }
     71 
     72  removeFrame(frame: FrameType): void {
     73    this.#frames.delete(frame._id);
     74    this.#parentIds.delete(frame._id);
     75    if (frame._parentId) {
     76      this.#childIds.get(frame._parentId)?.delete(frame._id);
     77    } else {
     78      this.#isMainFrameStale = true;
     79    }
     80  }
     81 
     82  childFrames(frameId: string): FrameType[] {
     83    const childIds = this.#childIds.get(frameId);
     84    if (!childIds) {
     85      return [];
     86    }
     87    return Array.from(childIds)
     88      .map(id => {
     89        return this.getById(id);
     90      })
     91      .filter((frame): frame is FrameType => {
     92        return frame !== undefined;
     93      });
     94  }
     95 
     96  parentFrame(frameId: string): FrameType | undefined {
     97    const parentId = this.#parentIds.get(frameId);
     98    return parentId ? this.getById(parentId) : undefined;
     99  }
    100 }