UNPKG

chrome-devtools-frontend

Version:
1,672 lines (1,513 loc) • 96.7 kB
// 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