lighthouse 12.0.0-dev.20240611 → 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 (111) 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 +1 -1
  8. package/core/audits/long-tasks.js +1 -1
  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/tbt-impact-tasks.js +3 -2
  33. package/core/config/constants.js +1 -1
  34. package/core/lib/asset-saver.js +2 -2
  35. package/core/lib/lantern/{base-node.d.ts → BaseNode.d.ts} +7 -9
  36. package/core/lib/lantern/{base-node.js → BaseNode.js} +6 -6
  37. package/core/lib/lantern/BaseNode.test.js +385 -0
  38. package/core/lib/lantern/{cpu-node.d.ts → CpuNode.d.ts} +8 -9
  39. package/core/lib/lantern/{cpu-node.js → CpuNode.js} +4 -5
  40. package/core/lib/lantern/{lantern-error.d.ts → LanternError.d.ts} +1 -1
  41. package/core/lib/lantern/{metric.d.ts → Metric.d.ts} +17 -17
  42. package/core/lib/lantern/{metric.js → Metric.js} +10 -12
  43. package/core/lib/lantern/{network-node.d.ts → NetworkNode.d.ts} +4 -5
  44. package/core/lib/lantern/{network-node.js → NetworkNode.js} +4 -5
  45. package/core/lib/lantern/{page-dependency-graph.d.ts → PageDependencyGraph.d.ts} +5 -5
  46. package/core/lib/lantern/{page-dependency-graph.js → PageDependencyGraph.js} +11 -10
  47. package/core/lib/lantern/PageDependencyGraph.test.js +654 -0
  48. package/core/lib/lantern/{tbt-utils.d.ts → TBTUtils.d.ts} +1 -1
  49. package/core/lib/lantern/TBTUtils.test.d.ts +2 -0
  50. package/core/lib/lantern/TBTUtils.test.js +130 -0
  51. package/core/lib/lantern/{trace-engine-computation-data.d.ts → TraceEngineComputationData.d.ts} +3 -3
  52. package/core/lib/lantern/{trace-engine-computation-data.js → TraceEngineComputationData.js} +5 -7
  53. package/core/lib/lantern/lantern.d.ts +16 -46
  54. package/core/lib/lantern/lantern.js +16 -40
  55. package/core/lib/lantern/metrics/{first-contentful-paint.d.ts → FirstContentfulPaint.d.ts} +11 -11
  56. package/core/lib/lantern/metrics/{first-contentful-paint.js → FirstContentfulPaint.js} +8 -10
  57. package/core/lib/lantern/metrics/FirstContentfulPaint.test.js +54 -0
  58. package/core/lib/lantern/metrics/{interactive.d.ts → Interactive.d.ts} +4 -5
  59. package/core/lib/lantern/metrics/{interactive.js → Interactive.js} +10 -13
  60. package/core/lib/lantern/metrics/Interactive.test.js +56 -0
  61. package/core/lib/lantern/metrics/{largest-contentful-paint.d.ts → LargestContentfulPaint.d.ts} +5 -6
  62. package/core/lib/lantern/metrics/{largest-contentful-paint.js → LargestContentfulPaint.js} +9 -12
  63. package/core/lib/lantern/metrics/LargestContentfulPaint.test.js +42 -0
  64. package/core/lib/lantern/metrics/{max-potential-fid.d.ts → MaxPotentialFID.d.ts} +5 -6
  65. package/core/lib/lantern/metrics/{max-potential-fid.js → MaxPotentialFID.js} +8 -9
  66. package/core/lib/lantern/metrics/MetricTestUtils.d.ts +19 -0
  67. package/core/lib/lantern/metrics/MetricTestUtils.js +48 -0
  68. package/core/lib/lantern/metrics/{speed-index.d.ts → SpeedIndex.d.ts} +5 -6
  69. package/core/lib/lantern/metrics/{speed-index.js → SpeedIndex.js} +7 -9
  70. package/core/lib/lantern/metrics/SpeedIndex.test.js +83 -0
  71. package/core/lib/lantern/metrics/{total-blocking-time.d.ts → TotalBlockingTime.d.ts} +5 -6
  72. package/core/lib/lantern/metrics/{total-blocking-time.js → TotalBlockingTime.js} +9 -12
  73. package/core/lib/lantern/metrics/__snapshots__/first-contentful-paint-test.js.snap +11 -0
  74. package/core/lib/lantern/metrics/__snapshots__/interactive-test.js.snap +17 -0
  75. package/core/lib/lantern/metrics/metrics.d.ts +9 -0
  76. package/core/lib/lantern/metrics/metrics.js +16 -0
  77. package/core/lib/lantern/{simulator/connection-pool.d.ts → simulation/ConnectionPool.d.ts} +5 -5
  78. package/core/lib/lantern/{simulator/connection-pool.js → simulation/ConnectionPool.js} +3 -3
  79. package/core/lib/lantern/simulation/ConnectionPool.test.js +195 -0
  80. package/core/lib/lantern/simulation/Constants.d.ts +52 -0
  81. package/core/lib/lantern/simulation/Constants.js +48 -0
  82. package/core/lib/lantern/{simulator/dns-cache.d.ts → simulation/DNSCache.d.ts} +2 -2
  83. package/core/lib/lantern/{simulator/dns-cache.js → simulation/DNSCache.js} +1 -1
  84. package/core/lib/lantern/simulation/DNSCache.test.js +72 -0
  85. package/core/lib/lantern/{simulator/network-analyzer.d.ts → simulation/NetworkAnalyzer.d.ts} +2 -2
  86. package/core/lib/lantern/{simulator/network-analyzer.js → simulation/NetworkAnalyzer.js} +1 -1
  87. package/core/lib/lantern/simulation/NetworkAnalyzer.test.js +475 -0
  88. package/core/lib/lantern/{simulator/simulator-timing-map.d.ts → simulation/SimulationTimingMap.d.ts} +7 -7
  89. package/core/lib/lantern/{simulator/simulator-timing-map.js → simulation/SimulationTimingMap.js} +5 -6
  90. package/core/lib/lantern/{simulator/simulator.d.ts → simulation/Simulator.d.ts} +13 -15
  91. package/core/lib/lantern/{simulator/simulator.js → simulation/Simulator.js} +25 -26
  92. package/core/lib/lantern/simulation/Simulator.test.js +434 -0
  93. package/core/lib/lantern/simulation/TCPConnection.test.d.ts +2 -0
  94. package/core/lib/lantern/simulation/TCPConnection.test.js +374 -0
  95. package/core/lib/lantern/{simulator/tcp-connection.d.ts → simulation/TcpConnection.d.ts} +2 -2
  96. package/core/lib/lantern/{simulator/tcp-connection.js → simulation/TcpConnection.js} +1 -1
  97. package/core/lib/lantern/simulation/simulation.d.ts +21 -0
  98. package/core/lib/lantern/simulation/simulation.js +28 -0
  99. package/core/lib/lantern/types/lantern.d.ts +5 -5
  100. package/core/lib/lantern-trace-saver.d.ts +4 -4
  101. package/core/lib/lantern-trace-saver.js +2 -2
  102. package/core/lib/navigation-error.js +3 -3
  103. package/core/lib/network-recorder.js +2 -2
  104. package/core/lib/network-request.d.ts +1 -1
  105. package/core/lib/network-request.js +1 -1
  106. package/package.json +2 -2
  107. package/tsconfig.json +10 -8
  108. package/types/artifacts.d.ts +3 -4
  109. package/types/gatherer.d.ts +1 -1
  110. /package/core/lib/lantern/{lantern-error.js → LanternError.js} +0 -0
  111. /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
