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 }