lighthouse 12.0.0-dev.20240610 → 12.0.0-dev.20240612

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.
Files changed (118) hide show
  1. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +2 -2
  2. package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
  3. package/core/audits/byte-efficiency/render-blocking-resources.d.ts +3 -3
  4. package/core/audits/byte-efficiency/render-blocking-resources.js +4 -4
  5. package/core/audits/dobetterweb/uses-http2.d.ts +2 -2
  6. package/core/audits/dobetterweb/uses-http2.js +2 -2
  7. package/core/audits/long-tasks.d.ts +7 -6
  8. package/core/audits/long-tasks.js +5 -4
  9. package/core/audits/prioritize-lcp-image.d.ts +1 -1
  10. package/core/computed/document-urls.js +3 -2
  11. package/core/computed/load-simulator.d.ts +4 -4
  12. package/core/computed/load-simulator.js +3 -3
  13. package/core/computed/main-resource.js +3 -2
  14. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +4 -4
  15. package/core/computed/metrics/lantern-first-contentful-paint.js +3 -3
  16. package/core/computed/metrics/lantern-interactive.d.ts +4 -4
  17. package/core/computed/metrics/lantern-interactive.js +3 -3
  18. package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +4 -4
  19. package/core/computed/metrics/lantern-largest-contentful-paint.js +3 -3
  20. package/core/computed/metrics/lantern-max-potential-fid.d.ts +4 -4
  21. package/core/computed/metrics/lantern-max-potential-fid.js +3 -3
  22. package/core/computed/metrics/lantern-metric.d.ts +4 -3
  23. package/core/computed/metrics/lantern-metric.js +4 -4
  24. package/core/computed/metrics/lantern-speed-index.d.ts +4 -4
  25. package/core/computed/metrics/lantern-speed-index.js +3 -3
  26. package/core/computed/metrics/lantern-total-blocking-time.d.ts +4 -4
  27. package/core/computed/metrics/lantern-total-blocking-time.js +3 -3
  28. package/core/computed/metrics/total-blocking-time.js +3 -1
  29. package/core/computed/network-analysis.js +2 -2
  30. package/core/computed/page-dependency-graph.d.ts +2 -2
  31. package/core/computed/page-dependency-graph.js +6 -6
  32. package/core/computed/processed-navigation.d.ts +1 -1
  33. package/core/computed/tbt-impact-tasks.js +4 -2
  34. package/core/config/constants.d.ts +28 -53
  35. package/core/config/constants.js +2 -43
  36. package/core/lib/asset-saver.js +2 -2
  37. package/core/lib/lantern/{base-node.d.ts → BaseNode.d.ts} +7 -9
  38. package/core/lib/lantern/{base-node.js → BaseNode.js} +6 -6
  39. package/core/lib/lantern/BaseNode.test.js +385 -0
  40. package/core/lib/lantern/CpuNode.d.ts +43 -0
  41. package/core/lib/lantern/{cpu-node.js → CpuNode.js} +8 -9
  42. package/core/lib/lantern/{lantern-error.d.ts → LanternError.d.ts} +1 -1
  43. package/core/lib/lantern/{metric.d.ts → Metric.d.ts} +17 -17
  44. package/core/lib/lantern/{metric.js → Metric.js} +10 -12
  45. package/core/lib/lantern/{network-node.d.ts → NetworkNode.d.ts} +4 -5
  46. package/core/lib/lantern/{network-node.js → NetworkNode.js} +4 -5
  47. package/core/lib/lantern/{page-dependency-graph.d.ts → PageDependencyGraph.d.ts} +22 -11
  48. package/core/lib/lantern/{page-dependency-graph.js → PageDependencyGraph.js} +56 -22
  49. package/core/lib/lantern/PageDependencyGraph.test.js +654 -0
  50. package/core/lib/lantern/{tbt-utils.d.ts → TBTUtils.d.ts} +1 -1
  51. package/core/lib/lantern/TBTUtils.test.d.ts +2 -0
  52. package/core/lib/lantern/TBTUtils.test.js +130 -0
  53. package/core/lib/lantern/{trace-engine-computation-data.d.ts → TraceEngineComputationData.d.ts} +7 -7
  54. package/core/lib/lantern/{trace-engine-computation-data.js → TraceEngineComputationData.js} +18 -16
  55. package/core/lib/lantern/lantern.d.ts +19 -7
  56. package/core/lib/lantern/lantern.js +21 -1
  57. package/core/lib/lantern/metrics/{first-contentful-paint.d.ts → FirstContentfulPaint.d.ts} +11 -11
  58. package/core/lib/lantern/metrics/{first-contentful-paint.js → FirstContentfulPaint.js} +8 -10
  59. package/core/lib/lantern/metrics/FirstContentfulPaint.test.js +54 -0
  60. package/core/lib/lantern/metrics/Interactive.d.ts +20 -0
  61. package/core/lib/lantern/metrics/{interactive.js → Interactive.js} +14 -17
  62. package/core/lib/lantern/metrics/Interactive.test.js +56 -0
  63. package/core/lib/lantern/metrics/LargestContentfulPaint.d.ts +19 -0
  64. package/core/lib/lantern/metrics/{largest-contentful-paint.js → LargestContentfulPaint.js} +12 -15
  65. package/core/lib/lantern/metrics/LargestContentfulPaint.test.js +42 -0
  66. package/core/lib/lantern/metrics/MaxPotentialFID.d.ts +24 -0
  67. package/core/lib/lantern/metrics/{max-potential-fid.js → MaxPotentialFID.js} +12 -13
  68. package/core/lib/lantern/metrics/MetricTestUtils.d.ts +19 -0
  69. package/core/lib/lantern/metrics/MetricTestUtils.js +48 -0
  70. package/core/lib/lantern/metrics/{speed-index.d.ts → SpeedIndex.d.ts} +7 -13
  71. package/core/lib/lantern/metrics/{speed-index.js → SpeedIndex.js} +12 -14
  72. package/core/lib/lantern/metrics/SpeedIndex.test.js +83 -0
  73. package/core/lib/lantern/metrics/TotalBlockingTime.d.ts +25 -0
  74. package/core/lib/lantern/metrics/{total-blocking-time.js → TotalBlockingTime.js} +13 -16
  75. package/core/lib/lantern/metrics/__snapshots__/first-contentful-paint-test.js.snap +11 -0
  76. package/core/lib/lantern/metrics/__snapshots__/interactive-test.js.snap +17 -0
  77. package/core/lib/lantern/metrics/metrics.d.ts +9 -0
  78. package/core/lib/lantern/metrics/metrics.js +16 -0
  79. package/core/lib/lantern/{simulator/connection-pool.d.ts → simulation/ConnectionPool.d.ts} +5 -5
  80. package/core/lib/lantern/{simulator/connection-pool.js → simulation/ConnectionPool.js} +3 -3
  81. package/core/lib/lantern/simulation/ConnectionPool.test.js +195 -0
  82. package/core/lib/lantern/simulation/Constants.d.ts +52 -0
  83. package/core/lib/lantern/simulation/Constants.js +48 -0
  84. package/core/lib/lantern/{simulator/dns-cache.d.ts → simulation/DNSCache.d.ts} +2 -2
  85. package/core/lib/lantern/{simulator/dns-cache.js → simulation/DNSCache.js} +1 -1
  86. package/core/lib/lantern/simulation/DNSCache.test.js +72 -0
  87. package/core/lib/lantern/{simulator/network-analyzer.d.ts → simulation/NetworkAnalyzer.d.ts} +8 -8
  88. package/core/lib/lantern/{simulator/network-analyzer.js → simulation/NetworkAnalyzer.js} +3 -3
  89. package/core/lib/lantern/simulation/NetworkAnalyzer.test.js +475 -0
  90. package/core/lib/lantern/{simulator/simulator-timing-map.d.ts → simulation/SimulationTimingMap.d.ts} +7 -7
  91. package/core/lib/lantern/{simulator/simulator-timing-map.js → simulation/SimulationTimingMap.js} +5 -6
  92. package/core/lib/lantern/{simulator/simulator.d.ts → simulation/Simulator.d.ts} +13 -15
  93. package/core/lib/lantern/{simulator/simulator.js → simulation/Simulator.js} +29 -30
  94. package/core/lib/lantern/simulation/Simulator.test.js +434 -0
  95. package/core/lib/lantern/simulation/TCPConnection.test.d.ts +2 -0
  96. package/core/lib/lantern/simulation/TCPConnection.test.js +374 -0
  97. package/core/lib/lantern/{simulator/tcp-connection.d.ts → simulation/TcpConnection.d.ts} +2 -2
  98. package/core/lib/lantern/{simulator/tcp-connection.js → simulation/TcpConnection.js} +1 -1
  99. package/core/lib/lantern/simulation/simulation.d.ts +21 -0
  100. package/core/lib/lantern/simulation/simulation.js +28 -0
  101. package/core/lib/lantern/types/lantern.d.ts +81 -9
  102. package/core/lib/lantern-trace-saver.d.ts +4 -4
  103. package/core/lib/lantern-trace-saver.js +3 -3
  104. package/core/lib/navigation-error.js +3 -3
  105. package/core/lib/network-recorder.js +2 -2
  106. package/core/lib/network-request.d.ts +1 -1
  107. package/core/lib/network-request.js +1 -1
  108. package/package.json +5 -5
  109. package/tsconfig.json +10 -8
  110. package/types/artifacts.d.ts +3 -10
  111. package/types/gatherer.d.ts +1 -1
  112. package/core/lib/lantern/cpu-node.d.ts +0 -44
  113. package/core/lib/lantern/metrics/interactive.d.ts +0 -26
  114. package/core/lib/lantern/metrics/largest-contentful-paint.d.ts +0 -19
  115. package/core/lib/lantern/metrics/max-potential-fid.d.ts +0 -30
  116. package/core/lib/lantern/metrics/total-blocking-time.d.ts +0 -31
  117. /package/core/lib/lantern/{lantern-error.js → LanternError.js} +0 -0
  118. /package/core/lib/lantern/{tbt-utils.js → TBTUtils.js} +0 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Lantern from './lantern.js';
