chrome-devtools-frontend
Version:
Chrome DevTools UI
1,672 lines (1,513 loc) • 96.7 kB
text/typescript
// Copyright 2022 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 Platform from '../../../core/platform/platform.js';
import type * as Protocol from '../../../generated/protocol.js';
import type {Micro, Milli, Seconds, TraceWindowMicro} from './Timing.js';
// Trace Events.
export const enum Phase {
// Standard
BEGIN = 'B',
END = 'E',
COMPLETE = 'X',
INSTANT = 'I',
COUNTER = 'C',
// Async
ASYNC_NESTABLE_START = 'b',
ASYNC_NESTABLE_INSTANT = 'n',
ASYNC_NESTABLE_END = 'e',
ASYNC_STEP_INTO = 'T',
ASYNC_BEGIN = 'S',
ASYNC_END = 'F',
ASYNC_STEP_PAST = 'p',
// Flow
FLOW_START = 's',
FLOW_STEP = 't',
FLOW_END = 'f',
// Sample
SAMPLE = 'P',
// Object
OBJECT_CREATED = 'N',
OBJECT_SNAPSHOT = 'O',
OBJECT_DESTROYED = 'D',
// Metadata
METADATA = 'M',
// Memory Dump
MEMORY_DUMP_GLOBAL = 'V',
MEMORY_DUMP_PROCESS = 'v',
// Mark
MARK = 'R',
// Clock sync
CLOCK_SYNC = 'c',
}
export function isNestableAsyncPhase(phase: Phase): boolean {
return phase === Phase.ASYNC_NESTABLE_START || phase === Phase.ASYNC_NESTABLE_END ||
phase === Phase.ASYNC_NESTABLE_INSTANT;
}
export function isPhaseAsync(phase: Phase): boolean {
return isNestableAsyncPhase(phase) || phase === Phase.ASYNC_BEGIN || phase === Phase.ASYNC_STEP_INTO ||
phase === Phase.ASYNC_END || phase === Phase.ASYNC_STEP_PAST;
}
export function isFlowPhase(phase: Phase): boolean {
return phase === Phase.FLOW_START || phase === Phase.FLOW_STEP || phase === Phase.FLOW_END;
}
export const enum Scope {
THREAD = 't',
PROCESS = 'p',
GLOBAL = 'g',
}
export interface Event {
args?: Args;
cat: string;
name: string;
ph: Phase;
pid: ProcessID;
tid: ThreadID;
tts?: Micro;
ts: Micro;
tdur?: Micro;
dur?: Micro;
}
export interface Args {
data?: ArgsData;
sampleTraceId?: number;
stackTrace?: CallFrame[];
}
export interface ArgsData {
stackTrace?: CallFrame[];
sampleTraceId?: number;
url?: string;
navigationId?: string;
frame?: string;
}
export interface CallFrame {
codeType?: string;
functionName: string;
// Trace events are inconsistent here sadly :(
scriptId: number|string;
columnNumber: number;
lineNumber: number;
url: string;
}
export function objectIsCallFrame(object: object): object is CallFrame {
return ('functionName' in object && typeof object.functionName === 'string') &&
('scriptId' in object && (typeof object.scriptId === 'string' || typeof object.scriptId === 'number')) &&
('columnNumber' in object && typeof object.columnNumber === 'number') &&
('lineNumber' in object && typeof object.lineNumber === 'number') &&
('url' in object && typeof object.url === 'string');
}
export interface TraceFrame {
frame: string;
name: string;
processId: ProcessID;
url: string;
parent?: string;
// Added to Chromium in April 2024:
// crrev.com/c/5424783
isOutermostMainFrame?: boolean;
// Added to Chromium in June 2024:
// crrev.com/c/5595033
isInPrimaryMainFrame?: boolean;
}
// Sample events.
export interface Sample extends Event {
ph: Phase.SAMPLE;
}
/**
* A fake trace event created to support CDP.Profiler.Profiles in the
* trace engine.
*/
export interface SyntheticCpuProfile extends Instant, SyntheticBased<Phase.INSTANT> {
name: 'CpuProfile';
args: Args&{
data: ArgsData & {
cpuProfile: Protocol.Profiler.Profile,
},
};
}
export interface Profile extends Sample {
name: 'Profile';
id: ProfileID;
args: Args&{
data: ArgsData & {
startTime: Micro,
},
};
}
export interface ProfileChunk extends Sample {
name: 'ProfileChunk';
id: ProfileID;
args: Args&{
// `data` is only missing in "fake" traces
data?: ArgsData & {
cpuProfile?: PartialProfile,
timeDeltas?: Micro[],
lines?: Micro[],
},
};
}
export interface PartialProfile {
nodes?: PartialNode[];
samples: CallFrameID[];
/**
* Contains trace ids assigned to samples, if any. Trace ids are
* keyed by the sample index in the profile (the keys of the object
* are strings containing the numeric index).
*/
/* eslint-disable @typescript-eslint/naming-convention */
trace_ids?: Record<string, number>;
/* eslint-enable @typescript-eslint/naming-convention */
}
export interface PartialNode {
callFrame: CallFrame;
id: CallFrameID;
parent?: CallFrameID;
}
// Complete events.
export interface Complete extends Event {
ph: Phase.COMPLETE;
dur: Micro;
}
export interface RunTask extends Complete {
name: Name.RUN_TASK;
}
export function isRunTask(event: Event): event is RunTask {
return event.name === Name.RUN_TASK;
}
export interface FireIdleCallback extends Complete {
name: Name.FIRE_IDLE_CALLBACK;
args: Args&{
data: ArgsData & {
allottedMilliseconds: Milli,
frame: string,
id: number,
timedOut: boolean,
},
};
}
export interface SchedulePostMessage extends Instant {
name: Name.SCHEDULE_POST_MESSAGE;
args: Args&{
data: ArgsData & {
traceId: string,
},
};
}
export interface HandlePostMessage extends Complete {
name: Name.HANDLE_POST_MESSAGE;
args: Args&{
data: ArgsData & {
traceId: string,
},
};
}
export interface Dispatch extends Complete {
name: 'EventDispatch';
args: Args&{
data: ArgsData & {
type: string,
},
};
}
export interface ParseHTML extends Complete {
name: 'ParseHTML';
args: Args&{
beginData: {
frame: string,
startLine: number,
url: string,
sampleTraceId?: number,
},
endData?: {
endLine: number,
},
};
}
export interface Begin extends Event {
ph: Phase.BEGIN;
}
export interface End extends Event {
ph: Phase.END;
}
/**
* This denotes a complete event created from a pair of begin and end
* events. For practicality, instead of always having to look for the
* end event corresponding to a begin event, we create a synthetic
* complete event that comprises the data of both from the beginning in
* the RendererHandler.
*/
export type SyntheticComplete = Complete;
// TODO(paulirish): Migrate to the new (Sept 2024) EventTiming trace events.
// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/window_performance.cc;l=900-901;drc=b503c262e425eae59ced4a80d59d176ed07152c7
export type EventTimingBeginOrEnd = EventTimingBegin|EventTimingEnd;
export interface EventTimingBegin extends Event {
ph: Phase.ASYNC_NESTABLE_START;
name: Name.EVENT_TIMING;
id: string;
args: Args&{
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/performance_event_timing.cc;l=297;drc=4f00803ca25c0d0480ed14844d6406933c21e80e
data: ArgsData & {
cancelable: boolean,
duration: Milli,
type: string,
interactionId: number,
interactionOffset: number,
nodeId: Protocol.DOM.BackendNodeId,
frame?: string, // From May 2022 onwards, this is where frame is located. https://chromium-review.googlesource.com/c/chromium/src/+/3632661
processingEnd?: Milli,
processingStart?: Milli,
timeStamp?: Milli,
enqueuedToMainThreadTime?: Milli,
commitFinishTime?: Milli,
},
frame?: string, // Prior to May 2022, `frame` was here in args.
};
}
export interface EventTimingEnd extends Event {
ph: Phase.ASYNC_NESTABLE_END;
name: Name.EVENT_TIMING;
id: string;
args: Args;
}
export interface GPUTask extends Complete {
name: 'GPUTask';
args: Args&{
data?: ArgsData & {
/* eslint-disable @typescript-eslint/naming-convention */
renderer_pid: ProcessID,
used_bytes: number,
/* eslint-enable @typescript-eslint/naming-convention */
},
};
}
export interface SyntheticNetworkRedirect {
url: string;
priority: string;
requestMethod?: string;
ts: Micro;
dur: Micro;
}
// ProcessedArgsData is used to store the processed data of a network
// request. Which is used to distinguish from the date we extract from the
// trace event directly.
interface SyntheticArgsData {
dnsLookup: Micro;
download: Micro;
downloadStart: Micro;
finishTime: Micro;
initialConnection: Micro;
isDiskCached: boolean;
isHttps: boolean;
isMemoryCached: boolean;
isPushedResource: boolean;
networkDuration: Micro;
processingDuration: Micro;
proxyNegotiation: Micro;
queueing: Micro;
redirectionDuration: Micro;
requestSent: Micro;
sendStartTime: Micro;
ssl: Micro;
stalled: Micro;
totalTime: Micro;
/** Server response time (receiveHeadersEnd - sendEnd) */
waiting: Micro;
}
export interface SyntheticNetworkRequest extends Complete, SyntheticBased<Phase.COMPLETE> {
rawSourceEvent: ResourceSendRequest;
args: Args&{
data: ArgsData & {
syntheticData: SyntheticArgsData,
// All fields below are from TraceEventsForNetworkRequest,
// Required fields
/** Size of the resource after decompression (if applicable). */
decodedBodyLength: number,
/**
* Size of the resource over the network. Includes size of headers and
* anything else in the HTTP response packet.
*/
encodedDataLength: number,
frame: string,
fromServiceWorker: boolean|undefined,
isLinkPreload: boolean,
/** Empty string if no response. */
mimeType: string,
priority: Protocol.Network.ResourcePriority,
initialPriority: Protocol.Network.ResourcePriority,
/**
* This is the protocol used to resolve the request.
*
* Note, this is not the same as URL.protocol.
*
* Example values (not exhaustive): http/0.9, http/1.0, http/1.1, http, h2, h3-Q050, data, blob
*/
protocol: string,
redirects: SyntheticNetworkRedirect[],
renderBlocking: RenderBlocking,
requestId: string,
requestingFrameUrl: string,
/** 0 if no response. */
statusCode: number,
resourceType: Protocol.Network.ResourceType,
responseHeaders: Array<{name: string, value: string}>|null,
fetchPriorityHint: FetchPriorityHint,
url: string,
/** True only if got a 'resourceFinish' event indicating a failure. */
failed: boolean,
/** True only if got a 'resourceFinish' event. Note even failed requests with no response may be "finished". */
finished: boolean,
hasResponse: boolean,
/** If undefined, trace was either too old or had no response. */
connectionId: number|undefined,
/** If undefined, trace was either too old or had no response. */
connectionReused: boolean|undefined,
// Optional fields
initiator?: Initiator,
requestMethod?: string,
timing?: ResourceReceiveResponseTimingData,
},
};
cat: 'loading';
name: Name.SYNTHETIC_NETWORK_REQUEST;
ph: Phase.COMPLETE;
dur: Micro;
tdur: Micro;
ts: Micro;
tts: Micro;
pid: ProcessID;
tid: ThreadID;
}
export interface SyntheticWebSocketConnection extends Complete, SyntheticBased<Phase.COMPLETE> {
rawSourceEvent: Event;
args: Args&{
data: ArgsData & {
identifier: number,
priority: Protocol.Network.ResourcePriority,
url: string,
},
};
cat: string;
name: 'SyntheticWebSocketConnection';
ph: Phase.COMPLETE;
dur: Micro;
ts: Micro;
pid: ProcessID;
tid: ThreadID;
s: Scope;
}
export const enum AuctionWorkletType {
BIDDER = 'bidder',
SELLER = 'seller',
// Not expected to be used, but here as a fallback in case new types get
// added and we have yet to update the trace engine.
UNKNOWN = 'unknown',
}
export interface SyntheticAuctionWorklet extends Instant, SyntheticBased<Phase.INSTANT> {
rawSourceEvent: Event;
name: 'SyntheticAuctionWorklet';
// The PID that the AuctionWorklet is running in.
pid: ProcessID;
// URL
host: string;
// An ID used to pair up runningInProcessEvents with doneWithProcessEvents
target: string;
type: AuctionWorkletType;
args: Args&{
data: ArgsData & {
// There are two threads for a worklet that we care about, so we gather
// the thread_name events so we can know the PID and TID for them (and
// hence display the right events in the track for each thread)
utilityThread: ThreadName,
v8HelperThread: ThreadName,
} &
(
// This type looks odd, but this is because these events could either have:
// 1. Just the DoneWithProcess event
// 2. Just the RunningInProcess event
// 3. Both events
// But crucially it cannot have both events missing, hence listing all the
// allowed cases.
// Clang is disabled as the combination of nested types and optional
// properties cause it to weirdly indent some of the properties and make it
// very unreadable.
// clang-format off
{
runningInProcessEvent: AuctionWorkletRunningInProcess,
doneWithProcessEvent: AuctionWorkletDoneWithProcess,
} |
{
doneWithProcessEvent: AuctionWorkletDoneWithProcess,
runningInProcessEvent?: AuctionWorkletRunningInProcess,
} |
{
runningInProcessEvent: AuctionWorkletRunningInProcess,
doneWithProcessEvent?: AuctionWorkletDoneWithProcess,
}),
// clang-format on
};
}
export interface AuctionWorkletRunningInProcess extends Event {
name: 'AuctionWorkletRunningInProcess';
ph: Phase.INSTANT;
args: Args&{
data: ArgsData & {
host: string,
pid: ProcessID,
target: string,
type: AuctionWorkletType,
},
};
}
export interface AuctionWorkletDoneWithProcess extends Event {
name: 'AuctionWorkletDoneWithProcess';
ph: Phase.INSTANT;
args: Args&{
data: ArgsData & {
host: string,
pid: ProcessID,
target: string,
type: AuctionWorkletType,
},
};
}
export function isAuctionWorkletRunningInProcess(event: Event): event is AuctionWorkletRunningInProcess {
return event.name === 'AuctionWorkletRunningInProcess';
}
export function isAuctionWorkletDoneWithProcess(event: Event): event is AuctionWorkletDoneWithProcess {
return event.name === 'AuctionWorkletDoneWithProcess';
}
// Snapshot events.
/**
* In January 2025 when crrev.com/c/6197645 landed, it changed the format of screenshot events.
* That is why we two screenshot types:
* `LegacyScreenshot` and `LegacySyntheticScreenshot`: BEFORE the above CL.
* `Screenshot`: AFTER the above CL.
* Important things to note:
* 1. Both the "old" and "new" events share the name "Screenshot" but their format is very different.
* 2. The old events had both a raw event (LegacyScreenshot) and a synthetic
* event (LegacySyntheticScreenshot). The new events only have a raw event, as
* we do not need the additional complexity of a synthetic event.
* 3. Because we like to support "old" traces, DevTools will maintain its
* support for both screenshot events for the foreseeable future. If you are
* consuming screenshot events from the ScreenshotHandler, you must make sure
* to have your code deal with the two different formats.
*/
// These are nullable because in January 2025 a CL in Chromium
export interface LegacyScreenshot extends Event {
/**
* @deprecated This value is incorrect. Use ScreenshotHandler.getPresentationTimestamp()
*/
ts: Micro;
/** The id is the frame sequence number in hex */
id: string;
args: Args&{
snapshot: string,
};
name: Name.SCREENSHOT;
cat: 'disabled-by-default-devtools.screenshot';
ph: Phase.OBJECT_SNAPSHOT;
}
export function isLegacyScreenshot(event: Event): event is LegacyScreenshot {
return event.name === Name.SCREENSHOT && 'id' in event;
}
export function isLegacySyntheticScreenshot(event: Event): event is LegacySyntheticScreenshot {
return event.name === Name.SCREENSHOT && 'dataUri' in (event.args ?? {});
}
export function isScreenshot(event: Event): event is Screenshot {
return event.name === Name.SCREENSHOT && 'source_id' in (event.args ?? {});
}
export interface LegacySyntheticScreenshot extends Event, SyntheticBased {
rawSourceEvent: LegacyScreenshot;
/** This is the correct presentation timestamp. */
ts: Micro;
args: Args&{
dataUri: string,
};
name: Name.SCREENSHOT;
cat: 'disabled-by-default-devtools.screenshot';
ph: Phase.OBJECT_SNAPSHOT;
}
export interface Screenshot extends Instant {
args: Args&{
snapshot: string,
// eslint-disable-next-line @typescript-eslint/naming-convention
source_id: number,
// eslint-disable-next-line @typescript-eslint/naming-convention
frame_sequence: number,
// eslint-disable-next-line @typescript-eslint/naming-convention
expected_display_time: number,
};
}
// Animation events.
export interface Animation extends Event {
args: Args&{
data: ArgsData & {
nodeName?: string,
nodeId?: Protocol.DOM.BackendNodeId,
displayName?: string,
id?: string,
name?: string,
state?: string,
compositeFailed?: number,
unsupportedProperties?: string[],
},
};
name: 'Animation';
id2?: {
local?: string,
};
ph: Phase.ASYNC_NESTABLE_START|Phase.ASYNC_NESTABLE_END|Phase.ASYNC_NESTABLE_INSTANT;
}
// Metadata events.
export interface Metadata extends Event {
ph: Phase.METADATA;
args: Args&{
name?: string,
uptime?: string,
};
}
export interface ThreadName extends Metadata {
name: Name.THREAD_NAME;
args: Args&{
name?: string,
};
}
export interface ProcessName extends Metadata {
name: 'process_name';
}
// Mark events.
export interface Mark extends Event {
ph: Phase.MARK;
}
export interface NavigationStart extends Mark {
name: 'navigationStart';
args: Args&{
frame: string,
data?: ArgsData&{
/** Must be non-empty to be valid. An empty documentLoaderURL means the event can be ignored. */
documentLoaderURL: string,
isLoadingMainFrame: boolean,
navigationId: string,
/**
* `isOutermostMainFrame` was introduced in crrev.com/c/3625434 and exists because of Fenced Frames
* [github.com/WICG/fenced-frame/tree/master/explainer]. Fenced frames introduce a situation where
* `isLoadingMainFrame` could be true for a navigation, but that navigation be within an embedded "main frame", and
* therefore it wouldn't be on the top level main frame. In situations where we need to distinguish that, we can
* rely on `isOutermostMainFrame`, which will only be true for navigations on the top level main frame.
* This flag is optional as it was introduced in May 2022; so users reasonably may import traces from before that
* date that do not have this field present.
*/
isOutermostMainFrame?: boolean,
/**
* @deprecated use documentLoaderURL for navigation events URLs
*/
url?: string,
},
};
}
export interface FirstContentfulPaint extends Mark {
name: Name.MARK_FCP;
args: Args&{
frame: string,
data?: ArgsData&{
navigationId: string,
},
};
}
export interface FirstPaint extends Mark {
name: 'firstPaint';
args: Args&{
frame: string,
data?: ArgsData&{
navigationId: string,
},
};
}
export type PageLoadEvent = FirstContentfulPaint|MarkDOMContent|InteractiveTime|LargestContentfulPaintCandidate|
LayoutShift|FirstPaint|MarkLoad|NavigationStart;
const markerTypeGuards = [
isMarkDOMContent,
isMarkLoad,
isFirstPaint,
isFirstContentfulPaint,
isLargestContentfulPaintCandidate,
isNavigationStart,
];
export const MarkerName =
['MarkDOMContent', 'MarkLoad', 'firstPaint', 'firstContentfulPaint', 'largestContentfulPaint::Candidate'] as const;
export interface MarkerEvent extends Event {
name: typeof MarkerName[number];
}
export function isMarkerEvent(event: Event): event is MarkerEvent {
if (event.ph === Phase.INSTANT || event.ph === Phase.MARK) {
return markerTypeGuards.some(fn => fn(event));
}
return false;
}
const pageLoadEventTypeGuards = [
...markerTypeGuards,
isInteractiveTime,
];
export function eventIsPageLoadEvent(event: Event): event is PageLoadEvent {
if (event.ph === Phase.INSTANT || event.ph === Phase.MARK) {
return pageLoadEventTypeGuards.some(fn => fn(event));
}
return false;
}
export interface LargestContentfulPaintCandidate extends Mark {
name: Name.MARK_LCP_CANDIDATE;
args: Args&{
frame: string,
data?: ArgsData&{
candidateIndex: number,
isOutermostMainFrame: boolean,
isMainFrame: boolean,
navigationId: string,
nodeId: Protocol.DOM.BackendNodeId,
loadingAttr: string,
type?: string,
},
};
}
export interface LargestImagePaintCandidate extends Mark {
name: 'LargestImagePaint::Candidate';
args: Args&{
frame: string,
data?: ArgsData&{
candidateIndex: number,
imageUrl: string,
// eslint-disable-next-line @typescript-eslint/naming-convention
DOMNodeId: Protocol.DOM.BackendNodeId,
},
};
}
export interface LargestTextPaintCandidate extends Mark {
name: 'LargestTextPaint::Candidate';
args: Args&{
frame: string,
data?: ArgsData&{
candidateIndex: number,
// eslint-disable-next-line @typescript-eslint/naming-convention
DOMNodeId: Protocol.DOM.BackendNodeId,
},
};
}
export interface InteractiveTime extends Mark {
name: 'InteractiveTime';
args: Args&{
args: {
// eslint-disable-next-line @typescript-eslint/naming-convention
total_blocking_time_ms: number,
},
frame: string,
};
}
// Instant events.
export interface Instant extends Event {
ph: Phase.INSTANT;
s: Scope;
}
export interface DOMStats extends Instant {
name: 'DOMStats';
args: Args&{
data: ArgsData & {
frame: string,
totalElements: number,
maxChildren?: {
nodeId: Protocol.DOM.BackendNodeId,
nodeName: string,
numChildren: number,
},
maxDepth?: {
nodeId: Protocol.DOM.BackendNodeId,
nodeName: string,
depth: number,
},
},
};
}
export interface UpdateCounters extends Instant {
name: 'UpdateCounters';
args: Args&{
data: ArgsData & {
documents: number,
jsEventListeners: number,
jsHeapSizeUsed: number,
nodes: number,
gpuMemoryLimitKB?: number,
},
};
}
export type RendererEvent = Instant|Complete;
export interface TracingStartedInBrowser extends Instant {
name: Name.TRACING_STARTED_IN_BROWSER;
args: Args&{
data?: ArgsData & {
frameTreeNodeId: number,
// Frames can only missing in "fake" traces
persistentIds: boolean,
frames?: TraceFrame[],
},
};
}
export interface TracingSessionIdForWorker extends Instant {
name: 'TracingSessionIdForWorker';
args: Args&{
data?: ArgsData & {
url: string,
workerId: WorkerId,
workerThreadId: ThreadID,
frame: string,
},
};
}
export function isTracingSessionIdForWorker(event: Event): event is TracingSessionIdForWorker {
return event.name === 'TracingSessionIdForWorker';
}
export interface FrameCommittedInBrowser extends Instant {
name: 'FrameCommittedInBrowser';
args: Args&{
data?: ArgsData & TraceFrame,
};
}
export interface MainFrameViewport extends Instant {
name: 'PaintTimingVisualizer::Viewport';
args: {
data: ArgsData&{
// eslint-disable-next-line @typescript-eslint/naming-convention
viewport_rect: number[],
/** Device Pixel Ratio. Added in m128 */
dpr: number,
},
};
}
export interface CommitLoad extends Instant {
name: 'CommitLoad';
args: Args&{
data?: ArgsData & {
frame: string,
isMainFrame: boolean,
name: string,
nodeId: number,
page: string,
parent: string,
url: string,
},
};
}
export interface MarkDOMContent extends Instant {
name: 'MarkDOMContent';
args: Args&{
data?: ArgsData & {
frame: string,
isMainFrame: boolean,
page: string,
isOutermostMainFrame?: boolean,
},
};
}
export interface MarkLoad extends Instant {
name: 'MarkLoad';
args: Args&{
data?: ArgsData & {
frame: string,
isMainFrame: boolean,
page: string,
isOutermostMainFrame?: boolean,
},
};
}
export interface Async extends Event {
ph: Phase.ASYNC_NESTABLE_START|Phase.ASYNC_NESTABLE_INSTANT|Phase.ASYNC_NESTABLE_END|Phase.ASYNC_STEP_INTO|
Phase.ASYNC_BEGIN|Phase.ASYNC_END|Phase.ASYNC_STEP_PAST;
}
export type TraceRect = [number, number, number, number];
export interface TraceImpactedNode {
// These keys come from the trace data, so we have to use underscores.
/* eslint-disable @typescript-eslint/naming-convention */
new_rect: TraceRect;
node_id: Protocol.DOM.BackendNodeId;
old_rect: TraceRect;
/* eslint-enable @typescript-eslint/naming-convention */
}
type LayoutShiftData = ArgsData&{
// These keys come from the trace data, so we have to use underscores.
/* eslint-disable @typescript-eslint/naming-convention */
cumulative_score: number,
frame_max_distance: number,
had_recent_input: boolean,
impacted_nodes: TraceImpactedNode[] | undefined,
is_main_frame: boolean,
overall_max_distance: number,
region_rects: TraceRect[],
/** @deprecated This value will incorrectly overreport for shifts within an iframe. */
score: number,
/** This is the preferred "score", used for CLS. If `is_main_frame` is true, `score` and `weighted_score_delta` will be equal. But if the shift is from an iframe, `weighted_score_delta` will be appropriately reduced to account for the viewport size of that iframe. https://wicg.github.io/layout-instability/#subframe-weighting-factor and b/275509162 */
weighted_score_delta: number,
navigationId?: string,
/* eslint-enable @typescript-eslint/naming-convention */
};
export interface LayoutShift extends Instant {
name: Name.LAYOUT_SHIFT;
normalized?: boolean;
args: Args&{
frame: string,
data?: LayoutShiftData,
};
}
interface LayoutShiftSessionWindowData {
// The sum of the weighted score of all the shifts
// that belong to a session window.
cumulativeWindowScore: number;
// A consecutive generated in the frontend to
// to identify a session window.
id: number;
}
export interface LayoutShiftParsedData {
/** screenshot taken before and after this shift. Before *should* always exist, but after might not at the end of a trace. */
screenshots: {before: LegacySyntheticScreenshot|Screenshot|null, after: LegacySyntheticScreenshot|Screenshot|null};
timeFromNavigation?: Micro;
// The sum of the weighted scores of the shifts that
// belong to a session window up until this shift
// (inclusive).
cumulativeWeightedScoreInWindow: number;
sessionWindowData: LayoutShiftSessionWindowData;
}
export interface SyntheticLayoutShift extends Omit<LayoutShift, 'name'>, SyntheticBased<Phase.INSTANT> {
name: Name.SYNTHETIC_LAYOUT_SHIFT;
rawSourceEvent: LayoutShift;
args: Args&{
frame: string,
data?: LayoutShiftData&{
rawEvent: LayoutShift,
},
};
parsedData: LayoutShiftParsedData;
}
export const NO_NAVIGATION = 'NO_NAVIGATION';
/**
* This maybe be a navigation id string from Chromium, or `NO_NAVIGATION`, which represents the
* portion of the trace for which we don't have any navigation event for (as it happeneded prior
* to the trace start).
*/
export type NavigationId = string|typeof NO_NAVIGATION;
/**
* This is a synthetic Layout shift cluster. Not based on a raw event as there's no concept
* of this as a trace event.
*/
export interface SyntheticLayoutShiftCluster {
name: 'SyntheticLayoutShiftCluster';
clusterWindow: TraceWindowMicro;
clusterCumulativeScore: number;
events: SyntheticLayoutShift[];
// For convenience we split apart the cluster into good, NI, and bad windows.
// Since a cluster may remain in the good window, we mark NI and bad as being
// possibly null.
scoreWindows: {
good: TraceWindowMicro,
needsImprovement?: TraceWindowMicro,
bad?: TraceWindowMicro,
};
// The last navigation that happened before this cluster.
navigationId?: NavigationId;
worstShiftEvent?: Event;
// This is the start of the cluster: the start of the first layout shift of the cluster.
ts: Micro;
// The duration of the cluster. This should include up until the end of the last
// layout shift in this cluster.
dur: Micro;
cat: '';
ph: Phase.COMPLETE;
pid: ProcessID;
tid: ThreadID;
}
export type FetchPriorityHint = 'low'|'high'|'auto';
export type RenderBlocking =
'blocking'|'non_blocking'|'in_body_parser_blocking'|'potentially_blocking'|'dynamically_injected_non_blocking';
export interface Initiator {
type: Protocol.Network.InitiatorType;
fetchType: string;
columnNumber?: number;
lineNumber?: number;
url?: string;
}
export interface ResourceSendRequest extends Instant {
name: 'ResourceSendRequest';
args: Args&{
data: ArgsData & {
frame: string,
requestId: string,
url: string,
priority: Protocol.Network.ResourcePriority,
/** Added Feb 2024. https://crrev.com/c/5277583 */
resourceType?: Protocol.Network.ResourceType,
/** Added Feb 2024. https://crrev.com/c/5297615 */
fetchPriorityHint?: FetchPriorityHint,
// TODO(crbug.com/1457985): change requestMethod to enum when confirm in the backend code.
requestMethod?: string,
renderBlocking?: RenderBlocking,
initiator?: Initiator,
isLinkPreload?: boolean,
},
};
}
export interface ResourceChangePriority extends Instant {
name: 'ResourceChangePriority';
args: Args&{
data: ArgsData & {
requestId: string,
priority: Protocol.Network.ResourcePriority,
},
};
}
/** Only sent for navigations. https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_instrumentation.cc;l=1612-1647;drc=ec7daf93d0479b758610c75f4e146fd4d2d6ed2b */
export interface ResourceWillSendRequest extends Instant {
name: Name.RESOURCE_WILL_SEND_REQUEST;
args: Args&{
data: ArgsData & {
requestId: string,
},
};
}
export interface ResourceFinish extends Instant {
name: 'ResourceFinish';
args: Args&{
data: ArgsData & {
decodedBodyLength: number,
didFail: boolean,
encodedDataLength: number,
finishTime: Seconds,
requestId: string,
},
};
}
export interface ResourceReceivedData extends Instant {
name: 'ResourceReceivedData';
args: Args&{
data: ArgsData & {
encodedDataLength: number,
frame: string,
requestId: string,
},
};
}
/** See https://mdn.github.io/shared-assets/images/diagrams/api/performance/timestamp-diagram.svg */
interface ResourceReceiveResponseTimingData {
connectEnd: Milli;
connectStart: Milli;
dnsEnd: Milli;
dnsStart: Milli;
proxyEnd: Milli;
proxyStart: Milli;
pushEnd: Milli;
pushStart: Milli;
receiveHeadersEnd: Milli;
receiveHeadersStart: Milli;
/** When the network service is about to handle a request, ie. just before going to the HTTP cache or going to the network for DNS/connection setup. */
requestTime: Seconds;
sendEnd: Milli;
sendStart: Milli;
sslEnd: Milli;
sslStart: Milli;
workerReady: Milli;
workerStart: Milli;
}
export interface ResourceReceiveResponse extends Instant {
name: 'ResourceReceiveResponse';
args: Args&{
data: ArgsData & {
/**
* This is the protocol used to resolve the request.
*
* Note, this is not the same as URL.protocol.
*
* Example values (not exhaustive): http/0.9, http/1.0, http/1.1, http, h2, h3-Q050, data, blob
*/
protocol: string,
encodedDataLength: number,
frame: string,
fromCache: boolean,
fromServiceWorker: boolean,
mimeType: string,
requestId: string,
responseTime: Milli,
statusCode: number,
// Some cached events don't have this field
connectionId: number,
connectionReused: boolean,
timing?: ResourceReceiveResponseTimingData,
headers?: Array<{name: string, value: string}>,
},
};
}
export interface ResourceMarkAsCached extends Instant {
name: 'ResourceMarkAsCached';
args: Args&{
data: ArgsData & {
requestId: string,
},
};
}
export const enum LayoutInvalidationReason {
SIZE_CHANGED = 'Size changed',
ATTRIBUTE = 'Attribute',
ADDED_TO_LAYOUT = 'Added to layout',
SCROLLBAR_CHANGED = 'Scrollbar changed',
REMOVED_FROM_LAYOUT = 'Removed from layout',
STYLE_CHANGED = 'Style changed',
FONTS_CHANGED = 'Fonts changed',
UNKNOWN = 'Unknown',
}
export interface LayoutInvalidationTracking extends Instant {
name: Name.LAYOUT_INVALIDATION_TRACKING;
args: Args&{
data: ArgsData & {
frame: string,
nodeId: Protocol.DOM.BackendNodeId,
reason: LayoutInvalidationReason,
nodeName?: string,
},
};
}
export interface ScheduleStyleInvalidationTracking extends Instant {
name: Name.SCHEDULE_STYLE_INVALIDATION_TRACKING;
args: Args&{
data: ArgsData & {
frame: string,
nodeId: Protocol.DOM.BackendNodeId,
invalidationSet?: string,
invalidatedSelectorId?: string,
reason?: LayoutInvalidationReason,
changedClass?: string,
changedAttribute?: string,
changedId?: string,
nodeName?: string,
stackTrace?: CallFrame[],
},
};
}
export function isScheduleStyleInvalidationTracking(event: Event): event is ScheduleStyleInvalidationTracking {
return event.name === Name.SCHEDULE_STYLE_INVALIDATION_TRACKING;
}
export const enum StyleRecalcInvalidationReason {
ANIMATION = 'Animation',
}
export interface StyleRecalcInvalidationTracking extends Instant {
name: Name.STYLE_RECALC_INVALIDATION_TRACKING;
args: Args&{
data: ArgsData & {
frame: string,
nodeId: Protocol.DOM.BackendNodeId,
reason: StyleRecalcInvalidationReason,
subtree: boolean,
nodeName?: string,
extraData?: string,
},
};
}
export function isStyleRecalcInvalidationTracking(event: Event): event is StyleRecalcInvalidationTracking {
return event.name === Name.STYLE_RECALC_INVALIDATION_TRACKING;
}
export interface StyleInvalidatorInvalidationTracking extends Instant {
name: Name.STYLE_INVALIDATOR_INVALIDATION_TRACKING;
args: Args&{
data: ArgsData & {
frame: string,
nodeId: Protocol.DOM.BackendNodeId,
reason: string,
invalidationList: Array<{id: string, classes?: string[]}>,
subtree: boolean,
nodeName?: string,
extraData?: string,
},
};
}
export function isStyleInvalidatorInvalidationTracking(event: Event): event is StyleInvalidatorInvalidationTracking {
return event.name === Name.STYLE_INVALIDATOR_INVALIDATION_TRACKING;
}
export interface BeginCommitCompositorFrame extends Instant {
name: Name.BEGIN_COMMIT_COMPOSITOR_FRAME;
args: Args&{
frame: string,
// eslint-disable-next-line @typescript-eslint/naming-convention
is_mobile_optimized: boolean,
};
}
export function isBeginCommitCompositorFrame(event: Event): event is BeginCommitCompositorFrame {
return event.name === Name.BEGIN_COMMIT_COMPOSITOR_FRAME;
}
export interface ParseMetaViewport extends Instant {
name: Name.PARSE_META_VIEWPORT;
args: Args&{
data: {
// eslint-disable-next-line @typescript-eslint/naming-convention
node_id: Protocol.DOM.BackendNodeId,
content: string,
frame?: string,
},
};
}
export function isParseMetaViewport(event: Event): event is ParseMetaViewport {
return event.name === Name.PARSE_META_VIEWPORT;
}
export interface LinkPreconnect extends Instant {
name: Name.LINK_PRECONNECT;
args: Args&{
data: {
// eslint-disable-next-line @typescript-eslint/naming-convention
node_id: Protocol.DOM.BackendNodeId,
url: string,
frame?: string,
},
};
}
export function isLinkPreconnect(event: Event): event is LinkPreconnect {
return event.name === Name.LINK_PRECONNECT;
}
export interface ScheduleStyleRecalculation extends Instant {
name: Name.SCHEDULE_STYLE_RECALCULATION;
args: Args&{
data: {
frame: string,
},
};
}
export function isScheduleStyleRecalculation(event: Event): event is ScheduleStyleRecalculation {
return event.name === Name.SCHEDULE_STYLE_RECALCULATION;
}
export interface RenderFrameImplCreateChildFrame extends Event {
name: Name.RENDER_FRAME_IMPL_CREATE_CHILD_FRAME;
/* eslint-disable @typescript-eslint/naming-convention */
args: Args&{
child_frame_token: string,
frame_token: string,
};
}
export function isRenderFrameImplCreateChildFrame(event: Event): event is RenderFrameImplCreateChildFrame {
return event.name === Name.RENDER_FRAME_IMPL_CREATE_CHILD_FRAME;
}
export interface LayoutImageUnsized extends Event {
name: Name.LAYOUT_IMAGE_UNSIZED;
args: Args&{
data: {
nodeId: Protocol.DOM.BackendNodeId,
frameId: string,
},
};
}
export function isLayoutImageUnsized(event: Event): event is LayoutImageUnsized {
return event.name === Name.LAYOUT_IMAGE_UNSIZED;
}
export interface PrePaint extends Complete {
name: 'PrePaint';
}
export interface PairableAsync extends Event {
ph: Phase.ASYNC_NESTABLE_START|Phase.ASYNC_NESTABLE_END|Phase.ASYNC_NESTABLE_INSTANT;
// The id2 field gives flexibility to explicitly specify if an event
// id is global among processes or process local. However not all
// events use it, so both kind of ids need to be marked as optional.
id2?: {local?: string, global?: string};
id?: string;
}
export interface PairableAsyncBegin extends PairableAsync {
ph: Phase.ASYNC_NESTABLE_START;
}
export interface PairableAsyncInstant extends PairableAsync {
ph: Phase.ASYNC_NESTABLE_INSTANT;
}
export interface PairableAsyncEnd extends PairableAsync {
ph: Phase.ASYNC_NESTABLE_END;
}
export interface AnimationFrame extends PairableAsync {
name: Name.ANIMATION_FRAME;
args?: AnimationFrameArgs;
}
export type AnimationFrameArgs = Args&{
animation_frame_timing_info: {
blocking_duration_ms: number,
duration_ms: number,
num_scripts: number,
},
id: string,
};
export interface AnimationFrameAsyncStart extends AnimationFrame {
ph: Phase.ASYNC_NESTABLE_START;
}
export interface AnimationFrameAsyncEnd extends AnimationFrame {
ph: Phase.ASYNC_NESTABLE_END;
}
export function isAnimationFrameAsyncStart(data: Event): data is AnimationFrameAsyncStart {
return data.name === Name.ANIMATION_FRAME && data.ph === Phase.ASYNC_NESTABLE_START;
}
export function isAnimationFrameAsyncEnd(data: Event): data is AnimationFrameAsyncEnd {
return data.name === Name.ANIMATION_FRAME && data.ph === Phase.ASYNC_NESTABLE_END;
}
export interface AnimationFramePresentation extends Event {
name: Name.ANIMATION_FRAME_PRESENTATION;
ph: Phase.ASYNC_NESTABLE_INSTANT;
args?: Args&{
id: string,
};
}
export function isAnimationFramePresentation(data: Event): data is AnimationFramePresentation {
return data.name === Name.ANIMATION_FRAME_PRESENTATION;
}
export interface UserTiming extends Event {
id2?: {local?: string, global?: string};
id?: string;
cat: 'blink.user_timing';
// Note that the timestamp for user timing trace events is set to the
// start time passed by the user at the call site of the timing (based
// on the UserTiming spec).
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/performance_user_timing.cc;l=236;drc=494419358caf690316f160a1f27d9e771a14c033
}
export interface DomLoading extends UserTiming {
name: Name.DOM_LOADING;
args: Args&{
frame?: string,
};
}
export interface BeginRemoteFontLoad extends UserTiming {
name: Name.BEGIN_REMOTE_FONT_LOAD;
args: Args&{
display: string,
id: number,
url?: string,
};
}
export interface RemoteFontLoaded extends UserTiming {
name: Name.REMOTE_FONT_LOADED;
args: Args&{
url: string,
name: string,
};
}
export type PairableUserTiming = UserTiming&PairableAsync;
export interface PerformanceMeasureBegin extends PairableUserTiming {
args: Args&{
detail?: string,
callTime?: Micro,
traceId?: number,
};
ph: Phase.ASYNC_NESTABLE_START;
}
export type PerformanceMeasureEnd = PairableUserTiming&PairableAsyncEnd;
export type PerformanceMeasure = PerformanceMeasureBegin|PerformanceMeasureEnd;
export interface PerformanceMark extends UserTiming {
args: Args&{
data?: ArgsData & {
detail?: string,
callTime?: Micro,
},
};
ph: Phase.INSTANT|Phase.MARK|Phase.ASYNC_NESTABLE_INSTANT;
}
export interface ConsoleTimeBegin extends PairableAsyncBegin {
cat: 'blink.console';
}
export interface ConsoleTimeEnd extends PairableAsyncEnd {
cat: 'blink.console';
}
export type ConsoleTime = ConsoleTimeBegin|ConsoleTimeEnd;
export interface ConsoleTimeStamp extends Event {
cat: 'devtools.timeline';
name: Name.TIME_STAMP;
ph: Phase.INSTANT;
args: Args&{
data?: ArgsData & {
// The console.timeStamp allows to pass integers as values as well
// as strings
message: string,
name?: string|number,
start?: string|number,
end?: string|number,
track?: string|number,
trackGroup?: string|number,
color?: string|number,
sampleTraceId?: number,
},
};
}
export interface SyntheticConsoleTimeStamp extends Event, SyntheticBased {
cat: 'disabled-by-default-v8.inspector';
ph: Phase.COMPLETE;
}
export interface UserTimingMeasure extends Event {
cat: 'devtools.timeline';
ph: Phase.COMPLETE;
name: Name.USER_TIMING_MEASURE;
args: Args&{
sampleTraceId: number,
traceId: number,
};
}
/** ChromeFrameReporter args for PipelineReporter event.
Matching proto: https://source.chromium.org/chromium/chromium/src/+/main:third_party/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.proto
*/
/* eslint-disable @typescript-eslint/naming-convention */
interface ChromeFrameReporter {
state: State;
enum: FrameDropReason;
/** The reason is set only if |state| is not |STATE_UPDATED_ALL|. */
reason: FrameDropReason;
frame_source: number;
/** Identifies a BeginFrameArgs (along with the source_id).
See comments in components/viz/common/frame_sinks/begin_frame_args.h. */
frame_sequence: number;
/** If this is a dropped frame (i.e. if |state| is set to |STATE_DROPPED| or
|STATE_PRESENTED_PARTIAL|), then indicates whether this frame impacts smoothness. */
affects_smoothness: boolean;
/** The type of active scroll. */
scroll_state: ScrollState;
/** If any main thread animation is active during this frame. */
has_main_animation: boolean;
/** If any compositor thread animation is active during this frame. */
has_compositor_animation: boolean;
/** If any touch-driven UX (not scroll) is active during this frame. */
has_smooth_input_main: boolean;
/** Whether the frame contained any missing content (i.e. whether there was
checkerboarding in the frame). */
has_missing_content: boolean;
/** The id of layer_tree_host that the frame has been produced for. */
layer_tree_host_id: number;
/** If total latency of PipelineReporter exceeds a certain limit. */
has_high_latency: boolean;
/** Indicate if the frame is "FORKED" (i.e. a PipelineReporter event starts at
the same frame sequence as another PipelineReporter) or "BACKFILL"
(i.e. dropped frames when there are no partial compositor updates). */
frame_type: FrameType;
/** The breakdown stage of PipelineReporter that is most likely accountable for
high latency. */
high_latency_contribution_stage: string[];
}
const enum State {
/** The frame did not have any updates to present. **/
STATE_NO_UPDATE_DESIRED = 'STATE_NO_UPDATE_DESIRED',
/** The frame presented all the desired updates (i.e. any updates requested
from both the compositor thread and main-threads were handled). **/
STATE_PRESENTED_ALL = 'STATE_PRESENTED_ALL',
/** The frame was presented with some updates, but also missed some updates
(e.g. missed updates from the main-thread, but included updates from the
compositor thread). **/
STATE_PRESENTED_PARTIAL = 'STATE_PRESENTED_PARTIAL',
/** The frame was dropped, i.e. some updates were desired for the frame, but
was not presented. **/
STATE_DROPPED = 'STATE_DROPPED',
}
const enum FrameDropReason {
REASON_UNSPECIFIED = 'REASON_UNSPECIFIED',
/** Frame was dropped by the display-compositor.
The display-compositor may drop a frame some times (e.g. the frame missed
the deadline, or was blocked on surface-sync, etc.) **/
REASON_DISPLAY_COMPOSITOR = 'REASON_DISPLAY_COMPOSITOR',
/** Frame was dropped because of the main-thread.
The main-thread may cause a frame to be dropped, e.g. if the main-thread
is running expensive javascript, or doing a lot of layout updates, etc. **/
REASON_MAIN_THREAD = 'REASON_MAIN_THREAD',
/** Frame was dropped by the client compositor.
The client compositor can drop some frames too (e.g. attempting to
recover latency, missing the deadline, etc.). **/
REASON_CLIENT_COMPOSITOR = 'REASON_CLIENT_COMPOSITOR',
}
const enum ScrollState {
SCROLL_NONE = 'SCROLL_NONE',
SCROLL_MAIN_THREAD = 'SCROLL_MAIN_THREAD',
SCROLL_COMPOSITOR_THREAD = 'SCROLL_COMPOSITOR_THREAD',
/** Used when it can't be determined whether a scroll is in progress or not. */
SCROLL_UNKNOWN = 'SCROLL_UNKNOWN',
}
const enum FrameType {
FORKED = 'FORKED',
BACKFILL = 'BACKFILL',
}
// TODO(crbug.com/409484302): Remove once Chrome migrates from
// ChromeTrackEvent.chrome_frame_reporter to ChromeTrackEvent.frame_reporter.
export interface OldChromeFrameReporterArgs {
chrome_frame_reporter: ChromeFrameReporter;
}
export interface NewChromeFrameReporterArgs {
frame_reporter: ChromeFrameReporter;
}
export interface PipelineReporter extends Event {
id2?: {
local?: string,
};
ph: Phase.ASYNC_NESTABLE_START|Phase.ASYNC_NESTABLE_END;
args: Args&(OldChromeFrameReporterArgs|NewChromeFrameReporterArgs);
}
export function isPipelineReporter(event: Event): event is PipelineReporter {
return event.name === Name.PIPELINE_REPORTER;
}
// A type used for synthetic events created based on a raw trace event.
// A branded type is used to ensure not all events can be typed as
// SyntheticBased and prevent places different to the
// SyntheticEventsManager from creating synthetic events. This is
// because synthetic events need to be registered in order to resolve
// serialized event keys into event objects, so we ensure events are
// registered at the time they are created by the SyntheticEventsManager.
export interface SyntheticBased<Ph extends Phase = Phase, T extends Event = Event> extends Event {
ph: Ph;
rawSourceEvent: T;
_tag: 'SyntheticEntryTag';
}
export function isSyntheticBased(event: Event): event is SyntheticBased {
return 'rawSourceEvent' in event;
}
// Nestable async events with a duration are made up of two distinct
// events: the begin, and the end. We need both of them to be able to
// display the right information, so we create these synthetic events.
export interface SyntheticEventPair<T extends PairableAsync = PairableAsync> extends SyntheticBased<Phase, T> {
rawSourceEvent: T;
name: T['name'];
cat: T['cat'];
id?: string;
id2?: {local?: string, global?: string};
dur: Micro;
args: Args&{
data: {
beginEvent: T & PairableAsyncBegin,
endEvent: T&PairableAsyncEnd,
instantEvents?: Array<T&PairableAsyncInstant>,
},
};
}
export type SyntheticPipelineReporterPair = SyntheticEventPair<PipelineReporter>;
export type SyntheticAnimationFramePair = SyntheticEventPair<AnimationFrame>;
export type SyntheticUserTimingPair = SyntheticEventPair<PerformanceMeasure>;
export type SyntheticConsoleTimingPair = SyntheticEventPair<ConsoleTime>;
export type SyntheticAnimationPair = SyntheticEventPair<Animation>;
export interface SyntheticInteractionPair extends SyntheticEventPair<EventTimingBeginOrEnd> {
// InteractionID and type are available within the beginEvent's data, but we
// put them on the top level for ease of access.
interactionId: number;
type: string;
// This is equivalent to startEvent.ts;
ts: Micro;
// This duration can be calculated via endEvent.ts - startEvent.ts, but we do
// that and put it here to make it easier. This also makes these events
// consistent with real events that have a dur field.
dur: Micro;
// These values are provided in the startEvent's args.data field as
// millisecond values, but during the handler phase we parse these into
// microseconds and put them on the top level for easy access.
processingStart: Micro;
processingEnd: Micro;
// These 3 values represent the breakdown of the parts of an interaction:
// 1. inputDelay: time from the user clicking to the input being handled
inputDelay: Micro;
// 2. mainThreadHandling: time spent processing the event handler
mainThreadHandling: Micro;
// 3. presentationDelay: delay between the event being processed and the frame being rendered
presentationDelay: Micro;
}
/**
* A profile call created in the frontend from samples disguised as a
* trace event.
*
* We store the sampleIndex, profileId and nodeId so that we can easily link
* back a Synthetic Trace Entry to an individual Sample trace event within a
* Profile.
*
* Because a sample contains a set of call frames representing the stack at the
* point in time that the sample was created, we also have to store the ID of
* the Node