lighthouse 12.0.0-dev.20240602 → 12.0.0-dev.20240603
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/audits/byte-efficiency/render-blocking-resources.js +1 -1
- package/core/audits/dobetterweb/uses-http2.js +2 -2
- package/core/audits/prioritize-lcp-image.js +1 -1
- package/core/audits/redirects.js +1 -1
- package/core/computed/metrics/lantern-metric.d.ts +19 -1
- package/core/computed/metrics/lantern-metric.js +51 -2
- package/core/computed/navigation-insights.d.ts +2 -0
- package/core/computed/trace-engine-result.d.ts +1 -0
- package/core/computed/trace-engine-result.js +1 -0
- package/core/lib/lantern/metric.js +3 -3
- package/core/lib/lantern/metrics/first-contentful-paint.js +1 -1
- package/core/lib/lantern/metrics/interactive.js +4 -4
- package/core/lib/lantern/metrics/largest-contentful-paint.js +2 -2
- package/core/lib/lantern/page-dependency-graph.d.ts +56 -0
- package/core/lib/lantern/page-dependency-graph.js +413 -2
- package/core/lib/lantern/simulator/simulator.d.ts +1 -1
- package/core/lib/lantern/simulator/simulator.js +1 -1
- package/core/lib/lantern/types/lantern.d.ts +6 -3
- package/core/lib/network-request.d.ts +21 -0
- package/core/lib/network-request.js +4 -1
- package/package.json +6 -4
- package/tsconfig.json +1 -0
- package/types/artifacts.d.ts +16 -0
|
@@ -53,7 +53,7 @@ function getNodesAndTimingByRequestId(nodeTimings) {
|
|
|
53
53
|
for (const [node, nodeTiming] of nodeTimings) {
|
|
54
54
|
if (node.type !== 'network') continue;
|
|
55
55
|
|
|
56
|
-
requestIdToNode.set(node.
|
|
56
|
+
requestIdToNode.set(node.request.requestId, {node, nodeTiming});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
return requestIdToNode;
|
|
@@ -89,7 +89,7 @@ class UsesHTTP2Audit extends Audit {
|
|
|
89
89
|
if (node.type !== 'network') return;
|
|
90
90
|
if (!urlsToChange.has(node.record.url)) return;
|
|
91
91
|
|
|
92
|
-
originalProtocols.set(node.
|
|
92
|
+
originalProtocols.set(node.request.requestId, node.record.protocol);
|
|
93
93
|
node.request.protocol = 'h2';
|
|
94
94
|
});
|
|
95
95
|
|
|
@@ -98,7 +98,7 @@ class UsesHTTP2Audit extends Audit {
|
|
|
98
98
|
// Restore the original protocol after we've done our simulation
|
|
99
99
|
graph.traverse(node => {
|
|
100
100
|
if (node.type !== 'network') return;
|
|
101
|
-
const originalProtocol = originalProtocols.get(node.
|
|
101
|
+
const originalProtocol = originalProtocols.get(node.request.requestId);
|
|
102
102
|
if (originalProtocol === undefined) return;
|
|
103
103
|
node.request.protocol = originalProtocol;
|
|
104
104
|
});
|
|
@@ -69,7 +69,7 @@ class PrioritizeLcpImage extends Audit {
|
|
|
69
69
|
static findLCPNode(graph, lcpRecord) {
|
|
70
70
|
for (const {node} of graph.traverseGenerator()) {
|
|
71
71
|
if (node.type !== 'network') continue;
|
|
72
|
-
if (node.
|
|
72
|
+
if (node.request.requestId === lcpRecord.requestId) {
|
|
73
73
|
return node;
|
|
74
74
|
}
|
|
75
75
|
}
|
package/core/audits/redirects.js
CHANGED
|
@@ -98,7 +98,7 @@ class Redirects extends Audit {
|
|
|
98
98
|
const nodeTimingsById = new Map();
|
|
99
99
|
for (const [node, timing] of metricResult.pessimisticEstimate.nodeTimings.entries()) {
|
|
100
100
|
if (node.type === 'network') {
|
|
101
|
-
nodeTimingsById.set(node.
|
|
101
|
+
nodeTimingsById.set(node.request.requestId, timing);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -2,11 +2,29 @@
|
|
|
2
2
|
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
3
3
|
* @param {LH.Artifacts.ComputedContext} context
|
|
4
4
|
*/
|
|
5
|
-
export function
|
|
5
|
+
export function getComputationDataParamsFromTrace(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<{
|
|
6
|
+
simulator: import("../../lib/lantern/simulator/simulator.js").Simulator<any>;
|
|
7
|
+
graph: import("../../lib/lantern/page-dependency-graph.js").Node;
|
|
8
|
+
processedNavigation: import("../../index.js").Artifacts.ProcessedNavigation;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
12
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
13
|
+
*/
|
|
14
|
+
export function getComputationDataParamsFromDevtoolsLog(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<{
|
|
6
15
|
simulator: import("../../lib/lantern/simulator/simulator.js").Simulator<any>;
|
|
7
16
|
graph: import("../page-dependency-graph.js").Node;
|
|
8
17
|
processedNavigation: import("../../index.js").Artifacts.ProcessedNavigation;
|
|
9
18
|
}>;
|
|
19
|
+
/**
|
|
20
|
+
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
21
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
22
|
+
*/
|
|
23
|
+
export function getComputationDataParams(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<{
|
|
24
|
+
simulator: import("../../lib/lantern/simulator/simulator.js").Simulator<any>;
|
|
25
|
+
graph: import("../../lib/lantern/page-dependency-graph.js").Node;
|
|
26
|
+
processedNavigation: import("../../index.js").Artifacts.ProcessedNavigation;
|
|
27
|
+
}>;
|
|
10
28
|
/**
|
|
11
29
|
* @param {unknown} err
|
|
12
30
|
* @return {never}
|
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {LanternError} from '../../lib/lantern/lantern-error.js';
|
|
8
|
+
import {PageDependencyGraph as LanternPageDependencyGraph} from '../../lib/lantern/page-dependency-graph.js';
|
|
8
9
|
import {LighthouseError} from '../../lib/lh-error.js';
|
|
9
10
|
import {LoadSimulator} from '../load-simulator.js';
|
|
10
|
-
import {PageDependencyGraph} from '../page-dependency-graph.js';
|
|
11
11
|
import {ProcessedNavigation} from '../processed-navigation.js';
|
|
12
|
+
import {ProcessedTrace} from '../processed-trace.js';
|
|
13
|
+
import {TraceEngineResult} from '../trace-engine-result.js';
|
|
14
|
+
import {PageDependencyGraph} from '../page-dependency-graph.js';
|
|
12
15
|
|
|
16
|
+
// TODO: we need to update all test traces (that use lantern) before this can be removed
|
|
13
17
|
/**
|
|
14
18
|
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
15
19
|
* @param {LH.Artifacts.ComputedContext} context
|
|
16
20
|
*/
|
|
17
|
-
async function
|
|
21
|
+
async function getComputationDataParamsFromDevtoolsLog(data, context) {
|
|
18
22
|
if (data.gatherContext.gatherMode !== 'navigation') {
|
|
19
23
|
throw new Error(`Lantern metrics can only be computed on navigations`);
|
|
20
24
|
}
|
|
@@ -26,6 +30,35 @@ async function getComputationDataParams(data, context) {
|
|
|
26
30
|
return {simulator, graph, processedNavigation};
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @param {LH.Artifacts.URL} theURL
|
|
35
|
+
* @param {LH.Trace} trace
|
|
36
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
37
|
+
*/
|
|
38
|
+
async function createGraphFromTrace(theURL, trace, context) {
|
|
39
|
+
const {mainThreadEvents} = await ProcessedTrace.request(trace, context);
|
|
40
|
+
const traceEngineResult = await TraceEngineResult.request({trace}, context);
|
|
41
|
+
return LanternPageDependencyGraph.createGraphFromTrace(
|
|
42
|
+
mainThreadEvents, trace, traceEngineResult, theURL);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
47
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
48
|
+
*/
|
|
49
|
+
async function getComputationDataParamsFromTrace(data, context) {
|
|
50
|
+
if (data.gatherContext.gatherMode !== 'navigation') {
|
|
51
|
+
throw new Error(`Lantern metrics can only be computed on navigations`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const {trace, URL} = data;
|
|
55
|
+
const {graph} = await createGraphFromTrace(URL, trace, context);
|
|
56
|
+
const processedNavigation = await ProcessedNavigation.request(data.trace, context);
|
|
57
|
+
const simulator = data.simulator || (await LoadSimulator.request(data, context));
|
|
58
|
+
|
|
59
|
+
return {simulator, graph, processedNavigation};
|
|
60
|
+
}
|
|
61
|
+
|
|
29
62
|
/**
|
|
30
63
|
* @param {unknown} err
|
|
31
64
|
* @return {never}
|
|
@@ -43,7 +76,23 @@ function lanternErrorAdapter(err) {
|
|
|
43
76
|
throw err;
|
|
44
77
|
}
|
|
45
78
|
|
|
79
|
+
/**
|
|
80
|
+
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
81
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
82
|
+
*/
|
|
83
|
+
function getComputationDataParams(data, context) {
|
|
84
|
+
// TODO(15841): remove when all tests use the trace, and we want to remove CDT graph impl.
|
|
85
|
+
if (process.env.INTERNAL_LANTERN_USE_TRACE !== undefined) {
|
|
86
|
+
return getComputationDataParamsFromTrace(data, context);
|
|
87
|
+
} else {
|
|
88
|
+
// This is the default behavior.
|
|
89
|
+
return getComputationDataParamsFromDevtoolsLog(data, context);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
46
93
|
export {
|
|
94
|
+
getComputationDataParamsFromTrace,
|
|
95
|
+
getComputationDataParamsFromDevtoolsLog,
|
|
47
96
|
getComputationDataParams,
|
|
48
97
|
lanternErrorAdapter,
|
|
49
98
|
};
|
|
@@ -11,6 +11,7 @@ declare const NavigationInsightsComputed: typeof NavigationInsights & {
|
|
|
11
11
|
Samples: typeof import("@paulirish/trace_engine/models/trace/handlers/SamplesHandler.js");
|
|
12
12
|
Screenshots: typeof import("@paulirish/trace_engine/models/trace/handlers/ScreenshotsHandler.js");
|
|
13
13
|
PageLoadMetrics: typeof import("@paulirish/trace_engine/models/trace/handlers/PageLoadMetricsHandler.js");
|
|
14
|
+
Workers: typeof import("@paulirish/trace_engine/models/trace/handlers/WorkersHandler.js");
|
|
14
15
|
}>>;
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
@@ -31,6 +32,7 @@ declare class NavigationInsights {
|
|
|
31
32
|
Samples: typeof import("@paulirish/trace_engine/models/trace/handlers/SamplesHandler.js");
|
|
32
33
|
Screenshots: typeof import("@paulirish/trace_engine/models/trace/handlers/ScreenshotsHandler.js");
|
|
33
34
|
PageLoadMetrics: typeof import("@paulirish/trace_engine/models/trace/handlers/PageLoadMetricsHandler.js");
|
|
35
|
+
Workers: typeof import("@paulirish/trace_engine/models/trace/handlers/WorkersHandler.js");
|
|
34
36
|
}>>;
|
|
35
37
|
}
|
|
36
38
|
//# sourceMappingURL=navigation-insights.d.ts.map
|
|
@@ -16,6 +16,7 @@ declare namespace ENABLED_HANDLERS {
|
|
|
16
16
|
const Samples: typeof import("@paulirish/trace_engine/models/trace/handlers/SamplesHandler.js");
|
|
17
17
|
const Screenshots: typeof import("@paulirish/trace_engine/models/trace/handlers/ScreenshotsHandler.js");
|
|
18
18
|
const PageLoadMetrics: typeof import("@paulirish/trace_engine/models/trace/handlers/PageLoadMetricsHandler.js");
|
|
19
|
+
const Workers: typeof import("@paulirish/trace_engine/models/trace/handlers/WorkersHandler.js");
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* @fileoverview Processes trace with the shared trace engine.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import * as Lantern from './types/lantern.js';
|
|
8
8
|
import {BaseNode} from '../../lib/lantern/base-node.js';
|
|
9
|
-
import {
|
|
9
|
+
import {RESOURCE_TYPES} from '../../lib/network-request.js';
|
|
10
10
|
|
|
11
11
|
/** @typedef {import('./base-node.js').Node} Node */
|
|
12
12
|
/** @typedef {import('./network-node.js').NetworkNode} NetworkNode */
|
|
@@ -33,9 +33,9 @@ class Metric {
|
|
|
33
33
|
|
|
34
34
|
dependencyGraph.traverse(node => {
|
|
35
35
|
if (node.type !== BaseNode.TYPES.NETWORK) return;
|
|
36
|
-
if (node.
|
|
36
|
+
if (node.request.resourceType !== RESOURCE_TYPES.Script) return;
|
|
37
37
|
if (treatNodeAsRenderBlocking?.(node)) {
|
|
38
|
-
scriptUrls.add(node.
|
|
38
|
+
scriptUrls.add(node.request.url);
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -153,7 +153,7 @@ class FirstContentfulPaint extends Metric {
|
|
|
153
153
|
const endedAfterPaint = node.endTime > cutoffTimestamp || node.startTime > cutoffTimestamp;
|
|
154
154
|
if (endedAfterPaint && !node.isMainDocument()) return false;
|
|
155
155
|
|
|
156
|
-
const url = node.
|
|
156
|
+
const url = node.request.url;
|
|
157
157
|
// If the URL definitely wasn't render-blocking then we filter it out.
|
|
158
158
|
if (definitelyNotRenderBlockingScriptUrls.has(url)) {
|
|
159
159
|
return false;
|
|
@@ -41,13 +41,13 @@ class Interactive extends Metric {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// Include all scripts and high priority requests, exclude all images
|
|
44
|
-
const isImage = node.
|
|
45
|
-
const isScript = node.
|
|
44
|
+
const isImage = node.request.resourceType === NetworkRequestTypes.Image;
|
|
45
|
+
const isScript = node.request.resourceType === NetworkRequestTypes.Script;
|
|
46
46
|
return (
|
|
47
47
|
!isImage &&
|
|
48
48
|
(isScript ||
|
|
49
|
-
node.
|
|
50
|
-
node.
|
|
49
|
+
node.request.priority === 'High' ||
|
|
50
|
+
node.request.priority === 'VeryHigh')
|
|
51
51
|
);
|
|
52
52
|
});
|
|
53
53
|
}
|
|
@@ -32,8 +32,8 @@ class LargestContentfulPaint extends Metric {
|
|
|
32
32
|
*/
|
|
33
33
|
static isNotLowPriorityImageNode(node) {
|
|
34
34
|
if (node.type !== 'network') return true;
|
|
35
|
-
const isImage = node.
|
|
36
|
-
const isLowPriority = node.
|
|
35
|
+
const isImage = node.request.resourceType === 'Image';
|
|
36
|
+
const isLowPriority = node.request.priority === 'Low' || node.request.priority === 'VeryLow';
|
|
37
37
|
return !isImage || !isLowPriority;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -39,6 +39,20 @@ export class PageDependencyGraph {
|
|
|
39
39
|
* @param {Node} node
|
|
40
40
|
*/
|
|
41
41
|
static _pruneNode(node: Node): void;
|
|
42
|
+
/**
|
|
43
|
+
* TODO(15841): remove when CDT backend is gone. until then, this is a useful debugging tool
|
|
44
|
+
* to find delta between using CDP or the trace to create the network requests.
|
|
45
|
+
*
|
|
46
|
+
* When a test fails using the trace backend, I enabled this debug method and copied the network
|
|
47
|
+
* requests when CDP was used, then when trace is used, and diff'd them. This method helped
|
|
48
|
+
* remove non-logical differences from the comparison (order of properties, slight rounding
|
|
49
|
+
* discrepancies, removing object cycles, etc).
|
|
50
|
+
*
|
|
51
|
+
* When using for a unit test, make sure to do `.only` so you are getting what you expect.
|
|
52
|
+
* @param {Lantern.NetworkRequest[]} lanternRequests
|
|
53
|
+
* @return {never}
|
|
54
|
+
*/
|
|
55
|
+
static _debugNormalizeRequests(lanternRequests: Lantern.NetworkRequest[]): never;
|
|
42
56
|
/**
|
|
43
57
|
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
44
58
|
* @param {Array<Lantern.NetworkRequest>} networkRecords
|
|
@@ -46,6 +60,48 @@ export class PageDependencyGraph {
|
|
|
46
60
|
* @return {Node}
|
|
47
61
|
*/
|
|
48
62
|
static createGraph(mainThreadEvents: LH.TraceEvent[], networkRecords: Array<Lantern.NetworkRequest>, URL: URLArtifact): Node;
|
|
63
|
+
/**
|
|
64
|
+
* @param {Lantern.NetworkRequest} record The record to find the initiator of
|
|
65
|
+
* @param {Map<string, Lantern.NetworkRequest[]>} recordsByURL
|
|
66
|
+
* @return {Lantern.NetworkRequest|null}
|
|
67
|
+
*/
|
|
68
|
+
static chooseInitiatorRequest(record: Lantern.NetworkRequest, recordsByURL: Map<string, Lantern.NetworkRequest[]>): Lantern.NetworkRequest | null;
|
|
69
|
+
/**
|
|
70
|
+
* Returns a map of `pid` -> `tid[]`.
|
|
71
|
+
* @param {LH.Trace} trace
|
|
72
|
+
* @return {Map<number, number[]>}
|
|
73
|
+
*/
|
|
74
|
+
static _findWorkerThreads(trace: LH.Trace): Map<number, number[]>;
|
|
75
|
+
/**
|
|
76
|
+
* @param {URL|string} url
|
|
77
|
+
*/
|
|
78
|
+
static _createParsedUrl(url: URL | string): {
|
|
79
|
+
scheme: string;
|
|
80
|
+
host: string;
|
|
81
|
+
securityOrigin: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* @param {LH.Artifacts.TraceEngineResult} traceEngineResult
|
|
85
|
+
* @param {Map<number, number[]>} workerThreads
|
|
86
|
+
* @param {import('@paulirish/trace_engine/models/trace/types/TraceEvents.js').SyntheticNetworkRequest} request
|
|
87
|
+
* @return {Lantern.NetworkRequest=}
|
|
88
|
+
*/
|
|
89
|
+
static _createLanternRequest(traceEngineResult: LH.Artifacts.TraceEngineResult, workerThreads: Map<number, number[]>, request: import('@paulirish/trace_engine/models/trace/types/TraceEvents.js').SyntheticNetworkRequest): Lantern.NetworkRequest | undefined;
|
|
90
|
+
/**
|
|
91
|
+
*
|
|
92
|
+
* @param {Lantern.NetworkRequest[]} lanternRequests
|
|
93
|
+
*/
|
|
94
|
+
static _linkInitiators(lanternRequests: Lantern.NetworkRequest[]): void;
|
|
95
|
+
/**
|
|
96
|
+
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
97
|
+
* @param {LH.Trace} trace
|
|
98
|
+
* @param {LH.Artifacts.TraceEngineResult} traceEngineResult
|
|
99
|
+
* @param {LH.Artifacts.URL} URL
|
|
100
|
+
*/
|
|
101
|
+
static createGraphFromTrace(mainThreadEvents: LH.TraceEvent[], trace: LH.Trace, traceEngineResult: LH.Artifacts.TraceEngineResult, URL: LH.Artifacts.URL): Promise<{
|
|
102
|
+
graph: Node;
|
|
103
|
+
records: Lantern.NetworkRequest<any>[];
|
|
104
|
+
}>;
|
|
49
105
|
/**
|
|
50
106
|
*
|
|
51
107
|
* @param {Node} rootNode
|
|
@@ -10,6 +10,7 @@ import {NetworkNode} from './network-node.js';
|
|
|
10
10
|
import {CPUNode} from './cpu-node.js';
|
|
11
11
|
import {TraceProcessor} from '../tracehouse/trace-processor.js';
|
|
12
12
|
import {NetworkAnalyzer} from './simulator/network-analyzer.js';
|
|
13
|
+
import {RESOURCE_TYPES} from '../network-request.js';
|
|
13
14
|
|
|
14
15
|
/** @typedef {import('./base-node.js').Node} Node */
|
|
15
16
|
/** @typedef {Omit<LH.Artifacts['URL'], 'finalDisplayedUrl'>} URLArtifact */
|
|
@@ -75,7 +76,7 @@ class PageDependencyGraph {
|
|
|
75
76
|
|
|
76
77
|
networkRecords.forEach(record => {
|
|
77
78
|
if (IGNORED_MIME_TYPES_REGEX.test(record.mimeType)) return;
|
|
78
|
-
if (record.
|
|
79
|
+
if (record.fromWorker) return;
|
|
79
80
|
|
|
80
81
|
// Network record requestIds can be duplicated for an unknown reason
|
|
81
82
|
// Suffix all subsequent records with `:duplicate` until it's unique
|
|
@@ -404,6 +405,107 @@ class PageDependencyGraph {
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
|
|
408
|
+
/**
|
|
409
|
+
* TODO(15841): remove when CDT backend is gone. until then, this is a useful debugging tool
|
|
410
|
+
* to find delta between using CDP or the trace to create the network requests.
|
|
411
|
+
*
|
|
412
|
+
* When a test fails using the trace backend, I enabled this debug method and copied the network
|
|
413
|
+
* requests when CDP was used, then when trace is used, and diff'd them. This method helped
|
|
414
|
+
* remove non-logical differences from the comparison (order of properties, slight rounding
|
|
415
|
+
* discrepancies, removing object cycles, etc).
|
|
416
|
+
*
|
|
417
|
+
* When using for a unit test, make sure to do `.only` so you are getting what you expect.
|
|
418
|
+
* @param {Lantern.NetworkRequest[]} lanternRequests
|
|
419
|
+
* @return {never}
|
|
420
|
+
*/
|
|
421
|
+
static _debugNormalizeRequests(lanternRequests) {
|
|
422
|
+
for (const request of lanternRequests) {
|
|
423
|
+
request.rendererStartTime = Math.round(request.rendererStartTime * 1000) / 1000;
|
|
424
|
+
request.networkRequestTime = Math.round(request.networkRequestTime * 1000) / 1000;
|
|
425
|
+
request.responseHeadersEndTime = Math.round(request.responseHeadersEndTime * 1000) / 1000;
|
|
426
|
+
request.networkEndTime = Math.round(request.networkEndTime * 1000) / 1000;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
for (const r of lanternRequests) {
|
|
430
|
+
delete r.record;
|
|
431
|
+
if (r.initiatorRequest) {
|
|
432
|
+
// @ts-expect-error
|
|
433
|
+
r.initiatorRequest = {id: r.initiatorRequest.requestId};
|
|
434
|
+
}
|
|
435
|
+
if (r.redirectDestination) {
|
|
436
|
+
// @ts-expect-error
|
|
437
|
+
r.redirectDestination = {id: r.redirectDestination.requestId};
|
|
438
|
+
}
|
|
439
|
+
if (r.redirectSource) {
|
|
440
|
+
// @ts-expect-error
|
|
441
|
+
r.redirectSource = {id: r.redirectSource.requestId};
|
|
442
|
+
}
|
|
443
|
+
if (r.redirects) {
|
|
444
|
+
// @ts-expect-error
|
|
445
|
+
r.redirects = r.redirects.map(r2 => r2.requestId);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/** @type {Lantern.NetworkRequest[]} */
|
|
449
|
+
const requests = lanternRequests.map(r => ({
|
|
450
|
+
requestId: r.requestId,
|
|
451
|
+
connectionId: r.connectionId,
|
|
452
|
+
connectionReused: r.connectionReused,
|
|
453
|
+
url: r.url,
|
|
454
|
+
protocol: r.protocol,
|
|
455
|
+
parsedURL: r.parsedURL,
|
|
456
|
+
documentURL: r.documentURL,
|
|
457
|
+
rendererStartTime: r.rendererStartTime,
|
|
458
|
+
networkRequestTime: r.networkRequestTime,
|
|
459
|
+
responseHeadersEndTime: r.responseHeadersEndTime,
|
|
460
|
+
networkEndTime: r.networkEndTime,
|
|
461
|
+
transferSize: r.transferSize,
|
|
462
|
+
resourceSize: r.resourceSize,
|
|
463
|
+
fromDiskCache: r.fromDiskCache,
|
|
464
|
+
fromMemoryCache: r.fromMemoryCache,
|
|
465
|
+
finished: r.finished,
|
|
466
|
+
statusCode: r.statusCode,
|
|
467
|
+
redirectSource: r.redirectSource,
|
|
468
|
+
redirectDestination: r.redirectDestination,
|
|
469
|
+
redirects: r.redirects,
|
|
470
|
+
failed: r.failed,
|
|
471
|
+
initiator: r.initiator,
|
|
472
|
+
timing: r.timing ? {
|
|
473
|
+
requestTime: r.timing.requestTime,
|
|
474
|
+
proxyStart: r.timing.proxyStart,
|
|
475
|
+
proxyEnd: r.timing.proxyEnd,
|
|
476
|
+
dnsStart: r.timing.dnsStart,
|
|
477
|
+
dnsEnd: r.timing.dnsEnd,
|
|
478
|
+
connectStart: r.timing.connectStart,
|
|
479
|
+
connectEnd: r.timing.connectEnd,
|
|
480
|
+
sslStart: r.timing.sslStart,
|
|
481
|
+
sslEnd: r.timing.sslEnd,
|
|
482
|
+
workerStart: r.timing.workerStart,
|
|
483
|
+
workerReady: r.timing.workerReady,
|
|
484
|
+
workerFetchStart: r.timing.workerFetchStart,
|
|
485
|
+
workerRespondWithSettled: r.timing.workerRespondWithSettled,
|
|
486
|
+
sendStart: r.timing.sendStart,
|
|
487
|
+
sendEnd: r.timing.sendEnd,
|
|
488
|
+
pushStart: r.timing.pushStart,
|
|
489
|
+
pushEnd: r.timing.pushEnd,
|
|
490
|
+
receiveHeadersStart: r.timing.receiveHeadersStart,
|
|
491
|
+
receiveHeadersEnd: r.timing.receiveHeadersEnd,
|
|
492
|
+
} : r.timing,
|
|
493
|
+
resourceType: r.resourceType,
|
|
494
|
+
mimeType: r.mimeType,
|
|
495
|
+
priority: r.priority,
|
|
496
|
+
initiatorRequest: r.initiatorRequest,
|
|
497
|
+
frameId: r.frameId,
|
|
498
|
+
fromWorker: r.fromWorker,
|
|
499
|
+
isLinkPreload: r.isLinkPreload,
|
|
500
|
+
serverResponseTime: r.serverResponseTime,
|
|
501
|
+
})).filter(r => !r.fromWorker);
|
|
502
|
+
// eslint-disable-next-line no-unused-vars
|
|
503
|
+
const debug = requests;
|
|
504
|
+
// Set breakpoint here.
|
|
505
|
+
// Copy `debug` and compare with https://www.diffchecker.com/text-compare/
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
|
|
407
509
|
/**
|
|
408
510
|
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
409
511
|
* @param {Array<Lantern.NetworkRequest>} networkRecords
|
|
@@ -411,6 +513,8 @@ class PageDependencyGraph {
|
|
|
411
513
|
* @return {Node}
|
|
412
514
|
*/
|
|
413
515
|
static createGraph(mainThreadEvents, networkRecords, URL) {
|
|
516
|
+
// This is for debugging trace/devtoolslog network records.
|
|
517
|
+
// const debug = PageDependencyGraph._debugNormalizeRequests(networkRecords);
|
|
414
518
|
const networkNodeOutput = PageDependencyGraph.getNetworkNodeOutput(networkRecords);
|
|
415
519
|
const cpuNodes = PageDependencyGraph.getCPUNodes(mainThreadEvents);
|
|
416
520
|
const {requestedUrl, mainDocumentUrl} = URL;
|
|
@@ -421,7 +525,6 @@ class PageDependencyGraph {
|
|
|
421
525
|
if (!rootRequest) throw new Error('rootRequest not found');
|
|
422
526
|
const rootNode = networkNodeOutput.idToNodeMap.get(rootRequest.requestId);
|
|
423
527
|
if (!rootNode) throw new Error('rootNode not found');
|
|
424
|
-
|
|
425
528
|
const mainDocumentRequest =
|
|
426
529
|
NetworkAnalyzer.findLastDocumentForUrl(networkRecords, mainDocumentUrl);
|
|
427
530
|
if (!mainDocumentRequest) throw new Error('mainDocumentRequest not found');
|
|
@@ -439,6 +542,314 @@ class PageDependencyGraph {
|
|
|
439
542
|
return rootNode;
|
|
440
543
|
}
|
|
441
544
|
|
|
545
|
+
/**
|
|
546
|
+
* @param {Lantern.NetworkRequest} record The record to find the initiator of
|
|
547
|
+
* @param {Map<string, Lantern.NetworkRequest[]>} recordsByURL
|
|
548
|
+
* @return {Lantern.NetworkRequest|null}
|
|
549
|
+
*/
|
|
550
|
+
static chooseInitiatorRequest(record, recordsByURL) {
|
|
551
|
+
if (record.redirectSource) {
|
|
552
|
+
return record.redirectSource;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const initiatorURL = PageDependencyGraph.getNetworkInitiators(record)[0];
|
|
556
|
+
let candidates = recordsByURL.get(initiatorURL) || [];
|
|
557
|
+
// The (valid) initiator must come before the initiated request.
|
|
558
|
+
candidates = candidates.filter(c => {
|
|
559
|
+
return c.responseHeadersEndTime <= record.rendererStartTime &&
|
|
560
|
+
c.finished && !c.failed;
|
|
561
|
+
});
|
|
562
|
+
if (candidates.length > 1) {
|
|
563
|
+
// Disambiguate based on prefetch. Prefetch requests have type 'Other' and cannot
|
|
564
|
+
// initiate requests, so we drop them here.
|
|
565
|
+
const nonPrefetchCandidates = candidates.filter(
|
|
566
|
+
cand => cand.resourceType !== RESOURCE_TYPES.Other);
|
|
567
|
+
if (nonPrefetchCandidates.length) {
|
|
568
|
+
candidates = nonPrefetchCandidates;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (candidates.length > 1) {
|
|
572
|
+
// Disambiguate based on frame. It's likely that the initiator comes from the same frame.
|
|
573
|
+
const sameFrameCandidates = candidates.filter(cand => cand.frameId === record.frameId);
|
|
574
|
+
if (sameFrameCandidates.length) {
|
|
575
|
+
candidates = sameFrameCandidates;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (candidates.length > 1 && record.initiator.type === 'parser') {
|
|
579
|
+
// Filter to just Documents when initiator type is parser.
|
|
580
|
+
const documentCandidates = candidates.filter(cand =>
|
|
581
|
+
cand.resourceType === RESOURCE_TYPES.Document);
|
|
582
|
+
if (documentCandidates.length) {
|
|
583
|
+
candidates = documentCandidates;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (candidates.length > 1) {
|
|
587
|
+
// If all real loads came from successful preloads (url preloaded and
|
|
588
|
+
// loads came from the cache), filter to link rel=preload request(s).
|
|
589
|
+
const linkPreloadCandidates = candidates.filter(c => c.isLinkPreload);
|
|
590
|
+
if (linkPreloadCandidates.length) {
|
|
591
|
+
const nonPreloadCandidates = candidates.filter(c => !c.isLinkPreload);
|
|
592
|
+
const allPreloaded = nonPreloadCandidates.every(c => c.fromDiskCache || c.fromMemoryCache);
|
|
593
|
+
if (nonPreloadCandidates.length && allPreloaded) {
|
|
594
|
+
candidates = linkPreloadCandidates;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Only return an initiator if the result is unambiguous.
|
|
600
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Returns a map of `pid` -> `tid[]`.
|
|
605
|
+
* @param {LH.Trace} trace
|
|
606
|
+
* @return {Map<number, number[]>}
|
|
607
|
+
*/
|
|
608
|
+
static _findWorkerThreads(trace) {
|
|
609
|
+
// TODO: WorkersHandler in TraceEngine needs to be updated to also include `pid` (only had `tid`).
|
|
610
|
+
const workerThreads = new Map();
|
|
611
|
+
const workerCreationEvents = ['ServiceWorker thread', 'DedicatedWorker thread'];
|
|
612
|
+
|
|
613
|
+
for (const event of trace.traceEvents) {
|
|
614
|
+
if (event.name !== 'thread_name' || !event.args.name) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (!workerCreationEvents.includes(event.args.name)) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const tids = workerThreads.get(event.pid);
|
|
622
|
+
if (tids) {
|
|
623
|
+
tids.push(event.tid);
|
|
624
|
+
} else {
|
|
625
|
+
workerThreads.set(event.pid, [event.tid]);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return workerThreads;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* @param {URL|string} url
|
|
634
|
+
*/
|
|
635
|
+
static _createParsedUrl(url) {
|
|
636
|
+
if (typeof url === 'string') {
|
|
637
|
+
url = new URL(url);
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
scheme: url.protocol.split(':')[0],
|
|
641
|
+
// Intentional, DevTools uses different terminology
|
|
642
|
+
host: url.hostname,
|
|
643
|
+
securityOrigin: url.origin,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* @param {LH.Artifacts.TraceEngineResult} traceEngineResult
|
|
649
|
+
* @param {Map<number, number[]>} workerThreads
|
|
650
|
+
* @param {import('@paulirish/trace_engine/models/trace/types/TraceEvents.js').SyntheticNetworkRequest} request
|
|
651
|
+
* @return {Lantern.NetworkRequest=}
|
|
652
|
+
*/
|
|
653
|
+
static _createLanternRequest(traceEngineResult, workerThreads, request) {
|
|
654
|
+
if (request.args.data.connectionId === undefined ||
|
|
655
|
+
request.args.data.connectionReused === undefined) {
|
|
656
|
+
throw new Error('Trace is too old');
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let url;
|
|
660
|
+
try {
|
|
661
|
+
url = new URL(request.args.data.url);
|
|
662
|
+
} catch (e) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const timing = request.args.data.timing ? {
|
|
667
|
+
// These two timings are not included in the trace.
|
|
668
|
+
workerFetchStart: -1,
|
|
669
|
+
workerRespondWithSettled: -1,
|
|
670
|
+
...request.args.data.timing,
|
|
671
|
+
} : undefined;
|
|
672
|
+
|
|
673
|
+
const networkRequestTime = timing ?
|
|
674
|
+
timing.requestTime * 1000 :
|
|
675
|
+
request.args.data.syntheticData.downloadStart / 1000;
|
|
676
|
+
|
|
677
|
+
let fromWorker = false;
|
|
678
|
+
const tids = workerThreads.get(request.pid);
|
|
679
|
+
if (tids?.includes(request.tid)) {
|
|
680
|
+
fromWorker = true;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// TraceEngine collects worker thread ids in a different manner than `workerThreads` does.
|
|
684
|
+
// AFAIK these should be equivalent, but in case they are not let's also check this for now.
|
|
685
|
+
if (traceEngineResult.data.Workers.workerIdByThread.has(request.tid)) {
|
|
686
|
+
fromWorker = true;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// `initiator` in the trace does not contain the stack trace for JS-initiated
|
|
690
|
+
// requests. Instead, that is stored in the `stackTrace` property of the SyntheticNetworkRequest.
|
|
691
|
+
// There are some minor differences in the fields, accounted for here.
|
|
692
|
+
// Most importantly, there seems to be fewer frames in the trace than the equivalent
|
|
693
|
+
// events over the CDP. This results in less accuracy in determining the initiator request,
|
|
694
|
+
// which means less edges in the graph, which mean worse results.
|
|
695
|
+
// TODO: Should fix in Chromium.
|
|
696
|
+
/** @type {Lantern.NetworkRequest['initiator']} */
|
|
697
|
+
const initiator = request.args.data.initiator ?? {type: 'other'};
|
|
698
|
+
if (request.args.data.stackTrace) {
|
|
699
|
+
const callFrames = request.args.data.stackTrace.map(f => {
|
|
700
|
+
return {
|
|
701
|
+
scriptId: String(f.scriptId),
|
|
702
|
+
url: f.url,
|
|
703
|
+
lineNumber: f.lineNumber - 1,
|
|
704
|
+
columnNumber: f.columnNumber - 1,
|
|
705
|
+
functionName: f.functionName,
|
|
706
|
+
};
|
|
707
|
+
});
|
|
708
|
+
initiator.stack = {callFrames};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let resourceType = request.args.data.resourceType;
|
|
712
|
+
if (request.args.data.initiator?.fetchType === 'xmlhttprequest') {
|
|
713
|
+
// @ts-expect-error yes XHR is a valid ResourceType. TypeScript const enums are so unhelpful.
|
|
714
|
+
resourceType = 'XHR';
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
requestId: request.args.data.requestId,
|
|
719
|
+
connectionId: request.args.data.connectionId,
|
|
720
|
+
connectionReused: request.args.data.connectionReused,
|
|
721
|
+
url: request.args.data.url,
|
|
722
|
+
protocol: request.args.data.protocol,
|
|
723
|
+
parsedURL: this._createParsedUrl(url),
|
|
724
|
+
documentURL: request.args.data.requestingFrameUrl,
|
|
725
|
+
rendererStartTime: request.ts / 1000,
|
|
726
|
+
networkRequestTime,
|
|
727
|
+
responseHeadersEndTime: request.args.data.syntheticData.downloadStart / 1000,
|
|
728
|
+
networkEndTime: request.args.data.syntheticData.finishTime / 1000,
|
|
729
|
+
transferSize: request.args.data.encodedDataLength,
|
|
730
|
+
resourceSize: request.args.data.decodedBodyLength,
|
|
731
|
+
fromDiskCache: request.args.data.syntheticData.isDiskCached,
|
|
732
|
+
fromMemoryCache: request.args.data.syntheticData.isMemoryCached,
|
|
733
|
+
isLinkPreload: request.args.data.isLinkPreload,
|
|
734
|
+
finished: request.args.data.finished,
|
|
735
|
+
failed: request.args.data.failed,
|
|
736
|
+
statusCode: request.args.data.statusCode,
|
|
737
|
+
initiator,
|
|
738
|
+
timing,
|
|
739
|
+
resourceType,
|
|
740
|
+
mimeType: request.args.data.mimeType,
|
|
741
|
+
priority: request.args.data.priority,
|
|
742
|
+
frameId: request.args.data.frame,
|
|
743
|
+
fromWorker,
|
|
744
|
+
record: request,
|
|
745
|
+
// Set later.
|
|
746
|
+
redirects: undefined,
|
|
747
|
+
redirectSource: undefined,
|
|
748
|
+
redirectDestination: undefined,
|
|
749
|
+
initiatorRequest: undefined,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
*
|
|
755
|
+
* @param {Lantern.NetworkRequest[]} lanternRequests
|
|
756
|
+
*/
|
|
757
|
+
static _linkInitiators(lanternRequests) {
|
|
758
|
+
/** @type {Map<string, Lantern.NetworkRequest[]>} */
|
|
759
|
+
const requestsByURL = new Map();
|
|
760
|
+
for (const request of lanternRequests) {
|
|
761
|
+
const requests = requestsByURL.get(request.url) || [];
|
|
762
|
+
requests.push(request);
|
|
763
|
+
requestsByURL.set(request.url, requests);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
for (const request of lanternRequests) {
|
|
767
|
+
const initiatorRequest = PageDependencyGraph.chooseInitiatorRequest(request, requestsByURL);
|
|
768
|
+
if (initiatorRequest) {
|
|
769
|
+
request.initiatorRequest = initiatorRequest;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
776
|
+
* @param {LH.Trace} trace
|
|
777
|
+
* @param {LH.Artifacts.TraceEngineResult} traceEngineResult
|
|
778
|
+
* @param {LH.Artifacts.URL} URL
|
|
779
|
+
*/
|
|
780
|
+
static async createGraphFromTrace(mainThreadEvents, trace, traceEngineResult, URL) {
|
|
781
|
+
const workerThreads = this._findWorkerThreads(trace);
|
|
782
|
+
|
|
783
|
+
/** @type {Lantern.NetworkRequest[]} */
|
|
784
|
+
const lanternRequests = [];
|
|
785
|
+
for (const request of traceEngineResult.data.NetworkRequests.byTime) {
|
|
786
|
+
const lanternRequest = this._createLanternRequest(traceEngineResult, workerThreads, request);
|
|
787
|
+
if (lanternRequest) {
|
|
788
|
+
lanternRequests.push(lanternRequest);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// TraceEngine consolidates all redirects into a single request object, but lantern needs
|
|
793
|
+
// an entry for each redirected request.
|
|
794
|
+
for (const request of [...lanternRequests]) {
|
|
795
|
+
if (!request.record) continue;
|
|
796
|
+
|
|
797
|
+
const redirects = request.record.args.data.redirects;
|
|
798
|
+
if (!redirects.length) continue;
|
|
799
|
+
|
|
800
|
+
const requestChain = [];
|
|
801
|
+
for (const redirect of redirects) {
|
|
802
|
+
const redirectedRequest = structuredClone(request);
|
|
803
|
+
if (redirectedRequest.timing) {
|
|
804
|
+
// TODO: These are surely wrong for when there is more than one redirect. Would be
|
|
805
|
+
// simpler if each redirect remembered it's `timing` object in this `redirects` array.
|
|
806
|
+
redirectedRequest.timing.requestTime = redirect.ts / 1000 / 1000;
|
|
807
|
+
redirectedRequest.timing.receiveHeadersStart -= redirect.dur / 1000 / 1000;
|
|
808
|
+
redirectedRequest.timing.receiveHeadersEnd -= redirect.dur / 1000 / 1000;
|
|
809
|
+
redirectedRequest.rendererStartTime = redirect.ts / 1000;
|
|
810
|
+
redirectedRequest.networkRequestTime = redirect.ts / 1000;
|
|
811
|
+
redirectedRequest.networkEndTime = (redirect.ts + redirect.dur) / 1000;
|
|
812
|
+
redirectedRequest.responseHeadersEndTime =
|
|
813
|
+
redirectedRequest.timing.requestTime * 1000 +
|
|
814
|
+
redirectedRequest.timing.receiveHeadersEnd;
|
|
815
|
+
}
|
|
816
|
+
redirectedRequest.url = redirect.url;
|
|
817
|
+
redirectedRequest.parsedURL = this._createParsedUrl(redirect.url);
|
|
818
|
+
// TODO: TraceEngine is not retaining the actual status code.
|
|
819
|
+
redirectedRequest.statusCode = 302;
|
|
820
|
+
requestChain.push(redirectedRequest);
|
|
821
|
+
lanternRequests.push(redirectedRequest);
|
|
822
|
+
}
|
|
823
|
+
requestChain.push(request);
|
|
824
|
+
|
|
825
|
+
for (let i = 0; i < requestChain.length; i++) {
|
|
826
|
+
const request = requestChain[i];
|
|
827
|
+
if (i > 0) {
|
|
828
|
+
request.redirectSource = requestChain[i - 1];
|
|
829
|
+
request.redirects = requestChain.slice(0, i);
|
|
830
|
+
}
|
|
831
|
+
if (i !== requestChain.length - 1) {
|
|
832
|
+
request.redirectDestination = requestChain[i + 1];
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Apply the `:redirect` requestId convention: only redirects[0].requestId is the actual
|
|
837
|
+
// requestId, all the rest have n occurences of `:redirect` as a suffix.
|
|
838
|
+
for (let i = 1; i < requestChain.length; i++) {
|
|
839
|
+
requestChain[i].requestId = `${requestChain[i - 1].requestId}:redirect`;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
this._linkInitiators(lanternRequests);
|
|
844
|
+
|
|
845
|
+
// This would already be sorted by rendererStartTime, if not for the redirect unwrapping done
|
|
846
|
+
// above.
|
|
847
|
+
lanternRequests.sort((a, b) => a.rendererStartTime - b.rendererStartTime);
|
|
848
|
+
|
|
849
|
+
const graph = PageDependencyGraph.createGraph(mainThreadEvents, lanternRequests, URL);
|
|
850
|
+
return {graph, records: lanternRequests};
|
|
851
|
+
}
|
|
852
|
+
|
|
442
853
|
/**
|
|
443
854
|
*
|
|
444
855
|
* @param {Node} rootNode
|
|
@@ -10,7 +10,7 @@ export class Simulator<T = any> {
|
|
|
10
10
|
/**
|
|
11
11
|
* @param {Lantern.Simulation.Settings} settings
|
|
12
12
|
*/
|
|
13
|
-
static createSimulator(settings: Lantern.Simulation.Settings):
|
|
13
|
+
static createSimulator(settings: Lantern.Simulation.Settings): Simulator<any>;
|
|
14
14
|
/** @return {Map<string, Map<Node, CompleteNodeTiming>>} */
|
|
15
15
|
static get ALL_NODE_TIMINGS(): Map<string, Map<Node, CompleteNodeTiming>>;
|
|
16
16
|
/**
|
|
@@ -53,7 +53,7 @@ class Simulator {
|
|
|
53
53
|
/**
|
|
54
54
|
* @param {Lantern.Simulation.Settings} settings
|
|
55
55
|
*/
|
|
56
|
-
static
|
|
56
|
+
static createSimulator(settings) {
|
|
57
57
|
const {throttlingMethod, throttling, precomputedLanternData, networkAnalysis} = settings;
|
|
58
58
|
|
|
59
59
|
/** @type {Lantern.Simulation.Options} */
|
|
@@ -67,11 +67,14 @@ export class NetworkRequest<T = any> {
|
|
|
67
67
|
resourceSize: number;
|
|
68
68
|
fromDiskCache: boolean;
|
|
69
69
|
fromMemoryCache: boolean;
|
|
70
|
+
isLinkPreload: boolean;
|
|
70
71
|
finished: boolean;
|
|
72
|
+
failed: boolean;
|
|
71
73
|
statusCode: number;
|
|
74
|
+
/** The network request that redirected to this one */
|
|
75
|
+
redirectSource: NetworkRequest<T> | undefined;
|
|
72
76
|
/** The network request that this one redirected to */
|
|
73
77
|
redirectDestination: NetworkRequest<T> | undefined;
|
|
74
|
-
failed: boolean;
|
|
75
78
|
initiator: LH.Crdp.Network.Initiator;
|
|
76
79
|
initiatorRequest: NetworkRequest<T> | undefined;
|
|
77
80
|
/** The chain of network requests that redirected to this one */
|
|
@@ -81,12 +84,12 @@ export class NetworkRequest<T = any> {
|
|
|
81
84
|
* Optional value for how long the server took to respond to this request.
|
|
82
85
|
* When not provided, the server response time is derived from the timing object.
|
|
83
86
|
*/
|
|
84
|
-
serverResponseTime
|
|
87
|
+
serverResponseTime?: number;
|
|
85
88
|
resourceType: LH.Crdp.Network.ResourceType | undefined;
|
|
86
89
|
mimeType: string;
|
|
87
90
|
priority: LH.Crdp.Network.ResourcePriority;
|
|
88
91
|
frameId: string | undefined;
|
|
89
|
-
|
|
92
|
+
fromWorker: boolean;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
export namespace Simulation {
|
|
@@ -149,6 +149,7 @@ export class NetworkRequest {
|
|
|
149
149
|
sessionId: string | undefined;
|
|
150
150
|
/** @type {LH.Protocol.TargetType|undefined} */
|
|
151
151
|
sessionTargetType: LH.Protocol.TargetType | undefined;
|
|
152
|
+
fromWorker: boolean;
|
|
152
153
|
isLinkPreload: boolean;
|
|
153
154
|
/**
|
|
154
155
|
* @return {boolean}
|
|
@@ -258,6 +259,26 @@ export namespace NetworkRequest {
|
|
|
258
259
|
export { HEADER_FETCHED_SIZE };
|
|
259
260
|
export { HEADER_PROTOCOL_IS_H2 };
|
|
260
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* @typedef HeaderEntry
|
|
264
|
+
* @property {string} name
|
|
265
|
+
* @property {string} value
|
|
266
|
+
*/
|
|
267
|
+
/**
|
|
268
|
+
* @typedef ParsedURL
|
|
269
|
+
* @property {string} scheme Equivalent to a `new URL(url).protocol` BUT w/o the trailing colon (:)
|
|
270
|
+
* @property {string} host Equivalent to a `new URL(url).hostname`
|
|
271
|
+
* @property {string} securityOrigin
|
|
272
|
+
*/
|
|
273
|
+
/**
|
|
274
|
+
* @typedef LightriderStatistics
|
|
275
|
+
* @property {number} endTimeDeltaMs The difference in networkEndTime between the observed Lighthouse networkEndTime and Lightrider's derived networkEndTime.
|
|
276
|
+
* @property {number} TCPMs The time spent making a TCP connection (connect + SSL). Note: this is poorly named.
|
|
277
|
+
* @property {number} requestMs 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".
|
|
278
|
+
* @property {number} responseMs Time to receive the entire response payload starting the clock on receiving the first fragment (first non-header byte).
|
|
279
|
+
*/
|
|
280
|
+
/** @type {LH.Util.SelfMap<LH.Crdp.Network.ResourceType>} */
|
|
281
|
+
export const RESOURCE_TYPES: LH.Util.SelfMap<LH.Crdp.Network.ResourceType>;
|
|
261
282
|
import * as LH from '../../types/lh.js';
|
|
262
283
|
import * as Lantern from './lantern/types/lantern.js';
|
|
263
284
|
declare const HEADER_TCP: "X-TCPMs";
|
|
@@ -180,6 +180,7 @@ class NetworkRequest {
|
|
|
180
180
|
this.sessionId = undefined;
|
|
181
181
|
/** @type {LH.Protocol.TargetType|undefined} */
|
|
182
182
|
this.sessionTargetType = undefined;
|
|
183
|
+
this.fromWorker = false;
|
|
183
184
|
this.isLinkPreload = false;
|
|
184
185
|
}
|
|
185
186
|
|
|
@@ -600,6 +601,8 @@ class NetworkRequest {
|
|
|
600
601
|
}
|
|
601
602
|
}
|
|
602
603
|
|
|
604
|
+
record.fromWorker = record.sessionTargetType === 'worker';
|
|
605
|
+
|
|
603
606
|
return {
|
|
604
607
|
...record,
|
|
605
608
|
timing,
|
|
@@ -691,4 +694,4 @@ NetworkRequest.HEADER_TOTAL = HEADER_TOTAL;
|
|
|
691
694
|
NetworkRequest.HEADER_FETCHED_SIZE = HEADER_FETCHED_SIZE;
|
|
692
695
|
NetworkRequest.HEADER_PROTOCOL_IS_H2 = HEADER_PROTOCOL_IS_H2;
|
|
693
696
|
|
|
694
|
-
export {NetworkRequest};
|
|
697
|
+
export {NetworkRequest, RESOURCE_TYPES};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "12.0.0-dev.
|
|
4
|
+
"version": "12.0.0-dev.20240603",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -51,17 +51,19 @@
|
|
|
51
51
|
"test-proto": "yarn compile-proto && yarn build-proto-roundtrip",
|
|
52
52
|
"unit-core": "yarn mocha core",
|
|
53
53
|
"unit-cli": "yarn mocha --testMatch cli/**/*-test.js",
|
|
54
|
+
"unit-lantern-trace": "INTERNAL_LANTERN_USE_TRACE=1 yarn mocha core/test/computed/metrics core/test/audits",
|
|
54
55
|
"unit-report": "yarn mocha --testMatch report/**/*-test.js",
|
|
55
56
|
"unit-treemap": "yarn mocha --testMatch treemap/**/*-test.js",
|
|
56
57
|
"unit-viewer": "yarn mocha --testMatch viewer/**/*-test.js",
|
|
57
58
|
"unit-flow": "bash flow-report/test/run-flow-report-tests.sh",
|
|
58
|
-
"unit": "yarn unit-flow && yarn mocha",
|
|
59
|
+
"unit": "yarn unit-flow && yarn mocha && yarn unit-lantern-trace",
|
|
59
60
|
"unit:ci": "NODE_OPTIONS=--max-old-space-size=8192 npm run unit",
|
|
61
|
+
"unit-lantern-trace:ci": "NODE_OPTIONS=--max-old-space-size=8192 npm run unit-lantern-trace",
|
|
60
62
|
"core-unit": "yarn unit-core",
|
|
61
63
|
"cli-unit": "yarn unit-cli",
|
|
62
64
|
"viewer-unit": "yarn unit-viewer",
|
|
63
65
|
"watch": "yarn unit-core --watch",
|
|
64
|
-
"unit:cicoverage": "yarn c8 --all yarn unit:ci",
|
|
66
|
+
"unit:cicoverage": "yarn c8 --all yarn unit:ci && yarn c8 --all yarn unit-lantern-trace:ci",
|
|
65
67
|
"coverage": "yarn unit:cicoverage && c8 report --reporter html",
|
|
66
68
|
"coverage:smoke": "yarn c8 yarn smoke -j=1 && c8 report --reporter html",
|
|
67
69
|
"devtools": "bash core/scripts/roll-to-devtools.sh",
|
|
@@ -179,7 +181,7 @@
|
|
|
179
181
|
"webtreemap-cdt": "^3.2.1"
|
|
180
182
|
},
|
|
181
183
|
"dependencies": {
|
|
182
|
-
"@paulirish/trace_engine": "^0.0.
|
|
184
|
+
"@paulirish/trace_engine": "^0.0.23",
|
|
183
185
|
"@sentry/node": "^6.17.4",
|
|
184
186
|
"axe-core": "^4.9.0",
|
|
185
187
|
"chrome-launcher": "^1.1.1",
|
package/tsconfig.json
CHANGED
|
@@ -109,5 +109,6 @@
|
|
|
109
109
|
"core/test/computed/metrics/interactive-test.js",
|
|
110
110
|
"core/test/computed/tbt-impact-tasks-test.js",
|
|
111
111
|
"core/test/fixtures/config-plugins/lighthouse-plugin-simple/plugin-simple.js",
|
|
112
|
+
"core/test/lib/lantern/metrics/metric-test-utils.js",
|
|
112
113
|
],
|
|
113
114
|
}
|
package/types/artifacts.d.ts
CHANGED
|
@@ -942,6 +942,22 @@ export interface TraceEvent {
|
|
|
942
942
|
name?: string;
|
|
943
943
|
duration?: number;
|
|
944
944
|
blockingDuration?: number;
|
|
945
|
+
candidateIndex?: number;
|
|
946
|
+
priority?: string;
|
|
947
|
+
requestMethod?: string;
|
|
948
|
+
resourceType?: string;
|
|
949
|
+
fromCache?: boolean;
|
|
950
|
+
fromServiceWorker?: boolean;
|
|
951
|
+
mimeType?: string;
|
|
952
|
+
statusCode?: number;
|
|
953
|
+
timing?: any;
|
|
954
|
+
connectionId?: number;
|
|
955
|
+
connectionReused?: boolean;
|
|
956
|
+
encodedDataLength?: number;
|
|
957
|
+
decodedBodyLength?: number;
|
|
958
|
+
initiator?: {type: string, url?: string, stack?: any};
|
|
959
|
+
protocol?: string;
|
|
960
|
+
finishTime?: number;
|
|
945
961
|
};
|
|
946
962
|
frame?: string;
|
|
947
963
|
name?: string;
|