lighthouse 11.7.0 → 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.
- package/core/audits/accessibility/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- package/core/audits/accessibility/aria-dialog-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- package/core/audits/accessibility/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-text.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-active.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/empty-heading.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
- package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/image-redundant-alt.js +1 -1
- package/core/audits/accessibility/input-button-name.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/landmark-one-main.js +1 -1
- package/core/audits/accessibility/link-in-text-block.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/select-name.js +1 -1
- package/core/audits/accessibility/skip-link.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/table-duplicate-name.js +1 -1
- package/core/audits/accessibility/table-fake-caption.js +1 -1
- package/core/audits/accessibility/target-size.js +1 -1
- package/core/audits/accessibility/td-has-header.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -1
- package/core/audits/layout-shifts.js +1 -1
- package/core/computed/metrics/cumulative-layout-shift.js +2 -2
- package/core/computed/trace-engine-result.js +1 -1
- package/core/gather/gatherers/trace-elements.d.ts +2 -1
- package/core/gather/gatherers/trace-elements.js +40 -10
- package/core/lib/network-request.js +1 -1
- package/core/lib/proto-preprocessor.js +24 -8
- package/core/lib/tracehouse/trace-processor.js +15 -2
- package/package.json +3 -3
- package/shared/localization/locales/en-US.json +61 -61
- package/shared/localization/locales/en-XL.json +61 -61
|
@@ -20,7 +20,7 @@ const UIStrings = {
|
|
|
20
20
|
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
21
21
|
description: 'The summary attribute should describe the table structure, while `<caption>` ' +
|
|
22
22
|
'should have the onscreen title. Accurate table mark-up helps users of screen readers. ' +
|
|
23
|
-
'[Learn more about summary and caption](https://dequeuniversity.com/rules/axe/4.
|
|
23
|
+
'[Learn more about summary and caption](https://dequeuniversity.com/rules/axe/4.9/table-duplicate-name).',
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -23,7 +23,7 @@ const UIStrings = {
|
|
|
23
23
|
description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
|
|
24
24
|
'that tables use the actual caption element instead of cells with the `[colspan]` ' +
|
|
25
25
|
'attribute may improve the experience for screen reader users. ' +
|
|
26
|
-
'[Learn more about captions](https://dequeuniversity.com/rules/axe/4.
|
|
26
|
+
'[Learn more about captions](https://dequeuniversity.com/rules/axe/4.9/table-fake-caption).',
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -20,7 +20,7 @@ const UIStrings = {
|
|
|
20
20
|
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
21
21
|
description: 'Touch targets with sufficient size and spacing help users who may have ' +
|
|
22
22
|
'difficulty targeting small controls to activate the targets. ' +
|
|
23
|
-
'[Learn more about touch targets](https://dequeuniversity.com/rules/axe/4.
|
|
23
|
+
'[Learn more about touch targets](https://dequeuniversity.com/rules/axe/4.9/target-size).',
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -21,7 +21,7 @@ const UIStrings = {
|
|
|
21
21
|
description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
|
|
22
22
|
'that `<td>` elements in a large table (3 or more cells in width and height) have an ' +
|
|
23
23
|
'associated table header may improve the experience for screen reader users. ' +
|
|
24
|
-
'[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.
|
|
24
|
+
'[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.9/td-has-header).',
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -24,7 +24,7 @@ const UIStrings = {
|
|
|
24
24
|
description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
|
|
25
25
|
'`<td>` cells using the `[headers]` attribute only refer to other cells in the same ' +
|
|
26
26
|
'table may improve the experience for screen reader users. ' +
|
|
27
|
-
'[Learn more about the `headers` attribute](https://dequeuniversity.com/rules/axe/4.
|
|
27
|
+
'[Learn more about the `headers` attribute](https://dequeuniversity.com/rules/axe/4.9/td-headers-attr).',
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -23,7 +23,7 @@ const UIStrings = {
|
|
|
23
23
|
description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
|
|
24
24
|
'table headers always refer to some set of cells may improve the experience for screen ' +
|
|
25
25
|
'reader users. ' +
|
|
26
|
-
'[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.
|
|
26
|
+
'[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.9/th-has-data-cells).',
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -20,7 +20,7 @@ const UIStrings = {
|
|
|
20
20
|
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
21
21
|
description: 'Specifying a valid [BCP 47 language](https://www.w3.org/International/questions/qa-choosing-language-tags#question) ' +
|
|
22
22
|
'on elements helps ensure that text is pronounced correctly by a screen reader. ' +
|
|
23
|
-
'[Learn how to use the `lang` attribute](https://dequeuniversity.com/rules/axe/4.
|
|
23
|
+
'[Learn how to use the `lang` attribute](https://dequeuniversity.com/rules/axe/4.9/valid-lang).',
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -21,7 +21,7 @@ const UIStrings = {
|
|
|
21
21
|
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
22
22
|
description: 'When a video provides a caption it is easier for deaf and hearing impaired ' +
|
|
23
23
|
'users to access its information. ' +
|
|
24
|
-
'[Learn more about video captions](https://dequeuniversity.com/rules/axe/4.
|
|
24
|
+
'[Learn more about video captions](https://dequeuniversity.com/rules/axe/4.9/video-caption).',
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -78,7 +78,7 @@ class LayoutShifts extends Audit {
|
|
|
78
78
|
.slice(0, MAX_LAYOUT_SHIFTS);
|
|
79
79
|
for (const event of topLayoutShiftEvents) {
|
|
80
80
|
const biggestImpactNodeId = TraceElements.getBiggestImpactNodeForShiftEvent(
|
|
81
|
-
event.args.data.impacted_nodes || [], impactByNodeId);
|
|
81
|
+
event.args.data.impacted_nodes || [], impactByNodeId, event);
|
|
82
82
|
const biggestImpactElement = traceElements.find(t => t.nodeId === biggestImpactNodeId);
|
|
83
83
|
|
|
84
84
|
// Turn root causes into sub-items.
|
|
@@ -163,10 +163,10 @@ class CumulativeLayoutShift {
|
|
|
163
163
|
await processor.parse(/** @type {import('@paulirish/trace_engine').Types.TraceEvents.TraceEventData[]} */ (
|
|
164
164
|
events
|
|
165
165
|
));
|
|
166
|
-
if (!processor.
|
|
166
|
+
if (!processor.traceParsedData) {
|
|
167
167
|
throw new Error('null trace engine result');
|
|
168
168
|
}
|
|
169
|
-
return processor.
|
|
169
|
+
return processor.traceParsedData.LayoutShifts.sessionMaxScore;
|
|
170
170
|
};
|
|
171
171
|
const cumulativeLayoutShift = await run(allFrameShiftEvents.map(e => e.event));
|
|
172
172
|
const cumulativeLayoutShiftMainFrame = await run(mainFrameShiftEvents.map(e => e.event));
|
|
@@ -25,9 +25,10 @@ declare class TraceElements extends BaseGatherer {
|
|
|
25
25
|
*
|
|
26
26
|
* @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
|
|
27
27
|
* @param {Map<number, number>} impactByNodeId
|
|
28
|
+
* @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
|
|
28
29
|
* @return {number|undefined}
|
|
29
30
|
*/
|
|
30
|
-
static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number
|
|
31
|
+
static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>, event: import('../../lib/trace-engine.js').SaneSyntheticLayoutShift): number | undefined;
|
|
31
32
|
/**
|
|
32
33
|
* This function finds the top (up to 15) layout shifts on the page, and returns
|
|
33
34
|
* the id of the largest impacted node of each shift, along with any related nodes
|
|
@@ -88,19 +88,49 @@ class TraceElements extends BaseGatherer {
|
|
|
88
88
|
*
|
|
89
89
|
* @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
|
|
90
90
|
* @param {Map<number, number>} impactByNodeId
|
|
91
|
+
* @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
|
|
91
92
|
* @return {number|undefined}
|
|
92
93
|
*/
|
|
93
|
-
static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event) {
|
|
95
|
+
try {
|
|
96
|
+
let biggestImpactNodeId;
|
|
97
|
+
let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
|
|
98
|
+
for (const node of impactedNodes) {
|
|
99
|
+
const impactScore = impactByNodeId.get(node.node_id);
|
|
100
|
+
if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
|
|
101
|
+
biggestImpactNodeId = node.node_id;
|
|
102
|
+
biggestImpactNodeScore = impactScore;
|
|
103
|
+
}
|
|
101
104
|
}
|
|
105
|
+
return biggestImpactNodeId;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// See https://github.com/GoogleChrome/lighthouse/issues/15870
|
|
108
|
+
// `impactedNodes` should always be an array here, but it can randomly be something else for
|
|
109
|
+
// currently unknown reasons. This exception handling will help us identify what
|
|
110
|
+
// `impactedNodes` really is and also prevent the error from being fatal.
|
|
111
|
+
|
|
112
|
+
// It's possible `impactedNodes` is not JSON serializable, so let's add more supplemental
|
|
113
|
+
// fields just in case.
|
|
114
|
+
const impactedNodesType = typeof impactedNodes;
|
|
115
|
+
const impactedNodesClassName = impactedNodes?.constructor?.name;
|
|
116
|
+
|
|
117
|
+
let impactedNodesJson;
|
|
118
|
+
let eventJson;
|
|
119
|
+
try {
|
|
120
|
+
impactedNodesJson = JSON.parse(JSON.stringify(impactedNodes));
|
|
121
|
+
eventJson = JSON.parse(JSON.stringify(event));
|
|
122
|
+
} catch {}
|
|
123
|
+
|
|
124
|
+
Sentry.captureException(err, {
|
|
125
|
+
extra: {
|
|
126
|
+
impactedNodes: impactedNodesJson,
|
|
127
|
+
event: eventJson,
|
|
128
|
+
impactedNodesType,
|
|
129
|
+
impactedNodesClassName,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
102
133
|
}
|
|
103
|
-
return biggestImpactNodeId;
|
|
104
134
|
}
|
|
105
135
|
|
|
106
136
|
/**
|
|
@@ -129,7 +159,7 @@ class TraceElements extends BaseGatherer {
|
|
|
129
159
|
const nodeIds = [];
|
|
130
160
|
const impactedNodes = event.args.data.impacted_nodes || [];
|
|
131
161
|
const biggestImpactedNodeId =
|
|
132
|
-
this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId);
|
|
162
|
+
this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event);
|
|
133
163
|
if (biggestImpactedNodeId !== undefined) {
|
|
134
164
|
nodeIds.push(biggestImpactedNodeId);
|
|
135
165
|
}
|
|
@@ -634,7 +634,7 @@ class NetworkRequest {
|
|
|
634
634
|
/^content-encoding$/i,
|
|
635
635
|
/^x-content-encoding-over-network$/i,
|
|
636
636
|
];
|
|
637
|
-
const compressionTypes = ['gzip', 'br', 'deflate'];
|
|
637
|
+
const compressionTypes = ['gzip', 'br', 'deflate', 'zstd'];
|
|
638
638
|
return record.responseHeaders.some(header =>
|
|
639
639
|
patterns.some(p => header.name.match(p)) && compressionTypes.includes(header.value)
|
|
640
640
|
);
|
|
@@ -83,28 +83,44 @@ function processForProto(lhr) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* Execute `cb(obj, key)` on every object property where obj[key] is a string, recursively.
|
|
87
87
|
* @param {any} obj
|
|
88
|
+
* @param {(obj: Record<string, string>, key: string) => void} cb
|
|
88
89
|
*/
|
|
89
|
-
function
|
|
90
|
+
function iterateStrings(obj, cb) {
|
|
90
91
|
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
91
92
|
Object.keys(obj).forEach(key => {
|
|
92
|
-
if (typeof obj[key] === 'string'
|
|
93
|
-
|
|
94
|
-
} else
|
|
95
|
-
|
|
93
|
+
if (typeof obj[key] === 'string') {
|
|
94
|
+
cb(obj, key);
|
|
95
|
+
} else {
|
|
96
|
+
iterateStrings(obj[key], cb);
|
|
96
97
|
}
|
|
97
98
|
});
|
|
98
99
|
} else if (Array.isArray(obj)) {
|
|
99
100
|
obj.forEach(item => {
|
|
100
101
|
if (typeof item === 'object' || Array.isArray(item)) {
|
|
101
|
-
|
|
102
|
+
iterateStrings(item, cb);
|
|
102
103
|
}
|
|
103
104
|
});
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
iterateStrings(reportJson, (obj, key) => {
|
|
109
|
+
const value = obj[key];
|
|
110
|
+
|
|
111
|
+
// Remove empty strings, as they are dropped after round-tripping anyway.
|
|
112
|
+
if (value === '') {
|
|
113
|
+
delete obj[key];
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sanitize lone surrogates.
|
|
118
|
+
// @ts-expect-error node 20
|
|
119
|
+
if (String.prototype.isWellFormed && !value.isWellFormed()) {
|
|
120
|
+
// @ts-expect-error node 20
|
|
121
|
+
obj[key] = value.toWellFormed();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
108
124
|
|
|
109
125
|
return reportJson;
|
|
110
126
|
}
|
|
@@ -403,6 +403,9 @@ class TraceProcessor {
|
|
|
403
403
|
*/
|
|
404
404
|
static getMainThreadTopLevelEvents(trace, startTime = 0, endTime = Infinity) {
|
|
405
405
|
const topLevelEvents = [];
|
|
406
|
+
/** @type {ToplevelEvent|undefined} */
|
|
407
|
+
let prevToplevel = undefined;
|
|
408
|
+
|
|
406
409
|
// note: mainThreadEvents is already sorted by event start
|
|
407
410
|
for (const event of trace.mainThreadEvents) {
|
|
408
411
|
if (!this.isScheduleableTask(event) || !event.dur) continue;
|
|
@@ -411,11 +414,21 @@ class TraceProcessor {
|
|
|
411
414
|
const end = (event.ts + event.dur - trace.timeOriginEvt.ts) / 1000;
|
|
412
415
|
if (start > endTime || end < startTime) continue;
|
|
413
416
|
|
|
414
|
-
|
|
417
|
+
// Temporary fix for a Chrome bug where some RunTask events can be overlapping.
|
|
418
|
+
// We correct that here be ensuring each RunTask ends at least 1 microsecond before the next
|
|
419
|
+
// https://github.com/GoogleChrome/lighthouse/issues/15896
|
|
420
|
+
// https://issues.chromium.org/issues/329678173
|
|
421
|
+
if (prevToplevel && start < prevToplevel.end) {
|
|
422
|
+
prevToplevel.end = start - 0.001;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
prevToplevel = {
|
|
415
426
|
start,
|
|
416
427
|
end,
|
|
417
428
|
duration: event.dur / 1000,
|
|
418
|
-
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
topLevelEvents.push(prevToplevel);
|
|
419
432
|
}
|
|
420
433
|
|
|
421
434
|
return topLevelEvents;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "11.7.
|
|
4
|
+
"version": "11.7.1",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -179,9 +179,9 @@
|
|
|
179
179
|
"webtreemap-cdt": "^3.2.1"
|
|
180
180
|
},
|
|
181
181
|
"dependencies": {
|
|
182
|
-
"@paulirish/trace_engine": "^0.0.
|
|
182
|
+
"@paulirish/trace_engine": "^0.0.19",
|
|
183
183
|
"@sentry/node": "^6.17.4",
|
|
184
|
-
"axe-core": "^4.
|
|
184
|
+
"axe-core": "^4.9.0",
|
|
185
185
|
"chrome-launcher": "^1.1.1",
|
|
186
186
|
"configstore": "^5.0.1",
|
|
187
187
|
"csp_evaluator": "1.1.1",
|