Skip to content

Commit 02ca36f

Browse files
jackfranklinDevtools-frontend LUCI CQ
authored andcommitted
RPP-AI: support origins in Performance AI assistance and auto-selection
This CL introduces the ability to calculate the origin for a given event from the timeline that has been selected. The side-effect of this is now as you click around the timeline on events, the AI Assistance panel will update with the new event that you clicked on. Before this CL you had to explicitly right click on an event to access the AI Assistance with the selected event. Some decisions that were made as part of this CL: 1. We calculate the origin from the non-resolved URL, which in this context means ignoring sourcemaps, and using the raw URL from the trace. This is on purpose because from a security perspective, we care about the URL it was served from, not if locally on the user's machine it was `node_modules/blah...`. 2. I take the origin from the _selected node_ in the call tree, not the root node. I could be argued on this, but most of the time the root node is something like a "Task" which does not have a URL assocated with it. But if you pick the "Evaluate Script" below it, that has a useful URL we can work with. 3. For events without a URL, we calculate a uuid which is unique and deterministic, thus ensuring that each event without a URL is treated as its own origin, so we don't mistakenly bucket up events without exact URLs into the same origin and risk any security concerns. 4. We decided not to cache the AICallTree creation. We find it unlikely that users will go back and forth on an event multiple times and the work isn't so expensive to be of concern. Fixed: 390371494 Change-Id: Ib77a6c45cca7190e08f0ac4b2f5fe8e462c2ba3f Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6178276 Commit-Queue: Jack Franklin <[email protected]> Reviewed-by: Alex Rudenko <[email protected]> Reviewed-by: Paul Irish <[email protected]>
1 parent 12b4c09 commit 02ca36f

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

front_end/panels/ai_assistance/agents/PerformanceAgent.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import * as Host from '../../../core/host/host.js';
6+
import * as Trace from '../../../models/trace/trace.js';
67
import {describeWithEnvironment, getGetHostConfigStub} from '../../../testing/EnvironmentHelpers.js';
78
import {TraceLoader} from '../../../testing/TraceLoader.js';
89
import * as TimelineUtils from '../../timeline/utils/utils.js';
@@ -27,6 +28,30 @@ describeWithEnvironment('PerformanceAgent', () => {
2728
};
2829
}
2930

31+
describe('getOrigin()', () => {
32+
it('calculates the origin of the selected node when it has a URL associated with it', async function() {
33+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
34+
// An Evaluate Script event, picked because it has a URL of googletagmanager.com/...
35+
const evalScriptEvent = parsedTrace.Renderer.allTraceEntries.find(
36+
event => event.name === Trace.Types.Events.Name.EVALUATE_SCRIPT && event.ts === 122411195649);
37+
assert.exists(evalScriptEvent);
38+
const aiCallTree = TimelineUtils.AICallTree.AICallTree.from(evalScriptEvent, parsedTrace);
39+
const callTreeContext = new CallTreeContext(aiCallTree);
40+
assert.strictEqual(callTreeContext.getOrigin(), 'https://www.googletagmanager.com');
41+
});
42+
43+
it('returns a random but deterministic "origin" for nodes that have no URL associated', async function() {
44+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
45+
// A random layout event with no URL associated
46+
const layoutEvent = parsedTrace.Renderer.allTraceEntries.find(
47+
event => event.name === Trace.Types.Events.Name.LAYOUT && event.ts === 122411130078);
48+
assert.exists(layoutEvent);
49+
const aiCallTree = TimelineUtils.AICallTree.AICallTree.from(layoutEvent, parsedTrace);
50+
const callTreeContext = new CallTreeContext(aiCallTree);
51+
assert.strictEqual(callTreeContext.getOrigin(), 'Layout_90829_259_122411130078');
52+
});
53+
});
54+
3055
describe('buildRequest', () => {
3156
beforeEach(() => {
3257
sinon.restore();

front_end/panels/ai_assistance/agents/PerformanceAgent.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import * as Common from '../../../core/common/common.js';
66
import * as Host from '../../../core/host/host.js';
77
import * as i18n from '../../../core/i18n/i18n.js';
8+
import * as Trace from '../../../models/trace/trace.js';
89
import * as TimelineUtils from '../../timeline/utils/utils.js';
910
import * as PanelUtils from '../../utils/utils.js';
1011

@@ -130,8 +131,25 @@ export class CallTreeContext extends ConversationContext<TimelineUtils.AICallTre
130131
}
131132

132133
override getOrigin(): string {
133-
// TODO: implement cross-origin checks for the PerformanceAgent.
134-
return '';
134+
const selectedEvent = this.#callTree.selectedNode.event;
135+
// Get the non-resolved (ignore sourcemaps) URL for the event. We use the
136+
// non-resolved URL as in the context of the AI Assistance panel, we care
137+
// about the origin it was served on.
138+
const nonResolvedURL = Trace.Handlers.Helpers.getNonResolvedURL(selectedEvent, this.#callTree.parsedTrace);
139+
if (nonResolvedURL) {
140+
const origin = Common.ParsedURL.ParsedURL.extractOrigin(nonResolvedURL);
141+
if (origin) { // origin could be the empty string.
142+
return origin;
143+
}
144+
}
145+
// Generate a random "origin". We do this rather than return an empty
146+
// string or some "unknown" string so that each event without a definite
147+
// URL is considered a new, standalone origin. This is safer from a privacy
148+
// & security perspective, else we risk bucketing events together that
149+
// should not be. We also don't want to make it entirely random so we
150+
// cannot calculate it deterministically.
151+
const uuid = `${selectedEvent.name}_${selectedEvent.pid}_${selectedEvent.tid}_${selectedEvent.ts}`;
152+
return uuid;
135153
}
136154

137155
override getItem(): TimelineUtils.AICallTree.AICallTree {

front_end/panels/timeline/TimelineFlameChartView.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,16 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
12631263
this.mainFlameChart.setSelectedEntry(mainIndex);
12641264
this.networkFlameChart.setSelectedEntry(networkIndex);
12651265

1266+
// Set the context's "flavor" to be the AI Call Tree of the active event.
1267+
// This is listened to by the AI Assistance panel.
1268+
if (selectionIsEvent(selection) && this.#parsedTrace) {
1269+
// TODO: should we cache this?
1270+
const aiCallTree = Utils.AICallTree.AICallTree.from(selection.event, this.#parsedTrace);
1271+
UI.Context.Context.instance().setFlavor(Utils.AICallTree.AICallTree, aiCallTree);
1272+
} else {
1273+
UI.Context.Context.instance().setFlavor(Utils.AICallTree.AICallTree, null);
1274+
}
1275+
12661276
// Clear any existing entry selection.
12671277
this.#overlays.removeOverlaysOfType('ENTRY_SELECTED');
12681278
// If:

0 commit comments

Comments
 (0)