-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
core(lantern): add interface for network request #15845
Changes from 8 commits
0786eb5
25471ba
f37fc61
36a9ff9
5e54849
d2a438e
cb781ac
02a0504
7bc9679
291dc23
b0450ab
06b2125
d4ee2ef
8c1c375
c0cfa56
408109e
eb193b0
03df941
6dfd8f0
6432824
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/** | ||
connorjclark marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @license | ||
Check failure on line 2 in core/lib/dependency-graph/lantern.d.ts GitHub Actions / basics
|
||
* Copyright 2024 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import * as LH from '../../../types/lh.js' | ||
|
||
export type ParsedURL = { | ||
/** | ||
* Equivalent to a `new URL(url).protocol` BUT w/o the trailing colon (:) | ||
*/ | ||
scheme: string; | ||
/** | ||
* Equivalent to a `new URL(url).hostname` | ||
*/ | ||
host: string; | ||
securityOrigin: string; | ||
}; | ||
export type LightriderStatistics = { | ||
/** | ||
* The difference in networkEndTime between the observed Lighthouse networkEndTime and Lightrider's derived networkEndTime. | ||
*/ | ||
endTimeDeltaMs: number; | ||
/** | ||
* The time spent making a TCP connection (connect + SSL). Note: this is poorly named. | ||
*/ | ||
TCPMs: number; | ||
/** | ||
* The time spent requesting a resource from a remote server, we use this to approx RTT. Note: this is poorly names, it really should be "server response time". | ||
*/ | ||
requestMs: number; | ||
/** | ||
* Time to receive the entire response payload starting the clock on receiving the first fragment (first non-header byte). | ||
*/ | ||
responseMs: number; | ||
}; | ||
export class NetworkRequest<T=any> { | ||
/** The canonical network record. */ | ||
record?: T; | ||
|
||
static get TYPES(): LH.Util.SelfMap<LH.Crdp.Network.ResourceType>; | ||
|
||
isNonNetworkRequest(): boolean; | ||
requestId: string; | ||
connectionId: string; | ||
connectionReused: boolean; | ||
url: string; | ||
protocol: string; | ||
parsedURL: ParsedURL; | ||
/** When the renderer process initially discovers a network request, in milliseconds. */ | ||
rendererStartTime: number; | ||
/** | ||
* When the network service is about to handle a request, ie. just before going to the | ||
* HTTP cache or going to the network for DNS/connection setup, in milliseconds. | ||
*/ | ||
networkRequestTime: number; | ||
/** When the last byte of the response headers is received, in milliseconds. */ | ||
responseHeadersEndTime: number; | ||
/** When the last byte of the response body is received, in milliseconds. */ | ||
networkEndTime: number; | ||
transferSize: number; | ||
resourceSize: number; | ||
fromDiskCache: boolean; | ||
fromMemoryCache: boolean; | ||
// TODO(15841): remove from lantern. | ||
/** Extra timing information available only when run in Lightrider. */ | ||
lrStatistics: LightriderStatistics | undefined; | ||
finished: boolean; | ||
statusCode: number; | ||
/** The network request that this one redirected to */ | ||
redirectDestination: NetworkRequest<T> | undefined; | ||
failed: boolean; | ||
initiator: LH.Crdp.Network.Initiator; | ||
timing: LH.Crdp.Network.ResourceTiming | undefined; | ||
resourceType: LH.Crdp.Network.ResourceType | undefined; | ||
priority: LH.Crdp.Network.ResourcePriority; | ||
} | ||
|
||
export {}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** | ||
* @license | ||
* Copyright 2024 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
const NetworkRequest = { | ||
/** @type {LH.Util.SelfMap<LH.Crdp.Network.ResourceType>} */ | ||
TYPES: { | ||
XHR: 'XHR', | ||
Fetch: 'Fetch', | ||
EventSource: 'EventSource', | ||
Script: 'Script', | ||
Stylesheet: 'Stylesheet', | ||
Image: 'Image', | ||
Media: 'Media', | ||
Font: 'Font', | ||
Document: 'Document', | ||
TextTrack: 'TextTrack', | ||
WebSocket: 'WebSocket', | ||
Other: 'Other', | ||
Manifest: 'Manifest', | ||
SignedExchange: 'SignedExchange', | ||
Ping: 'Ping', | ||
Preflight: 'Preflight', | ||
CSPViolationReport: 'CSPViolationReport', | ||
Prefetch: 'Prefetch', | ||
}, | ||
}; | ||
|
||
export { | ||
NetworkRequest, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,18 +4,21 @@ | |
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import * as LH from '../../../types/lh.js'; | ||
import * as Lantern from './lantern.js'; | ||
import {BaseNode} from './base-node.js'; | ||
import {NetworkRequest} from '../network-request.js'; | ||
|
||
/** | ||
* @template [T=any] | ||
* @extends {BaseNode<T>} | ||
*/ | ||
class NetworkNode extends BaseNode { | ||
/** | ||
* @param {LH.Artifacts.NetworkRequest} networkRecord | ||
* @param {Lantern.NetworkRequest<T>} networkRequest | ||
*/ | ||
constructor(networkRecord) { | ||
super(networkRecord.requestId); | ||
constructor(networkRequest) { | ||
super(networkRequest.requestId); | ||
/** @private */ | ||
this._record = networkRecord; | ||
this._request = networkRequest; | ||
} | ||
|
||
get type() { | ||
|
@@ -26,42 +29,49 @@ class NetworkNode extends BaseNode { | |
* @return {number} | ||
*/ | ||
get startTime() { | ||
return this._record.rendererStartTime * 1000; | ||
return this._request.rendererStartTime * 1000; | ||
} | ||
|
||
/** | ||
* @return {number} | ||
*/ | ||
get endTime() { | ||
return this._record.networkEndTime * 1000; | ||
return this._request.networkEndTime * 1000; | ||
} | ||
|
||
/** | ||
* @return {LH.Artifacts.NetworkRequest} | ||
* @return {T} | ||
*/ | ||
get record() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's kinda confusing to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kept record in this PR just to prevent a big name churn in our usage of Lantern. We can rename them, in another PR to keep this one clearer. I don't know what good names would be right now. |
||
return this._record; | ||
return /** @type {T} */ (this._request.record); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I wouldn't expect this type cast to be necessary, are there errors if it's removed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This removes undefined from the type from |
||
} | ||
|
||
/** | ||
* @return {Lantern.NetworkRequest<T>} | ||
*/ | ||
get request() { | ||
return this._request; | ||
} | ||
|
||
/** | ||
* @return {string} | ||
*/ | ||
get initiatorType() { | ||
return this._record.initiator && this._record.initiator.type; | ||
return this._request.initiator && this._request.initiator.type; | ||
} | ||
|
||
/** | ||
* @return {boolean} | ||
*/ | ||
get fromDiskCache() { | ||
return !!this._record.fromDiskCache; | ||
return !!this._request.fromDiskCache; | ||
} | ||
|
||
/** | ||
* @return {boolean} | ||
*/ | ||
get isNonNetworkProtocol() { | ||
return NetworkRequest.isNonNetworkRequest(this._record); | ||
return this._request.isNonNetworkRequest(); | ||
} | ||
|
||
|
||
|
@@ -78,19 +88,19 @@ class NetworkNode extends BaseNode { | |
* @return {boolean} | ||
*/ | ||
hasRenderBlockingPriority() { | ||
const priority = this._record.priority; | ||
const isScript = this._record.resourceType === NetworkRequest.TYPES.Script; | ||
const isDocument = this._record.resourceType === NetworkRequest.TYPES.Document; | ||
const priority = this._request.priority; | ||
const isScript = this._request.resourceType === Lantern.NetworkRequest.TYPES.Script; | ||
const isDocument = this._request.resourceType === Lantern.NetworkRequest.TYPES.Document; | ||
const isBlockingScript = priority === 'High' && isScript; | ||
const isBlockingHtmlImport = priority === 'High' && isDocument; | ||
return priority === 'VeryHigh' || isBlockingScript || isBlockingHtmlImport; | ||
} | ||
|
||
/** | ||
* @return {NetworkNode} | ||
* @return {NetworkNode<T>} | ||
*/ | ||
cloneWithoutRelationships() { | ||
const node = new NetworkNode(this._record); | ||
const node = new NetworkNode(this._request); | ||
node.setIsMainDocument(this._isMainDocument); | ||
return node; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming
CPUNode
does eventually need a generic, this type asserts thatT
will be the same betweenNetworkNode
andCPUNode
which would never be the case. Maybe we should just remove the generic fromCPUNode
for now?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a requirement for type narrowing to continue to work.
Remove the type parameter from BaseNode, CPUNode, or Node, and in the above
node.record
is any.