8
+
9
+ const {calculateSumOfBlockingTime} = Lantern.TBTUtils;
10
+
11
+ describe('TotalBlockingTime utils', () => {
12
+ it('reports 0 when no task is longer than 50ms', () => {
13
+ const events = [
14
+ {start: 1000, end: 1050, duration: 50},
15
+ {start: 2000, end: 2010, duration: 10},
16
+ ];
17
+
18
+ const fcpTimeMs = 500;
19
+ const interactiveTimeMs = 4000;
20
+
21
+ expect(
22
+ calculateSumOfBlockingTime(events, fcpTimeMs, interactiveTimeMs)
23
+ ).toBe(0);
24
+ });
25
+
26
+ it('only looks at tasks within FCP and TTI', () => {
27
+ const events = [
28
+ {start: 1000, end: 1060, duration: 60},
29
+ {start: 2000, end: 2100, duration: 100},
30
+ {start: 2300, end: 2450, duration: 150},
31
+ {start: 2600, end: 2800, duration: 200},
32
+ ];
33
+
34
+ const fcpTimeMs = 1500;
35
+ const interactiveTimeMs = 2500;
36
+
37
+ expect(
38
+ calculateSumOfBlockingTime(events, fcpTimeMs, interactiveTimeMs)
39
+ ).toBe(150);
40
+ });
41
+
42
+ it('clips before finding blocking regions', () => {
43
+ const fcpTimeMs = 150;
44
+ const interactiveTimeMs = 300;
45
+
46
+ const events = [
47
+ // The clipping is done first, so the task becomes [150, 200] after clipping and contributes
48
+ // 0ms of blocking time. This is in contrast to first calculating the blocking region ([100,
49
+ // 200]) and then clipping at FCP (150ms), which yields 50ms blocking time.
50
+ {start: 50, end: 200, duration: 150},
51
+ // Similarly, the task is first clipped above to be [240, 300], and then contributes 10ms
52
+ // blocking time.
53
+ {start: 240, end: 460, duration: 120},
54
+ ];
55
+
56
+ expect(
57
+ calculateSumOfBlockingTime(events, fcpTimeMs, interactiveTimeMs)
58
+ ).toBe(10); // 0ms + 10ms.
59
+ });
60
+
61
+ // TTI can happen in the middle of a task, for example, if TTI is at FMP which occurs as part
62
+ // of a larger task, or in the lantern case where we use estimate TTI using a different graph
63
+ // from the one used to estimate TBT.
64
+ it('clips properly if TTI falls in the middle of a task', () => {
65
+ const fcpTimeMs = 1000;
66
+ const interactiveTimeMs = 2000;
67
+
68
+ expect(
69
+ calculateSumOfBlockingTime(
70
+ [{start: 1951, end: 2100, duration: 149}],
71
+ fcpTimeMs,
72
+ interactiveTimeMs
73
+ )
74
+ ).toBe(0); // Duration after clipping is 49, which is < 50.
75
+ expect(
76
+ calculateSumOfBlockingTime(
77
+ [{start: 1950, end: 2100, duration: 150}],
78
+ fcpTimeMs,
79
+ interactiveTimeMs
80
+ )
81
+ ).toBe(0); // Duration after clipping is 50, so time after 50ms is 0ms.
82
+ expect(
83
+ calculateSumOfBlockingTime(
84
+ [{start: 1949, end: 2100, duration: 151}],
85
+ fcpTimeMs,
86
+ interactiveTimeMs
87
+ )
88
+ ).toBe(1); // Duration after clipping is 51, so time after 50ms is 1ms.
89
+ });
90
+
91
+ it('clips properly if FCP falls in the middle of a task', () => {
92
+ const fcpTimeMs = 1000;
93
+ const interactiveTimeMs = 2000;
94
+
95
+ expect(
96
+ calculateSumOfBlockingTime(
97
+ [{start: 900, end: 1049, duration: 149}],
98
+ fcpTimeMs,
99
+ interactiveTimeMs
100
+ )
101
+ ).toBe(0); // Duration after clipping is 49, which is < 50.
102
+ expect(
103
+ calculateSumOfBlockingTime(
104
+ [{start: 900, end: 1050, duration: 150}],
105
+ fcpTimeMs,
106
+ interactiveTimeMs
107
+ )
108
+ ).toBe(0); // Duration after clipping is 50, so time after 50ms is 0ms.
109
+ expect(
110
+ calculateSumOfBlockingTime(
111
+ [{start: 900, end: 1051, duration: 151}],
112
+ fcpTimeMs,
113
+ interactiveTimeMs
114
+ )
115
+ ).toBe(1); // Duration after clipping is 51, so time after 50ms is 1ms.
116
+ });
117
+
118
+ // This can happen in the lantern metric case, where we use the optimistic
119
+ // TTI and pessimistic FCP.
120
+ it('returns 0 if interactiveTime is earlier than FCP', () => {
121
+ const fcpTimeMs = 2050;
122
+ const interactiveTimeMs = 1050;
123
+
124
+ const events = [{start: 500, end: 3000, duration: 2500}];
125
+
126
+ expect(
127
+ calculateSumOfBlockingTime(events, fcpTimeMs, interactiveTimeMs)
128
+ ).toBe(0);
129
+ });
130
+ });
@@ -8,18 +8,18 @@ export type MetricScore = import('@paulirish/trace_engine/models/trace/handlers/
8
8
  */
9
9
  export function createProcessedNavigation(traceEngineData: TraceEngine.Handlers.Types.TraceParseData): Lantern.Simulation.ProcessedNavigation;
10
10
  /**
11
- * @param {LH.Trace} trace
11
+ * @param {Lantern.Trace} trace
12
12
  * @param {TraceEngine.Handlers.Types.TraceParseData} traceEngineData
13
13
  * @return {Lantern.NetworkRequest[]}
14
14
  */
15
- export function createNetworkRequests(trace: LH.Trace, traceEngineData: TraceEngine.Handlers.Types.TraceParseData): Lantern.NetworkRequest[];
15
+ export function createNetworkRequests(trace: Lantern.Trace, traceEngineData: TraceEngine.Handlers.Types.TraceParseData): Lantern.NetworkRequest[];
16
16
  /**
17
17
  * @param {Lantern.NetworkRequest[]} requests
18
- * @param {LH.Trace} trace
18
+ * @param {Lantern.Trace} trace
19
19
  * @param {TraceEngine.Handlers.Types.TraceParseData} traceEngineData
20
- * @param {LH.Artifacts.URL=} URL
20
+ * @param {Lantern.Simulation.URL=} URL
21
21
  */
22
- export function createGraph(requests: Lantern.NetworkRequest[], trace: LH.Trace, traceEngineData: TraceEngine.Handlers.Types.TraceParseData, URL?: LH.Artifacts.URL | undefined): import("./page-dependency-graph.js").Node;
22
+ export function createGraph(requests: Lantern.NetworkRequest[], trace: Lantern.Trace, traceEngineData: TraceEngine.Handlers.Types.TraceParseData, URL?: Lantern.Simulation.URL | undefined): import("./PageDependencyGraph.js").Node;
23
23
  import * as TraceEngine from '@paulirish/trace_engine';
24
- import * as Lantern from './types/lantern.js';
25
- //# sourceMappingURL=trace-engine-computation-data.d.ts.map
24
+ import * as Lantern from './lantern.js';
25
+ //# sourceMappingURL=TraceEngineComputationData.d.ts.map
@@ -5,10 +5,9 @@
5
5
  */
6
6
 
7
7
  import * as TraceEngine from '@paulirish/trace_engine';
8
+ import * as Protocol from '@paulirish/trace_engine/generated/protocol.js';
8
9
 
9
- import * as Lantern from './types/lantern.js';
10
- import {PageDependencyGraph} from './page-dependency-graph.js';
11
- import {RESOURCE_TYPES} from '../network-request.js';
10
+ import * as Lantern from './lantern.js';
12
11
 
13
12
  /** @typedef {import('@paulirish/trace_engine/models/trace/handlers/PageLoadMetricsHandler.js').MetricName} MetricName */
14
13
  /** @typedef {import('@paulirish/trace_engine/models/trace/handlers/PageLoadMetricsHandler.js').MetricScore} MetricScore */
@@ -71,7 +70,7 @@ function createParsedUrl(url) {
71
70
 
72
71
  /**
73
72
  * Returns a map of `pid` -> `tid[]`.
74
- * @param {LH.Trace} trace
73
+ * @param {Lantern.Trace} trace
75
74
  * @return {Map<number, number[]>}
76
75
  */
77
76
  function findWorkerThreads(trace) {
@@ -140,6 +139,9 @@ function createLanternRequest(traceEngineData, workerThreads, request) {
140
139
  fromWorker = true;
141
140
  }
142
141
 
142
+ // typescript const enums.... gotta stop using those ...
143
+ const Other = /** @type {Protocol.Network.InitiatorType.Other} */ ('other');
144
+
143
145
  // `initiator` in the trace does not contain the stack trace for JS-initiated
144
146
  // requests. Instead, that is stored in the `stackTrace` property of the SyntheticNetworkRequest.
145
147
  // There are some minor differences in the fields, accounted for here.
@@ -148,11 +150,11 @@ function createLanternRequest(traceEngineData, workerThreads, request) {
148
150
  // which means less edges in the graph, which mean worse results.
149
151
  // TODO: Should fix in Chromium.
150
152
  /** @type {Lantern.NetworkRequest['initiator']} */
151
- const initiator = request.args.data.initiator ?? {type: 'other'};
153
+ const initiator = request.args.data.initiator ?? {type: Other};
152
154
  if (request.args.data.stackTrace) {
153
155
  const callFrames = request.args.data.stackTrace.map(f => {
154
156
  return {
155
- scriptId: String(f.scriptId),
157
+ scriptId: /** @type {Protocol.Runtime.ScriptId} */(String(f.scriptId)),
156
158
  url: f.url,
157
159
  lineNumber: f.lineNumber - 1,
158
160
  columnNumber: f.columnNumber - 1,
@@ -160,6 +162,7 @@ function createLanternRequest(traceEngineData, workerThreads, request) {
160
162
  };
161
163
  });
162
164
  initiator.stack = {callFrames};
165
+ // Note: there is no `parent` to set ...
163
166
  }
164
167
 
165
168
  let resourceType = request.args.data.resourceType;
@@ -227,7 +230,7 @@ function chooseInitiatorRequest(request, requestsByURL) {
227
230
  return request.redirectSource;
228
231
  }
229
232
 
230
- const initiatorURL = PageDependencyGraph.getNetworkInitiators(request)[0];
233
+ const initiatorURL = Lantern.PageDependencyGraph.getNetworkInitiators(request)[0];
231
234
  let candidates = requestsByURL.get(initiatorURL) || [];
232
235
  // The (valid) initiator must come before the initiated request.
233
236
  candidates = candidates.filter(c => {
@@ -238,7 +241,7 @@ function chooseInitiatorRequest(request, requestsByURL) {
238
241
  // Disambiguate based on prefetch. Prefetch requests have type 'Other' and cannot
239
242
  // initiate requests, so we drop them here.
240
243
  const nonPrefetchCandidates = candidates.filter(
241
- cand => cand.resourceType !== RESOURCE_TYPES.Other);
244
+ cand => cand.resourceType !== Lantern.NetworkRequestTypes.Other);
242
245
  if (nonPrefetchCandidates.length) {
243
246
  candidates = nonPrefetchCandidates;
244
247
  }
@@ -253,7 +256,7 @@ function chooseInitiatorRequest(request, requestsByURL) {
253
256
  if (candidates.length > 1 && request.initiator.type === 'parser') {
254
257
  // Filter to just Documents when initiator type is parser.
255
258
  const documentCandidates = candidates.filter(cand =>
256
- cand.resourceType === RESOURCE_TYPES.Document);
259
+ cand.resourceType === Lantern.NetworkRequestTypes.Document);
257
260
  if (documentCandidates.length) {
258
261
  candidates = documentCandidates;
259
262
  }
@@ -296,7 +299,7 @@ function linkInitiators(lanternRequests) {
296
299
  }
297
300
 
298
301
  /**
299
- * @param {LH.Trace} trace
302
+ * @param {Lantern.Trace} trace
300
303
  * @param {TraceEngine.Handlers.Types.TraceParseData} traceEngineData
301
304
  * @return {Lantern.NetworkRequest[]}
302
305
  */
@@ -390,9 +393,9 @@ function createNetworkRequests(trace, traceEngineData) {
390
393
  }
391
394
 
392
395
  /**
393
- * @param {LH.Trace} trace
396
+ * @param {Lantern.Trace} trace
394
397
  * @param {TraceEngine.Handlers.Types.TraceParseData} traceEngineData
395
- * @return {LH.TraceEvent[]}
398
+ * @return {Lantern.TraceEvent[]}
396
399
  */
397
400
  function collectMainThreadEvents(trace, traceEngineData) {
398
401
  const Meta = traceEngineData.Meta;
@@ -431,9 +434,9 @@ function collectMainThreadEvents(trace, traceEngineData) {
431
434
 
432
435
  /**
433
436
  * @param {Lantern.NetworkRequest[]} requests
434
- * @param {LH.Trace} trace
437
+ * @param {Lantern.Trace} trace
435
438
  * @param {TraceEngine.Handlers.Types.TraceParseData} traceEngineData
436
- * @param {LH.Artifacts.URL=} URL
439
+ * @param {Lantern.Simulation.URL=} URL
437
440
  */
438
441
  function createGraph(requests, trace, traceEngineData, URL) {
439
442
  const mainThreadEvents = collectMainThreadEvents(trace, traceEngineData);
@@ -444,7 +447,6 @@ function createGraph(requests, trace, traceEngineData, URL) {
444
447
  URL = {
445
448
  requestedUrl: requests[0].url,
446
449
  mainDocumentUrl: '',
447
- finalDisplayedUrl: '',
448
450
  };
449
451
 
450
452
  let request = requests[0];
@@ -454,7 +456,7 @@ function createGraph(requests, trace, traceEngineData, URL) {
454
456
  URL.mainDocumentUrl = request.url;
455
457
  }
456
458
 
457
- return PageDependencyGraph.createGraph(mainThreadEvents, requests, URL);
459
+ return Lantern.PageDependencyGraph.createGraph(mainThreadEvents, requests, URL);
458
460
  }
459
461
 
460
462
  export {
@@ -1,8 +1,20 @@
1
- /**
2
- * @license
3
- * Copyright 2024 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- /** @type {LH.Util.SelfMap<LH.Crdp.Network.ResourceType>} */
7
- export const NetworkRequestTypes: LH.Util.SelfMap<LH.Crdp.Network.ResourceType>;
1
+ export { BaseNode } from "./BaseNode.js";
2
+ export { CPUNode } from "./CpuNode.js";
3
+ export { LanternError as Error } from "./LanternError.js";
4
+ export { Metric } from "./Metric.js";
5
+ export { NetworkNode } from "./NetworkNode.js";
6
+ export { PageDependencyGraph } from "./PageDependencyGraph.js";
7
+ export * as Metrics from "./metrics/metrics.js";
8
+ export * as Simulation from "./simulation/simulation.js";
9
+ export * as TBTUtils from "./TBTUtils.js";
10
+ export * as TraceEngineComputationData from "./TraceEngineComputationData.js";
11
+ export type NetworkRequest<T = any> = Lantern.NetworkRequest<T>;
12
+ export type ResourcePriority = Lantern.ResourcePriority;
13
+ export type ResourceTiming = Lantern.ResourceTiming;
14
+ export type ResourceType = Lantern.ResourceType;
15
+ export type Trace = Lantern.Trace;
16
+ export type TraceEvent = Lantern.TraceEvent;
17
+ /** @type {Lantern.Util.SelfMap<Lantern.ResourceType>} */
18
+ export const NetworkRequestTypes: Lantern.Util.SelfMap<Lantern.ResourceType>;
19
+ import * as Lantern from './types/lantern.js';
8
20
  //# sourceMappingURL=lantern.d.ts.map
@@ -4,7 +4,9 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- /** @type {LH.Util.SelfMap<LH.Crdp.Network.ResourceType>} */
7
+ import * as Lantern from './types/lantern.js';
8
+
9
+ /** @type {Lantern.Util.SelfMap<Lantern.ResourceType>} */
8
10
  const NetworkRequestTypes = {
9
11
  XHR: 'XHR',
10
12
  Fetch: 'Fetch',
@@ -26,6 +28,24 @@ const NetworkRequestTypes = {
26
28
  Prefetch: 'Prefetch',
27
29
  };
28
30
 
31
+ export {BaseNode} from './BaseNode.js';
32
+ export {CPUNode} from './CpuNode.js';
33
+ export {LanternError as Error} from './LanternError.js';
34
+ export {Metric} from './Metric.js';
35
+ export {NetworkNode} from './NetworkNode.js';
36
+ export {PageDependencyGraph} from './PageDependencyGraph.js';
37
+ export * as Metrics from './metrics/metrics.js';
38
+ export * as Simulation from './simulation/simulation.js';
39
+ export * as TBTUtils from './TBTUtils.js';
40
+ export * as TraceEngineComputationData from './TraceEngineComputationData.js';
41
+
42
+ /** @template [T=any] @typedef {Lantern.NetworkRequest<T>} NetworkRequest */
43
+ /** @typedef {Lantern.ResourcePriority} ResourcePriority */
44
+ /** @typedef {Lantern.ResourceTiming} ResourceTiming */
45
+ /** @typedef {Lantern.ResourceType} ResourceType */
46
+ /** @typedef {Lantern.Trace} Trace */
47
+ /** @typedef {Lantern.TraceEvent} TraceEvent */
48
+
29
49
  export {
30
50
  NetworkRequestTypes,
31
51
  };
@@ -1,10 +1,10 @@
1
- export type Node = import('../base-node.js').Node;
2
- export type NetworkNode<T> = import('../network-node.js').NetworkNode<T>;
3
- export type CpuNode = import('../cpu-node.js').CPUNode;
4
- /** @typedef {import('../base-node.js').Node} Node */
5
- /** @template T @typedef {import('../network-node.js').NetworkNode<T>} NetworkNode */
6
- /** @typedef {import('../cpu-node.js').CPUNode} CpuNode */
7
- export class FirstContentfulPaint extends Metric {
1
+ export type Node = import('../BaseNode.js').Node;
2
+ export type NetworkNode<T> = import('../NetworkNode.js').NetworkNode<T>;
3
+ export type CpuNode = import('../CpuNode.js').CPUNode;
4
+ /** @typedef {import('../BaseNode.js').Node} Node */
5
+ /** @template T @typedef {import('../NetworkNode.js').NetworkNode<T>} NetworkNode */
6
+ /** @typedef {import('../CpuNode.js').CPUNode} CpuNode */
7
+ export class FirstContentfulPaint extends Lantern.Metric {
8
8
  /**
9
9
  * @template T
10
10
  * @typedef FirstPaintBasedGraphOpts
@@ -37,7 +37,7 @@ export class FirstContentfulPaint extends Metric {
37
37
  * The function that determines
38
38
  * which resources should be considered *possibly* render-blocking.
39
39
  */
40
- treatNodeAsRenderBlocking: (arg0: import("../network-node.js").NetworkNode<T>) => boolean;
40
+ treatNodeAsRenderBlocking: (arg0: Lantern.NetworkNode<T>) => boolean;
41
41
  /**
42
42
  * The function that
43
43
  * determines which CPU nodes should also be included in our blocking node IDs set,
@@ -66,7 +66,7 @@ export class FirstContentfulPaint extends Metric {
66
66
  * The function that determines
67
67
  * which resources should be considered *possibly* render-blocking.
68
68
  */
69
- treatNodeAsRenderBlocking: (arg0: import("../network-node.js").NetworkNode<T_1>) => boolean;
69
+ treatNodeAsRenderBlocking: (arg0: Lantern.NetworkNode<T_1>) => boolean;
70
70
  /**
71
71
  * The function that
72
72
  * determines which CPU nodes should also be included in our blocking node IDs set,
@@ -75,5 +75,5 @@ export class FirstContentfulPaint extends Metric {
75
75
  additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CpuNode) => boolean) | undefined;
76
76
  }): Node;
77
77
  }
78
- import { Metric } from '../metric.js';
79
- //# sourceMappingURL=first-contentful-paint.d.ts.map
78
+ import * as Lantern from '../lantern.js';
79
+ //# sourceMappingURL=FirstContentfulPaint.d.ts.map
@@ -4,15 +4,13 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import * as Lantern from '../types/lantern.js';
8
- import {Metric} from '../metric.js';
9
- import {BaseNode} from '../base-node.js';
7
+ import * as Lantern from '../lantern.js';
10
8
 
11
- /** @typedef {import('../base-node.js').Node} Node */
12
- /** @template T @typedef {import('../network-node.js').NetworkNode<T>} NetworkNode */
13
- /** @typedef {import('../cpu-node.js').CPUNode} CpuNode */
9
+ /** @typedef {import('../BaseNode.js').Node} Node */
10
+ /** @template T @typedef {import('../NetworkNode.js').NetworkNode<T>} NetworkNode */
11
+ /** @typedef {import('../CpuNode.js').CPUNode} CpuNode */
14
12
 
15
- class FirstContentfulPaint extends Metric {
13
+ class FirstContentfulPaint extends Lantern.Metric {
16
14
  /**
17
15
  * @return {Lantern.Simulation.MetricCoefficients}
18
16
  */
@@ -57,7 +55,7 @@ class FirstContentfulPaint extends Metric {
57
55
  /** @type {Array<CpuNode>} */
58
56
  const cpuNodes = [];
59
57
  graph.traverse(node => {
60
- if (node.type === BaseNode.TYPES.CPU) {
58
+ if (node.type === Lantern.BaseNode.TYPES.CPU) {
61
59
  // A task is *possibly* render blocking if it *started* before cutoffTimestamp.
62
60
  // We use startTime here because the paint event can be *inside* the task that was render blocking.
63
61
  if (node.startTime <= cutoffTimestamp) cpuNodes.push(node);
@@ -75,7 +73,7 @@ class FirstContentfulPaint extends Metric {
75
73
  cpuNodes.sort((a, b) => a.startTime - b.startTime);
76
74
 
77
75
  // A script is *possibly* render blocking if it finished loading before cutoffTimestamp.
78
- const possiblyRenderBlockingScriptUrls = Metric.getScriptUrls(graph, node => {
76
+ const possiblyRenderBlockingScriptUrls = Lantern.Metric.getScriptUrls(graph, node => {
79
77
  // The optimistic LCP treatNodeAsRenderBlocking fn wants to exclude some images in the graph,
80
78
  // but here it only receives scripts to evaluate. It's a no-op in this case, but it will
81
79
  // matter below in the getFirstPaintBasedGraph clone operation.
@@ -147,7 +145,7 @@ class FirstContentfulPaint extends Metric {
147
145
  const {definitelyNotRenderBlockingScriptUrls, renderBlockingCpuNodeIds} = rbData;
148
146
 
149
147
  return dependencyGraph.cloneWithRelationships(node => {
150
- if (node.type === BaseNode.TYPES.NETWORK) {
148
+ if (node.type === Lantern.BaseNode.TYPES.NETWORK) {
151
149
  // Exclude all nodes that ended after cutoffTimestamp (except for the main document which we always consider necessary)
152
150
  // endTime is negative if request does not finish, make sure startTime isn't after cutoffTimestamp in this case.
153
151
  const endedAfterPaint = node.endTime > cutoffTimestamp || node.startTime > cutoffTimestamp;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import assert from 'assert/strict';
8
+
9
+ import * as Lantern from '../lantern.js';
10
+ import {readJson} from '../../../test/test-utils.js';
11
+ import {getComputationDataFromFixture} from './MetricTestUtils.js';
12
+
13
+ const {FirstContentfulPaint} = Lantern.Metrics;
14
+
15
+ const trace = readJson('../../../fixtures/artifacts/progressive-app/trace.json', import.meta);
16
+
17
+ describe('Metrics: Lantern FCP', () => {
18
+ it('should compute predicted value', async () => {
19
+ const data = await getComputationDataFromFixture({trace});
20
+ const result = await FirstContentfulPaint.compute(data);
21
+
22
+ expect({
23
+ timing: Math.round(result.timing),
24
+ optimistic: Math.round(result.optimisticEstimate.timeInMs),
25
+ pessimistic: Math.round(result.pessimisticEstimate.timeInMs),
26
+ optimisticNodeTimings: result.optimisticEstimate.nodeTimings.size,
27
+ pessimisticNodeTimings: result.pessimisticEstimate.nodeTimings.size,
28
+ }).toMatchSnapshot();
29
+ assert.ok(result.optimisticGraph, 'should have created optimistic graph');
30
+ assert.ok(result.pessimisticGraph, 'should have created pessimistic graph');
31
+ });
32
+
33
+ it('should handle negative request networkEndTime', async () => {
34
+ const data = await getComputationDataFromFixture({trace});
35
+ data.graph.request.networkEndTime = -1;
36
+ const result = await FirstContentfulPaint.compute(data);
37
+
38
+ const optimisticNodes = [];
39
+ result.optimisticGraph.traverse(node => {
40
+ if (node.type === 'network') {
41
+ optimisticNodes.push(node);
42
+ }
43
+ });
44
+ expect(optimisticNodes.map(node => node.request.url)).toEqual(['https://squoosh.app/']);
45
+
46
+ const pessimisticNodes = [];
47
+ result.pessimisticGraph.traverse(node => {
48
+ if (node.type === 'network') {
49
+ pessimisticNodes.push(node);
50
+ }
51
+ });
52
+ expect(pessimisticNodes.map(node => node.request.url)).toEqual(['https://squoosh.app/']);
53
+ });
54
+ });
@@ -0,0 +1,20 @@
1
+ export type Node = import('../BaseNode.js').Node;
2
+ export class Interactive extends Lantern.Metric {
3
+ /**
4
+ * @param {Node} dependencyGraph
5
+ * @return {Node}
6
+ */
7
+ static getOptimisticGraph(dependencyGraph: Node): Node;
8
+ /**
9
+ * @param {Node} dependencyGraph
10
+ * @return {Node}
11
+ */
12
+ static getPessimisticGraph(dependencyGraph: Node): Node;
13
+ /**
14
+ * @param {Lantern.Simulation.Result['nodeTimings']} nodeTimings
15
+ * @return {number}
16
+ */
17
+ static getLastLongTaskEndTime(nodeTimings: Lantern.Simulation.Result['nodeTimings'], duration?: number): number;
18
+ }
19
+ import * as Lantern from '../lantern.js';
20
+ //# sourceMappingURL=Interactive.d.ts.map
@@ -4,19 +4,16 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import * as Lantern from '../types/lantern.js';
8
- import {Metric} from '../metric.js';
9
- import {BaseNode} from '../base-node.js';
10
- import {NetworkRequestTypes} from '../lantern.js';
7
+ import * as Lantern from '../lantern.js';
11
8
 
12
- /** @typedef {import('../base-node.js').Node} Node */
9
+ /** @typedef {import('../BaseNode.js').Node} Node */
13
10
 
14
11
  // Any CPU task of 20 ms or more will end up being a critical long task on mobile
15
12
  const CRITICAL_LONG_TASK_THRESHOLD = 20;
16
13
 
17
- class Interactive extends Metric {
14
+ class Interactive extends Lantern.Metric {
18
15
  /**
19
- * @return {LH.Gatherer.Simulation.MetricCoefficients}
16
+ * @return {Lantern.Simulation.MetricCoefficients}
20
17
  */
21
18
  static get COEFFICIENTS() {
22
19
  return {
@@ -36,13 +33,13 @@ class Interactive extends Metric {
36
33
 
37
34
  return dependencyGraph.cloneWithRelationships(node => {
38
35
  // Include everything that might be a long task
39
- if (node.type === BaseNode.TYPES.CPU) {
36
+ if (node.type === Lantern.BaseNode.TYPES.CPU) {
40
37
  return node.duration > minimumCpuTaskDuration;
41
38
  }
42
39
 
43
40
  // Include all scripts and high priority requests, exclude all images
44
- const isImage = node.request.resourceType === NetworkRequestTypes.Image;
45
- const isScript = node.request.resourceType === NetworkRequestTypes.Script;
41
+ const isImage = node.request.resourceType === Lantern.NetworkRequestTypes.Image;
42
+ const isScript = node.request.resourceType === Lantern.NetworkRequestTypes.Script;
46
43
  return (
47
44
  !isImage &&
48
45
  (isScript ||
@@ -61,9 +58,9 @@ class Interactive extends Metric {
61
58
  }
62
59
 
63
60
  /**
64
- * @param {LH.Gatherer.Simulation.Result} simulationResult
65
- * @param {import('../metric.js').Extras} extras
66
- * @return {LH.Gatherer.Simulation.Result}
61
+ * @param {Lantern.Simulation.Result} simulationResult
62
+ * @param {import('../Metric.js').Extras} extras
63
+ * @return {Lantern.Simulation.Result}
67
64
  */
68
65
  static getEstimateFromSimulation(simulationResult, extras) {
69
66
  if (!extras.lcpResult) throw new Error('missing lcpResult');
@@ -80,8 +77,8 @@ class Interactive extends Metric {
80
77
 
81
78
  /**
82
79
  * @param {Lantern.Simulation.MetricComputationDataInput} data
83
- * @param {Omit<import('../metric.js').Extras, 'optimistic'>=} extras
84
- * @return {Promise<LH.Artifacts.LanternMetric>}
80
+ * @param {Omit<import('../Metric.js').Extras, 'optimistic'>=} extras
81
+ * @return {Promise<Lantern.Metrics.Result>}
85
82
  */
86
83
  static async compute(data, extras) {
87
84
  const lcpResult = extras?.lcpResult;
@@ -95,13 +92,13 @@ class Interactive extends Metric {
95
92
  }
96
93
 
97
94
  /**
98
- * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
95
+ * @param {Lantern.Simulation.Result['nodeTimings']} nodeTimings
99
96
  * @return {number}
100
97
  */
101
98
  static getLastLongTaskEndTime(nodeTimings, duration = 50) {
102
99
  return Array.from(nodeTimings.entries())
103
100
  .filter(([node, timing]) => {
104
- if (node.type !== BaseNode.TYPES.CPU) return false;
101
+ if (node.type !== Lantern.BaseNode.TYPES.CPU) return false;
105
102
  return timing.duration > duration;
106
103
  })
107
104
  .map(([_, timing]) => timing.endTime)
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import assert from 'assert/strict';
8
+
9
+ import * as Lantern from '../lantern.js';
10
+ import {getComputationDataFromFixture} from './MetricTestUtils.js';
11
+ import {readJson} from '../../../test/test-utils.js';
12
+
13
+ const {Interactive, FirstContentfulPaint, LargestContentfulPaint} = Lantern.Metrics;
14
+
15
+ const trace = readJson('../../../fixtures/artifacts/progressive-app/trace.json', import.meta);
16
+ const iframeTrace = readJson('../../../fixtures/artifacts/iframe/trace.json', import.meta);
17
+
18
+ describe('Metrics: Lantern TTI', () => {
19
+ it('should compute predicted value', async () => {
20
+ const data = await getComputationDataFromFixture({trace});
21
+ const result = await Interactive.compute(data, {
22
+ lcpResult: await LargestContentfulPaint.compute(data, {
23
+ fcpResult: await FirstContentfulPaint.compute(data),
24
+ }),
25
+ });
26
+
27
+ expect({
28
+ timing: Math.round(result.timing),
29
+ optimistic: Math.round(result.optimisticEstimate.timeInMs),
30
+ pessimistic: Math.round(result.pessimisticEstimate.timeInMs),
31
+ }).toMatchSnapshot();
32
+ assert.equal(result.optimisticEstimate.nodeTimings.size, 14);
33
+ assert.equal(result.pessimisticEstimate.nodeTimings.size, 31);
34
+ assert.ok(result.optimisticGraph, 'should have created optimistic graph');
35
+ assert.ok(result.pessimisticGraph, 'should have created pessimistic graph');
36
+ });
37
+
38
+ it('should compute predicted value on iframes with substantial layout', async () => {
39
+ const data = await getComputationDataFromFixture({
40
+ trace: iframeTrace,
41
+ });
42
+ const result = await Interactive.compute(data, {
43
+ lcpResult: await LargestContentfulPaint.compute(data, {
44
+ fcpResult: await FirstContentfulPaint.compute(data),
45
+ }),
46
+ });
47
+
48
+ expect({
49
+ timing: Math.round(result.timing),
50
+ optimistic: Math.round(result.optimisticEstimate.timeInMs),
51
+ pessimistic: Math.round(result.pessimisticEstimate.timeInMs),
52
+ }).toMatchSnapshot();
53
+ assert.ok(result.optimisticGraph, 'should have created optimistic graph');
54
+ assert.ok(result.pessimisticGraph, 'should have created pessimistic graph');
55
+ });
56
+ });