Skip to content

Commit 34a7163

Browse files
authored
core(network-requests): add frame and preload debug data (GoogleChrome#14161)
1 parent 00a45a1 commit 34a7163

File tree

6 files changed

+329
-138
lines changed

6 files changed

+329
-138
lines changed

lighthouse-cli/test/smokehouse/test-definitions/oopif-requests.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,19 @@ const expectations = {
5050
// We want to make sure we are finding the iframe's requests (paulirish.com) *AND*
5151
// the iframe's iframe's iframe's requests (youtube.com/doubleclick/etc).
5252
_includes: [
53-
{url: 'http://localhost:10200/oopif-requests.html', finished: true, statusCode: 200, resourceType: 'Document'},
53+
{url: 'http://localhost:10200/oopif-requests.html', finished: true, statusCode: 200, resourceType: 'Document', experimentalFromMainFrame: true},
5454

5555
// Paulirish iframe and subresource
56-
{url: 'https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/', finished: true, statusCode: 200, resourceType: 'Document'},
57-
{url: 'https://www.paulirish.com/avatar150.jpg', finished: true, statusCode: 200, resourceType: 'Image'},
58-
{url: 'https://www.googletagmanager.com/gtag/js?id=G-PGXNGYWP8E', finished: true, statusCode: 200, resourceType: 'Script'},
59-
{url: /^https:\/\/fonts\.googleapis\.com\/css/, finished: true, statusCode: 200, resourceType: 'Stylesheet'},
56+
{url: 'https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/', finished: true, statusCode: 200, resourceType: 'Document', experimentalFromMainFrame: undefined},
57+
{url: 'https://www.paulirish.com/avatar150.jpg', finished: true, statusCode: 200, resourceType: 'Image', experimentalFromMainFrame: undefined},
58+
{url: 'https://www.googletagmanager.com/gtag/js?id=G-PGXNGYWP8E', finished: true, statusCode: 200, resourceType: 'Script', experimentalFromMainFrame: undefined},
59+
{url: /^https:\/\/fonts\.googleapis\.com\/css/, finished: true, statusCode: 200, resourceType: 'Stylesheet', experimentalFromMainFrame: undefined},
6060

6161
// Youtube iframe (OOPIF) and some subresources
6262
// FYI: Youtube has a ServiceWorker which sometimes cancels the document request. As a result, there will sometimes be multiple requests for this file.
6363
{url: 'https://www.youtube.com/embed/NZelrwd_iRs', finished: true, statusCode: 200, resourceType: 'Document'},
6464
{url: /^https:\/\/www\.youtube\.com\/.*?player.*?css/, finished: true, statusCode: 200, resourceType: 'Stylesheet'},
65-
{url: /^https:\/\/www\.youtube\.com\/.*?\/embed.js/, finished: true, statusCode: 200, resourceType: 'Script'},
65+
{url: /^https:\/\/www\.youtube\.com\/.*?\/embed.js/, finished: true, statusCode: 200, resourceType: 'Script', experimentalFromMainFrame: undefined},
6666

6767
// Disqus iframe (OOPIF)
6868
{url: /^https:\/\/disqus\.com\/embed\/comments\//, finished: true, statusCode: 200, resourceType: 'Document'},

lighthouse-cli/test/smokehouse/test-definitions/perf-preload.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ const expectations = {
7878
'network-requests': {
7979
details: {
8080
items: {
81+
_includes: [
82+
{url: 'http://localhost:10200/preload.html', isLinkPreload: undefined, experimentalFromMainFrame: true},
83+
{url: 'http://localhost:10200/perf/level-2.js?warning&delay=500', isLinkPreload: true, experimentalFromMainFrame: true},
84+
{url: 'http://localhost:10200/perf/preload_tester.js', isLinkPreload: undefined, experimentalFromMainFrame: true},
85+
],
8186
length: '>5',
8287
},
8388
},

lighthouse-core/audits/network-requests.js

Lines changed: 79 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
const Audit = require('./audit.js');
99
const URL = require('../lib/url-shim.js');
1010
const NetworkRecords = require('../computed/network-records.js');
11+
const MainResource = require('../computed/main-resource.js');
1112

1213
class NetworkRequests extends Audit {
1314
/**
@@ -19,7 +20,7 @@ class NetworkRequests extends Audit {
1920
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
2021
title: 'Network Requests',
2122
description: 'Lists the network requests that were made during page load.',
22-
requiredArtifacts: ['devtoolsLogs'],
23+
requiredArtifacts: ['devtoolsLogs', 'URL', 'GatherContext'],
2324
};
2425
}
2526

@@ -28,75 +29,91 @@ class NetworkRequests extends Audit {
2829
* @param {LH.Audit.Context} context
2930
* @return {Promise<LH.Audit.Product>}
3031
*/
31-
static audit(artifacts, context) {
32+
static async audit(artifacts, context) {
3233
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
33-
return NetworkRecords.request(devtoolsLog, context).then(records => {
34-
const earliestStartTime = records.reduce(
35-
(min, record) => Math.min(min, record.startTime),
36-
Infinity
37-
);
34+
const records = await NetworkRecords.request(devtoolsLog, context);
35+
const earliestStartTime = records.reduce(
36+
(min, record) => Math.min(min, record.startTime),
37+
Infinity
38+
);
3839

39-
/** @param {number} time */
40-
const timeToMs = time => time < earliestStartTime || !Number.isFinite(time) ?
41-
undefined : (time - earliestStartTime) * 1000;
40+
// Optional mainFrameId check because the main resource is only available for
41+
// navigations. TODO: https://github.com/GoogleChrome/lighthouse/issues/14157
42+
// for the general solution to this.
43+
/** @type {string|undefined} */
44+
let mainFrameId;
45+
if (artifacts.GatherContext.gatherMode === 'navigation') {
46+
const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
47+
mainFrameId = mainResource.frameId;
48+
}
4249

43-
const results = records.map(record => {
44-
const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
45-
const TCPMs = record.lrStatistics?.TCPMs;
46-
const requestMs = record.lrStatistics?.requestMs;
47-
const responseMs = record.lrStatistics?.responseMs;
50+
/** @param {number} time */
51+
const timeToMs = time => time < earliestStartTime || !Number.isFinite(time) ?
52+
undefined : (time - earliestStartTime) * 1000;
4853

49-
return {
50-
url: URL.elideDataURI(record.url),
51-
protocol: record.protocol,
52-
startTime: timeToMs(record.startTime),
53-
endTime: timeToMs(record.endTime),
54-
finished: record.finished,
55-
transferSize: record.transferSize,
56-
resourceSize: record.resourceSize,
57-
statusCode: record.statusCode,
58-
mimeType: record.mimeType,
59-
resourceType: record.resourceType,
60-
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
61-
lrTCPMs: TCPMs, // Only exists on Lightrider runs
62-
lrRequestMs: requestMs, // Only exists on Lightrider runs
63-
lrResponseMs: responseMs, // Only exists on Lightrider runs
64-
};
65-
});
66-
67-
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
68-
/** @type {LH.Audit.Details.Table['headings']} */
69-
const headings = [
70-
{key: 'url', itemType: 'url', text: 'URL'},
71-
{key: 'protocol', itemType: 'text', text: 'Protocol'},
72-
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
73-
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
74-
{
75-
key: 'transferSize',
76-
itemType: 'bytes',
77-
displayUnit: 'kb',
78-
granularity: 1,
79-
text: 'Transfer Size',
80-
},
81-
{
82-
key: 'resourceSize',
83-
itemType: 'bytes',
84-
displayUnit: 'kb',
85-
granularity: 1,
86-
text: 'Resource Size',
87-
},
88-
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
89-
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
90-
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
91-
];
92-
93-
const tableDetails = Audit.makeTableDetails(headings, results);
54+
const results = records.map(record => {
55+
const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
56+
const TCPMs = record.lrStatistics?.TCPMs;
57+
const requestMs = record.lrStatistics?.requestMs;
58+
const responseMs = record.lrStatistics?.responseMs;
59+
// Default these to undefined so omitted from JSON in the negative case.
60+
const isLinkPreload = record.isLinkPreload || undefined;
61+
const experimentalFromMainFrame = mainFrameId ?
62+
((record.frameId === mainFrameId) || undefined) :
63+
undefined;
9464

9565
return {
96-
score: 1,
97-
details: tableDetails,
66+
url: URL.elideDataURI(record.url),
67+
protocol: record.protocol,
68+
startTime: timeToMs(record.startTime),
69+
endTime: timeToMs(record.endTime),
70+
finished: record.finished,
71+
transferSize: record.transferSize,
72+
resourceSize: record.resourceSize,
73+
statusCode: record.statusCode,
74+
mimeType: record.mimeType,
75+
resourceType: record.resourceType,
76+
isLinkPreload,
77+
experimentalFromMainFrame,
78+
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
79+
lrTCPMs: TCPMs, // Only exists on Lightrider runs
80+
lrRequestMs: requestMs, // Only exists on Lightrider runs
81+
lrResponseMs: responseMs, // Only exists on Lightrider runs
9882
};
9983
});
84+
85+
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
86+
/** @type {LH.Audit.Details.Table['headings']} */
87+
const headings = [
88+
{key: 'url', itemType: 'url', text: 'URL'},
89+
{key: 'protocol', itemType: 'text', text: 'Protocol'},
90+
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
91+
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
92+
{
93+
key: 'transferSize',
94+
itemType: 'bytes',
95+
displayUnit: 'kb',
96+
granularity: 1,
97+
text: 'Transfer Size',
98+
},
99+
{
100+
key: 'resourceSize',
101+
itemType: 'bytes',
102+
displayUnit: 'kb',
103+
granularity: 1,
104+
text: 'Resource Size',
105+
},
106+
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
107+
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
108+
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
109+
];
110+
111+
const tableDetails = Audit.makeTableDetails(headings, results);
112+
113+
return {
114+
score: 1,
115+
details: tableDetails,
116+
};
100117
}
101118
}
102119

lighthouse-core/test/audits/network-requests-test.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ import networkRecordsToDevtoolsLog from '../network-records-to-devtools-log.js';
1010

1111
const cutoffLoadDevtoolsLog = readJson('../fixtures/traces/cutoff-load-m83.devtoolslog.json', import.meta);
1212

13+
const GatherContext = {
14+
gatherMode: 'navigation',
15+
};
16+
1317
describe('Network requests audit', () => {
1418
it('should report finished and unfinished network requests', async () => {
1519
const artifacts = {
1620
devtoolsLogs: {
1721
[NetworkRequests.DEFAULT_PASS]: cutoffLoadDevtoolsLog,
1822
},
23+
URL: {mainDocumentUrl: 'https://googlechrome.github.io/lighthouse/viewer/'},
24+
GatherContext,
1925
};
2026

2127
const output = await NetworkRequests.audit(artifacts, {computedCache: new Map()});
@@ -62,6 +68,8 @@ describe('Network requests audit', () => {
6268
devtoolsLogs: {
6369
[NetworkRequests.DEFAULT_PASS]: networkRecordsToDevtoolsLog(records),
6470
},
71+
URL: {mainDocumentUrl: 'https://example.com/0'},
72+
GatherContext,
6573
};
6674
const output = await NetworkRequests.audit(artifacts, {computedCache: new Map()});
6775

@@ -75,4 +83,76 @@ describe('Network requests audit', () => {
7583
finished: true,
7684
}]);
7785
});
86+
87+
it('should report if records are from the main frame', async () => {
88+
const records = [
89+
{url: 'https://example.com/'},
90+
{url: 'https://iframed.local/', frameId: '71D866EC199B90A2E0B2D9CF88DCBC4E'},
91+
];
92+
93+
const artifacts = {
94+
devtoolsLogs: {
95+
[NetworkRequests.DEFAULT_PASS]: networkRecordsToDevtoolsLog(records),
96+
},
97+
URL: {mainDocumentUrl: 'https://example.com/'},
98+
GatherContext,
99+
};
100+
const output = await NetworkRequests.audit(artifacts, {computedCache: new Map()});
101+
102+
expect(output.details.items).toMatchObject([{
103+
url: 'https://example.com/',
104+
experimentalFromMainFrame: true,
105+
}, {
106+
url: 'https://iframed.local/',
107+
experimentalFromMainFrame: undefined,
108+
}]);
109+
});
110+
111+
it('should not include main frame information outside of navigations', async () => {
112+
const records = [
113+
{url: 'https://example.com/'},
114+
{url: 'https://iframed.local/', frameId: '71D866EC199B90A2E0B2D9CF88DCBC4E'},
115+
];
116+
117+
const artifacts = {
118+
devtoolsLogs: {
119+
[NetworkRequests.DEFAULT_PASS]: networkRecordsToDevtoolsLog(records),
120+
},
121+
URL: {mainDocumentUrl: 'https://example.com/'},
122+
GatherContext: {gatherMode: 'timespan'},
123+
};
124+
const output = await NetworkRequests.audit(artifacts, {computedCache: new Map()});
125+
126+
expect(output.details.items).toMatchObject([{
127+
url: 'https://example.com/',
128+
experimentalFromMainFrame: undefined,
129+
}, {
130+
url: 'https://iframed.local/',
131+
experimentalFromMainFrame: undefined,
132+
}]);
133+
});
134+
135+
it('should include if network request was preloaded', async () => {
136+
const records = [
137+
{url: 'https://example.com/'},
138+
{url: 'https://example.com/img.jpg', isLinkPreload: true},
139+
];
140+
141+
const artifacts = {
142+
devtoolsLogs: {
143+
[NetworkRequests.DEFAULT_PASS]: networkRecordsToDevtoolsLog(records),
144+
},
145+
URL: {mainDocumentUrl: 'https://example.com/'},
146+
GatherContext,
147+
};
148+
const output = await NetworkRequests.audit(artifacts, {computedCache: new Map()});
149+
150+
expect(output.details.items).toMatchObject([{
151+
url: 'https://example.com/',
152+
isLinkPreload: undefined,
153+
}, {
154+
url: 'https://example.com/img.jpg',
155+
isLinkPreload: true,
156+
}]);
157+
});
78158
});

0 commit comments

Comments
 (0)