UNPKG

chrome-devtools-frontend

Version:
209 lines (182 loc) • 8.52 kB
// Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type * as Common from '../../core/common/common.js'; import type * as SDK from '../../core/sdk/sdk.js'; import * as Annotations from '../../models/annotations/annotations.js'; import {Annotation} from './Annotation.js'; interface AnnotationData { id: number; type: Annotations.AnnotationType; annotation: Annotation; } interface AnnotationPlacement { parentElement: Element; insertBefore?: Node|null; resolveInitialState: (parentElement: Element, reveal: boolean, lookupId: string, anchor?: SDK.DOMModel.DOMNode|SDK.NetworkRequest.NetworkRequest) => Promise<{x: number, y: number}|null>; } // This class handles general management of Annotations, the data needed to display them and any panel-specific things // that the AnnotationRepository must be free from. It is created on-demand the first time a panel, that wants to show // an Annotation, appears. // // NOTE: For now this class is not for general use and is inactive (unless a specific flag is supplied). export class AnnotationManager { static #instance: AnnotationManager|null = null; #annotationPlacements: Map<Annotations.AnnotationType, AnnotationPlacement>|null = null; #annotations = new Map<number, AnnotationData>(); #synced = false; constructor() { if (!Annotations.AnnotationRepository.annotationsEnabled()) { console.warn('AnnotationManager created with annotations disabled'); return; } Annotations.AnnotationRepository.instance().addEventListener( Annotations.Events.ANNOTATION_ADDED, this.#onAnnotationAdded, this); Annotations.AnnotationRepository.instance().addEventListener( Annotations.Events.ANNOTATION_DELETED, this.#onAnnotationDeleted, this); Annotations.AnnotationRepository.instance().addEventListener( Annotations.Events.ALL_ANNOTATIONS_DELETED, this.#onAllAnnotationsDeleted, this); } static instance(): AnnotationManager { if (!AnnotationManager.#instance) { AnnotationManager.#instance = new AnnotationManager(); } return AnnotationManager.#instance; } initializePlacementForAnnotationType( type: Annotations.AnnotationType, resolveInitialState: (parentElement: Element, reveal: boolean, lookupId: string, anchor?: SDK.DOMModel.DOMNode|SDK.NetworkRequest.NetworkRequest) => Promise<{x: number, y: number}|null>, parentElement: Element, insertBefore: Node|null = null): void { if (!Annotations.AnnotationRepository.annotationsEnabled()) { return; } if (!this.#annotationPlacements) { this.#annotationPlacements = new Map(); } this.#annotationPlacements.set(type, {parentElement, insertBefore, resolveInitialState}); // eslint-disable-next-line no-console console.log( `[AnnotationManager] initializing placement for ${Annotations.AnnotationType[type]}`, {parentElement}, 'placement count:', this.#annotationPlacements); this.#syncAnnotations(); } #syncAnnotations(): void { if (this.#synced) { return; } // eslint-disable-next-line no-console console.log('[AnnotationManager] **** SYNC STARTED ***'); const repository = Annotations.AnnotationRepository.instance(); for (const type of Object.values(Annotations.AnnotationType)) { for (const annotation of repository.getAnnotationDataByType(type as Annotations.AnnotationType)) { // eslint-disable-next-line no-console console.log( '[AnnotationManager] Available annotation:', annotation, 'need sync:', !this.#annotations.has(annotation.id)); if (!this.#annotations.has(annotation.id)) { this.#addAnnotation(annotation); } } } this.#synced = true; } #onAllAnnotationsDeleted(): void { for (const annotation of this.#annotations.values()) { annotation.annotation.hide(); } this.#annotations = new Map(); // eslint-disable-next-line no-console console.log('[AnnotationManager] deleted all annotations'); } #onAnnotationDeleted( event: Common.EventTarget.EventTargetEvent<Annotations.EventTypes[Annotations.Events.ANNOTATION_DELETED]>): void { const {id} = event.data; const annotation = this.#annotations.get(id); if (annotation) { annotation.annotation.hide(); this.#annotations.delete(id); } // eslint-disable-next-line no-console console.log(`[AnnotationManager] Deleted annotation with id ${id}`); } #onAnnotationAdded( event: Common.EventTarget.EventTargetEvent<Annotations.EventTypes[Annotations.Events.ANNOTATION_ADDED]>): void { const annotationData = event.data; // eslint-disable-next-line no-console console.log('[AnnotationManager] handleAddAnnotation', annotationData); this.#addAnnotation(annotationData); } #addAnnotation(annotationData: Annotations.BaseAnnotationData): void { const expandable = annotationData.type !== Annotations.AnnotationType.NETWORK_REQUEST; const showExpanded = annotationData.type !== Annotations.AnnotationType.NETWORK_REQUEST; const showAnchored = annotationData.type !== Annotations.AnnotationType.NETWORK_REQUEST; const showCloseButton = annotationData.type !== Annotations.AnnotationType.NETWORK_REQUEST; const annotation = new Annotation( annotationData.id, annotationData.message, showExpanded, showAnchored, expandable, showCloseButton); this.#annotations.set(annotationData.id, {id: annotationData.id, type: annotationData.type, annotation}); // eslint-disable-next-line no-console console.log('[AnnotationManager] addAnnotation called. Annotations now', this.#annotations); requestAnimationFrame(async () => { await this.#resolveAnnotationWithId(annotationData.id); }); } async resolveAnnotationsOfType(type: Annotations.AnnotationType): Promise<void> { for (const annotationData of this.#annotations.values()) { if (annotationData.type === type) { await this.#resolveAnnotationWithId(annotationData.id); } } } async #resolveAnnotationWithId(id: number): Promise<void> { const annotation = this.#annotations.get(id); if (!annotation) { console.warn('Unable to find annotation with id', id, ' in annotations map', this.#annotations); return; } const placement = this.#annotationPlacements?.get(annotation.type); if (!placement) { console.warn( 'Unable to find placement for annotation with id', id, '(note: this is expected if its panel hasn\'t been shown yet).'); return; } let position = undefined; const annotationRegistration = Annotations.AnnotationRepository.instance().getAnnotationDataById(id); const reveal = !annotation.annotation.hasShown(); switch (annotationRegistration?.type) { case Annotations.AnnotationType.ELEMENT_NODE: { const elementData = annotationRegistration as Annotations.ElementsAnnotationData; position = await placement.resolveInitialState( placement.parentElement, reveal, elementData.lookupId, elementData.anchor); break; } case Annotations.AnnotationType.NETWORK_REQUEST: { const networkRequestData = annotationRegistration as Annotations.NetworkRequestAnnotationData; position = await placement.resolveInitialState( placement.parentElement, reveal, networkRequestData.lookupId, networkRequestData.anchor); break; } case Annotations.AnnotationType.NETWORK_REQUEST_SUBPANEL_HEADERS: { const networkRequestDetailsData = annotationRegistration as Annotations.NetworkRequestDetailsAnnotationData; position = await placement.resolveInitialState( placement.parentElement, reveal, networkRequestDetailsData.lookupId, networkRequestDetailsData.anchor); break; } default: console.warn('[AnnotationManager] Unknown AnnotationType', annotationRegistration?.type); } if (!position) { // eslint-disable-next-line no-console console.log(`Unable to calculate position for annotation with id ${annotationRegistration?.id}`); return; } annotation.annotation.setCoordinates(position.x, position.y); if (!annotation.annotation.isShowing()) { annotation.annotation.show(placement.parentElement, placement.insertBefore); } } }