+ });
@@ -19,7 +19,7 @@ export function createNetworkRequests(trace: Lantern.Trace, traceEngineData: Tra
19
19
  * @param {TraceEngine.Handlers.Types.TraceParseData} traceEngineData
20
20
  * @param {Lantern.Simulation.URL=} URL
21
21
  */
22
- export function createGraph(requests: Lantern.NetworkRequest[], trace: Lantern.Trace, traceEngineData: TraceEngine.Handlers.Types.TraceParseData, URL?: Lantern.Simulation.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
@@ -7,9 +7,7 @@
7
7
  import * as TraceEngine from '@paulirish/trace_engine';
8
8
  import * as Protocol from '@paulirish/trace_engine/generated/protocol.js';
9
9
 
10
- import * as Lantern from './types/lantern.js';
11
- import {PageDependencyGraph} from './page-dependency-graph.js';
12
- import {RESOURCE_TYPES} from '../network-request.js';
10
+ import * as Lantern from './lantern.js';
13
11
 
14
12
  /** @typedef {import('@paulirish/trace_engine/models/trace/handlers/PageLoadMetricsHandler.js').MetricName} MetricName */
15
13
  /** @typedef {import('@paulirish/trace_engine/models/trace/handlers/PageLoadMetricsHandler.js').MetricScore} MetricScore */
@@ -232,7 +230,7 @@ function chooseInitiatorRequest(request, requestsByURL) {
232
230
  return request.redirectSource;
233
231
  }
234
232
 
235
- const initiatorURL = PageDependencyGraph.getNetworkInitiators(request)[0];
233
+ const initiatorURL = Lantern.PageDependencyGraph.getNetworkInitiators(request)[0];
236
234
  let candidates = requestsByURL.get(initiatorURL) || [];
237
235
  // The (valid) initiator must come before the initiated request.
238
236
  candidates = candidates.filter(c => {
@@ -243,7 +241,7 @@ function chooseInitiatorRequest(request, requestsByURL) {
243
241
  // Disambiguate based on prefetch. Prefetch requests have type 'Other' and cannot
244
242
  // initiate requests, so we drop them here.
245
243
  const nonPrefetchCandidates = candidates.filter(
246
- cand => cand.resourceType !== RESOURCE_TYPES.Other);
244
+ cand => cand.resourceType !== Lantern.NetworkRequestTypes.Other);
247
245
  if (nonPrefetchCandidates.length) {
248
246
  candidates = nonPrefetchCandidates;
249
247
  }
@@ -258,7 +256,7 @@ function chooseInitiatorRequest(request, requestsByURL) {
258
256
  if (candidates.length > 1 && request.initiator.type === 'parser') {
259
257
  // Filter to just Documents when initiator type is parser.
260
258
  const documentCandidates = candidates.filter(cand =>
261
- cand.resourceType === RESOURCE_TYPES.Document);
259
+ cand.resourceType === Lantern.NetworkRequestTypes.Document);
262
260
  if (documentCandidates.length) {
263
261
  candidates = documentCandidates;
264
262
  }
@@ -458,7 +456,7 @@ function createGraph(requests, trace, traceEngineData, URL) {
458
456
  URL.mainDocumentUrl = request.url;
459
457
  }
460
458
 
461
- return PageDependencyGraph.createGraph(mainThreadEvents, requests, URL);
459
+ return Lantern.PageDependencyGraph.createGraph(mainThreadEvents, requests, URL);
462
460
  }
463
461
 
464
462
  export {
@@ -1,50 +1,20 @@
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;
1
17
  /** @type {Lantern.Util.SelfMap<Lantern.ResourceType>} */
2
18
  export const NetworkRequestTypes: Lantern.Util.SelfMap<Lantern.ResourceType>;
3
- export namespace constants {
4
- export { throttling };
5
- }
6
19
  import * as Lantern from './types/lantern.js';
7
- declare namespace throttling {
8
- export { DEVTOOLS_RTT_ADJUSTMENT_FACTOR };
9
- export { DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR };
10
- export namespace mobileSlow4G {
11
- const rttMs: number;
12
- const throughputKbps: number;
13
- const requestLatencyMs: number;
14
- const downloadThroughputKbps: number;
15
- const uploadThroughputKbps: number;
16
- const cpuSlowdownMultiplier: number;
17
- }
18
- export namespace mobileRegular3G {
19
- const rttMs_1: number;
20
- export { rttMs_1 as rttMs };
21
- const throughputKbps_1: number;
22
- export { throughputKbps_1 as throughputKbps };
23
- const requestLatencyMs_1: number;
24
- export { requestLatencyMs_1 as requestLatencyMs };
25
- const downloadThroughputKbps_1: number;
26
- export { downloadThroughputKbps_1 as downloadThroughputKbps };
27
- const uploadThroughputKbps_1: number;
28
- export { uploadThroughputKbps_1 as uploadThroughputKbps };
29
- const cpuSlowdownMultiplier_1: number;
30
- export { cpuSlowdownMultiplier_1 as cpuSlowdownMultiplier };
31
- }
32
- export namespace desktopDense4G {
33
- const rttMs_2: number;
34
- export { rttMs_2 as rttMs };
35
- const throughputKbps_2: number;
36
- export { throughputKbps_2 as throughputKbps };
37
- const cpuSlowdownMultiplier_2: number;
38
- export { cpuSlowdownMultiplier_2 as cpuSlowdownMultiplier };
39
- const requestLatencyMs_2: number;
40
- export { requestLatencyMs_2 as requestLatencyMs };
41
- const downloadThroughputKbps_2: number;
42
- export { downloadThroughputKbps_2 as downloadThroughputKbps };
43
- const uploadThroughputKbps_2: number;
44
- export { uploadThroughputKbps_2 as uploadThroughputKbps };
45
- }
46
- }
47
- declare const DEVTOOLS_RTT_ADJUSTMENT_FACTOR: 3.75;
48
- declare const DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR: 0.9;
49
- export {};
50
20
  //# sourceMappingURL=lantern.d.ts.map
@@ -28,48 +28,24 @@ const NetworkRequestTypes = {
28
28
  Prefetch: 'Prefetch',
29
29
  };
30
30
 
31
- const DEVTOOLS_RTT_ADJUSTMENT_FACTOR = 3.75;
32
- const DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR = 0.9;
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';
33
41
 
34
- const throttling = {
35
- DEVTOOLS_RTT_ADJUSTMENT_FACTOR,
36
- DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
37
- // These values align with WebPageTest's definition of "Fast 3G"
38
- // But offer similar characteristics to roughly the 75th percentile of 4G connections.
39
- mobileSlow4G: {
40
- rttMs: 150,
41
- throughputKbps: 1.6 * 1024,
42
- requestLatencyMs: 150 * DEVTOOLS_RTT_ADJUSTMENT_FACTOR,
43
- downloadThroughputKbps: 1.6 * 1024 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
44
- uploadThroughputKbps: 750 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
45
- cpuSlowdownMultiplier: 4,
46
- },
47
- // These values partially align with WebPageTest's definition of "Regular 3G".
48
- // These values are meant to roughly align with Chrome UX report's 3G definition which are based
49
- // on HTTP RTT of 300-1400ms and downlink throughput of <700kbps.
50
- mobileRegular3G: {
51
- rttMs: 300,
52
- throughputKbps: 700,
53
- requestLatencyMs: 300 * DEVTOOLS_RTT_ADJUSTMENT_FACTOR,
54
- downloadThroughputKbps: 700 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
55
- uploadThroughputKbps: 700 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
56
- cpuSlowdownMultiplier: 4,
57
- },
58
- // Using a "broadband" connection type
59
- // Corresponds to "Dense 4G 25th percentile" in https://docs.google.com/document/d/1Ft1Bnq9-t4jK5egLSOc28IL4TvR-Tt0se_1faTA4KTY/edit#heading=h.bb7nfy2x9e5v
60
- desktopDense4G: {
61
- rttMs: 40,
62
- throughputKbps: 10 * 1024,
63
- cpuSlowdownMultiplier: 1,
64
- requestLatencyMs: 0, // 0 means unset
65
- downloadThroughputKbps: 0,
66
- uploadThroughputKbps: 0,
67
- },
68
- };
69
-
70
- const constants = {throttling};
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 */
71
48
 
72
49
  export {
73
50
  NetworkRequestTypes,
74
- constants,
75
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
+ });
@@ -1,5 +1,5 @@
1
- export type Node = import('../base-node.js').Node;
2
- export class Interactive extends Metric {
1
+ export type Node = import('../BaseNode.js').Node;
2
+ export class Interactive extends Lantern.Metric {
3
3
  /**
4
4
  * @param {Node} dependencyGraph
5
5
  * @return {Node}
@@ -16,6 +16,5 @@ export class Interactive extends Metric {
16
16
  */
17
17
  static getLastLongTaskEndTime(nodeTimings: Lantern.Simulation.Result['nodeTimings'], duration?: number): number;
18
18
  }
19
- import { Metric } from '../metric.js';
20
- import * as Lantern from '../types/lantern.js';
21
- //# sourceMappingURL=interactive.d.ts.map
19
+ import * as Lantern from '../lantern.js';
20
+ //# sourceMappingURL=Interactive.d.ts.map
@@ -4,17 +4,14 @@
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
16
  * @return {Lantern.Simulation.MetricCoefficients}
20
17
  */
@@ -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 ||
@@ -62,7 +59,7 @@ class Interactive extends Metric {
62
59
 
63
60
  /**
64
61
  * @param {Lantern.Simulation.Result} simulationResult
65
- * @param {import('../metric.js').Extras} extras
62
+ * @param {import('../Metric.js').Extras} extras
66
63
  * @return {Lantern.Simulation.Result}
67
64
  */
68
65
  static getEstimateFromSimulation(simulationResult, extras) {
@@ -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<Lantern.Metric>}
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;
@@ -101,7 +98,7 @@ class Interactive extends Metric {
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
+ });
@@ -1,6 +1,6 @@
1
- export type Node = import('../base-node.js').Node;
2
- /** @typedef {import('../base-node.js').Node} Node */
3
- export class LargestContentfulPaint extends Metric {
1
+ export type Node = import('../BaseNode.js').Node;
2
+ /** @typedef {import('../BaseNode.js').Node} Node */
3
+ export class LargestContentfulPaint extends Lantern.Metric {
4
4
  /**
5
5
  * Low priority image nodes are usually offscreen and very unlikely to be the
6
6
  * resource that is required for LCP. Our LCP graphs include everything except for these images.
@@ -15,6 +15,5 @@ export class LargestContentfulPaint extends Metric {
15
15
  */
16
16
  static getEstimateFromSimulation(simulationResult: Lantern.Simulation.Result): Lantern.Simulation.Result;
17
17
  }
18
- import { Metric } from '../metric.js';
19
- import * as Lantern from '../types/lantern.js';
20
- //# sourceMappingURL=largest-contentful-paint.d.ts.map
18
+ import * as Lantern from '../lantern.js';
19
+ //# sourceMappingURL=LargestContentfulPaint.d.ts.map