Skip to content
13 changes: 13 additions & 0 deletions front_end/models/trace/handlers/UserTimingsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {HandlerState} from './types.js';
let syntheticEvents: Types.Events.SyntheticEventPair<Types.Events.PairableAsync>[] = [];
const performanceMeasureEvents: Types.Events.PerformanceMeasure[] = [];
const performanceMarkEvents: Types.Events.PerformanceMark[] = [];
const performanceAttributionEvents: Types.Events.PerformanceAttribution[] = [];

const consoleTimings: (Types.Events.ConsoleTimeBegin|Types.Events.ConsoleTimeEnd)[] = [];

Expand Down Expand Up @@ -42,13 +43,20 @@ export interface UserTimingsData {
* https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp
*/
timestampEvents: readonly Types.Events.TimeStamp[];

/**
* Attribution events triggered with the performance.mark() API
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/attribution
*/
performanceAttributions: readonly Types.Events.PerformanceAttribution[];
}
let handlerState = HandlerState.UNINITIALIZED;

export function reset(): void {
syntheticEvents.length = 0;
performanceMeasureEvents.length = 0;
performanceMarkEvents.length = 0;
performanceAttributionEvents.length = 0;
consoleTimings.length = 0;
timestampEvents.length = 0;
handlerState = HandlerState.INITIALIZED;
Expand Down Expand Up @@ -157,6 +165,10 @@ export function handleEvent(event: Types.Events.Event): void {
}
if (Types.Events.isPerformanceMark(event)) {
performanceMarkEvents.push(event);

if (Types.Events.isPerformanceAttribution(event)) {
performanceAttributionEvents.push(event);
}
}
if (Types.Events.isConsoleTime(event)) {
consoleTimings.push(event);
Expand Down Expand Up @@ -189,5 +201,6 @@ export function data(): UserTimingsData {
// TODO(crbug/41484172): UserTimingsHandler.test.ts fails if this is not copied.
performanceMarks: [...performanceMarkEvents],
timestampEvents: [...timestampEvents],
performanceAttributions: [...performanceAttributionEvents],
};
}
13 changes: 13 additions & 0 deletions front_end/models/trace/types/TraceEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export interface ArgsData {
url?: string;
navigationId?: string;
frame?: string;
attribution?: string;
}

export interface CallFrame {
Expand Down Expand Up @@ -1284,6 +1285,14 @@ export interface PerformanceMark extends UserTiming {
ph: Phase.INSTANT|Phase.MARK|Phase.ASYNC_NESTABLE_INSTANT;
}

export interface PerformanceAttribution extends UserTiming {
args: Args&{
data: ArgsData & {
detail: string,
},
};
}

export interface ConsoleTimeBegin extends PairableAsyncBegin {
cat: 'blink.console';
}
Expand Down Expand Up @@ -2132,6 +2141,10 @@ export function isPerformanceMark(event: Event): event is PerformanceMark {
return isUserTiming(event) && (event.ph === Phase.MARK || event.ph === Phase.INSTANT);
}

export function isPerformanceAttribution(event: Event): event is PerformanceAttribution {
return event.name.startsWith('attribution::');
}

export function isConsoleTime(event: Event): event is ConsoleTime {
return event.cat === 'blink.console' && isPhaseAsync(event.ph);
}
Expand Down
12 changes: 12 additions & 0 deletions front_end/panels/freestyler/AiAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import * as Host from '../../core/host/host.js';
import { TimelineUIUtils } from '../timeline/TimelineUIUtils.js';

export const enum ResponseType {
CONTEXT = 'context',
Expand Down Expand Up @@ -375,6 +376,17 @@ STOP`;
yield response;
}

// Potentially enhance the query with Attribution context.
if (options.selected?.selectedNode?.id === 'EvaluateScript' || options.selected?.selectedNode?.id === 'CompileScript') {
const url = options.selected?.selectedNode?.event?.args?.data?.url;
if (url) {
const attribution = TimelineUIUtils.getAttributionForUrl(url, [...options.selected.parsedTrace.UserTimings.performanceAttributions]);
if (attribution) {
query = `${query}\n\nNote:Attribution for this source: ${attribution}`;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the prompt strings need to be localized?

}
}
}

query = await this.enhanceQuery(query, options.selected);

for (let i = 0; i < MAX_STEP; i++) {
Expand Down
56 changes: 56 additions & 0 deletions front_end/panels/timeline/TimelineUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const UIStrings = {
*@description Text for a module, the programming concept
*/
module: 'Module',

/**
*@description Label for a group of JavaScript files
*/
Expand Down Expand Up @@ -557,6 +558,22 @@ const UIStrings = {
* @description Label for a string that describes the priority at which a task was scheduled, like 'background' for low-priority tasks, and 'user-blocking' for high priority.
*/
priority: 'Priority',
/**
*@description Text for a performance attribution event
*/
attribution: 'Attribution',
/**
*@description Text for a WordPress core attribution
*/
wordpressCore: 'Core',
/**
*@description Text for a WordPress plugin attribution
*/
wordpressPlugin: 'WordPress Plugin',
/**
*@description Text for a WordPress theme attribution
*/
wordpressTheme: 'WordPress Theme',
};
const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelineUIUtils.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
Expand Down Expand Up @@ -1209,6 +1226,10 @@ export class TimelineUIUtils {
if (url) {
const {lineNumber, columnNumber} = Trace.Helpers.Trace.getZeroIndexedLineAndColumnForEvent(event);
contentHelper.appendLocationRow(i18nString(UIStrings.script), url, lineNumber || 0, columnNumber);
const attribution = TimelineUIUtils.getAttributionForUrl(url, [...parsedTrace.UserTimings.performanceAttributions]);
if (attribution) {
contentHelper.appendTextRow(i18nString(UIStrings.attribution), attribution);
}
}
const isEager = Boolean(event.args.data?.eager);
if (isEager) {
Expand Down Expand Up @@ -1315,6 +1336,10 @@ export class TimelineUIUtils {
if (url) {
const {lineNumber, columnNumber} = Trace.Helpers.Trace.getZeroIndexedLineAndColumnForEvent(event);
contentHelper.appendLocationRow(i18nString(UIStrings.script), url, lineNumber || 0, columnNumber);
const attribution = TimelineUIUtils.getAttributionForUrl(url, [...parsedTrace.UserTimings.performanceAttributions]);
if (attribution) {
contentHelper.appendTextRow(i18nString(UIStrings.attribution), attribution);
}
}
break;
}
Expand Down Expand Up @@ -1708,6 +1733,37 @@ export class TimelineUIUtils {
return contentHelper.fragment;
}

/**
* Get attribution data for a given URL.
*
* @param {string} url URL to check for attribution data.
* @param {Trace.Types.Events.PerformanceAttribution[]} attributions Array of performance attributions. Immutable.
*
* @return {string|null} Attribution name or null if no attribution is found.
*/
static getAttributionForUrl(url: string, attributions: Trace.Types.Events.PerformanceAttribution[]): string|null {
const attribution = attributions.find(attribution => {
const detail = JSON.parse(attribution.args.data.detail);
const parsedUrl = new URL(url);
return detail.path.includes(parsedUrl.pathname);
});
if (attribution) {
const detail = JSON.parse(attribution.args.data.detail);

// Determine the type of attribution is based on the attribution name. It can be either a plugin, theme or core enqueue.
let type = '';
if (attribution.name.includes('core')) {
type = UIStrings.wordpressCore;
} else if (attribution.name.includes('plugin')) {
type = UIStrings.wordpressPlugin;
} else if (attribution.name.includes('theme')) {
type = UIStrings.wordpressTheme;
}
return `${type} - ${detail.name}`;
}
return null;
}

static statsForTimeRange(
events: Trace.Types.Events.Event[], startTime: Trace.Types.Timing.MilliSeconds,
endTime: Trace.Types.Timing.MilliSeconds): {
Expand Down