lighthouse 11.7.0-dev.20240408 → 11.7.1

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 (141) hide show
  1. package/CONTRIBUTING.md +2 -2
  2. package/cli/test/smokehouse/core-tests.js +2 -0
  3. package/core/audits/accessibility/duplicate-id-active.d.ts +10 -0
  4. package/core/audits/accessibility/duplicate-id-active.js +42 -0
  5. package/core/audits/accessibility/target-size.js +1 -0
  6. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +16 -0
  7. package/core/audits/byte-efficiency/byte-efficiency-audit.js +38 -1
  8. package/core/audits/byte-efficiency/offscreen-images.d.ts +11 -0
  9. package/core/audits/byte-efficiency/offscreen-images.js +15 -0
  10. package/core/audits/dobetterweb/uses-http2.d.ts +13 -0
  11. package/core/audits/dobetterweb/uses-http2.js +34 -2
  12. package/core/audits/layout-shift-elements.d.ts +16 -0
  13. package/core/audits/layout-shift-elements.js +101 -0
  14. package/core/audits/no-unload-listeners.d.ts +16 -0
  15. package/core/audits/no-unload-listeners.js +86 -0
  16. package/core/audits/preload-fonts.d.ts +5 -1
  17. package/core/audits/preload-fonts.js +10 -1
  18. package/core/audits/script-elements-test-audit.d.ts +14 -0
  19. package/core/audits/script-elements-test-audit.js +29 -0
  20. package/core/audits/seo/is-crawlable.d.ts +1 -2
  21. package/core/audits/seo/plugins.d.ts +15 -0
  22. package/core/audits/seo/plugins.js +150 -0
  23. package/core/audits/seo/tap-targets.d.ts +50 -0
  24. package/core/audits/seo/tap-targets.js +352 -0
  25. package/core/audits/uses-rel-preload.d.ts +5 -1
  26. package/core/audits/uses-rel-preload.js +11 -1
  27. package/core/audits/work-during-interaction.d.ts +2 -2
  28. package/core/audits/work-during-interaction.js +2 -2
  29. package/core/computed/js-bundles.d.ts +1 -1
  30. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +82 -13
  31. package/core/computed/metrics/lantern-first-contentful-paint.js +178 -15
  32. package/core/computed/metrics/lantern-first-meaningful-paint.d.ts +12 -13
  33. package/core/computed/metrics/lantern-first-meaningful-paint.js +49 -10
  34. package/core/computed/metrics/lantern-interactive.d.ts +21 -13
  35. package/core/computed/metrics/lantern-interactive.js +84 -11
  36. package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +25 -13
  37. package/core/computed/metrics/lantern-largest-contentful-paint.js +80 -12
  38. package/core/computed/metrics/lantern-max-potential-fid.d.ts +24 -13
  39. package/core/computed/metrics/lantern-max-potential-fid.js +64 -11
  40. package/core/computed/metrics/lantern-metric.d.ts +18 -9
  41. package/core/computed/metrics/lantern-metric.js +31 -13
  42. package/core/computed/metrics/lantern-speed-index.d.ts +32 -13
  43. package/core/computed/metrics/lantern-speed-index.js +116 -12
  44. package/core/computed/metrics/lantern-total-blocking-time.d.ts +25 -13
  45. package/core/computed/metrics/lantern-total-blocking-time.js +94 -10
  46. package/core/computed/metrics/metric.js +3 -1
  47. package/core/computed/module-duplication.d.ts +1 -1
  48. package/core/computed/page-dependency-graph.js +1 -2
  49. package/core/config/default-config.js +74 -46
  50. package/core/config/experimental-config.js +0 -11
  51. package/core/config/metrics-to-audits.d.ts +19 -0
  52. package/core/config/metrics-to-audits.js +62 -0
  53. package/core/gather/gatherers/accessibility.js +3 -1
  54. package/core/gather/gatherers/global-listeners.d.ts +27 -0
  55. package/core/gather/gatherers/global-listeners.js +108 -0
  56. package/core/gather/gatherers/script-elements.d.ts +21 -0
  57. package/core/gather/gatherers/script-elements.js +100 -0
  58. package/core/gather/gatherers/seo/embedded-content.d.ts +10 -0
  59. package/core/gather/gatherers/seo/embedded-content.js +63 -0
  60. package/core/gather/gatherers/seo/tap-targets.d.ts +21 -0
  61. package/core/gather/gatherers/seo/tap-targets.js +389 -0
  62. package/core/gather/gatherers/trace-elements.d.ts +10 -0
  63. package/core/gather/gatherers/trace-elements.js +19 -0
  64. package/core/lib/lantern/page-dependency-graph.d.ts +4 -4
  65. package/core/lib/lantern/page-dependency-graph.js +5 -5
  66. package/core/lib/lighthouse-compatibility.js +10 -53
  67. package/dist/report/bundle.esm.js +1 -1
  68. package/dist/report/flow.js +14 -14
  69. package/dist/report/standalone.js +1 -1
  70. package/flow-report/src/summary/category.tsx +12 -10
  71. package/package.json +1 -1
  72. package/report/renderer/performance-category-renderer.d.ts +7 -0
  73. package/report/renderer/performance-category-renderer.js +11 -1
  74. package/report/renderer/report-utils.js +10 -13
  75. package/shared/localization/locales/ar-XB.json +64 -1
  76. package/shared/localization/locales/ar.json +64 -1
  77. package/shared/localization/locales/bg.json +64 -1
  78. package/shared/localization/locales/ca.json +64 -1
  79. package/shared/localization/locales/cs.json +64 -1
  80. package/shared/localization/locales/da.json +64 -1
  81. package/shared/localization/locales/de.json +64 -1
  82. package/shared/localization/locales/el.json +64 -1
  83. package/shared/localization/locales/en-GB.json +64 -1
  84. package/shared/localization/locales/en-US.json +65 -2
  85. package/shared/localization/locales/en-XA.json +64 -1
  86. package/shared/localization/locales/en-XL.json +65 -2
  87. package/shared/localization/locales/es-419.json +64 -1
  88. package/shared/localization/locales/es.json +64 -1
  89. package/shared/localization/locales/fi.json +64 -1
  90. package/shared/localization/locales/fil.json +64 -1
  91. package/shared/localization/locales/fr.json +64 -1
  92. package/shared/localization/locales/he.json +64 -1
  93. package/shared/localization/locales/hi.json +64 -1
  94. package/shared/localization/locales/hr.json +64 -1
  95. package/shared/localization/locales/hu.json +64 -1
  96. package/shared/localization/locales/id.json +64 -1
  97. package/shared/localization/locales/it.json +64 -1
  98. package/shared/localization/locales/ja.json +64 -1
  99. package/shared/localization/locales/ko.json +64 -1
  100. package/shared/localization/locales/lt.json +64 -1
  101. package/shared/localization/locales/lv.json +64 -1
  102. package/shared/localization/locales/nl.json +64 -1
  103. package/shared/localization/locales/no.json +64 -1
  104. package/shared/localization/locales/pl.json +64 -1
  105. package/shared/localization/locales/pt-PT.json +64 -1
  106. package/shared/localization/locales/pt.json +64 -1
  107. package/shared/localization/locales/ro.json +64 -1
  108. package/shared/localization/locales/ru.json +64 -1
  109. package/shared/localization/locales/sk.json +64 -1
  110. package/shared/localization/locales/sl.json +64 -1
  111. package/shared/localization/locales/sr-Latn.json +64 -1
  112. package/shared/localization/locales/sr.json +64 -1
  113. package/shared/localization/locales/sv.json +64 -1
  114. package/shared/localization/locales/ta.json +64 -1
  115. package/shared/localization/locales/te.json +64 -1
  116. package/shared/localization/locales/th.json +64 -1
  117. package/shared/localization/locales/tr.json +64 -1
  118. package/shared/localization/locales/uk.json +64 -1
  119. package/shared/localization/locales/vi.json +64 -1
  120. package/shared/localization/locales/zh-HK.json +64 -1
  121. package/shared/localization/locales/zh-TW.json +64 -1
  122. package/shared/localization/locales/zh.json +64 -1
  123. package/tsconfig.json +0 -1
  124. package/types/artifacts.d.ts +17 -3
  125. package/types/config.d.ts +1 -0
  126. package/types/lhr/audit-details.d.ts +4 -9
  127. package/types/lhr/lhr.d.ts +2 -0
  128. package/core/lib/lantern/metrics/first-contentful-paint.d.ts +0 -79
  129. package/core/lib/lantern/metrics/first-contentful-paint.js +0 -200
  130. package/core/lib/lantern/metrics/first-meaningful-paint.d.ts +0 -6
  131. package/core/lib/lantern/metrics/first-meaningful-paint.js +0 -66
  132. package/core/lib/lantern/metrics/interactive.d.ts +0 -26
  133. package/core/lib/lantern/metrics/interactive.js +0 -112
  134. package/core/lib/lantern/metrics/largest-contentful-paint.d.ts +0 -19
  135. package/core/lib/lantern/metrics/largest-contentful-paint.js +0 -108
  136. package/core/lib/lantern/metrics/max-potential-fid.d.ts +0 -30
  137. package/core/lib/lantern/metrics/max-potential-fid.js +0 -92
  138. package/core/lib/lantern/metrics/speed-index.d.ts +0 -38
  139. package/core/lib/lantern/metrics/speed-index.js +0 -145
  140. package/core/lib/lantern/metrics/total-blocking-time.d.ts +0 -31
  141. package/core/lib/lantern/metrics/total-blocking-time.js +0 -128
