// 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 * as Common from '../../../core/common/common.js'; import * as SDK from '../../../core/sdk/sdk.js'; import type * as Protocol from '../../../generated/protocol.js'; import * as Trace from '../../../models/trace/trace.js'; import type * as Marked from '../../../third_party/marked/marked.js'; import * as Lit from '../../../ui/lit/lit.js'; import * as PanelsCommon from '../../common/common.js'; import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js'; const {html} = Lit.StaticHtml; const {ref, createRef} = Lit.Directives; export class PerformanceAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock { constructor( private mainFrameId = '', private lookupEvent: (key: Trace.Types.File.SerializableKey) => Trace.Types.Events.Event | null = () => null) { super(); } override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null { if (token.type === 'link' && token.href.startsWith('#')) { if (token.href.startsWith('#node-')) { const nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId; const templateRef = createRef(); void this.#linkifyNode(nodeId, token.text).then(node => { if (!templateRef.value || !node) { return; } templateRef.value.textContent = ''; templateRef.value.append(node); }); return html`${token.text}`; } const event = this.lookupEvent(token.href.slice(1) as Trace.Types.File.SerializableKey); if (!event) { return html`${token.text}`; } let label = token.text; let title = ''; if (Trace.Types.Events.isSyntheticNetworkRequest(event)) { title = event.args.data.url; } else { label += ` (${event.name})`; } // eslint-disable-next-line @devtools/no-a-tags-in-lit return html` { e.stopPropagation(); void Common.Revealer.reveal(new SDK.TraceObject.RevealableEvent(event)); }}>${label}`; } return super.templateForToken(token); } // Taken from front_end/panels/timeline/components/insights/NodeLink.ts // Would be nice to move the above component to somewhere that allows the AI // Assistance panel to also use it. async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise { if (backendNodeId === undefined) { return; } const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget(); const domModel = target?.model(SDK.DOMModel.DOMModel); if (!domModel) { return undefined; } const domNodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([backendNodeId])); const node = domNodesMap?.get(backendNodeId); if (!node) { return; } if (node.frameId() !== this.mainFrameId) { return; } const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label}); return linkedNode; } }