UNPKG

chrome-devtools-frontend

Version:
249 lines (231 loc) • 9.46 kB
// Copyright 2024 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type * as Protocol from '../../generated/protocol.js'; import {UserVisibleError} from '../platform/platform.js'; import type { HydratingDataPerTarget, RehydratingExecutionContext, RehydratingScript, RehydratingTarget} from './RehydratingObject.js'; interface RehydratingTraceBase { cat: string; pid: number; args: {data: object}; } interface TraceEventTargetRundown extends RehydratingTraceBase { cat: 'disabled-by-default-devtools.target-rundown'; args: { data: { frame: Protocol.Page.FrameId, frameType: string, url: string, isolate: string, v8context: string, origin: string, scriptId: Protocol.Runtime.ScriptId, isDefault?: boolean, contextType?: string, }, }; } interface TraceEventScriptRundown extends RehydratingTraceBase { cat: 'disabled-by-default-devtools.v8-source-rundown'; args: { data: { isolate: string, executionContextId: Protocol.Runtime.ExecutionContextId, scriptId: Protocol.Runtime.ScriptId, startLine: number, startColumn: number, endLine: number, endColumn: number, url: string, hash: string, isModule: boolean, hasSourceUrl: boolean, sourceMapUrl?: string, }, }; } interface TraceEventScriptRundownSource extends RehydratingTraceBase { cat: 'disabled-by-default-devtools.v8-source-rundown-sources'; args: { data: { isolate: string, scriptId: Protocol.Runtime.ScriptId, length?: number, sourceText?: string, }, }; } export class EnhancedTracesParser { #scriptRundownEvents: TraceEventScriptRundown[] = []; #scriptToV8Context: Map<string, string> = new Map<string, string>(); #scriptToScriptSource: Map<string, string> = new Map<string, string>(); #largeScriptToScriptSource: Map<string, string[]> = new Map<string, string[]>(); #scriptToSourceLength: Map<string, number> = new Map<string, number>(); #targets: RehydratingTarget[] = []; #executionContexts: RehydratingExecutionContext[] = []; #scripts: RehydratingScript[] = []; static readonly enhancedTraceVersion: number = 1; constructor(traceEvents: unknown[]) { // Initialize with the trace events provided. try { this.parseEnhancedTrace(traceEvents); } catch (e) { throw new UserVisibleError.UserVisibleError(e); } } parseEnhancedTrace(traceEvents: unknown[]): void { for (const event of traceEvents) { if (this.isTargetRundownEvent(event)) { // Set up script to v8 context mapping const data = event.args?.data; this.#scriptToV8Context.set(this.getScriptIsolateId(data.isolate, data.scriptId), data.v8context); // Add target if (!this.#targets.find(target => target.targetId === data.frame)) { this.#targets.push({ targetId: data.frame, type: data.frameType, isolate: data.isolate, pid: event.pid, url: data.url, }); } // Add execution context, need to put back execution context id with info from other traces if (!this.#executionContexts.find(executionContext => executionContext.v8Context === data.v8context)) { this.#executionContexts.push({ id: -1 as Protocol.Runtime.ExecutionContextId, origin: data.origin, v8Context: data.v8context, auxData: { frameId: data.frame, isDefault: data.isDefault, type: data.contextType, }, isolate: data.isolate, }); } } else if (this.isScriptRundownEvent(event)) { this.#scriptRundownEvents.push(event); const data = event.args.data; // Add script if (!this.#scripts.find(script => script.scriptId === data.scriptId && script.isolate === data.isolate)) { this.#scripts.push({ scriptId: data.scriptId, isolate: data.isolate, executionContextId: data.executionContextId, startLine: data.startLine, startColumn: data.startColumn, endLine: data.endLine, endColumn: data.endColumn, hash: data.hash, isModule: data.isModule, url: data.url, hasSourceUrl: data.hasSourceUrl, sourceMapUrl: data.sourceMapUrl, }); } } else if (this.isScriptRundownSourceEvent(event)) { // Set up script to source text and length mapping const data = event.args.data; const scriptIsolateId = this.getScriptIsolateId(data.isolate, data.scriptId); if ('splitIndex' in data && 'splitCount' in data) { if (!this.#largeScriptToScriptSource.has(scriptIsolateId)) { this.#largeScriptToScriptSource.set(scriptIsolateId, new Array(data.splitCount).fill('') as string[]); } const splittedSource = this.#largeScriptToScriptSource.get(scriptIsolateId); if (splittedSource && data.sourceText) { splittedSource[data.splitIndex as number] = data.sourceText; } } else { if (data.sourceText) { this.#scriptToScriptSource.set(scriptIsolateId, data.sourceText); } if (data.length) { this.#scriptToSourceLength.set(scriptIsolateId, data.length); } } } } } data(): HydratingDataPerTarget { // Put back execution context id const v8ContextToExecutionContextId: Map<string, Protocol.Runtime.ExecutionContextId> = new Map<string, Protocol.Runtime.ExecutionContextId>(); this.#scriptRundownEvents.forEach(scriptRundownEvent => { const data = scriptRundownEvent.args.data; const v8Context = this.#scriptToV8Context.get(this.getScriptIsolateId(data.isolate, data.scriptId)); if (v8Context) { v8ContextToExecutionContextId.set(v8Context, data.executionContextId); } }); this.#executionContexts.forEach(executionContext => { if (executionContext.v8Context) { const id = v8ContextToExecutionContextId.get(executionContext.v8Context); if (id) { executionContext.id = id; } } }); // Put back script source text and length this.#scripts.forEach(script => { const scriptIsolateId = this.getScriptIsolateId(script.isolate, script.scriptId); if (this.#scriptToScriptSource.has(scriptIsolateId)) { script.sourceText = this.#scriptToScriptSource.get(scriptIsolateId); script.length = this.#scriptToSourceLength.get(scriptIsolateId); } else if (this.#largeScriptToScriptSource.has(scriptIsolateId)) { const splittedSources = this.#largeScriptToScriptSource.get(scriptIsolateId); if (splittedSources) { script.sourceText = splittedSources.join(''); script.length = script.sourceText.length; } } // put in the aux data script.auxData = this.#executionContexts .find(context => context.id === script.executionContextId && context.isolate === script.isolate) ?.auxData; }); const data = new Map<RehydratingTarget, [RehydratingExecutionContext[], RehydratingScript[]]>(); for (const target of this.#targets) { data.set(target, this.groupContextsAndScriptsUnderTarget(target, this.#executionContexts, this.#scripts)); } return data; } private getScriptIsolateId(isolate: string, scriptId: Protocol.Runtime.ScriptId): string { return scriptId + '@' + isolate; } private isTraceEvent(event: unknown): event is RehydratingTraceBase { return 'cat' in (event as RehydratingTraceBase) && 'pid' in (event as RehydratingTraceBase) && 'args' in (event as RehydratingTraceBase) && 'data' in (event as RehydratingTraceBase).args; } private isTargetRundownEvent(event: unknown): event is TraceEventTargetRundown { return this.isTraceEvent(event) && event.cat === 'disabled-by-default-devtools.target-rundown'; } private isScriptRundownEvent(event: unknown): event is TraceEventScriptRundown { return this.isTraceEvent(event) && event.cat === 'disabled-by-default-devtools.v8-source-rundown'; } private isScriptRundownSourceEvent(event: unknown): event is TraceEventScriptRundownSource { return this.isTraceEvent(event) && event.cat === 'disabled-by-default-devtools.v8-source-rundown-sources'; } private groupContextsAndScriptsUnderTarget( target: RehydratingTarget, executionContexts: RehydratingExecutionContext[], scripts: RehydratingScript[]): [RehydratingExecutionContext[], RehydratingScript[]] { const filteredExecutionContexts: RehydratingExecutionContext[] = []; const filteredScripts: RehydratingScript[] = []; for (const executionContext of executionContexts) { if (executionContext.auxData?.frameId === target.targetId) { filteredExecutionContexts.push(executionContext); } } for (const script of scripts) { if (script.auxData === null) { console.error(script + ' missing aux data'); } if (script.auxData?.frameId === target.targetId) { filteredScripts.push(script); } } return [filteredExecutionContexts, filteredScripts]; } }