lighthouse 10.0.1 → 10.0.2
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/cli/test/smokehouse/config/exclusions.js +2 -0
- package/core/audits/bf-cache.js +6 -2
- package/core/audits/byte-efficiency/polyfill-graph-data.json +51 -56
- package/core/audits/prioritize-lcp-image.d.ts +24 -15
- package/core/audits/prioritize-lcp-image.js +103 -47
- package/core/audits/redirects.d.ts +5 -5
- package/core/audits/redirects.js +16 -18
- package/core/gather/navigation-runner.js +3 -1
- package/core/lib/lighthouse-compatibility.js +12 -4
- package/core/lib/network-recorder.js +5 -2
- package/core/lib/proto-preprocessor.js +2 -0
- package/core/lib/tracehouse/trace-processor.js +12 -8
- package/dist/report/bundle.esm.js +12 -4
- package/dist/report/flow.js +1 -1
- package/dist/report/standalone.js +3 -3
- package/package.json +1 -1
- package/types/artifacts.d.ts +3 -2
|
@@ -25,6 +25,8 @@ const exclusions = {
|
|
|
25
25
|
'metrics-tricky-tti', 'metrics-tricky-tti-late-fcp', 'screenshot',
|
|
26
26
|
// Disabled because of differences that need further investigation
|
|
27
27
|
'byte-efficiency', 'byte-gzip', 'perf-preload',
|
|
28
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=1420418
|
|
29
|
+
'issues-mixed-content',
|
|
28
30
|
],
|
|
29
31
|
};
|
|
30
32
|
|
package/core/audits/bf-cache.js
CHANGED
|
@@ -74,6 +74,8 @@ class BFCache extends Audit {
|
|
|
74
74
|
// TODO: Analyze more than one bf cache failure.
|
|
75
75
|
const {notRestoredReasonsTree} = failures[0];
|
|
76
76
|
|
|
77
|
+
let totalIssues = 0;
|
|
78
|
+
|
|
77
79
|
/** @type {LH.Audit.Details.TableItem[]} */
|
|
78
80
|
const results = [];
|
|
79
81
|
|
|
@@ -81,6 +83,8 @@ class BFCache extends Audit {
|
|
|
81
83
|
const reasonsMap = notRestoredReasonsTree[failureType];
|
|
82
84
|
|
|
83
85
|
for (const [reason, frameUrls] of Object.entries(reasonsMap)) {
|
|
86
|
+
totalIssues += frameUrls.length;
|
|
87
|
+
|
|
84
88
|
results.push({
|
|
85
89
|
reason: NotRestoredReasonDescription[reason]?.name ?? reason,
|
|
86
90
|
failureType: FAILURE_TYPE_TO_STRING[failureType],
|
|
@@ -104,8 +108,8 @@ class BFCache extends Audit {
|
|
|
104
108
|
|
|
105
109
|
const details = Audit.makeTableDetails(headings, results);
|
|
106
110
|
|
|
107
|
-
const displayValue =
|
|
108
|
-
str_(UIStrings.displayValue, {itemCount:
|
|
111
|
+
const displayValue = totalIssues ?
|
|
112
|
+
str_(UIStrings.displayValue, {itemCount: totalIssues}) :
|
|
109
113
|
undefined;
|
|
110
114
|
|
|
111
115
|
return {
|
|
@@ -1,59 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
"moduleSizes": [498,
|
|
2
|
+
"moduleSizes": [498, 265, 277, 263, 453, 219, 216, 546, 339, 1608, 671, 1525, 420, 214, 504, 98, 524, 196, 268, 642, 204, 742, 618, 169, 394, 127, 433, 1473, 779, 239, 144, 182, 254, 77, 508, 124, 1388, 75, 133, 301, 362, 170, 1078, 182, 490, 195, 321, 316, 447, 551, 216, 284, 253, 17, 107, 295, 356, 345, 1939, 1596, 291, 139, 259, 1291, 179, 528, 174, 61, 326, 20, 444, 522, 104, 1945, 120, 1943, 680, 1409, 850, 630, 288, 38, 695, 569, 106, 587, 208, 370, 606, 766, 535, 616, 200, 170, 224, 422, 970, 978, 498, 284, 241, 210, 151, 194, 178, 814, 205, 189, 215, 111, 236, 147, 237, 191, 691, 212, 432, 499, 445, 176, 333, 129, 414, 617, 380, 251, 199, 524, 515, 681, 160, 259, 295, 283, 178, 472, 786, 520, 202, 575, 575, 349, 549, 458, 166, 173, 508, 1522, 743, 414, 431, 393, 899, 137, 270, 131, 472, 457, 205, 778, 801, 133],
|
|
3
3
|
"dependencies": {
|
|
4
|
-
"Array.prototype.fill": [
|
|
5
|
-
"Array.prototype.filter": [
|
|
6
|
-
"Array.prototype.find": [
|
|
7
|
-
"Array.prototype.findIndex": [
|
|
8
|
-
"Array.prototype.forEach": [
|
|
9
|
-
"Array.from": [
|
|
10
|
-
"Array.isArray": [
|
|
11
|
-
"Array.prototype.map": [
|
|
12
|
-
"Array.of": [
|
|
13
|
-
"Array.prototype.
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"Date.
|
|
17
|
-
"Date.prototype.
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"Object.
|
|
25
|
-
"Object.
|
|
26
|
-
"Object.
|
|
27
|
-
"Object.
|
|
28
|
-
"Object.
|
|
29
|
-
"Object.
|
|
30
|
-
"Object.
|
|
31
|
-
"Object.
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"Reflect.
|
|
37
|
-
"Reflect.
|
|
38
|
-
"Reflect.
|
|
39
|
-
"Reflect.
|
|
40
|
-
"Reflect.
|
|
41
|
-
"Reflect.
|
|
42
|
-
"Reflect.
|
|
43
|
-
"Reflect.
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"String.prototype.repeat": [8, 17, 19, 21, 22, 29, 30, 35, 38, 39, 42, 43, 45, 48, 51, 53, 58, 60, 61, 63, 65, 68, 69, 70, 71, 72, 73, 74, 75, 78, 80, 81, 82, 83, 87, 142],
|
|
52
|
-
"Array.prototype.includes": [3, 8, 12, 17, 19, 21, 22, 29, 30, 35, 38, 39, 40, 42, 43, 45, 48, 51, 52, 53, 55, 56, 58, 60, 61, 63, 64, 65, 68, 69, 70, 71, 72, 73, 74, 75, 80, 81, 82, 83, 87, 88, 89, 97],
|
|
53
|
-
"Object.entries": [8, 17, 19, 21, 22, 29, 30, 35, 38, 39, 42, 43, 45, 48, 51, 53, 58, 60, 61, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 80, 81, 82, 83, 87, 114],
|
|
54
|
-
"Object.getOwnPropertyDescriptors": [8, 17, 19, 21, 22, 23, 29, 30, 35, 38, 39, 42, 43, 45, 48, 51, 53, 58, 60, 61, 63, 65, 68, 69, 70, 71, 72, 73, 74, 75, 80, 81, 82, 83, 87, 116],
|
|
55
|
-
"Object.values": [8, 17, 19, 21, 22, 29, 30, 35, 38, 39, 42, 43, 45, 48, 51, 53, 58, 60, 61, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 80, 81, 82, 83, 87, 126]
|
|
4
|
+
"Array.prototype.fill": [4, 7, 10, 20, 22, 24, 25, 28, 35, 36, 53, 54, 56, 57, 59, 65, 72, 73, 74, 75, 76, 78, 80, 81, 85, 86, 87, 91, 93, 100, 101, 102, 103, 113, 115],
|
|
5
|
+
"Array.prototype.filter": [10, 11, 12, 16, 17, 20, 21, 22, 24, 25, 28, 35, 36, 40, 45, 53, 56, 57, 59, 61, 63, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 107, 113, 116],
|
|
6
|
+
"Array.prototype.find": [4, 10, 11, 16, 17, 20, 21, 22, 24, 25, 28, 35, 36, 40, 45, 53, 54, 56, 57, 59, 61, 63, 65, 72, 73, 74, 75, 76, 78, 80, 81, 85, 86, 87, 91, 93, 100, 101, 102, 103, 107, 113, 118],
|
|
7
|
+
"Array.prototype.findIndex": [4, 10, 11, 16, 17, 20, 21, 22, 24, 25, 28, 35, 36, 40, 45, 53, 54, 56, 57, 59, 61, 63, 65, 72, 73, 74, 75, 76, 78, 80, 81, 85, 86, 87, 91, 93, 100, 101, 102, 103, 107, 113, 117],
|
|
8
|
+
"Array.prototype.forEach": [8, 10, 11, 13, 16, 17, 20, 21, 22, 24, 25, 28, 35, 36, 40, 45, 53, 56, 57, 59, 61, 63, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 107, 113, 119],
|
|
9
|
+
"Array.from": [9, 10, 18, 19, 20, 21, 22, 24, 25, 26, 28, 35, 36, 40, 45, 48, 49, 53, 56, 57, 59, 60, 63, 65, 71, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 107, 113, 120],
|
|
10
|
+
"Array.isArray": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 61, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 121],
|
|
11
|
+
"Array.prototype.map": [10, 11, 12, 16, 17, 20, 21, 22, 24, 25, 28, 35, 36, 40, 45, 53, 56, 57, 59, 61, 63, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 107, 113, 122],
|
|
12
|
+
"Array.of": [10, 20, 21, 22, 24, 25, 26, 28, 35, 36, 53, 56, 57, 59, 63, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 107, 113, 123],
|
|
13
|
+
"Array.prototype.some": [10, 11, 13, 16, 17, 20, 21, 22, 24, 25, 28, 35, 36, 40, 45, 53, 56, 57, 59, 61, 63, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 107, 113, 124],
|
|
14
|
+
"Date.now": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 125],
|
|
15
|
+
"Date.prototype.toISOString": [10, 20, 21, 22, 24, 25, 27, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 97, 98, 100, 101, 102, 103, 107, 108, 113, 126],
|
|
16
|
+
"Date.prototype.toJSON": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 127],
|
|
17
|
+
"Date.prototype.toString": [24, 25, 28, 53, 57, 59, 73, 93, 113, 128],
|
|
18
|
+
"Function.prototype.name": [129],
|
|
19
|
+
"Number.isInteger": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 66, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 130],
|
|
20
|
+
"Number.isSafeInteger": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 66, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 131],
|
|
21
|
+
"Object.defineProperties": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 76, 78, 80, 81, 85, 86, 87, 91, 93, 100, 101, 102, 103, 113, 132],
|
|
22
|
+
"Object.defineProperty": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 133],
|
|
23
|
+
"Object.freeze": [6, 10, 14, 20, 22, 24, 25, 26, 28, 35, 36, 38, 53, 56, 57, 58, 59, 65, 72, 73, 74, 78, 79, 80, 81, 83, 85, 87, 91, 93, 100, 101, 102, 103, 113, 135],
|
|
24
|
+
"Object.getPrototypeOf": [10, 20, 22, 23, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 82, 85, 87, 91, 93, 100, 101, 102, 103, 113, 137],
|
|
25
|
+
"Object.isExtensible": [6, 10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 83, 85, 87, 91, 93, 100, 101, 102, 103, 113, 138],
|
|
26
|
+
"Object.isFrozen": [6, 10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 139],
|
|
27
|
+
"Object.isSealed": [6, 10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 140],
|
|
28
|
+
"Object.keys": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 86, 87, 91, 93, 100, 101, 102, 103, 113, 141],
|
|
29
|
+
"Object.preventExtensions": [6, 10, 14, 20, 22, 24, 25, 26, 28, 35, 36, 38, 53, 56, 57, 58, 59, 65, 72, 73, 74, 78, 79, 80, 81, 83, 85, 87, 91, 93, 100, 101, 102, 103, 113, 142],
|
|
30
|
+
"Object.seal": [6, 10, 14, 20, 22, 24, 25, 26, 28, 35, 36, 38, 53, 56, 57, 58, 59, 65, 72, 73, 74, 78, 79, 80, 81, 83, 85, 87, 91, 93, 100, 101, 102, 103, 113, 143],
|
|
31
|
+
"Object.setPrototypeOf": [3, 10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 88, 91, 93, 100, 101, 102, 103, 113, 144],
|
|
32
|
+
"Reflect.apply": [10, 20, 22, 24, 25, 28, 35, 36, 39, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 146],
|
|
33
|
+
"Reflect.construct": [2, 10, 15, 20, 21, 22, 24, 25, 28, 35, 36, 39, 42, 53, 54, 56, 57, 59, 63, 65, 72, 73, 74, 75, 76, 78, 80, 81, 85, 86, 87, 91, 93, 100, 101, 102, 103, 107, 113, 147],
|
|
34
|
+
"Reflect.defineProperty": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 148],
|
|
35
|
+
"Reflect.deleteProperty": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 149],
|
|
36
|
+
"Reflect.get": [10, 20, 22, 23, 24, 25, 28, 35, 36, 53, 56, 57, 59, 64, 65, 72, 73, 74, 78, 80, 81, 82, 85, 87, 91, 93, 100, 101, 102, 103, 113, 152],
|
|
37
|
+
"Reflect.getOwnPropertyDescriptor": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 150],
|
|
38
|
+
"Reflect.getPrototypeOf": [10, 20, 22, 23, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 82, 85, 87, 91, 93, 100, 101, 102, 103, 113, 151],
|
|
39
|
+
"Reflect.has": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 153],
|
|
40
|
+
"Reflect.isExtensible": [6, 10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 83, 85, 87, 91, 93, 100, 101, 102, 103, 113, 154],
|
|
41
|
+
"Reflect.ownKeys": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 155],
|
|
42
|
+
"Reflect.preventExtensions": [10, 20, 22, 24, 25, 28, 35, 36, 38, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 156],
|
|
43
|
+
"Reflect.setPrototypeOf": [3, 10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 88, 91, 93, 100, 101, 102, 103, 113, 157],
|
|
44
|
+
"String.prototype.codePointAt": [10, 20, 21, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 96, 100, 101, 102, 103, 107, 108, 113, 158],
|
|
45
|
+
"String.fromCodePoint": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 159],
|
|
46
|
+
"String.raw": [10, 20, 21, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 107, 108, 113, 160],
|
|
47
|
+
"String.prototype.repeat": [10, 20, 21, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 98, 100, 101, 102, 103, 107, 108, 113, 161],
|
|
48
|
+
"Object.entries": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 86, 87, 89, 91, 93, 100, 101, 102, 103, 113, 134],
|
|
49
|
+
"Object.getOwnPropertyDescriptors": [10, 20, 22, 24, 25, 26, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 87, 91, 93, 100, 101, 102, 103, 113, 136],
|
|
50
|
+
"Object.values": [10, 20, 22, 24, 25, 28, 35, 36, 53, 56, 57, 59, 65, 72, 73, 74, 78, 80, 81, 85, 86, 87, 89, 91, 93, 100, 101, 102, 103, 113, 145]
|
|
56
51
|
},
|
|
57
|
-
"maxSize":
|
|
58
|
-
"baseSize":
|
|
59
|
-
}
|
|
52
|
+
"maxSize": 72786,
|
|
53
|
+
"baseSize": 11897
|
|
54
|
+
}
|
|
@@ -1,48 +1,57 @@
|
|
|
1
1
|
export default PrioritizeLcpImage;
|
|
2
|
+
export type InitiatorType = LH.Crdp.Network.Initiator['type'] | 'redirect' | 'fallbackToMain';
|
|
2
3
|
export type InitiatorPath = Array<{
|
|
3
4
|
url: string;
|
|
4
|
-
initiatorType:
|
|
5
|
+
initiatorType: InitiatorType;
|
|
5
6
|
}>;
|
|
6
7
|
/**
|
|
7
|
-
* @typedef {
|
|
8
|
+
* @typedef {LH.Crdp.Network.Initiator['type']|'redirect'|'fallbackToMain'} InitiatorType
|
|
9
|
+
* @typedef {Array<{url: string, initiatorType: InitiatorType}>} InitiatorPath
|
|
8
10
|
*/
|
|
9
11
|
declare class PrioritizeLcpImage extends Audit {
|
|
10
12
|
/**
|
|
11
13
|
*
|
|
12
14
|
* @param {LH.Artifacts.NetworkRequest} request
|
|
13
15
|
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
14
|
-
* @param {
|
|
16
|
+
* @param {InitiatorPath} initiatorPath
|
|
15
17
|
* @return {boolean}
|
|
16
18
|
*/
|
|
17
|
-
static shouldPreloadRequest(request: LH.Artifacts.NetworkRequest, mainResource: LH.Artifacts.NetworkRequest, initiatorPath:
|
|
19
|
+
static shouldPreloadRequest(request: LH.Artifacts.NetworkRequest, mainResource: LH.Artifacts.NetworkRequest, initiatorPath: InitiatorPath): boolean;
|
|
18
20
|
/**
|
|
19
21
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
20
|
-
* @param {
|
|
21
|
-
* @return {
|
|
22
|
+
* @param {NetworkRequest} lcpRecord
|
|
23
|
+
* @return {LH.Gatherer.Simulation.GraphNetworkNode|undefined}
|
|
22
24
|
*/
|
|
23
|
-
static findLCPNode(graph: LH.Gatherer.Simulation.GraphNode,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
static findLCPNode(graph: LH.Gatherer.Simulation.GraphNode, lcpRecord: NetworkRequest): LH.Gatherer.Simulation.GraphNetworkNode | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Get the initiator path starting with lcpRecord back to mainResource, inclusive.
|
|
28
|
+
* Navigation redirects *to* the mainResource are not included.
|
|
29
|
+
* Path returned will always be at least [lcpRecord, mainResource].
|
|
30
|
+
* @param {NetworkRequest} lcpRecord
|
|
31
|
+
* @param {NetworkRequest} mainResource
|
|
32
|
+
* @return {InitiatorPath}
|
|
33
|
+
*/
|
|
34
|
+
static getLcpInitiatorPath(lcpRecord: NetworkRequest, mainResource: NetworkRequest): InitiatorPath;
|
|
27
35
|
/**
|
|
28
36
|
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
29
37
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
30
|
-
* @param {
|
|
38
|
+
* @param {NetworkRequest|undefined} lcpRecord
|
|
31
39
|
* @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
|
|
32
40
|
*/
|
|
33
|
-
static getLCPNodeToPreload(mainResource: LH.Artifacts.NetworkRequest, graph: LH.Gatherer.Simulation.GraphNode,
|
|
41
|
+
static getLCPNodeToPreload(mainResource: LH.Artifacts.NetworkRequest, graph: LH.Gatherer.Simulation.GraphNode, lcpRecord: NetworkRequest | undefined): {
|
|
34
42
|
lcpNodeToPreload?: import("../lib/dependency-graph/network-node.js").NetworkNode | undefined;
|
|
35
43
|
initiatorPath?: InitiatorPath | undefined;
|
|
36
44
|
};
|
|
37
45
|
/**
|
|
38
|
-
* Match the LCP event with the paint event to get the
|
|
46
|
+
* Match the LCP event with the paint event to get the request of the image actually painted.
|
|
39
47
|
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
40
48
|
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
41
49
|
* @param {LH.Trace} trace
|
|
42
50
|
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
43
|
-
* @
|
|
51
|
+
* @param {Array<NetworkRequest>} networkRecords
|
|
52
|
+
* @return {NetworkRequest|undefined}
|
|
44
53
|
*/
|
|
45
|
-
static
|
|
54
|
+
static getLcpRecord(trace: LH.Trace, processedNavigation: LH.Artifacts.ProcessedNavigation, networkRecords: Array<NetworkRequest>): NetworkRequest | undefined;
|
|
46
55
|
/**
|
|
47
56
|
* Computes the estimated effect of preloading the LCP image.
|
|
48
57
|
* @param {LH.Artifacts.TraceElement} lcpElement
|
|
@@ -12,6 +12,7 @@ import {LanternLargestContentfulPaint} from '../computed/metrics/lantern-largest
|
|
|
12
12
|
import {LoadSimulator} from '../computed/load-simulator.js';
|
|
13
13
|
import {ByteEfficiencyAudit} from './byte-efficiency/byte-efficiency-audit.js';
|
|
14
14
|
import {ProcessedNavigation} from '../computed/processed-navigation.js';
|
|
15
|
+
import {NetworkRecords} from '../computed/network-records.js';
|
|
15
16
|
|
|
16
17
|
const UIStrings = {
|
|
17
18
|
/** Title of a lighthouse audit that tells a user to preload an image in order to improve their LCP time. */
|
|
@@ -24,7 +25,8 @@ const UIStrings = {
|
|
|
24
25
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
* @typedef {
|
|
28
|
+
* @typedef {LH.Crdp.Network.Initiator['type']|'redirect'|'fallbackToMain'} InitiatorType
|
|
29
|
+
* @typedef {Array<{url: string, initiatorType: InitiatorType}>} InitiatorPath
|
|
28
30
|
*/
|
|
29
31
|
|
|
30
32
|
class PrioritizeLcpImage extends Audit {
|
|
@@ -46,64 +48,92 @@ class PrioritizeLcpImage extends Audit {
|
|
|
46
48
|
*
|
|
47
49
|
* @param {LH.Artifacts.NetworkRequest} request
|
|
48
50
|
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
49
|
-
* @param {
|
|
51
|
+
* @param {InitiatorPath} initiatorPath
|
|
50
52
|
* @return {boolean}
|
|
51
53
|
*/
|
|
52
54
|
static shouldPreloadRequest(request, mainResource, initiatorPath) {
|
|
53
|
-
const mainResourceDepth = mainResource.redirects ? mainResource.redirects.length : 0;
|
|
54
|
-
|
|
55
55
|
// If it's already preloaded, no need to recommend it.
|
|
56
56
|
if (request.isLinkPreload) return false;
|
|
57
57
|
// It's not a request loaded over the network, don't recommend it.
|
|
58
58
|
if (NetworkRequest.isNonNetworkRequest(request)) return false;
|
|
59
|
-
// It's already discoverable from the main document, don't recommend it.
|
|
60
|
-
if (initiatorPath.length <=
|
|
59
|
+
// It's already discoverable from the main document (a path of [lcpRecord, mainResource]), don't recommend it.
|
|
60
|
+
if (initiatorPath.length <= 2) return false;
|
|
61
61
|
// Finally, return whether or not it belongs to the main frame
|
|
62
62
|
return request.frameId === mainResource.frameId;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
67
|
-
* @param {
|
|
68
|
-
* @return {
|
|
67
|
+
* @param {NetworkRequest} lcpRecord
|
|
68
|
+
* @return {LH.Gatherer.Simulation.GraphNetworkNode|undefined}
|
|
69
69
|
*/
|
|
70
|
-
static findLCPNode(graph,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (node.record.url === imageUrl) {
|
|
76
|
-
lcpNode = node;
|
|
77
|
-
path =
|
|
78
|
-
traversalPath.slice(1).filter(initiator => initiator.type === 'network');
|
|
70
|
+
static findLCPNode(graph, lcpRecord) {
|
|
71
|
+
for (const {node} of graph.traverseGenerator()) {
|
|
72
|
+
if (node.type !== 'network') continue;
|
|
73
|
+
if (node.record.requestId === lcpRecord.requestId) {
|
|
74
|
+
return node;
|
|
79
75
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the initiator path starting with lcpRecord back to mainResource, inclusive.
|
|
81
|
+
* Navigation redirects *to* the mainResource are not included.
|
|
82
|
+
* Path returned will always be at least [lcpRecord, mainResource].
|
|
83
|
+
* @param {NetworkRequest} lcpRecord
|
|
84
|
+
* @param {NetworkRequest} mainResource
|
|
85
|
+
* @return {InitiatorPath}
|
|
86
|
+
*/
|
|
87
|
+
static getLcpInitiatorPath(lcpRecord, mainResource) {
|
|
88
|
+
/** @type {InitiatorPath} */
|
|
89
|
+
const initiatorPath = [];
|
|
90
|
+
let mainResourceReached = false;
|
|
91
|
+
/** @type {NetworkRequest|undefined} */
|
|
92
|
+
let request = lcpRecord;
|
|
93
|
+
|
|
94
|
+
while (request) {
|
|
95
|
+
mainResourceReached ||= request.requestId === mainResource.requestId;
|
|
96
|
+
|
|
97
|
+
/** @type {InitiatorType} */
|
|
98
|
+
let initiatorType = request.initiator?.type ?? 'other';
|
|
99
|
+
// Initiator type usually comes from redirect, but 'redirect' is used for more informative debugData.
|
|
100
|
+
if (request.initiatorRequest && request.initiatorRequest === request.redirectSource) {
|
|
101
|
+
initiatorType = 'redirect';
|
|
102
|
+
}
|
|
103
|
+
// Sometimes the initiator chain is broken and the best that can be done is stitch
|
|
104
|
+
// back to the main resource. Note this in the initiatorType.
|
|
105
|
+
if (!request.initiatorRequest && !mainResourceReached) {
|
|
106
|
+
initiatorType = 'fallbackToMain';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
initiatorPath.push({url: request.url, initiatorType});
|
|
110
|
+
|
|
111
|
+
// Can't preload before the main resource, so break off initiator path there.
|
|
112
|
+
if (mainResourceReached) break;
|
|
113
|
+
|
|
114
|
+
// Continue up chain, falling back to mainResource if chain is broken.
|
|
115
|
+
request = request.initiatorRequest || mainResource;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return initiatorPath;
|
|
85
119
|
}
|
|
86
120
|
|
|
87
121
|
/**
|
|
88
122
|
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
89
123
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
90
|
-
* @param {
|
|
124
|
+
* @param {NetworkRequest|undefined} lcpRecord
|
|
91
125
|
* @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
|
|
92
126
|
*/
|
|
93
|
-
static getLCPNodeToPreload(mainResource, graph,
|
|
94
|
-
if (!
|
|
95
|
-
const
|
|
96
|
-
|
|
127
|
+
static getLCPNodeToPreload(mainResource, graph, lcpRecord) {
|
|
128
|
+
if (!lcpRecord) return {};
|
|
129
|
+
const lcpNode = PrioritizeLcpImage.findLCPNode(graph, lcpRecord);
|
|
130
|
+
const initiatorPath = PrioritizeLcpImage.getLcpInitiatorPath(lcpRecord, mainResource);
|
|
131
|
+
if (!lcpNode) return {initiatorPath};
|
|
97
132
|
|
|
98
133
|
// eslint-disable-next-line max-len
|
|
99
|
-
const shouldPreload = PrioritizeLcpImage.shouldPreloadRequest(
|
|
134
|
+
const shouldPreload = PrioritizeLcpImage.shouldPreloadRequest(lcpRecord, mainResource, initiatorPath);
|
|
100
135
|
const lcpNodeToPreload = shouldPreload ? lcpNode : undefined;
|
|
101
136
|
|
|
102
|
-
const initiatorPath = [
|
|
103
|
-
{url: lcpNode.record.url, initiatorType: lcpNode.initiatorType},
|
|
104
|
-
...path.map(n => ({url: n.record.url, initiatorType: n.initiatorType})),
|
|
105
|
-
];
|
|
106
|
-
|
|
107
137
|
return {
|
|
108
138
|
lcpNodeToPreload,
|
|
109
139
|
initiatorPath,
|
|
@@ -111,14 +141,15 @@ class PrioritizeLcpImage extends Audit {
|
|
|
111
141
|
}
|
|
112
142
|
|
|
113
143
|
/**
|
|
114
|
-
* Match the LCP event with the paint event to get the
|
|
144
|
+
* Match the LCP event with the paint event to get the request of the image actually painted.
|
|
115
145
|
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
116
146
|
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
117
147
|
* @param {LH.Trace} trace
|
|
118
148
|
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
119
|
-
* @
|
|
149
|
+
* @param {Array<NetworkRequest>} networkRecords
|
|
150
|
+
* @return {NetworkRequest|undefined}
|
|
120
151
|
*/
|
|
121
|
-
static
|
|
152
|
+
static getLcpRecord(trace, processedNavigation, networkRecords) {
|
|
122
153
|
// Use main-frame-only LCP to match the metric value.
|
|
123
154
|
const lcpEvent = processedNavigation.largestContentfulPaintEvt;
|
|
124
155
|
if (!lcpEvent) return;
|
|
@@ -131,7 +162,31 @@ class PrioritizeLcpImage extends Audit {
|
|
|
131
162
|
// Get last candidate, in case there was more than one.
|
|
132
163
|
}).sort((a, b) => b.ts - a.ts)[0];
|
|
133
164
|
|
|
134
|
-
|
|
165
|
+
const lcpUrl = lcpImagePaintEvent?.args.data?.imageUrl;
|
|
166
|
+
if (!lcpUrl) return;
|
|
167
|
+
|
|
168
|
+
const candidates = networkRecords.filter(record => {
|
|
169
|
+
return record.url === lcpUrl &&
|
|
170
|
+
record.finished &&
|
|
171
|
+
// Same frame as LCP trace event.
|
|
172
|
+
record.frameId === lcpImagePaintEvent.args.frame &&
|
|
173
|
+
record.networkRequestTime < (processedNavigation.timestamps.largestContentfulPaint || 0);
|
|
174
|
+
}).map(record => {
|
|
175
|
+
// Follow any redirects to find the real image request.
|
|
176
|
+
while (record.redirectDestination) {
|
|
177
|
+
record = record.redirectDestination;
|
|
178
|
+
}
|
|
179
|
+
return record;
|
|
180
|
+
}).filter(record => {
|
|
181
|
+
// Don't select if also loaded by some other means (xhr, etc). `resourceType`
|
|
182
|
+
// isn't set on redirect _sources_, so have to check after following redirects.
|
|
183
|
+
return record.resourceType === 'Image';
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// If there are still multiple candidates, at this point it appears the page
|
|
187
|
+
// simply made multiple requests for the image. The first loaded is the best
|
|
188
|
+
// guess of the request that made the image available for use.
|
|
189
|
+
return candidates.sort((a, b) => a.networkEndTime - b.networkEndTime)[0];
|
|
135
190
|
}
|
|
136
191
|
|
|
137
192
|
/**
|
|
@@ -233,7 +288,8 @@ class PrioritizeLcpImage extends Audit {
|
|
|
233
288
|
const trace = artifacts.traces[PrioritizeLcpImage.DEFAULT_PASS];
|
|
234
289
|
const devtoolsLog = artifacts.devtoolsLogs[PrioritizeLcpImage.DEFAULT_PASS];
|
|
235
290
|
const URL = artifacts.URL;
|
|
236
|
-
const
|
|
291
|
+
const settings = context.settings;
|
|
292
|
+
const metricData = {trace, devtoolsLog, gatherContext, settings, URL};
|
|
237
293
|
const lcpElement = artifacts.TraceElements
|
|
238
294
|
.find(element => element.traceEventType === 'largest-contentful-paint');
|
|
239
295
|
|
|
@@ -241,17 +297,17 @@ class PrioritizeLcpImage extends Audit {
|
|
|
241
297
|
return {score: null, notApplicable: true};
|
|
242
298
|
}
|
|
243
299
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
]);
|
|
300
|
+
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
301
|
+
const processedNavigation = await ProcessedNavigation.request(trace, context);
|
|
302
|
+
const mainResource = await MainResource.request({devtoolsLog, URL}, context);
|
|
303
|
+
const lanternLCP = await LanternLargestContentfulPaint.request(metricData, context);
|
|
304
|
+
const simulator = await LoadSimulator.request({devtoolsLog, settings}, context);
|
|
250
305
|
|
|
251
|
-
const
|
|
306
|
+
const lcpRecord = PrioritizeLcpImage.getLcpRecord(trace, processedNavigation, networkRecords);
|
|
252
307
|
const graph = lanternLCP.pessimisticGraph;
|
|
253
|
-
//
|
|
254
|
-
const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource,
|
|
308
|
+
// Note: if moving to LCPAllFrames, mainResource would need to be the LCP frame's main resource.
|
|
309
|
+
const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource,
|
|
310
|
+
graph, lcpRecord);
|
|
255
311
|
|
|
256
312
|
const {results, wastedMs} =
|
|
257
313
|
PrioritizeLcpImage.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
|
|
@@ -4,19 +4,19 @@ declare class Redirects extends Audit {
|
|
|
4
4
|
* This method generates the document request chain including client-side and server-side redirects.
|
|
5
5
|
*
|
|
6
6
|
* Example:
|
|
7
|
-
* GET /
|
|
7
|
+
* GET /requestedUrl => 302 /firstRedirect
|
|
8
8
|
* GET /firstRedirect => 200 /firstRedirect, window.location = '/secondRedirect'
|
|
9
9
|
* GET /secondRedirect => 302 /thirdRedirect
|
|
10
|
-
* GET /thirdRedirect =>
|
|
10
|
+
* GET /thirdRedirect => 302 /mainDocumentUrl
|
|
11
|
+
* GET /mainDocumentUrl => 200 /mainDocumentUrl
|
|
11
12
|
*
|
|
12
|
-
* Returns network records [/
|
|
13
|
+
* Returns network records [/requestedUrl, /firstRedirect, /secondRedirect, /thirdRedirect, /mainDocumentUrl]
|
|
13
14
|
*
|
|
14
|
-
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
15
15
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
16
16
|
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
17
17
|
* @return {Array<LH.Artifacts.NetworkRequest>}
|
|
18
18
|
*/
|
|
19
|
-
static getDocumentRequestChain(
|
|
19
|
+
static getDocumentRequestChain(networkRecords: Array<LH.Artifacts.NetworkRequest>, processedTrace: LH.Artifacts.ProcessedTrace): Array<LH.Artifacts.NetworkRequest>;
|
|
20
20
|
/**
|
|
21
21
|
* @param {LH.Artifacts} artifacts
|
|
22
22
|
* @param {LH.Audit.Context} context
|