diff --git a/lighthouse-core/lib/network-recorder.js b/lighthouse-core/lib/network-recorder.js index 5df4dab88056..d37b069fe0a8 100644 --- a/lighthouse-core/lib/network-recorder.js +++ b/lighthouse-core/lib/network-recorder.js @@ -89,6 +89,19 @@ class NetworkRecorder extends EventEmitter { } } + /** + * QUIC network requests don't always "finish" even when they're done loading data, use recievedHeaders + * @see https://github.com/GoogleChrome/lighthouse/issues/5254 + * @param {LH.WebInspector.NetworkRequest} record + * @return {boolean} + */ + static _isQUICAndFinished(record) { + const isQUIC = record._responseHeaders && record._responseHeaders + .some(header => header.name.toLowerCase() === 'alt-svc' && /quic/.test(header.value)); + const receivedHeaders = record._timing && record._timing.receiveHeadersEnd > 0; + return !!(isQUIC && receivedHeaders && record.endTime); + } + /** * Finds all time periods where the number of inflight requests is less than or equal to the * number of allowed concurrent requests. @@ -109,7 +122,7 @@ class NetworkRecorder extends EventEmitter { // convert the network record timestamp to ms timeBoundaries.push({time: record.startTime * 1000, isStart: true}); - if (record.finished) { + if (record.finished || NetworkRecorder._isQUICAndFinished(record)) { timeBoundaries.push({time: record.endTime * 1000, isStart: false}); } }); @@ -143,7 +156,7 @@ class NetworkRecorder extends EventEmitter { quietPeriods.push({start: quietPeriodStart, end: endTime}); } - return quietPeriods; + return quietPeriods.filter(period => period.start !== period.end); } /** diff --git a/lighthouse-core/test/gather/network-recorder-test.js b/lighthouse-core/test/gather/network-recorder-test.js index 8a0a4b54713a..2aadd9bb6376 100644 --- a/lighthouse-core/test/gather/network-recorder-test.js +++ b/lighthouse-core/test/gather/network-recorder-test.js @@ -16,4 +16,96 @@ describe('network recorder', function() { const records = NetworkRecorder.recordsFromLogs(devtoolsLogItems); assert.equal(records.length, 76); }); + + describe('#findNetworkQuietPeriods', () => { + function record(data) { + const url = data.url || 'https://example.com'; + const scheme = url.split(':')[0]; + return Object.assign({ + url, + finished: !!data.endTime, + parsedURL: {scheme}, + }, data); + } + + it('should find the 0-quiet periods', () => { + const records = [ + record({startTime: 0, endTime: 1}), + record({startTime: 2, endTime: 3}), + record({startTime: 4, endTime: 5}), + ]; + + const periods = NetworkRecorder.findNetworkQuietPeriods(records, 0); + assert.deepStrictEqual(periods, [ + {start: 1000, end: 2000}, + {start: 3000, end: 4000}, + {start: 5000, end: Infinity}, + ]); + }); + + it('should find the 2-quiet periods', () => { + const records = [ + record({startTime: 0, endTime: 1.5}), + record({startTime: 0, endTime: 2}), + record({startTime: 0, endTime: 2.5}), + record({startTime: 2, endTime: 3}), + record({startTime: 4, endTime: 5}), + ]; + + const periods = NetworkRecorder.findNetworkQuietPeriods(records, 2); + assert.deepStrictEqual(periods, [ + {start: 1500, end: Infinity}, + ]); + }); + + it('should handle unfinished requests', () => { + const records = [ + record({startTime: 0, endTime: 1.5}), + record({startTime: 0, endTime: 2}), + record({startTime: 0, endTime: 2.5}), + record({startTime: 2, endTime: 3}), + record({startTime: 2}), + record({startTime: 2}), + record({startTime: 4, endTime: 5}), + record({startTime: 5.5}), + ]; + + const periods = NetworkRecorder.findNetworkQuietPeriods(records, 2); + assert.deepStrictEqual(periods, [ + {start: 1500, end: 2000}, + {start: 3000, end: 4000}, + {start: 5000, end: 5500}, + ]); + }); + + it('should ignore data URIs', () => { + const records = [ + record({startTime: 0, endTime: 1}), + record({startTime: 0, endTime: 2, url: 'data:image/png;base64,'}), + ]; + + const periods = NetworkRecorder.findNetworkQuietPeriods(records, 0); + assert.deepStrictEqual(periods, [ + {start: 1000, end: Infinity}, + ]); + }); + + it('should handle QUIC requests', () => { + const quicRequest = { + finished: false, + _responseHeaders: [{name: 'ALT-SVC', value: 'hq=":49288";quic="1,1abadaba,51303334,0"'}], + _timing: {receiveHeadersEnd: 1.28}, + }; + + const records = [ + record({startTime: 0, endTime: 1}), + record({startTime: 0, endTime: 2, ...quicRequest}), + ]; + + const periods = NetworkRecorder.findNetworkQuietPeriods(records, 0); + assert.deepStrictEqual(periods, [ + {start: 2000, end: Infinity}, + ]); + }); + }); });