Skip to content

Commit 9412902

Browse files
jackfranklinDevtools-frontend LUCI CQ
authored andcommitted
AI: Format and sanitize trace network requests
Bug: 394552594 Change-Id: I87cd95fcc1d5b40f0c7377550241eb09ab78b73d Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6277285 Commit-Queue: Jack Franklin <[email protected]> Reviewed-by: Alex Rudenko <[email protected]> Auto-Submit: Jack Franklin <[email protected]>
1 parent 9306264 commit 9412902

File tree

4 files changed

+137
-22
lines changed

4 files changed

+137
-22
lines changed

front_end/panels/ai_assistance/data_formatters/NetworkRequestFormatter.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {NetworkRequestFormatter} from '../ai_assistance.js';
77
describe('NetworkRequestFormatter', () => {
88
describe('allowHeader', () => {
99
it('allows a header from the list', () => {
10-
assert.isTrue(NetworkRequestFormatter.allowHeader({name: 'content-type', value: 'foo'}));
10+
assert.isTrue(NetworkRequestFormatter.allowHeader('content-type'));
1111
});
1212

1313
it('disallows headers not on the list', () => {
14-
assert.isFalse(NetworkRequestFormatter.allowHeader({name: 'cookie', value: 'foo'}));
15-
assert.isFalse(NetworkRequestFormatter.allowHeader({name: 'set-cookie', value: 'foo'}));
16-
assert.isFalse(NetworkRequestFormatter.allowHeader({name: 'authorization', value: 'foo'}));
14+
assert.isFalse(NetworkRequestFormatter.allowHeader('cookie'));
15+
assert.isFalse(NetworkRequestFormatter.allowHeader('set-cookie'));
16+
assert.isFalse(NetworkRequestFormatter.allowHeader('authorization'));
1717
});
1818
});
1919

front_end/panels/ai_assistance/data_formatters/NetworkRequestFormatter.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,29 @@ import * as Network from '../../network/network.js';
99

1010
const MAX_HEADERS_SIZE = 1000;
1111

12+
/**
13+
* Sanitizes the set of headers, removing values that are not on the allow-list and replacing them with '<redacted>'.
14+
*/
15+
function sanitizeHeaders(headers: Array<{name: string, value: string}>): Array<{name: string, value: string}> {
16+
return headers.map(header => {
17+
if (NetworkRequestFormatter.allowHeader(header.name)) {
18+
return header;
19+
}
20+
return {name: header.name, value: '<redacted>'};
21+
});
22+
}
23+
1224
export class NetworkRequestFormatter {
13-
static allowHeader(header: SDK.NetworkRequest.NameValue): boolean {
14-
return allowedHeaders.has(header.name.toLowerCase().trim());
25+
static allowHeader(headerName: string): boolean {
26+
return allowedHeaders.has(headerName.toLowerCase().trim());
1527
}
16-
static formatHeaders(title: string, headers: SDK.NetworkRequest.NameValue[]): string {
28+
static formatHeaders(title: string, headers: Array<{name: string, value: string}>, addListPrefixToEachLine?: boolean):
29+
string {
1730
return formatLines(
18-
title,
19-
headers
20-
.map(header => {
21-
if (NetworkRequestFormatter.allowHeader(header)) {
22-
return header;
23-
}
24-
return {
25-
name: header.name,
26-
value: '<redacted>',
27-
};
28-
})
29-
.map(header => header.name + ': ' + header.value + '\n'),
31+
title, sanitizeHeaders(headers).map(header => {
32+
const prefix = addListPrefixToEachLine ? '- ' : '';
33+
return prefix + header.name + ': ' + header.value + '\n';
34+
}),
3035
MAX_HEADERS_SIZE);
3136
}
3237

front_end/panels/ai_assistance/data_formatters/PerformanceInsightFormatter.test.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js';
66
import {getFirstOrError, getInsightOrError} from '../../../testing/InsightHelpers.js';
77
import {TraceLoader} from '../../../testing/TraceLoader.js';
8-
import {PerformanceInsightFormatter} from '../ai_assistance.js';
8+
import {PerformanceInsightFormatter, TraceEventFormatter} from '../ai_assistance.js';
99

10-
describe('PerformanceInsightFormatter', () => {
11-
describeWithEnvironment('for LCP by Phase', () => {
10+
describeWithEnvironment('PerformanceInsightFormatter', () => {
11+
describe('for LCP by Phase', () => {
1212
it('serializes the correct details', async function() {
1313
const {parsedTrace, insights} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
1414
assert.isOk(insights);
@@ -40,4 +40,47 @@ We can break this time down into the 4 phases that combine to make up the LCP ti
4040
assert.strictEqual(output, expected);
4141
});
4242
});
43+
44+
describe('Formatting TraceEvents', () => {
45+
it('formats network requests', async function() {
46+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'lcp-images.json.gz');
47+
const requestUrl = 'https://fonts.googleapis.com/css2?family=Poppins:ital,wght@1,800';
48+
const request = parsedTrace.NetworkRequests.byTime.find(r => r.args.data.url === requestUrl);
49+
assert.isOk(request);
50+
const output = TraceEventFormatter.networkRequest(request, parsedTrace);
51+
const expected = `## Network request: https://fonts.googleapis.com/css2?family=Poppins:ital,wght@1,800
52+
Timings:
53+
- Start time: 37.62 ms
54+
- Queued at: 43.24 ms
55+
- Request sent at: 41.71 ms
56+
- Download complete at: 48.04 ms
57+
- Fully completed at: 51.55 ms
58+
- Total request duration: 13.93 ms
59+
Status code: 200
60+
MIME Type: text/css
61+
Priority:
62+
- Initial: VeryHigh
63+
- Final: VeryHigh
64+
Render blocking?: Yes
65+
From a service worker: No
66+
Response headers
67+
- date: Thu, 07 Mar 2024 21:17:02 GMT
68+
- content-encoding: gzip
69+
- x-content-type-options: nosniff
70+
- last-modified: Thu, 07 Mar 2024 21:17:02 GMT
71+
- server: ESF
72+
- cross-origin-opener-policy: <redacted>
73+
- x-frame-options: SAMEORIGIN
74+
- content-type: text/css; charset=utf-8
75+
- access-control-allow-origin: *
76+
- cache-control: private, max-age=86400, stale-while-revalidate=604800
77+
- cross-origin-resource-policy: <redacted>
78+
- timing-allow-origin: *
79+
- link: <https://fonts.gstatic.com>; rel=preconnect; crossorigin
80+
- x-xss-protection: 0
81+
- expires: Thu, 07 Mar 2024 21:17:02 GMT`;
82+
83+
assert.strictEqual(output, expected);
84+
});
85+
});
4386
});

front_end/panels/ai_assistance/data_formatters/PerformanceInsightFormatter.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@
44
import * as i18n from '../../../core/i18n/i18n.js';
55
import * as Trace from '../../../models/trace/trace.js';
66

7+
import {
8+
NetworkRequestFormatter,
9+
} from './NetworkRequestFormatter.js';
10+
711
function formatMilli(x: number|undefined): string {
812
if (x === undefined) {
913
return '';
1014
}
1115
return i18n.TimeUtilities.preciseMillisToString(x, 2);
1216
}
1317

18+
function formatMicro(x: number|undefined): string {
19+
if (x === undefined) {
20+
return '';
21+
}
22+
return formatMilli(Trace.Helpers.Timing.microToMilli(x as Trace.Types.Timing.Micro));
23+
}
24+
1425
export class PerformanceInsightFormatter {
1526
#insight: Trace.Insights.Types.InsightModel<{}, {}>;
1627
constructor(insight: Trace.Insights.Types.InsightModel<{}, {}>) {
@@ -121,3 +132,59 @@ We can break this time down into the 4 phases that combine to make up the LCP ti
121132
}
122133
}
123134
}
135+
136+
export class TraceEventFormatter {
137+
/**
138+
* This is the data passed to a network request when the Performance Insights
139+
* agent is asking for information. It is a slimmed down version of the
140+
* request's data to avoid using up too much of the context window.
141+
* IMPORTANT: these set of fields have been reviewed by Chrome Privacy &
142+
* Security; be careful about adding new data here. If you are in doubt please
143+
* talk to jacktfranklin@.
144+
*/
145+
static networkRequest(
146+
request: Trace.Types.Events.SyntheticNetworkRequest, parsedTrace: Trace.Handlers.Types.ParsedTrace): string {
147+
const {url, statusCode, initialPriority, priority, fromServiceWorker, mimeType, responseHeaders, syntheticData} =
148+
request.args.data;
149+
150+
// Note: unlike other agents, we do have the ability to include
151+
// cross-origins, hence why we do not sanitize the URLs here.
152+
const navigationForEvent = Trace.Helpers.Trace.getNavigationForTraceEvent(
153+
request,
154+
request.args.data.frame,
155+
parsedTrace.Meta.navigationsByFrameId,
156+
);
157+
const baseTime = navigationForEvent?.ts ?? parsedTrace.Meta.traceBounds.min;
158+
159+
// Gets all the timings for this request, relative to the base time.
160+
// Note that this is the start time, not total time. E.g. "queueing: X"
161+
// means that the request was queued at Xms, not that it queued for Xms.
162+
const startTimesForLifecycle = {
163+
start: request.ts - baseTime,
164+
queueing: syntheticData.downloadStart - baseTime,
165+
requestSent: syntheticData.sendStartTime - baseTime,
166+
downloadComplete: syntheticData.finishTime - baseTime,
167+
processingComplete: request.ts + request.dur - baseTime,
168+
169+
} as const;
170+
171+
const renderBlocking = Trace.Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(request);
172+
173+
return `## Network request: ${url}
174+
Timings:
175+
- Start time: ${formatMicro(startTimesForLifecycle.start)}
176+
- Queued at: ${formatMicro(startTimesForLifecycle.queueing)}
177+
- Request sent at: ${formatMicro(startTimesForLifecycle.requestSent)}
178+
- Download complete at: ${formatMicro(startTimesForLifecycle.downloadComplete)}
179+
- Fully completed at: ${formatMicro(startTimesForLifecycle.processingComplete)}
180+
- Total request duration: ${formatMicro(request.dur)}
181+
Status code: ${statusCode}
182+
MIME Type: ${mimeType}
183+
Priority:
184+
- Initial: ${initialPriority}
185+
- Final: ${priority}
186+
Render blocking?: ${renderBlocking ? 'Yes' : 'No'}
187+
From a service worker: ${fromServiceWorker ? 'Yes' : 'No'}
188+
${NetworkRequestFormatter.formatHeaders('Response headers', responseHeaders, true)}`;
189+
}
190+
}

0 commit comments

Comments
 (0)