@@ -1,25 +1,94 @@
1
1
  export { LanternFirstContentfulPaintComputed as LanternFirstContentfulPaint };
2
- export type Extras = import('../../lib/lantern/metric.js').Extras;
2
+ export type Node = import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>;
3
+ export type CPUNode = import('../../lib/lantern/cpu-node.js').CPUNode<LH.Artifacts.NetworkRequest>;
4
+ export type NetworkNode = import('../../lib/lantern/network-node.js').NetworkNode<LH.Artifacts.NetworkRequest>;
3
5
  declare const LanternFirstContentfulPaintComputed: typeof LanternFirstContentfulPaint & {
4
6
  request: (dependencies: import("../../index.js").Artifacts.MetricComputationDataInput, context: import("../../../types/utility-types.js").default.ImmutableObject<{
5
7
  computedCache: Map<string, import("../../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
6
8
  }>) => Promise<import("../../index.js").Artifacts.LanternMetric>;
7
9
  };
8
- /** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */
9
- declare class LanternFirstContentfulPaint extends FirstContentfulPaint {
10
+ /** @typedef {import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
11
+ /** @typedef {import('../../lib/lantern/cpu-node.js').CPUNode<LH.Artifacts.NetworkRequest>} CPUNode */
12
+ /** @typedef {import('../../lib/lantern/network-node.js').NetworkNode<LH.Artifacts.NetworkRequest>} NetworkNode */
13
+ declare class LanternFirstContentfulPaint extends LanternMetric {
10
14
  /**
11
- * @param {LH.Artifacts.MetricComputationDataInput} data
12
- * @param {LH.Artifacts.ComputedContext} context
13
- * @param {Omit<Extras, 'optimistic'>=} extras
14
- * @return {Promise<LH.Artifacts.LanternMetric>}
15
+ * @typedef FirstPaintBasedGraphOpts
16
+ * @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
17
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
18
+ * @property {function(NetworkNode):boolean} treatNodeAsRenderBlocking The function that determines
19
+ * which resources should be considered *possibly* render-blocking.
20
+ * @property {(function(CPUNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
21
+ * determines which CPU nodes should also be included in our blocking node IDs set,
22
+ * beyond what getRenderBlockingNodeData() already includes.
15
23
  */
16
- static computeMetricWithGraphs(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext, extras?: Omit<Extras, 'optimistic'> | undefined): Promise<LH.Artifacts.LanternMetric>;
17
24
  /**
18
- * @param {LH.Artifacts.MetricComputationDataInput} data
19
- * @param {LH.Artifacts.ComputedContext} context
20
- * @return {Promise<LH.Artifacts.LanternMetric>}
25
+ * This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
26
+ * *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
27
+ * It also computes the set of corresponding CPU node ids that were needed for the paint at the
28
+ * given timestamp.
29
+ *
30
+ * @param {Node} graph
31
+ * @param {FirstPaintBasedGraphOpts} opts
32
+ * @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
21
33
  */
22
- static compute_(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
34
+ static getRenderBlockingNodeData(graph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
35
+ /**
36
+ * The timestamp used to filter out tasks that occured after
37
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
38
+ */
39
+ cutoffTimestamp: number;
40
+ /**
41
+ * The function that determines
42
+ * which resources should be considered *possibly* render-blocking.
43
+ */
44
+ treatNodeAsRenderBlocking: (arg0: NetworkNode) => boolean;
45
+ /**
46
+ * The function that
47
+ * determines which CPU nodes should also be included in our blocking node IDs set,
48
+ * beyond what getRenderBlockingNodeData() already includes.
49
+ */
50
+ additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CPUNode) => boolean) | undefined;
51
+ }): {
52
+ definitelyNotRenderBlockingScriptUrls: Set<string>;
53
+ renderBlockingCpuNodeIds: Set<string>;
54
+ };
55
+ /**
56
+ * This function computes the graph required for the first paint of interest.
57
+ *
58
+ * @param {Node} dependencyGraph
59
+ * @param {FirstPaintBasedGraphOpts} opts
60
+ * @return {Node}
61
+ */
62
+ static getFirstPaintBasedGraph(dependencyGraph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
63
+ /**
64
+ * The timestamp used to filter out tasks that occured after
65
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
66
+ */
67
+ cutoffTimestamp: number;
68
+ /**
69
+ * The function that determines
70
+ * which resources should be considered *possibly* render-blocking.
71
+ */
72
+ treatNodeAsRenderBlocking: (arg0: NetworkNode) => boolean;
73
+ /**
74
+ * The function that
75
+ * determines which CPU nodes should also be included in our blocking node IDs set,
76
+ * beyond what getRenderBlockingNodeData() already includes.
77
+ */
78
+ additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CPUNode) => boolean) | undefined;
79
+ }): Node;
80
+ /**
81
+ * @param {Node} dependencyGraph
82
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
83
+ * @return {Node}
84
+ */
85
+ static getOptimisticGraph(dependencyGraph: Node, processedNavigation: LH.Artifacts.ProcessedNavigation): Node;
86
+ /**
87
+ * @param {Node} dependencyGraph
88
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
89
+ * @return {Node}
90
+ */
91
+ static getPessimisticGraph(dependencyGraph: Node, processedNavigation: LH.Artifacts.ProcessedNavigation): Node;
23
92
  }
