lighthouse 11.3.0-dev.20231206 → 11.3.0-dev.20231208
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/audits/bf-cache.d.ts +1 -0
- package/core/audits/bf-cache.js +11 -1
- package/core/audits/bootup-time.js +3 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -4
- package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -6
- package/core/audits/byte-efficiency/duplicated-javascript.js +9 -37
- package/core/audits/byte-efficiency/legacy-javascript.js +1 -1
- package/core/audits/byte-efficiency/unminified-javascript.js +2 -2
- package/core/audits/byte-efficiency/unused-javascript.js +10 -10
- package/core/audits/layout-shift-elements.js +23 -12
- package/core/audits/metrics/cumulative-layout-shift.js +5 -1
- package/core/audits/resource-summary.d.ts +11 -0
- package/core/audits/resource-summary.js +86 -0
- package/core/audits/third-party-cookies.d.ts +22 -0
- package/core/audits/third-party-cookies.js +112 -0
- package/core/computed/metrics/cumulative-layout-shift.d.ts +13 -1
- package/core/computed/metrics/cumulative-layout-shift.js +47 -1
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +50 -16
- package/core/computed/metrics/lantern-first-contentful-paint.js +56 -59
- package/core/computed/metrics/lantern-first-meaningful-paint.js +10 -12
- package/core/computed/metrics/lantern-largest-contentful-paint.js +9 -12
- package/core/computed/metrics/lantern-metric.d.ts +2 -2
- package/core/computed/metrics/lantern-metric.js +6 -5
- package/core/config/default-config.js +4 -0
- package/core/config/filters.js +1 -0
- package/core/gather/base-artifacts.js +2 -1
- package/core/gather/gatherers/css-usage.js +5 -0
- package/core/gather/gatherers/trace-elements.d.ts +6 -15
- package/core/gather/gatherers/trace-elements.js +15 -71
- package/core/lib/i18n/i18n.d.ts +2 -2
- package/core/lib/i18n/i18n.js +2 -2
- package/core/lib/network-request.js +4 -1
- package/core/lib/rect-helpers.d.ts +5 -0
- package/core/lib/rect-helpers.js +15 -0
- package/core/lib/script-helpers.d.ts +1 -1
- package/core/lib/script-helpers.js +1 -1
- package/dist/report/bundle.esm.js +3 -3
- package/dist/report/flow.js +5 -5
- package/dist/report/standalone.js +4 -4
- package/package.json +1 -1
- package/report/renderer/performance-category-renderer.js +4 -2
- package/shared/localization/locales/en-US.json +15 -0
- package/shared/localization/locales/en-XL.json +15 -0
- package/tsconfig.json +1 -0
- package/types/artifacts.d.ts +3 -2
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {makeComputedArtifact} from '../computed-artifact.js';
|
|
8
8
|
import {ProcessedTrace} from '../processed-trace.js';
|
|
9
|
+
import * as RectHelpers from '../../lib/rect-helpers.js';
|
|
9
10
|
|
|
10
11
|
/** @typedef {{ts: number, isMainFrame: boolean, weightedScore: number, impactedNodes?: LH.Artifacts.TraceImpactedNode[]}} LayoutShiftEvent */
|
|
11
12
|
|
|
@@ -72,6 +73,49 @@ class CumulativeLayoutShift {
|
|
|
72
73
|
return layoutShiftEvents;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Each layout shift event has a 'score' which is the amount added to the CLS as a result of the given shift(s).
|
|
78
|
+
* We calculate the score per element by taking the 'score' of each layout shift event and
|
|
79
|
+
* distributing it between all the nodes that were shifted, proportianal to the impact region of
|
|
80
|
+
* each shifted element.
|
|
81
|
+
*
|
|
82
|
+
* @param {LayoutShiftEvent[]} layoutShiftEvents
|
|
83
|
+
* @return {Map<number, number>}
|
|
84
|
+
*/
|
|
85
|
+
static getImpactByNodeId(layoutShiftEvents) {
|
|
86
|
+
/** @type {Map<number, number>} */
|
|
87
|
+
const impactByNodeId = new Map();
|
|
88
|
+
|
|
89
|
+
for (const event of layoutShiftEvents) {
|
|
90
|
+
if (!event.impactedNodes) continue;
|
|
91
|
+
|
|
92
|
+
let totalAreaOfImpact = 0;
|
|
93
|
+
/** @type {Map<number, number>} */
|
|
94
|
+
const pixelsMovedPerNode = new Map();
|
|
95
|
+
|
|
96
|
+
for (const node of event.impactedNodes) {
|
|
97
|
+
if (!node.node_id || !node.old_rect || !node.new_rect) continue;
|
|
98
|
+
|
|
99
|
+
const oldRect = RectHelpers.traceRectToLHRect(node.old_rect);
|
|
100
|
+
const newRect = RectHelpers.traceRectToLHRect(node.new_rect);
|
|
101
|
+
const areaOfImpact = RectHelpers.getRectArea(oldRect) +
|
|
102
|
+
RectHelpers.getRectArea(newRect) -
|
|
103
|
+
RectHelpers.getRectOverlapArea(oldRect, newRect);
|
|
104
|
+
|
|
105
|
+
pixelsMovedPerNode.set(node.node_id, areaOfImpact);
|
|
106
|
+
totalAreaOfImpact += areaOfImpact;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const [nodeId, pixelsMoved] of pixelsMovedPerNode.entries()) {
|
|
110
|
+
let clsContribution = impactByNodeId.get(nodeId) || 0;
|
|
111
|
+
clsContribution += (pixelsMoved / totalAreaOfImpact) * event.weightedScore;
|
|
112
|
+
impactByNodeId.set(nodeId, clsContribution);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return impactByNodeId;
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
/**
|
|
76
120
|
* Calculates cumulative layout shifts per cluster (session) of LayoutShift
|
|
77
121
|
* events -- where a new cluster is created when there's a gap of more than
|
|
@@ -104,18 +148,20 @@ class CumulativeLayoutShift {
|
|
|
104
148
|
/**
|
|
105
149
|
* @param {LH.Trace} trace
|
|
106
150
|
* @param {LH.Artifacts.ComputedContext} context
|
|
107
|
-
* @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number}>}
|
|
151
|
+
* @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number, impactByNodeId: Map<number, number>}>}
|
|
108
152
|
*/
|
|
109
153
|
static async compute_(trace, context) {
|
|
110
154
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
111
155
|
|
|
112
156
|
const allFrameShiftEvents =
|
|
113
157
|
CumulativeLayoutShift.getLayoutShiftEvents(processedTrace);
|
|
158
|
+
const impactByNodeId = CumulativeLayoutShift.getImpactByNodeId(allFrameShiftEvents);
|
|
114
159
|
const mainFrameShiftEvents = allFrameShiftEvents.filter(e => e.isMainFrame);
|
|
115
160
|
|
|
116
161
|
return {
|
|
117
162
|
cumulativeLayoutShift: CumulativeLayoutShift.calculate(allFrameShiftEvents),
|
|
118
163
|
cumulativeLayoutShiftMainFrame: CumulativeLayoutShift.calculate(mainFrameShiftEvents),
|
|
164
|
+
impactByNodeId,
|
|
119
165
|
};
|
|
120
166
|
}
|
|
121
167
|
}
|
|
@@ -11,6 +11,16 @@ declare const LanternFirstContentfulPaintComputed: typeof LanternFirstContentful
|
|
|
11
11
|
/** @typedef {import('../../lib/dependency-graph/cpu-node').CPUNode} CPUNode */
|
|
12
12
|
/** @typedef {import('../../lib/dependency-graph/network-node').NetworkNode} NetworkNode */
|
|
13
13
|
declare class LanternFirstContentfulPaint extends LanternMetric {
|
|
14
|
+
/**
|
|
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.
|
|
23
|
+
*/
|
|
14
24
|
/**
|
|
15
25
|
* This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
|
|
16
26
|
* *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
|
|
@@ -18,31 +28,55 @@ declare class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
18
28
|
* given timestamp.
|
|
19
29
|
*
|
|
20
30
|
* @param {Node} graph
|
|
21
|
-
* @param {
|
|
22
|
-
*
|
|
23
|
-
* @param {function(NetworkNode):boolean} blockingScriptFilter The function that determines which scripts
|
|
24
|
-
* should be considered *possibly* render-blocking.
|
|
25
|
-
* @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
|
|
26
|
-
* should also be included in our blocking node IDs set.
|
|
27
|
-
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, blockingCpuNodeIds: Set<string>}}
|
|
31
|
+
* @param {FirstPaintBasedGraphOpts} opts
|
|
32
|
+
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
|
|
28
33
|
*/
|
|
29
|
-
static
|
|
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
|
+
}): {
|
|
30
52
|
definitelyNotRenderBlockingScriptUrls: Set<string>;
|
|
31
|
-
|
|
53
|
+
renderBlockingCpuNodeIds: Set<string>;
|
|
32
54
|
};
|
|
33
55
|
/**
|
|
34
56
|
* This function computes the graph required for the first paint of interest.
|
|
35
57
|
*
|
|
36
58
|
* @param {Node} dependencyGraph
|
|
37
|
-
* @param {
|
|
38
|
-
* paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
39
|
-
* @param {function(NetworkNode):boolean} blockingResourcesFilter The function that determines which resources
|
|
40
|
-
* should be considered *possibly* render-blocking.
|
|
41
|
-
* @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
|
|
42
|
-
* should also be included in our blocking node IDs set.
|
|
59
|
+
* @param {FirstPaintBasedGraphOpts} opts
|
|
43
60
|
* @return {Node}
|
|
44
61
|
*/
|
|
45
|
-
static getFirstPaintBasedGraph(dependencyGraph: Node,
|
|
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;
|
|
46
80
|
}
|
|
47
81
|
import { LanternMetric } from './lantern-metric.js';
|
|
48
82
|
//# sourceMappingURL=lantern-first-contentful-paint.d.ts.map
|
|
@@ -24,6 +24,17 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
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.
|
|
36
|
+
*/
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
|
|
29
40
|
* *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
|
|
@@ -31,19 +42,12 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
31
42
|
* given timestamp.
|
|
32
43
|
*
|
|
33
44
|
* @param {Node} graph
|
|
34
|
-
* @param {
|
|
35
|
-
*
|
|
36
|
-
* @param {function(NetworkNode):boolean} blockingScriptFilter The function that determines which scripts
|
|
37
|
-
* should be considered *possibly* render-blocking.
|
|
38
|
-
* @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
|
|
39
|
-
* should also be included in our blocking node IDs set.
|
|
40
|
-
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, blockingCpuNodeIds: Set<string>}}
|
|
45
|
+
* @param {FirstPaintBasedGraphOpts} opts
|
|
46
|
+
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
|
|
41
47
|
*/
|
|
42
|
-
static
|
|
48
|
+
static getRenderBlockingNodeData(
|
|
43
49
|
graph,
|
|
44
|
-
|
|
45
|
-
blockingScriptFilter,
|
|
46
|
-
extraBlockingCpuNodesToIncludeFilter
|
|
50
|
+
{cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
|
|
47
51
|
) {
|
|
48
52
|
/** @type {Map<string, CPUNode>} A map of blocking script URLs to the earliest EvaluateScript task node that executed them. */
|
|
49
53
|
const scriptUrlToNodeMap = new Map();
|
|
@@ -52,9 +56,9 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
52
56
|
const cpuNodes = [];
|
|
53
57
|
graph.traverse(node => {
|
|
54
58
|
if (node.type === BaseNode.TYPES.CPU) {
|
|
55
|
-
// A task is *possibly* render blocking if it *started* before
|
|
59
|
+
// A task is *possibly* render blocking if it *started* before cutoffTimestamp.
|
|
56
60
|
// We use startTime here because the paint event can be *inside* the task that was render blocking.
|
|
57
|
-
if (node.startTime <=
|
|
61
|
+
if (node.startTime <= cutoffTimestamp) cpuNodes.push(node);
|
|
58
62
|
|
|
59
63
|
// Build our script URL map to find the earliest EvaluateScript task node.
|
|
60
64
|
const scriptUrls = node.getEvaluateScriptURLs();
|
|
@@ -68,16 +72,19 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
68
72
|
|
|
69
73
|
cpuNodes.sort((a, b) => a.startTime - b.startTime);
|
|
70
74
|
|
|
71
|
-
// A script is *possibly* render blocking if it finished loading before
|
|
75
|
+
// A script is *possibly* render blocking if it finished loading before cutoffTimestamp.
|
|
72
76
|
const possiblyRenderBlockingScriptUrls = LanternMetric.getScriptUrls(graph, node => {
|
|
73
|
-
|
|
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);
|
|
74
81
|
});
|
|
75
82
|
|
|
76
|
-
// A script is *definitely not* render blocking if its EvaluateScript task started after
|
|
83
|
+
// A script is *definitely not* render blocking if its EvaluateScript task started after cutoffTimestamp.
|
|
77
84
|
/** @type {Set<string>} */
|
|
78
85
|
const definitelyNotRenderBlockingScriptUrls = new Set();
|
|
79
86
|
/** @type {Set<string>} */
|
|
80
|
-
const
|
|
87
|
+
const renderBlockingCpuNodeIds = new Set();
|
|
81
88
|
for (const url of possiblyRenderBlockingScriptUrls) {
|
|
82
89
|
// Lookup the CPU node that had the earliest EvaluateScript for this URL.
|
|
83
90
|
const cpuNodeForUrl = scriptUrlToNodeMap.get(url);
|
|
@@ -85,9 +92,9 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
85
92
|
// If we can't find it at all, we can't conclude anything, so just skip it.
|
|
86
93
|
if (!cpuNodeForUrl) continue;
|
|
87
94
|
|
|
88
|
-
// If we found it and it was in our `cpuNodes` set that means it finished before
|
|
95
|
+
// If we found it and it was in our `cpuNodes` set that means it finished before cutoffTimestamp, so it really is render-blocking.
|
|
89
96
|
if (cpuNodes.includes(cpuNodeForUrl)) {
|
|
90
|
-
|
|
97
|
+
renderBlockingCpuNodeIds.add(cpuNodeForUrl.id);
|
|
91
98
|
continue;
|
|
92
99
|
}
|
|
93
100
|
|
|
@@ -99,22 +106,22 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
99
106
|
// The first layout, first paint, and first ParseHTML are almost always necessary for first paint,
|
|
100
107
|
// so we always include those CPU nodes.
|
|
101
108
|
const firstLayout = cpuNodes.find(node => node.didPerformLayout());
|
|
102
|
-
if (firstLayout)
|
|
109
|
+
if (firstLayout) renderBlockingCpuNodeIds.add(firstLayout.id);
|
|
103
110
|
const firstPaint = cpuNodes.find(node => node.childEvents.some(e => e.name === 'Paint'));
|
|
104
|
-
if (firstPaint)
|
|
111
|
+
if (firstPaint) renderBlockingCpuNodeIds.add(firstPaint.id);
|
|
105
112
|
const firstParse = cpuNodes.find(node => node.childEvents.some(e => e.name === 'ParseHTML'));
|
|
106
|
-
if (firstParse)
|
|
113
|
+
if (firstParse) renderBlockingCpuNodeIds.add(firstParse.id);
|
|
107
114
|
|
|
108
115
|
// If a CPU filter was passed in, we also want to include those extra nodes.
|
|
109
|
-
if (
|
|
116
|
+
if (additionalCpuNodesToTreatAsRenderBlocking) {
|
|
110
117
|
cpuNodes
|
|
111
|
-
.filter(
|
|
112
|
-
.forEach(node =>
|
|
118
|
+
.filter(additionalCpuNodesToTreatAsRenderBlocking)
|
|
119
|
+
.forEach(node => renderBlockingCpuNodeIds.add(node.id));
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
return {
|
|
116
123
|
definitelyNotRenderBlockingScriptUrls,
|
|
117
|
-
|
|
124
|
+
renderBlockingCpuNodeIds,
|
|
118
125
|
};
|
|
119
126
|
}
|
|
120
127
|
|
|
@@ -122,35 +129,25 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
122
129
|
* This function computes the graph required for the first paint of interest.
|
|
123
130
|
*
|
|
124
131
|
* @param {Node} dependencyGraph
|
|
125
|
-
* @param {
|
|
126
|
-
* paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
127
|
-
* @param {function(NetworkNode):boolean} blockingResourcesFilter The function that determines which resources
|
|
128
|
-
* should be considered *possibly* render-blocking.
|
|
129
|
-
* @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
|
|
130
|
-
* should also be included in our blocking node IDs set.
|
|
132
|
+
* @param {FirstPaintBasedGraphOpts} opts
|
|
131
133
|
* @return {Node}
|
|
132
134
|
*/
|
|
133
135
|
static getFirstPaintBasedGraph(
|
|
134
136
|
dependencyGraph,
|
|
135
|
-
|
|
136
|
-
blockingResourcesFilter,
|
|
137
|
-
extraBlockingCpuNodesToIncludeFilter
|
|
137
|
+
{cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
|
|
138
138
|
) {
|
|
139
|
-
const {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
blockingResourcesFilter,
|
|
146
|
-
extraBlockingCpuNodesToIncludeFilter
|
|
147
|
-
);
|
|
139
|
+
const rbData = this.getRenderBlockingNodeData(dependencyGraph, {
|
|
140
|
+
cutoffTimestamp,
|
|
141
|
+
treatNodeAsRenderBlocking,
|
|
142
|
+
additionalCpuNodesToTreatAsRenderBlocking,
|
|
143
|
+
});
|
|
144
|
+
const {definitelyNotRenderBlockingScriptUrls, renderBlockingCpuNodeIds} = rbData;
|
|
148
145
|
|
|
149
146
|
return dependencyGraph.cloneWithRelationships(node => {
|
|
150
147
|
if (node.type === BaseNode.TYPES.NETWORK) {
|
|
151
|
-
// Exclude all nodes that ended after
|
|
152
|
-
// endTime is negative if request does not finish, make sure startTime isn't after
|
|
153
|
-
const endedAfterPaint = node.endTime >
|
|
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;
|
|
154
151
|
if (endedAfterPaint && !node.isMainDocument()) return false;
|
|
155
152
|
|
|
156
153
|
const url = node.record.url;
|
|
@@ -159,10 +156,11 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
159
156
|
return false;
|
|
160
157
|
}
|
|
161
158
|
|
|
162
|
-
|
|
159
|
+
// Lastly, build up the FCP graph of all nodes we consider render blocking
|
|
160
|
+
return treatNodeAsRenderBlocking(node);
|
|
163
161
|
} else {
|
|
164
162
|
// If it's a CPU node, just check if it was blocking.
|
|
165
|
-
return
|
|
163
|
+
return renderBlockingCpuNodeIds.has(node.id);
|
|
166
164
|
}
|
|
167
165
|
});
|
|
168
166
|
}
|
|
@@ -173,14 +171,14 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
173
171
|
* @return {Node}
|
|
174
172
|
*/
|
|
175
173
|
static getOptimisticGraph(dependencyGraph, processedNavigation) {
|
|
176
|
-
return this.getFirstPaintBasedGraph(
|
|
177
|
-
|
|
178
|
-
processedNavigation.timestamps.firstContentfulPaint,
|
|
174
|
+
return this.getFirstPaintBasedGraph(dependencyGraph, {
|
|
175
|
+
cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
|
|
179
176
|
// In the optimistic graph we exclude resources that appeared to be render blocking but were
|
|
180
177
|
// initiated by a script. While they typically have a very high importance and tend to have a
|
|
181
178
|
// significant impact on the page's content, these resources don't technically block rendering.
|
|
182
|
-
node =>
|
|
183
|
-
|
|
179
|
+
treatNodeAsRenderBlocking: node =>
|
|
180
|
+
node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
|
|
181
|
+
});
|
|
184
182
|
}
|
|
185
183
|
|
|
186
184
|
/**
|
|
@@ -189,11 +187,10 @@ class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
189
187
|
* @return {Node}
|
|
190
188
|
*/
|
|
191
189
|
static getPessimisticGraph(dependencyGraph, processedNavigation) {
|
|
192
|
-
return this.getFirstPaintBasedGraph(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
190
|
+
return this.getFirstPaintBasedGraph(dependencyGraph, {
|
|
191
|
+
cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
|
|
192
|
+
treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
|
|
193
|
+
});
|
|
197
194
|
}
|
|
198
195
|
}
|
|
199
196
|
|
|
@@ -33,14 +33,13 @@ class LanternFirstMeaningfulPaint extends LanternMetric {
|
|
|
33
33
|
if (!fmp) {
|
|
34
34
|
throw new LighthouseError(LighthouseError.errors.NO_FMP);
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
dependencyGraph,
|
|
39
|
-
fmp,
|
|
36
|
+
return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
37
|
+
cutoffTimestamp: fmp,
|
|
40
38
|
// See LanternFirstContentfulPaint's getOptimisticGraph implementation for a longer description
|
|
41
39
|
// of why we exclude script initiated resources here.
|
|
42
|
-
node =>
|
|
43
|
-
|
|
40
|
+
treatNodeAsRenderBlocking: node =>
|
|
41
|
+
node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
|
|
42
|
+
});
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
/**
|
|
@@ -54,13 +53,12 @@ class LanternFirstMeaningfulPaint extends LanternMetric {
|
|
|
54
53
|
throw new LighthouseError(LighthouseError.errors.NO_FMP);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
node => node.hasRenderBlockingPriority(),
|
|
56
|
+
return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
57
|
+
cutoffTimestamp: fmp,
|
|
58
|
+
treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
|
|
61
59
|
// For pessimistic FMP we'll include *all* layout nodes
|
|
62
|
-
node => node.didPerformLayout()
|
|
63
|
-
);
|
|
60
|
+
additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
|
|
61
|
+
});
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
/**
|
|
@@ -32,7 +32,6 @@ class LanternLargestContentfulPaint extends LanternMetric {
|
|
|
32
32
|
*/
|
|
33
33
|
static isNotLowPriorityImageNode(node) {
|
|
34
34
|
if (node.type !== 'network') return true;
|
|
35
|
-
|
|
36
35
|
const isImage = node.record.resourceType === 'Image';
|
|
37
36
|
const isLowPriority = node.record.priority === 'Low' || node.record.priority === 'VeryLow';
|
|
38
37
|
return !isImage || !isLowPriority;
|
|
@@ -49,11 +48,10 @@ class LanternLargestContentfulPaint extends LanternMetric {
|
|
|
49
48
|
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
51
|
+
return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
52
|
+
cutoffTimestamp: lcp,
|
|
53
|
+
treatNodeAsRenderBlocking: LanternLargestContentfulPaint.isNotLowPriorityImageNode,
|
|
54
|
+
});
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
/**
|
|
@@ -67,13 +65,12 @@ class LanternLargestContentfulPaint extends LanternMetric {
|
|
|
67
65
|
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
_ => true,
|
|
68
|
+
return LanternFirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
69
|
+
cutoffTimestamp: lcp,
|
|
70
|
+
treatNodeAsRenderBlocking: _ => true,
|
|
74
71
|
// For pessimistic LCP we'll include *all* layout nodes
|
|
75
|
-
node => node.didPerformLayout()
|
|
76
|
-
);
|
|
72
|
+
additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
|
|
73
|
+
});
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
/**
|
|
@@ -24,10 +24,10 @@ export type Extras = {
|
|
|
24
24
|
export class LanternMetric {
|
|
25
25
|
/**
|
|
26
26
|
* @param {Node} dependencyGraph
|
|
27
|
-
* @param {function(NetworkNode):boolean=}
|
|
27
|
+
* @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
|
|
28
28
|
* @return {Set<string>}
|
|
29
29
|
*/
|
|
30
|
-
static getScriptUrls(dependencyGraph: Node,
|
|
30
|
+
static getScriptUrls(dependencyGraph: Node, treatNodeAsRenderBlocking?: ((arg0: NetworkNode) => boolean) | undefined): Set<string>;
|
|
31
31
|
/**
|
|
32
32
|
* @return {LH.Gatherer.Simulation.MetricCoefficients}
|
|
33
33
|
*/
|
|
@@ -26,18 +26,19 @@ import {LoadSimulator} from '../load-simulator.js';
|
|
|
26
26
|
class LanternMetric {
|
|
27
27
|
/**
|
|
28
28
|
* @param {Node} dependencyGraph
|
|
29
|
-
* @param {function(NetworkNode):boolean=}
|
|
29
|
+
* @param {function(NetworkNode):boolean=} treatNodeAsRenderBlocking
|
|
30
30
|
* @return {Set<string>}
|
|
31
31
|
*/
|
|
32
|
-
static getScriptUrls(dependencyGraph,
|
|
32
|
+
static getScriptUrls(dependencyGraph, treatNodeAsRenderBlocking) {
|
|
33
33
|
/** @type {Set<string>} */
|
|
34
34
|
const scriptUrls = new Set();
|
|
35
35
|
|
|
36
36
|
dependencyGraph.traverse(node => {
|
|
37
|
-
if (node.type
|
|
37
|
+
if (node.type !== BaseNode.TYPES.NETWORK) return;
|
|
38
38
|
if (node.record.resourceType !== NetworkRequest.TYPES.Script) return;
|
|
39
|
-
if (
|
|
40
|
-
|
|
39
|
+
if (treatNodeAsRenderBlocking?.(node)) {
|
|
40
|
+
scriptUrls.add(node.record.url);
|
|
41
|
+
}
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
return scriptUrls;
|
|
@@ -202,6 +202,7 @@ const defaultConfig = {
|
|
|
202
202
|
'image-size-responsive',
|
|
203
203
|
'preload-fonts',
|
|
204
204
|
'deprecations',
|
|
205
|
+
'third-party-cookies',
|
|
205
206
|
'mainthread-work-breakdown',
|
|
206
207
|
'bootup-time',
|
|
207
208
|
'uses-rel-preload',
|
|
@@ -215,6 +216,7 @@ const defaultConfig = {
|
|
|
215
216
|
'metrics',
|
|
216
217
|
'performance-budget',
|
|
217
218
|
'timing-budget',
|
|
219
|
+
'resource-summary',
|
|
218
220
|
'third-party-summary',
|
|
219
221
|
'third-party-facades',
|
|
220
222
|
'largest-contentful-paint-element',
|
|
@@ -499,6 +501,7 @@ const defaultConfig = {
|
|
|
499
501
|
{id: 'screenshot-thumbnails', weight: 0, group: 'hidden'},
|
|
500
502
|
{id: 'final-screenshot', weight: 0, group: 'hidden'},
|
|
501
503
|
{id: 'script-treemap-data', weight: 0, group: 'hidden'},
|
|
504
|
+
{id: 'resource-summary', weight: 0, group: 'hidden'},
|
|
502
505
|
],
|
|
503
506
|
},
|
|
504
507
|
'accessibility': {
|
|
@@ -607,6 +610,7 @@ const defaultConfig = {
|
|
|
607
610
|
{id: 'no-unload-listeners', weight: 1, group: 'best-practices-general'},
|
|
608
611
|
{id: 'js-libraries', weight: 0, group: 'best-practices-general'},
|
|
609
612
|
{id: 'deprecations', weight: 5, group: 'best-practices-general'},
|
|
613
|
+
{id: 'third-party-cookies', weight: 5, group: 'best-practices-general'},
|
|
610
614
|
{id: 'errors-in-console', weight: 1, group: 'best-practices-general'},
|
|
611
615
|
{id: 'valid-source-maps', weight: 0, group: 'best-practices-general'},
|
|
612
616
|
{id: 'inspector-issues', weight: 1, group: 'best-practices-general'},
|
package/core/config/filters.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
*/
|
|
20
20
|
async function getBaseArtifacts(resolvedConfig, driver, context) {
|
|
21
21
|
const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
|
|
22
|
-
const {userAgent} = await getBrowserVersion(driver.defaultSession);
|
|
22
|
+
const {userAgent, product} = await getBrowserVersion(driver.defaultSession);
|
|
23
23
|
|
|
24
24
|
return {
|
|
25
25
|
// Meta artifacts.
|
|
@@ -32,6 +32,7 @@ async function getBaseArtifacts(resolvedConfig, driver, context) {
|
|
|
32
32
|
HostUserAgent: userAgent,
|
|
33
33
|
HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
|
|
34
34
|
'mobile' : 'desktop',
|
|
35
|
+
HostProduct: product,
|
|
35
36
|
// Contextual artifacts whose collection changes based on gather mode.
|
|
36
37
|
URL: {
|
|
37
38
|
finalDisplayedUrl: '',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export default TraceElements;
|
|
2
2
|
export type TraceElementData = {
|
|
3
3
|
nodeId: number;
|
|
4
|
-
score?: number;
|
|
5
4
|
animations?: {
|
|
6
5
|
name?: string;
|
|
7
6
|
failureReasonsMask?: number;
|
|
@@ -11,20 +10,13 @@ export type TraceElementData = {
|
|
|
11
10
|
};
|
|
12
11
|
declare class TraceElements extends BaseGatherer {
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* This function finds the top (up to 5) elements that contribute to the CLS score of the page.
|
|
20
|
-
* Each layout shift event has a 'score' which is the amount added to the CLS as a result of the given shift(s).
|
|
21
|
-
* We calculate the score per element by taking the 'score' of each layout shift event and
|
|
22
|
-
* distributing it between all the nodes that were shifted, proportianal to the impact region of
|
|
23
|
-
* each shifted element.
|
|
24
|
-
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
25
|
-
* @return {Array<TraceElementData>}
|
|
13
|
+
* This function finds the top (up to 15) elements that contribute to the CLS score of the page.
|
|
14
|
+
*
|
|
15
|
+
* @param {LH.Trace} trace
|
|
16
|
+
* @param {LH.Gatherer.Context} context
|
|
17
|
+
* @return {Promise<Array<TraceElementData>>}
|
|
26
18
|
*/
|
|
27
|
-
static getTopLayoutShiftElements(
|
|
19
|
+
static getTopLayoutShiftElements(trace: LH.Trace, context: LH.Gatherer.Context): Promise<Array<TraceElementData>>;
|
|
28
20
|
/**
|
|
29
21
|
* @param {LH.Trace} trace
|
|
30
22
|
* @param {LH.Gatherer.Context} context
|
|
@@ -67,6 +59,5 @@ declare class TraceElements extends BaseGatherer {
|
|
|
67
59
|
getArtifact(context: LH.Gatherer.Context<'Trace'>): Promise<LH.Artifacts.TraceElement[]>;
|
|
68
60
|
}
|
|
69
61
|
import BaseGatherer from '../base-gatherer.js';
|
|
70
|
-
import { ProcessedTrace } from '../../computed/processed-trace.js';
|
|
71
62
|
import Trace from './trace.js';
|
|
72
63
|
//# sourceMappingURL=trace-elements.d.ts.map
|