// 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;
}
}