24
- import { FirstContentfulPaint } from '../../lib/lantern/metrics/first-contentful-paint.js';
93
+ import { LanternMetric } from './lantern-metric.js';
25
94
  //# sourceMappingURL=lantern-first-contentful-paint.d.ts.map
@@ -5,29 +5,192 @@
5
5
  */
6
6
 
7
7
  import {makeComputedArtifact} from '../computed-artifact.js';
8
- import {getComputationDataParams} from './lantern-metric.js';
9
- import {FirstContentfulPaint} from '../../lib/lantern/metrics/first-contentful-paint.js';
8
+ import {LanternMetric} from './lantern-metric.js';
9
+ import {BaseNode} from '../../lib/lantern/base-node.js';
10
10
 
11
- /** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */
11
+ /** @typedef {import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
12
+ /** @typedef {import('../../lib/lantern/cpu-node.js').CPUNode<LH.Artifacts.NetworkRequest>} CPUNode */
13
+ /** @typedef {import('../../lib/lantern/network-node.js').NetworkNode<LH.Artifacts.NetworkRequest>} NetworkNode */
12
14
 
13
- class LanternFirstContentfulPaint extends FirstContentfulPaint {
15
+ class LanternFirstContentfulPaint extends LanternMetric {
14
16
  /**
15
- * @param {LH.Artifacts.MetricComputationDataInput} data
16
- * @param {LH.Artifacts.ComputedContext} context
17
- * @param {Omit<Extras, 'optimistic'>=} extras
18
- * @return {Promise<LH.Artifacts.LanternMetric>}
17
+ * @return {LH.Gatherer.Simulation.MetricCoefficients}
19
18
  */
20
- static async computeMetricWithGraphs(data, context, extras) {
21
- return this.compute(await getComputationDataParams(data, context), extras);
19
+ static get COEFFICIENTS() {
20
+ return {
21
+ intercept: 0,
22
+ optimistic: 0.5,
23
+ pessimistic: 0.5,
24
+ };
22
25
  }
23
26
 
24
27
  /**
25
- * @param {LH.Artifacts.MetricComputationDataInput} data
26
- * @param {LH.Artifacts.ComputedContext} context
27
- * @return {Promise<LH.Artifacts.LanternMetric>}
28
+ * @typedef FirstPaintBasedGraphOpts
29
+ * @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
30
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
31
+ * @property {function(NetworkNode):boolean} treatNodeAsRenderBlocking The function that determines
32
+ * which resources should be considered *possibly* render-blocking.
33
+ * @property {(function(CPUNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
34
+ * determines which CPU nodes should also be included in our blocking node IDs set,
35
+ * beyond what getRenderBlockingNodeData() already includes.
28
36
  */
29
- static async compute_(data, context) {
30
- return this.computeMetricWithGraphs(data, context);
37
+
38
+ /**
39
+ * This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
40
+ * *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
41
+ * It also computes the set of corresponding CPU node ids that were needed for the paint at the
42
+ * given timestamp.
43
+ *
44
+ * @param {Node} graph
45
+ * @param {FirstPaintBasedGraphOpts} opts
46
+ * @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
47
+ */
48
+ static getRenderBlockingNodeData(
49
+ graph,
50
+ {cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
51
+ ) {
52
+ /** @type {Map<string, CPUNode>} A map of blocking script URLs to the earliest EvaluateScript task node that executed them. */
53
+ const scriptUrlToNodeMap = new Map();
54
+
55
+ /** @type {Array<CPUNode>} */
56
+ const cpuNodes = [];
57
+ graph.traverse(node => {
58
+ if (node.type === BaseNode.TYPES.CPU) {
59
+ // A task is *possibly* render blocking if it *started* before cutoffTimestamp.
60
+ // We use startTime here because the paint event can be *inside* the task that was render blocking.
61
+ if (node.startTime <= cutoffTimestamp) cpuNodes.push(node);
62
+
63
+ // Build our script URL map to find the earliest EvaluateScript task node.
64
+ const scriptUrls = node.getEvaluateScriptURLs();
65
+ for (const url of scriptUrls) {
66
+ // Use the earliest CPU node we find.
67
+ const existing = scriptUrlToNodeMap.get(url) || node;
68
+ scriptUrlToNodeMap.set(url, node.startTime < existing.startTime ? node : existing);
69
+ }
70
+ }
71
+ });
72
+
73
+ cpuNodes.sort((a, b) => a.startTime - b.startTime);
74
+
75
+ // A script is *possibly* render blocking if it finished loading before cutoffTimestamp.
76
+ const possiblyRenderBlockingScriptUrls = LanternMetric.getScriptUrls(graph, node => {
77
+ // The optimistic LCP treatNodeAsRenderBlocking fn wants to exclude some images in the graph,
78
+ // but here it only receives scripts to evaluate. It's a no-op in this case, but it will
79
+ // matter below in the getFirstPaintBasedGraph clone operation.
80
+ return node.endTime <= cutoffTimestamp && treatNodeAsRenderBlocking(node);
81
+ });
82
+
83
+ // A script is *definitely not* render blocking if its EvaluateScript task started after cutoffTimestamp.
84
+ /** @type {Set<string>} */
85
+ const definitelyNotRenderBlockingScriptUrls = new Set();
86
+ /** @type {Set<string>} */
87
+ const renderBlockingCpuNodeIds = new Set();
88
+ for (const url of possiblyRenderBlockingScriptUrls) {
89
+ // Lookup the CPU node that had the earliest EvaluateScript for this URL.
90
+ const cpuNodeForUrl = scriptUrlToNodeMap.get(url);
91
+
92
+ // If we can't find it at all, we can't conclude anything, so just skip it.
93
+ if (!cpuNodeForUrl) continue;
94
+
95
+ // If we found it and it was in our `cpuNodes` set that means it finished before cutoffTimestamp, so it really is render-blocking.
96
+ if (cpuNodes.includes(cpuNodeForUrl)) {
97
+ renderBlockingCpuNodeIds.add(cpuNodeForUrl.id);
98
+ continue;
99
+ }
100
+
101
+ // We couldn't find the evaluate script in the set of CPU nodes that ran before our paint, so
102
+ // it must not have been necessary for the paint.
103
+ definitelyNotRenderBlockingScriptUrls.add(url);
104
+ }
105
+
106
+ // The first layout, first paint, and first ParseHTML are almost always necessary for first paint,
107
+ // so we always include those CPU nodes.
108
+ const firstLayout = cpuNodes.find(node => node.didPerformLayout());
109
+ if (firstLayout) renderBlockingCpuNodeIds.add(firstLayout.id);
110
+ const firstPaint = cpuNodes.find(node => node.childEvents.some(e => e.name === 'Paint'));
111
+ if (firstPaint) renderBlockingCpuNodeIds.add(firstPaint.id);
112
+ const firstParse = cpuNodes.find(node => node.childEvents.some(e => e.name === 'ParseHTML'));
113
+ if (firstParse) renderBlockingCpuNodeIds.add(firstParse.id);
114
+
115
+ // If a CPU filter was passed in, we also want to include those extra nodes.
116
+ if (additionalCpuNodesToTreatAsRenderBlocking) {
117
+ cpuNodes
118
+ .filter(additionalCpuNodesToTreatAsRenderBlocking)
119
+ .forEach(node => renderBlockingCpuNodeIds.add(node.id));
120
+ }
121
+
122
+ return {
123
+ definitelyNotRenderBlockingScriptUrls,
124
+ renderBlockingCpuNodeIds,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * This function computes the graph required for the first paint of interest.
130
+ *
131
+ * @param {Node} dependencyGraph
132
+ * @param {FirstPaintBasedGraphOpts} opts
133
+ * @return {Node}
134
+ */
135
+ static getFirstPaintBasedGraph(
136
+ dependencyGraph,
137
+ {cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
138
+ ) {
139
+ const rbData = this.getRenderBlockingNodeData(dependencyGraph, {
140
+ cutoffTimestamp,
141
+ treatNodeAsRenderBlocking,
142
+ additionalCpuNodesToTreatAsRenderBlocking,
143
+ });
144
+ const {definitelyNotRenderBlockingScriptUrls, renderBlockingCpuNodeIds} = rbData;
145
+
146
+ return dependencyGraph.cloneWithRelationships(node => {
147
+ if (node.type === BaseNode.TYPES.NETWORK) {
148
+ // Exclude all nodes that ended after cutoffTimestamp (except for the main document which we always consider necessary)
149
+ // endTime is negative if request does not finish, make sure startTime isn't after cutoffTimestamp in this case.
150
+ const endedAfterPaint = node.endTime > cutoffTimestamp || node.startTime > cutoffTimestamp;
151
+ if (endedAfterPaint && !node.isMainDocument()) return false;
152
+
153
+ const url = node.record.url;
154
+ // If the URL definitely wasn't render-blocking then we filter it out.
155
+ if (definitelyNotRenderBlockingScriptUrls.has(url)) {
156
+ return false;
157
+ }
158
+
159
+ // Lastly, build up the FCP graph of all nodes we consider render blocking
160
+ return treatNodeAsRenderBlocking(node);
161
+ } else {
162
+ // If it's a CPU node, just check if it was blocking.
163
+ return renderBlockingCpuNodeIds.has(node.id);
164
+ }
165
+ });
166
+ }
167
+
168
+ /**
169
+ * @param {Node} dependencyGraph
170
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
171
+ * @return {Node}
172
+ */
173
+ static getOptimisticGraph(dependencyGraph, processedNavigation) {
174
+ return this.getFirstPaintBasedGraph(dependencyGraph, {
175
+ cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
176
+ // In the optimistic graph we exclude resources that appeared to be render blocking but were
177
+ // initiated by a script. While they typically have a very high importance and tend to have a
178
+ // significant impact on the page's content, these resources don't technically block rendering.
179
+ treatNodeAsRenderBlocking: node =>
180
+ node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
181
+ });
182
+ }
183
+
184
+ /**
185
+ * @param {Node} dependencyGraph
186
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
187
+ * @return {Node}
188
+ */
189
+ static getPessimisticGraph(dependencyGraph, processedNavigation) {
190
+ return this.getFirstPaintBasedGraph(dependencyGraph, {
191
+ cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
192
+ treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
193
+ });
31
194
  }
32
195
  }
33
196
 
@@ -1,25 +1,24 @@
1
1
  export { LanternFirstMeaningfulPaintComputed as LanternFirstMeaningfulPaint };
2
- export type Extras = import('../../lib/lantern/metric.js').Extras;
2
+ export type Node = import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>;
3
3
  declare const LanternFirstMeaningfulPaintComputed: typeof LanternFirstMeaningfulPaint & {
4
4
  request: (dependencies: import("../../index.js").Artifacts.MetricComputationDataInput, context: import("../../../types/utility-types.js").default.ImmutableObject<{
5
5
  computedCache: Map<string, import("../../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
6
6
  }>) => Promise<import("../../index.js").Artifacts.LanternMetric>;
7
7
  };
8
- /** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */
9
- declare class LanternFirstMeaningfulPaint extends FirstMeaningfulPaint {
8
+ /** @typedef {import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
9
+ declare class LanternFirstMeaningfulPaint extends LanternMetric {
10
10
  /**
11
- * @param {LH.Artifacts.MetricComputationDataInput} data
12
- * @param {LH.Artifacts.ComputedContext} context
13
- * @param {Omit<Extras, 'optimistic'>=} extras
14
- * @return {Promise<LH.Artifacts.LanternMetric>}
11
+ * @param {Node} dependencyGraph
12
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
13
+ * @return {Node}
15
14
  */
16
- static computeMetricWithGraphs(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext, extras?: Omit<Extras, 'optimistic'> | undefined): Promise<LH.Artifacts.LanternMetric>;
15
+ static getOptimisticGraph(dependencyGraph: Node, processedNavigation: LH.Artifacts.ProcessedNavigation): Node;
17
16
  /**
18
- * @param {LH.Artifacts.MetricComputationDataInput} data
19
- * @param {LH.Artifacts.ComputedContext} context
20
- * @return {Promise<LH.Artifacts.LanternMetric>}
17
+ * @param {Node} dependencyGraph
18
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
19
+ * @return {Node}
21
20
  */
22
- static compute_(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
21
+ static getPessimisticGraph(dependencyGraph: Node, processedNavigation: LH.Artifacts.ProcessedNavigation): Node;
23
22
  }
24
- import { FirstMeaningfulPaint } from '../../lib/lantern/metrics/first-meaningful-paint.js';
23
+ import { LanternMetric } from './lantern-metric.js';
25
24
  //# sourceMappingURL=lantern-first-meaningful-paint.d.ts.map
@@ -5,21 +5,60 @@
5
5
  */
6
6
 
7
7
  import {makeComputedArtifact} from '../computed-artifact.js';
8
- import {getComputationDataParams} from './lantern-metric.js';
9
- import {FirstMeaningfulPaint} from '../../lib/lantern/metrics/first-meaningful-paint.js';
8
+ import {LanternMetric} from './lantern-metric.js';
9
+ import {LighthouseError} from '../../lib/lh-error.js';
10
10
  import {LanternFirstContentfulPaint} from './lantern-first-contentful-paint.js';
11
11
 
12
- /** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */
12
+ /** @typedef {import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
13
13
 
14
- class LanternFirstMeaningfulPaint extends FirstMeaningfulPaint {
14
+ class LanternFirstMeaningfulPaint extends LanternMetric {
15
15
  /**
16
- * @param {LH.Artifacts.MetricComputationDataInput} data
17
- * @param {LH.Artifacts.ComputedContext} context
18
- * @param {Omit<Extras, 'optimistic'>=} extras
19
- * @return {Promise<LH.Artifacts.LanternMetric>}
16
+ * @return {LH.Gatherer.Simulation.MetricCoefficients}
17
+ */
18
+ static get COEFFICIENTS() {
19
+ return {
20
+ intercept: 0,
21
+ optimistic: 0.5,
22
+ pessimistic: 0.5,
23
+ };
24
+ }
25
+
26
+ /**
27
+ * @param {Node} dependencyGraph
28
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
29
+ * @return {Node}
30
+ */
31
+ static getOptimisticGraph(dependencyGraph, processedNavigation) {
32
+ const fmp = processedNavigation.timestamps.firstMeaningfulPaint;
33
+ if (!fmp) {
34
+ throw new LighthouseError(LighthouseError.errors.NO_FMP);
35
+ }
36
+ return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
37
+ cutoffTimestamp: fmp,
38
+ // See LanternFirstContentfulPaint's getOptimisticGraph implementation for a longer description
39
+ // of why we exclude script initiated resources here.
40
+ treatNodeAsRenderBlocking: node =>
41
+ node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
42
+ });
43
+ }
44
+
45
+ /**
46
+ * @param {Node} dependencyGraph
47
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
48
+ * @return {Node}
20
49
  */
21
- static async computeMetricWithGraphs(data, context, extras) {
22
- return this.compute(await getComputationDataParams(data, context), extras);
50
+ static getPessimisticGraph(dependencyGraph, processedNavigation) {
51
+ const fmp = processedNavigation.timestamps.firstMeaningfulPaint;
52
+ if (!fmp) {
53
+ throw new LighthouseError(LighthouseError.errors.NO_FMP);
54
+ }
55
+
56
+ return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
57
+ cutoffTimestamp: fmp,
58
+ treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
59
+ // For pessimistic FMP we'll include *all* layout nodes
60
+ additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
61
+ });
23
62
  }
24
63
 
25
64
  /**
@@ -1,25 +1,33 @@
1
1
  export { LanternInteractiveComputed as LanternInteractive };
2
- export type Extras = import('../../lib/lantern/metric.js').Extras;
2
+ export type Node = import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>;
3
3
  declare const LanternInteractiveComputed: typeof LanternInteractive & {
4
4
  request: (dependencies: import("../../index.js").Artifacts.MetricComputationDataInput, context: import("../../../types/utility-types.js").default.ImmutableObject<{
5
5
  computedCache: Map<string, import("../../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
6
6
  }>) => Promise<import("../../index.js").Artifacts.LanternMetric>;
7
7
  };
8
- /** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */
9
- declare class LanternInteractive extends Interactive {
8
+ import { NetworkRequest } from '../../lib/network-request.js';
9
+ declare class LanternInteractive extends LanternMetric {
10
10
  /**
11
- * @param {LH.Artifacts.MetricComputationDataInput} data
12
- * @param {LH.Artifacts.ComputedContext} context
13
- * @param {Omit<Extras, 'optimistic'>=} extras
14
- * @return {Promise<LH.Artifacts.LanternMetric>}
11
+ * @param {Node} dependencyGraph
12
+ * @return {Node}
15
13
  */
16
- static computeMetricWithGraphs(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext, extras?: Omit<Extras, 'optimistic'> | undefined): Promise<LH.Artifacts.LanternMetric>;
14
+ static getOptimisticGraph(dependencyGraph: Node): Node;
17
15
  /**
18
- * @param {LH.Artifacts.MetricComputationDataInput} data
19
- * @param {LH.Artifacts.ComputedContext} context
20
- * @return {Promise<LH.Artifacts.LanternMetric>}
16
+ * @param {Node} dependencyGraph
17
+ * @return {Node}
21
18
  */
22
- static compute_(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
19
+ static getPessimisticGraph(dependencyGraph: Node): Node;
20
+ /**
21
+ * @param {LH.Gatherer.Simulation.Result} simulationResult
22
+ * @param {import('../../lib/lantern/metric.js').Extras} extras
23
+ * @return {LH.Gatherer.Simulation.Result}
24
+ */
25
+ static getEstimateFromSimulation(simulationResult: LH.Gatherer.Simulation.Result, extras: import('../../lib/lantern/metric.js').Extras): LH.Gatherer.Simulation.Result;
26
+ /**
27
+ * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
28
+ * @return {number}
29
+ */
30
+ static getLastLongTaskEndTime(nodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], duration?: number): number;
23
31
  }
24
- import { Interactive } from '../../lib/lantern/metrics/interactive.js';
32
+ import { LanternMetric } from './lantern-metric.js';
25
33
  //# sourceMappingURL=lantern-interactive.d.ts.map
@@ -5,21 +5,78 @@
5
5
  */
6
6
 
7
7
  import {makeComputedArtifact} from '../computed-artifact.js';
8
+ import {LanternMetric} from './lantern-metric.js';
9
+ import {BaseNode} from '../../lib/lantern/base-node.js';
10
+ import {NetworkRequest} from '../../lib/network-request.js';
8
11
  import {LanternFirstMeaningfulPaint} from './lantern-first-meaningful-paint.js';
9
- import {Interactive} from '../../lib/lantern/metrics/interactive.js';
10
- import {getComputationDataParams} from './lantern-metric.js';
11
12
 
12
- /** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */
13
+ /** @typedef {import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
13
14
 
14
- class LanternInteractive extends Interactive {
15
+ // Any CPU task of 20 ms or more will end up being a critical long task on mobile
16
+ const CRITICAL_LONG_TASK_THRESHOLD = 20;
17
+
18
+ class LanternInteractive extends LanternMetric {
15
19
  /**
16
- * @param {LH.Artifacts.MetricComputationDataInput} data
17
- * @param {LH.Artifacts.ComputedContext} context
18
- * @param {Omit<Extras, 'optimistic'>=} extras
19
- * @return {Promise<LH.Artifacts.LanternMetric>}
20
+ * @return {LH.Gatherer.Simulation.MetricCoefficients}
21
+ */
22
+ static get COEFFICIENTS() {
23
+ return {
24
+ intercept: 0,
25
+ optimistic: 0.5,
26
+ pessimistic: 0.5,
27
+ };
28
+ }
29
+
30
+ /**
31
+ * @param {Node} dependencyGraph
32
+ * @return {Node}
20
33
  */
21
- static async computeMetricWithGraphs(data, context, extras) {
22
- return this.compute(await getComputationDataParams(data, context), extras);
34
+ static getOptimisticGraph(dependencyGraph) {
35
+ // Adjust the critical long task threshold for microseconds
36
+ const minimumCpuTaskDuration = CRITICAL_LONG_TASK_THRESHOLD * 1000;
37
+
38
+ return dependencyGraph.cloneWithRelationships(node => {
39
+ // Include everything that might be a long task
40
+ if (node.type === BaseNode.TYPES.CPU) {
41
+ return node.event.dur > minimumCpuTaskDuration;
42
+ }
43
+
44
+ // Include all scripts and high priority requests, exclude all images
45
+ const isImage = node.record.resourceType === NetworkRequest.TYPES.Image;
46
+ const isScript = node.record.resourceType === NetworkRequest.TYPES.Script;
47
+ return (
48
+ !isImage &&
49
+ (isScript ||
50
+ node.record.priority === 'High' ||
51
+ node.record.priority === 'VeryHigh')
52
+ );
53
+ });
54
+ }
55
+
56
+ /**
57
+ * @param {Node} dependencyGraph
58
+ * @return {Node}
59
+ */
60
+ static getPessimisticGraph(dependencyGraph) {
61
+ return dependencyGraph;
62
+ }
63
+
64
+ /**
65
+ * @param {LH.Gatherer.Simulation.Result} simulationResult
66
+ * @param {import('../../lib/lantern/metric.js').Extras} extras
67
+ * @return {LH.Gatherer.Simulation.Result}
68
+ */
69
+ static getEstimateFromSimulation(simulationResult, extras) {
70
+ if (!extras.fmpResult) throw new Error('missing fmpResult');
71
+
72
+ const lastTaskAt = LanternInteractive.getLastLongTaskEndTime(simulationResult.nodeTimings);
73
+ const minimumTime = extras.optimistic
74
+ ? extras.fmpResult.optimisticEstimate.timeInMs
75
+ : extras.fmpResult.pessimisticEstimate.timeInMs;
76
+ return {
77
+ timeInMs: Math.max(minimumTime, lastTaskAt),
78
+ nodeTimings: simulationResult.nodeTimings,
79
+ };
23
80
  }
24
81
 
25
82
  /**
@@ -29,7 +86,23 @@ class LanternInteractive extends Interactive {
29
86
  */
30
87
  static async compute_(data, context) {
31
88
  const fmpResult = await LanternFirstMeaningfulPaint.request(data, context);
32
- return this.computeMetricWithGraphs(data, context, {fmpResult});
89
+ const metricResult = await this.computeMetricWithGraphs(data, context, {fmpResult});
90
+ metricResult.timing = Math.max(metricResult.timing, fmpResult.timing);
91
+ return metricResult;
92
+ }
93
+
94
+ /**
95
+ * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
96
+ * @return {number}
97
+ */
98
+ static getLastLongTaskEndTime(nodeTimings, duration = 50) {
99
+ return Array.from(nodeTimings.entries())
100
+ .filter(([node, timing]) => {
101
+ if (node.type !== BaseNode.TYPES.CPU) return false;
102
+ return timing.duration > duration;
103
+ })
104
+ .map(([_, timing]) => timing.endTime)
105
+ .reduce((max, x) => Math.max(max || 0, x || 0), 0);
33
106
  }
34
107
  }
35
108
 
@@ -1,25 +1,37 @@
1
1
  export { LanternLargestContentfulPaintComputed as LanternLargestContentfulPaint };
2
- export type Extras = import('../../lib/lantern/metric.js').Extras;
2
+ export type Node = import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>;
3
3
  declare const LanternLargestContentfulPaintComputed: typeof LanternLargestContentfulPaint & {
4
4
  request: (dependencies: import("../../index.js").Artifacts.MetricComputationDataInput, context: import("../../../types/utility-types.js").default.ImmutableObject<{
5
5
  computedCache: Map<string, import("../../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
6
6
  }>) => Promise<import("../../index.js").Artifacts.LanternMetric>;
7
7
  };
8
- /** @typedef {import('../../lib/lantern/metric.js').Extras} Extras */
9
- declare class LanternLargestContentfulPaint extends LargestContentfulPaint {
8
+ /** @typedef {import('../../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
9
+ declare class LanternLargestContentfulPaint extends LanternMetric {
10
10
  /**
11
- * @param {LH.Artifacts.MetricComputationDataInput} data
12
- * @param {LH.Artifacts.ComputedContext} context
13
- * @param {Omit<Extras, 'optimistic'>=} extras
14
- * @return {Promise<LH.Artifacts.LanternMetric>}
11
+ * Low priority image nodes are usually offscreen and very unlikely to be the
12
+ * resource that is required for LCP. Our LCP graphs include everything except for these images.
13
+ *
14
+ * @param {Node} node
15
+ * @return {boolean}
15
16
  */
16
- static computeMetricWithGraphs(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext, extras?: Omit<Extras, 'optimistic'> | undefined): Promise<LH.Artifacts.LanternMetric>;
17
+ static isNotLowPriorityImageNode(node: Node): boolean;
17
18
  /**
18
- * @param {LH.Artifacts.MetricComputationDataInput} data
19
- * @param {LH.Artifacts.ComputedContext} context
20
- * @return {Promise<LH.Artifacts.LanternMetric>}
19
+ * @param {Node} dependencyGraph
20
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
21
+ * @return {Node}
21
22
  */
22
- static compute_(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
23
+ static getOptimisticGraph(dependencyGraph: Node, processedNavigation: LH.Artifacts.ProcessedNavigation): Node;
24
+ /**
25
+ * @param {Node} dependencyGraph
26
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
27
+ * @return {Node}
28
+ */
29
+ static getPessimisticGraph(dependencyGraph: Node, processedNavigation: LH.Artifacts.ProcessedNavigation): Node;
30
+ /**
31
+ * @param {LH.Gatherer.Simulation.Result} simulationResult
32
+ * @return {LH.Gatherer.Simulation.Result}
33
+ */
34
+ static getEstimateFromSimulation(simulationResult: LH.Gatherer.Simulation.Result): LH.Gatherer.Simulation.Result;
23
35
  }
24
- import { LargestContentfulPaint } from '../../lib/lantern/metrics/largest-contentful-paint.js';
36
+ import { LanternMetric } from './lantern-metric.js';
25
37
  //# sourceMappingURL=lantern-largest-contentful-paint.d.ts.map