Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
722 lines
No EOL
29 KiB
Text
722 lines
No EOL
29 KiB
Text
// 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 * as Platform from '../../../core/platform/platform.js';
|
|
import * as Types from '../types/types.js';
|
|
import { SyntheticEventsManager } from './SyntheticEvents.js';
|
|
import { eventTimingsMicroSeconds } from './Timing.js';
|
|
/**
|
|
* Extracts the raw stack trace in known trace events. Most likely than
|
|
* not you want to use `getZeroIndexedStackTraceForEvent`, which returns
|
|
* the stack with zero based numbering. Since some trace events are
|
|
* one based this function can yield unexpected results when used
|
|
* indiscriminately.
|
|
*
|
|
* Note: this only returns the stack trace contained in the payload of
|
|
* an event, which only contains the synchronous portion of the call
|
|
* stack. If you want to obtain the whole stack trace you might need to
|
|
* use the @see Trace.Extras.StackTraceForEvent util.
|
|
*/
|
|
export function stackTraceInEvent(event) {
|
|
if (event.args?.data?.stackTrace) {
|
|
return event.args.data.stackTrace;
|
|
}
|
|
if (event.args?.stackTrace) {
|
|
return event.args.stackTrace;
|
|
}
|
|
if (Types.Events.isUpdateLayoutTree(event)) {
|
|
return event.args.beginData?.stackTrace || null;
|
|
}
|
|
if (Types.Events.isLayout(event)) {
|
|
return event.args.beginData.stackTrace ?? null;
|
|
}
|
|
if (Types.Events.isFunctionCall(event)) {
|
|
const data = event.args.data;
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
const { columnNumber, lineNumber, url, scriptId, functionName } = data;
|
|
if (lineNumber === undefined || functionName === undefined || columnNumber === undefined ||
|
|
scriptId === undefined || url === undefined) {
|
|
return null;
|
|
}
|
|
return [{ columnNumber, lineNumber, url, scriptId, functionName }];
|
|
}
|
|
if (Types.Events.isProfileCall(event)) {
|
|
// Of type Protocol.Runtime.CallFrame, handle accordingly.
|
|
const callFrame = event.callFrame;
|
|
if (!callFrame) {
|
|
return null;
|
|
}
|
|
const { columnNumber, lineNumber, url, scriptId, functionName } = callFrame;
|
|
if (lineNumber === undefined || functionName === undefined || columnNumber === undefined ||
|
|
scriptId === undefined || url === undefined) {
|
|
return null;
|
|
}
|
|
return [{ columnNumber, lineNumber, url, scriptId, functionName }];
|
|
}
|
|
return null;
|
|
}
|
|
export function extractOriginFromTrace(firstNavigationURL) {
|
|
const url = new URL(firstNavigationURL);
|
|
if (url) {
|
|
// We do this to save some space in the toolbar - seeing the `www` is less
|
|
// useful than seeing `foo.com` if it's truncated at narrow widths
|
|
if (url.host.startsWith('www.')) {
|
|
return url.host.slice(4);
|
|
}
|
|
return url.host;
|
|
}
|
|
return null;
|
|
}
|
|
// Each thread contains events. Events indicate the thread and process IDs, which are
|
|
// used to store the event in the correct process thread entry below.
|
|
export function addEventToProcessThread(event, eventsInProcessThread) {
|
|
const { tid, pid } = event;
|
|
let eventsInThread = eventsInProcessThread.get(pid);
|
|
if (!eventsInThread) {
|
|
eventsInThread = new Map();
|
|
}
|
|
let events = eventsInThread.get(tid);
|
|
if (!events) {
|
|
events = [];
|
|
}
|
|
events.push(event);
|
|
eventsInThread.set(event.tid, events);
|
|
eventsInProcessThread.set(event.pid, eventsInThread);
|
|
}
|
|
export function compareBeginAndEnd(aBeginTime, bBeginTime, aEndTime, bEndTime) {
|
|
if (aBeginTime < bBeginTime) {
|
|
return -1;
|
|
}
|
|
if (aBeginTime > bBeginTime) {
|
|
return 1;
|
|
}
|
|
if (aEndTime > bEndTime) {
|
|
return -1;
|
|
}
|
|
if (aEndTime < bEndTime) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
export function eventTimeComparator(a, b) {
|
|
const aBeginTime = a.ts;
|
|
const bBeginTime = b.ts;
|
|
const aDuration = a.dur ?? 0;
|
|
const bDuration = b.dur ?? 0;
|
|
const aEndTime = aBeginTime + aDuration;
|
|
const bEndTime = bBeginTime + bDuration;
|
|
const timeDifference = compareBeginAndEnd(aBeginTime, bBeginTime, aEndTime, bEndTime);
|
|
if (timeDifference) {
|
|
return timeDifference;
|
|
}
|
|
// If times are equal, prioritize profile calls over trace events,
|
|
// since an exactly equal timestamp with a trace event is likely
|
|
// indicates that the SamplesIntegrator meant to parent the trace
|
|
// event with the profile call.
|
|
if (Types.Events.isProfileCall(a) && !Types.Events.isProfileCall(b)) {
|
|
return -1;
|
|
}
|
|
if (Types.Events.isProfileCall(b) && !Types.Events.isProfileCall(a)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
/**
|
|
* Sorts all the events in place, in order, by their start time. If they have
|
|
* the same start time, orders them by longest first.
|
|
*/
|
|
export function sortTraceEventsInPlace(events) {
|
|
events.sort(eventTimeComparator);
|
|
}
|
|
/**
|
|
* Returns an array of ordered events that results after merging the two
|
|
* ordered input arrays.
|
|
*/
|
|
export function mergeEventsInOrder(eventsArray1, eventsArray2) {
|
|
const result = [];
|
|
let i = 0;
|
|
let j = 0;
|
|
while (i < eventsArray1.length && j < eventsArray2.length) {
|
|
const event1 = eventsArray1[i];
|
|
const event2 = eventsArray2[j];
|
|
const compareValue = eventTimeComparator(event1, event2);
|
|
if (compareValue <= 0) {
|
|
result.push(event1);
|
|
i++;
|
|
}
|
|
if (compareValue === 1) {
|
|
result.push(event2);
|
|
j++;
|
|
}
|
|
}
|
|
while (i < eventsArray1.length) {
|
|
result.push(eventsArray1[i++]);
|
|
}
|
|
while (j < eventsArray2.length) {
|
|
result.push(eventsArray2[j++]);
|
|
}
|
|
return result;
|
|
}
|
|
export function parseDevtoolsDetails(timingDetail, key) {
|
|
try {
|
|
// Attempt to parse the detail as an object that might be coming from a
|
|
// DevTools Perf extension.
|
|
// Wrapped in a try-catch because timingDetail might either:
|
|
// 1. Not be `json.parse`-able (it should, but just in case...)
|
|
// 2. Not be an object - in which case the `in` check will error.
|
|
// If we hit either of these cases, we just ignore this mark and move on.
|
|
const detailObj = JSON.parse(timingDetail);
|
|
if (!(key in detailObj)) {
|
|
return null;
|
|
}
|
|
if (!Types.Extensions.isValidExtensionPayload(detailObj[key])) {
|
|
return null;
|
|
}
|
|
return detailObj[key];
|
|
}
|
|
catch {
|
|
// No need to worry about this error, just discard this event and don't
|
|
// treat it as having any useful information for the purposes of extensions.
|
|
return null;
|
|
}
|
|
}
|
|
export function getNavigationForTraceEvent(event, eventFrameId, navigationsByFrameId) {
|
|
const navigations = navigationsByFrameId.get(eventFrameId);
|
|
if (!navigations || eventFrameId === '') {
|
|
// This event's navigation has been filtered out by the meta handler as a noise event
|
|
// or contains an empty frameId.
|
|
return null;
|
|
}
|
|
const eventNavigationIndex = Platform.ArrayUtilities.nearestIndexFromEnd(navigations, navigation => navigation.ts <= event.ts);
|
|
if (eventNavigationIndex === null) {
|
|
// This event's navigation has been filtered out by the meta handler as a noise event.
|
|
return null;
|
|
}
|
|
return navigations[eventNavigationIndex];
|
|
}
|
|
export function extractId(event) {
|
|
return event.id ?? event.id2?.global ?? event.id2?.local;
|
|
}
|
|
export function activeURLForFrameAtTime(frameId, time, rendererProcessesByFrame) {
|
|
const processData = rendererProcessesByFrame.get(frameId);
|
|
if (!processData) {
|
|
return null;
|
|
}
|
|
for (const processes of processData.values()) {
|
|
for (const processInfo of processes) {
|
|
if (processInfo.window.min > time || processInfo.window.max < time) {
|
|
continue;
|
|
}
|
|
return processInfo.frame.url;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* @param node the node attached to the profile call. Here a node represents a function in the call tree.
|
|
* @param profileId the profile ID that the sample came from that backs this call.
|
|
* @param sampleIndex the index of the sample in the given profile that this call was created from
|
|
* @param ts the timestamp of the profile call
|
|
* @param pid the process ID of the profile call
|
|
* @param tid the thread ID of the profile call
|
|
*
|
|
* See `panels/timeline/docs/profile_calls.md` for more context on how these events are created.
|
|
*/
|
|
export function makeProfileCall(node, profileId, sampleIndex, ts, pid, tid) {
|
|
return {
|
|
cat: '',
|
|
name: 'ProfileCall',
|
|
nodeId: node.id,
|
|
args: {},
|
|
ph: Types.Events.Phase.COMPLETE,
|
|
pid,
|
|
tid,
|
|
ts,
|
|
dur: Types.Timing.Micro(0),
|
|
callFrame: node.callFrame,
|
|
sampleIndex,
|
|
profileId,
|
|
};
|
|
}
|
|
/**
|
|
* Matches beginning events with PairableAsyncEnd and PairableAsyncInstant (ASYNC_NESTABLE_INSTANT)
|
|
* if provided, though currently only coming from Animations. Traces may contain multiple instant events so we need to
|
|
* account for that.
|
|
*
|
|
* @returns Map of the animation's ID to it's matching events.
|
|
*/
|
|
export function matchEvents(unpairedEvents) {
|
|
// map to store begin and end of the event
|
|
const matchedPairs = new Map();
|
|
// looking for start and end
|
|
for (const event of unpairedEvents) {
|
|
const syntheticId = getSyntheticId(event);
|
|
if (syntheticId === undefined) {
|
|
continue;
|
|
}
|
|
// Create a synthetic id to prevent collisions across categories.
|
|
// Console timings can be dispatched with the same id, so use the
|
|
// event name as well to generate unique ids.
|
|
const otherEventsWithID = Platform.MapUtilities.getWithDefault(matchedPairs, syntheticId, () => {
|
|
return { begin: null, end: null, instant: [] };
|
|
});
|
|
const isStartEvent = event.ph === Types.Events.Phase.ASYNC_NESTABLE_START;
|
|
const isEndEvent = event.ph === Types.Events.Phase.ASYNC_NESTABLE_END;
|
|
const isInstantEvent = event.ph === Types.Events.Phase.ASYNC_NESTABLE_INSTANT;
|
|
if (isStartEvent) {
|
|
otherEventsWithID.begin = event;
|
|
}
|
|
else if (isEndEvent) {
|
|
otherEventsWithID.end = event;
|
|
}
|
|
else if (isInstantEvent) {
|
|
if (!otherEventsWithID.instant) {
|
|
otherEventsWithID.instant = [];
|
|
}
|
|
otherEventsWithID.instant.push(event);
|
|
}
|
|
}
|
|
return matchedPairs;
|
|
}
|
|
function getSyntheticId(event) {
|
|
const id = extractId(event);
|
|
return id && `${event.cat}:${id}:${event.name}`;
|
|
}
|
|
export function createSortedSyntheticEvents(matchedPairs, syntheticEventCallback) {
|
|
const syntheticEvents = [];
|
|
for (const [id, eventsTriplet] of matchedPairs.entries()) {
|
|
const beginEvent = eventsTriplet.begin;
|
|
const endEvent = eventsTriplet.end;
|
|
const instantEvents = eventsTriplet.instant;
|
|
if (!beginEvent || !(endEvent || instantEvents)) {
|
|
// This should never happen, the backend only creates the events once it
|
|
// has them both (beginEvent & endEvent/instantEvents), so we should never get into this state.
|
|
// If we do, something is very wrong, so let's just drop that problematic event.
|
|
continue;
|
|
}
|
|
const triplet = { beginEvent, endEvent, instantEvents };
|
|
/**
|
|
* When trying to pair events with instant events present, there are times when these
|
|
* ASYNC_NESTABLE_INSTANT ('n') don't have a corresponding ASYNC_NESTABLE_END ('e') event.
|
|
* In these cases, pair without needing the endEvent.
|
|
*/
|
|
function eventsArePairable(data) {
|
|
const instantEventsMatch = data.instantEvents ? data.instantEvents.some(e => id === getSyntheticId(e)) : false;
|
|
const endEventMatch = data.endEvent ? id === getSyntheticId(data.endEvent) : false;
|
|
return Boolean(id) && (instantEventsMatch || endEventMatch);
|
|
}
|
|
if (!eventsArePairable(triplet)) {
|
|
continue;
|
|
}
|
|
const targetEvent = endEvent || beginEvent;
|
|
const event = SyntheticEventsManager.registerSyntheticEvent({
|
|
rawSourceEvent: triplet.beginEvent,
|
|
cat: targetEvent.cat,
|
|
ph: targetEvent.ph,
|
|
pid: targetEvent.pid,
|
|
tid: targetEvent.tid,
|
|
id,
|
|
// Both events have the same name, so it doesn't matter which we pick to
|
|
// use as the description
|
|
name: beginEvent.name,
|
|
dur: Types.Timing.Micro(targetEvent.ts - beginEvent.ts),
|
|
ts: beginEvent.ts,
|
|
args: {
|
|
data: triplet,
|
|
},
|
|
});
|
|
if (event.dur < 0) {
|
|
// We have seen in the backend that sometimes animation events get
|
|
// generated with multiple begin entries, or multiple end entries, and this
|
|
// can cause invalid data on the performance panel, so we drop them.
|
|
// crbug.com/1472375
|
|
continue;
|
|
}
|
|
syntheticEventCallback?.(event);
|
|
syntheticEvents.push(event);
|
|
}
|
|
return syntheticEvents.sort((a, b) => a.ts - b.ts);
|
|
}
|
|
export function createMatchedSortedSyntheticEvents(unpairedAsyncEvents, syntheticEventCallback) {
|
|
const matchedPairs = matchEvents(unpairedAsyncEvents);
|
|
const syntheticEvents = createSortedSyntheticEvents(matchedPairs, syntheticEventCallback);
|
|
return syntheticEvents;
|
|
}
|
|
/**
|
|
* Different trace events return line/column numbers that are 1 or 0 indexed.
|
|
* This function knows which events return 1 indexed numbers and normalizes
|
|
* them. The UI expects 0 indexed line numbers, so that is what we return.
|
|
*/
|
|
export function getZeroIndexedLineAndColumnForEvent(event) {
|
|
// Some events emit line numbers that are 1 indexed, but the UI layer expects
|
|
// numbers to be 0 indexed. So here, if the event matches a known 1-indexed
|
|
// number event, we subtract one from the line and column numbers.
|
|
// Otherwise, if the event has args.data.lineNumber/colNumber, we return it
|
|
// as is.
|
|
const numbers = getRawLineAndColumnNumbersForEvent(event);
|
|
const { lineNumber, columnNumber } = numbers;
|
|
switch (event.name) {
|
|
// All these events have line/column numbers which are 1 indexed; so we
|
|
// subtract to make them 0 indexed.
|
|
case Types.Events.Name.FUNCTION_CALL:
|
|
case Types.Events.Name.EVALUATE_SCRIPT:
|
|
case Types.Events.Name.COMPILE:
|
|
case Types.Events.Name.CACHE_SCRIPT: {
|
|
return {
|
|
lineNumber: typeof lineNumber === 'number' ? lineNumber - 1 : undefined,
|
|
columnNumber: typeof columnNumber === 'number' ? columnNumber - 1 : undefined,
|
|
};
|
|
}
|
|
case Types.Events.Name.PROFILE_CALL: {
|
|
const callFrame = event.callFrame;
|
|
return {
|
|
lineNumber: typeof lineNumber === 'number' ? callFrame.lineNumber - 1 : undefined,
|
|
columnNumber: typeof columnNumber === 'number' ? callFrame.columnNumber - 1 : undefined,
|
|
};
|
|
}
|
|
default: {
|
|
return numbers;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Different trace events contain stack traces with line/column numbers
|
|
* that are 1 or 0 indexed.
|
|
* This function knows which events return 1 indexed numbers and normalizes
|
|
* them. The UI expects 0 indexed line numbers, so that is what we return.
|
|
*
|
|
* Note: this only returns the stack trace contained in the payload of
|
|
* an event, which only contains the synchronous portion of the call
|
|
* stack. If you want to obtain the whole stack trace you might need to
|
|
* use the @see Trace.Extras.StackTraceForEvent util.
|
|
*/
|
|
export function getZeroIndexedStackTraceInEventPayload(event) {
|
|
const stack = stackTraceInEvent(event);
|
|
if (!stack) {
|
|
return null;
|
|
}
|
|
return stack.map(callFrame => {
|
|
switch (event.name) {
|
|
case Types.Events.Name.SCHEDULE_STYLE_RECALCULATION:
|
|
case Types.Events.Name.INVALIDATE_LAYOUT:
|
|
case Types.Events.Name.FUNCTION_CALL:
|
|
case Types.Events.Name.LAYOUT:
|
|
case Types.Events.Name.UPDATE_LAYOUT_TREE: {
|
|
return makeZeroBasedCallFrame(callFrame);
|
|
}
|
|
default: {
|
|
if (Types.Events.isUserTiming(event) || Types.Extensions.isSyntheticExtensionEntry(event)) {
|
|
return makeZeroBasedCallFrame(callFrame);
|
|
}
|
|
}
|
|
}
|
|
return callFrame;
|
|
});
|
|
}
|
|
/**
|
|
* Given a 1-based call frame creates a 0-based one.
|
|
*/
|
|
export function makeZeroBasedCallFrame(callFrame) {
|
|
const normalizedCallFrame = { ...callFrame };
|
|
normalizedCallFrame.lineNumber = callFrame.lineNumber && callFrame.lineNumber - 1;
|
|
normalizedCallFrame.columnNumber = callFrame.columnNumber && callFrame.columnNumber - 1;
|
|
return normalizedCallFrame;
|
|
}
|
|
/**
|
|
* NOTE: you probably do not want this function! (Which is why it is not exported).
|
|
*
|
|
* Some trace events have 0 indexed line/column numbers, and others have 1
|
|
* indexed. This function does NOT normalize them, but
|
|
* `getZeroIndexedLineAndColumnNumbersForEvent` does. It is best to use that!
|
|
*
|
|
* @see {@link getZeroIndexedLineAndColumnForEvent}
|
|
**/
|
|
function getRawLineAndColumnNumbersForEvent(event) {
|
|
if (!event.args?.data) {
|
|
return {
|
|
lineNumber: undefined,
|
|
columnNumber: undefined,
|
|
};
|
|
}
|
|
let lineNumber = undefined;
|
|
let columnNumber = undefined;
|
|
if ('lineNumber' in event.args.data && typeof event.args.data.lineNumber === 'number') {
|
|
lineNumber = event.args.data.lineNumber;
|
|
}
|
|
if ('columnNumber' in event.args.data && typeof event.args.data.columnNumber === 'number') {
|
|
columnNumber = event.args.data.columnNumber;
|
|
}
|
|
return { lineNumber, columnNumber };
|
|
}
|
|
export function frameIDForEvent(event) {
|
|
// There are a few events (for example UpdateLayoutTree, ParseHTML) that have
|
|
// the frame stored in args.beginData
|
|
// Rather than list them all we just check for the presence of the field, so
|
|
// we are robust against future trace events also doing this.
|
|
// This check seems very robust, but it also helps satisfy TypeScript and
|
|
// prevents us against unexpected data.
|
|
if (event.args && 'beginData' in event.args && typeof event.args.beginData === 'object' &&
|
|
event.args.beginData !== null && 'frame' in event.args.beginData &&
|
|
typeof event.args.beginData.frame === 'string') {
|
|
return event.args.beginData.frame;
|
|
}
|
|
// Otherwise, we expect frame to be in args.data
|
|
if (event.args?.data?.frame) {
|
|
return event.args.data.frame;
|
|
}
|
|
// No known frame for this event.
|
|
return null;
|
|
}
|
|
const DevToolsTimelineEventCategory = 'disabled-by-default-devtools.timeline';
|
|
export function isTopLevelEvent(event) {
|
|
return event.cat.includes(DevToolsTimelineEventCategory) && event.name === Types.Events.Name.RUN_TASK;
|
|
}
|
|
export function isExtensionUrl(url) {
|
|
return url.startsWith('extensions:') || url.startsWith('chrome-extension:');
|
|
}
|
|
function topLevelEventIndexEndingAfter(events, time) {
|
|
let index = Platform.ArrayUtilities.upperBound(events, time, (time, event) => time - event.ts) - 1;
|
|
while (index > 0 && !isTopLevelEvent(events[index])) {
|
|
index--;
|
|
}
|
|
return Math.max(index, 0);
|
|
}
|
|
export function findUpdateLayoutTreeEvents(events, startTime, endTime) {
|
|
const foundEvents = [];
|
|
const startEventIndex = topLevelEventIndexEndingAfter(events, startTime);
|
|
for (let i = startEventIndex; i < events.length; i++) {
|
|
const event = events[i];
|
|
if (!Types.Events.isUpdateLayoutTree(event)) {
|
|
continue;
|
|
}
|
|
if (event.ts >= (endTime || Infinity)) {
|
|
continue;
|
|
}
|
|
foundEvents.push(event);
|
|
}
|
|
return foundEvents;
|
|
}
|
|
export function findNextEventAfterTimestamp(candidates, ts) {
|
|
const index = Platform.ArrayUtilities.nearestIndexFromBeginning(candidates, candidate => ts < candidate.ts);
|
|
return index === null ? null : candidates[index];
|
|
}
|
|
export function findPreviousEventBeforeTimestamp(candidates, ts) {
|
|
const index = Platform.ArrayUtilities.nearestIndexFromEnd(candidates, candidate => candidate.ts < ts);
|
|
return index === null ? null : candidates[index];
|
|
}
|
|
/**
|
|
* Iterates events in a tree hierarchically, from top to bottom,
|
|
* calling back on every event's start and end in the order
|
|
* dictated by the corresponding timestamp.
|
|
*
|
|
* Events are assumed to be in ascendent order by timestamp.
|
|
*
|
|
* Events with 0 duration are treated as instant events. These do not have a
|
|
* begin and end, but will be passed to the config.onInstantEvent callback as
|
|
* they are discovered. Do not provide this callback if you are not interested
|
|
* in them.
|
|
*
|
|
* For example, given this tree, the following callbacks
|
|
* are expected to be made in the following order
|
|
* |---------------A---------------|
|
|
* |------B------||-------D------|
|
|
* |---C---|
|
|
*
|
|
* 1. Start A
|
|
* 3. Start B
|
|
* 4. Start C
|
|
* 5. End C
|
|
* 6. End B
|
|
* 7. Start D
|
|
* 8. End D
|
|
* 9. End A
|
|
*
|
|
* By default, async events are skipped. This behaviour can be
|
|
* overridden making use of the config.ignoreAsyncEvents parameter.
|
|
*/
|
|
export function forEachEvent(events, config) {
|
|
const globalStartTime = config.startTime ?? Types.Timing.Micro(0);
|
|
const globalEndTime = config.endTime || Types.Timing.Micro(Infinity);
|
|
const ignoreAsyncEvents = config.ignoreAsyncEvents === false ? false : true;
|
|
const stack = [];
|
|
const startEventIndex = topLevelEventIndexEndingAfter(events, globalStartTime);
|
|
for (let i = startEventIndex; i < events.length; i++) {
|
|
const currentEvent = events[i];
|
|
const currentEventTimings = eventTimingsMicroSeconds(currentEvent);
|
|
if (currentEventTimings.endTime < globalStartTime) {
|
|
continue;
|
|
}
|
|
if (currentEventTimings.startTime > globalEndTime) {
|
|
break;
|
|
}
|
|
const isIgnoredAsyncEvent = ignoreAsyncEvents && Types.Events.isPhaseAsync(currentEvent.ph);
|
|
if (isIgnoredAsyncEvent || Types.Events.isFlowPhase(currentEvent.ph)) {
|
|
continue;
|
|
}
|
|
// If we have now reached an event that is after a bunch of events, we need
|
|
// to call the onEndEvent callback for those events before moving on.
|
|
let lastEventOnStack = stack.at(-1);
|
|
let lastEventEndTime = lastEventOnStack ? eventTimingsMicroSeconds(lastEventOnStack).endTime : null;
|
|
while (lastEventOnStack && lastEventEndTime && lastEventEndTime <= currentEventTimings.startTime) {
|
|
stack.pop();
|
|
config.onEndEvent(lastEventOnStack);
|
|
lastEventOnStack = stack.at(-1);
|
|
lastEventEndTime = lastEventOnStack ? eventTimingsMicroSeconds(lastEventOnStack).endTime : null;
|
|
}
|
|
// Now we have dealt with all events prior to this one, see if we need to care about this one.
|
|
if (config.eventFilter && !config.eventFilter(currentEvent)) {
|
|
// The user has chosen to filter this event out, so continue on and do nothing
|
|
continue;
|
|
}
|
|
if (currentEventTimings.duration) {
|
|
config.onStartEvent(currentEvent);
|
|
stack.push(currentEvent);
|
|
}
|
|
else if (config.onInstantEvent) {
|
|
// An event with 0 duration is an instant event.
|
|
config.onInstantEvent(currentEvent);
|
|
}
|
|
}
|
|
// Now we have finished looping over all events; any events remaining on the
|
|
// stack need to have their onEndEvent called.
|
|
while (stack.length) {
|
|
const last = stack.pop();
|
|
if (last) {
|
|
config.onEndEvent(last);
|
|
}
|
|
}
|
|
}
|
|
// Parsed categories are cached to prevent calling cat.split()
|
|
// multiple times on the same categories string.
|
|
const parsedCategories = new Map();
|
|
export function eventHasCategory(event, category) {
|
|
let parsedCategoriesForEvent = parsedCategories.get(event.cat);
|
|
if (!parsedCategoriesForEvent) {
|
|
parsedCategoriesForEvent = new Set(event.cat.split(',') || []);
|
|
}
|
|
return parsedCategoriesForEvent.has(category);
|
|
}
|
|
/**
|
|
* This compares Types.Events.CallFrame with Protocol.Runtime.CallFrame and checks for equality.
|
|
*/
|
|
export function isMatchingCallFrame(eventFrame, nodeFrame) {
|
|
return eventFrame.columnNumber === nodeFrame.columnNumber && eventFrame.lineNumber === nodeFrame.lineNumber &&
|
|
String(eventFrame.scriptId) === nodeFrame.scriptId && eventFrame.url === nodeFrame.url &&
|
|
eventFrame.functionName === nodeFrame.functionName;
|
|
}
|
|
export function eventContainsTimestamp(event, ts) {
|
|
return event.ts <= ts && event.ts + (event.dur || 0) >= ts;
|
|
}
|
|
export function extractSampleTraceId(event) {
|
|
if (!event.args) {
|
|
return null;
|
|
}
|
|
if ('beginData' in event.args) {
|
|
const beginData = event.args['beginData'];
|
|
return beginData.sampleTraceId ?? null;
|
|
}
|
|
return event.args?.sampleTraceId ?? event.args?.data?.sampleTraceId ?? null;
|
|
}
|
|
// This exactly matches EntryStyles.visibleTypes. See the runtime verification in maybeInitSylesMap.
|
|
// TODO(crbug.com/410884528)
|
|
export const VISIBLE_TRACE_EVENT_TYPES = new Set([
|
|
Types.Events.Name.ABORT_POST_TASK_CALLBACK,
|
|
Types.Events.Name.ANIMATION,
|
|
Types.Events.Name.ASYNC_TASK,
|
|
Types.Events.Name.BACKGROUND_DESERIALIZE,
|
|
Types.Events.Name.CACHE_MODULE,
|
|
Types.Events.Name.CACHE_SCRIPT,
|
|
Types.Events.Name.CANCEL_ANIMATION_FRAME,
|
|
Types.Events.Name.CANCEL_IDLE_CALLBACK,
|
|
Types.Events.Name.COMMIT,
|
|
Types.Events.Name.COMPILE_CODE,
|
|
Types.Events.Name.COMPILE_MODULE,
|
|
Types.Events.Name.COMPILE,
|
|
Types.Events.Name.COMPOSITE_LAYERS,
|
|
Types.Events.Name.COMPUTE_INTERSECTION,
|
|
Types.Events.Name.CONSOLE_TIME,
|
|
Types.Events.Name.CPPGC_SWEEP,
|
|
Types.Events.Name.CRYPTO_DO_DECRYPT_REPLY,
|
|
Types.Events.Name.CRYPTO_DO_DECRYPT,
|
|
Types.Events.Name.CRYPTO_DO_DIGEST_REPLY,
|
|
Types.Events.Name.CRYPTO_DO_DIGEST,
|
|
Types.Events.Name.CRYPTO_DO_ENCRYPT_REPLY,
|
|
Types.Events.Name.CRYPTO_DO_ENCRYPT,
|
|
Types.Events.Name.CRYPTO_DO_SIGN_REPLY,
|
|
Types.Events.Name.CRYPTO_DO_SIGN,
|
|
Types.Events.Name.CRYPTO_DO_VERIFY_REPLY,
|
|
Types.Events.Name.CRYPTO_DO_VERIFY,
|
|
Types.Events.Name.DECODE_IMAGE,
|
|
Types.Events.Name.EMBEDDER_CALLBACK,
|
|
Types.Events.Name.EVALUATE_MODULE,
|
|
Types.Events.Name.EVALUATE_SCRIPT,
|
|
Types.Events.Name.EVENT_DISPATCH,
|
|
Types.Events.Name.EVENT_TIMING,
|
|
Types.Events.Name.FINALIZE_DESERIALIZATION,
|
|
Types.Events.Name.FIRE_ANIMATION_FRAME,
|
|
Types.Events.Name.FIRE_IDLE_CALLBACK,
|
|
Types.Events.Name.FUNCTION_CALL,
|
|
Types.Events.Name.GC_COLLECT_GARBARGE,
|
|
Types.Events.Name.GC,
|
|
Types.Events.Name.GPU_TASK,
|
|
Types.Events.Name.HANDLE_POST_MESSAGE,
|
|
Types.Events.Name.HIT_TEST,
|
|
Types.Events.Name.JS_SAMPLE,
|
|
Types.Events.Name.LAYERIZE,
|
|
Types.Events.Name.LAYOUT,
|
|
Types.Events.Name.MAJOR_GC,
|
|
Types.Events.Name.MINOR_GC,
|
|
Types.Events.Name.OPTIMIZE_CODE,
|
|
Types.Events.Name.PAINT_SETUP,
|
|
Types.Events.Name.PAINT,
|
|
Types.Events.Name.PARSE_AUTHOR_STYLE_SHEET,
|
|
Types.Events.Name.PARSE_HTML,
|
|
Types.Events.Name.PRE_PAINT,
|
|
Types.Events.Name.PROFILE_CALL,
|
|
Types.Events.Name.PROGRAM,
|
|
Types.Events.Name.RASTER_TASK,
|
|
Types.Events.Name.REQUEST_ANIMATION_FRAME,
|
|
Types.Events.Name.REQUEST_IDLE_CALLBACK,
|
|
Types.Events.Name.RESOURCE_FINISH,
|
|
Types.Events.Name.RESOURCE_RECEIVE_DATA,
|
|
Types.Events.Name.RESOURCE_RECEIVE_RESPONSE,
|
|
Types.Events.Name.RESOURCE_SEND_REQUEST,
|
|
Types.Events.Name.RESOURCE_WILL_SEND_REQUEST,
|
|
Types.Events.Name.RUN_MICROTASKS,
|
|
Types.Events.Name.RUN_POST_TASK_CALLBACK,
|
|
Types.Events.Name.RUN_TASK,
|
|
Types.Events.Name.SCHEDULE_POST_MESSAGE,
|
|
Types.Events.Name.SCHEDULE_POST_TASK_CALLBACK,
|
|
Types.Events.Name.SCHEDULE_STYLE_RECALCULATION,
|
|
Types.Events.Name.SCROLL_LAYER,
|
|
Types.Events.Name.START_PROFILING,
|
|
Types.Events.Name.STREAMING_COMPILE_SCRIPT_PARSING,
|
|
Types.Events.Name.STREAMING_COMPILE_SCRIPT_WAITING,
|
|
Types.Events.Name.STREAMING_COMPILE_SCRIPT,
|
|
Types.Events.Name.SYNTHETIC_LAYOUT_SHIFT_CLUSTER,
|
|
Types.Events.Name.SYNTHETIC_LAYOUT_SHIFT,
|
|
Types.Events.Name.TIME_STAMP,
|
|
Types.Events.Name.TIMER_FIRE,
|
|
Types.Events.Name.TIMER_INSTALL,
|
|
Types.Events.Name.TIMER_REMOVE,
|
|
Types.Events.Name.UPDATE_LAYER_TREE,
|
|
Types.Events.Name.UPDATE_LAYOUT_TREE,
|
|
Types.Events.Name.USER_TIMING,
|
|
Types.Events.Name.V8_CONSOLE_RUN_TASK,
|
|
Types.Events.Name.WASM_CACHED_MODULE,
|
|
Types.Events.Name.WASM_COMPILED_MODULE,
|
|
Types.Events.Name.WASM_MODULE_CACHE_HIT,
|
|
Types.Events.Name.WASM_MODULE_CACHE_INVALID,
|
|
Types.Events.Name.WASM_STREAM_FROM_RESPONSE_CALLBACK,
|
|
Types.Events.Name.WEB_SOCKET_CREATE,
|
|
Types.Events.Name.WEB_SOCKET_DESTROY,
|
|
Types.Events.Name.WEB_SOCKET_RECEIVE_HANDSHAKE_REQUEST,
|
|
Types.Events.Name.WEB_SOCKET_RECEIVE,
|
|
Types.Events.Name.WEB_SOCKET_SEND_HANDSHAKE_REQUEST,
|
|
Types.Events.Name.WEB_SOCKET_SEND,
|
|
Types.Events.Name.XHR_LOAD,
|
|
Types.Events.Name.XHR_READY_STATE_CHANGED,
|
|
]);
|
|
//# sourceMappingURL=Trace.js.map |