lighthouse 10.2.0-dev.20230510 → 10.2.0-dev.20230512
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/td-has-header.d.ts +10 -0
- package/core/audits/accessibility/td-has-header.js +45 -0
- package/core/computed/metrics/cumulative-layout-shift.d.ts +1 -0
- package/core/computed/metrics/cumulative-layout-shift.js +2 -1
- package/core/config/default-config.js +2 -0
- package/core/gather/gatherers/accessibility.js +1 -1
- package/core/gather/gatherers/trace-elements.d.ts +3 -2
- package/core/gather/gatherers/trace-elements.js +10 -19
- package/package.json +1 -1
- package/shared/localization/locales/en-US.json +9 -0
- package/shared/localization/locales/en-XL.json +9 -0
- package/types/artifacts.d.ts +7 -5
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default TDHasHeader;
|
|
2
|
+
declare class TDHasHeader extends AxeAudit {
|
|
3
|
+
}
|
|
4
|
+
export namespace UIStrings {
|
|
5
|
+
const title: string;
|
|
6
|
+
const failureTitle: string;
|
|
7
|
+
const description: string;
|
|
8
|
+
}
|
|
9
|
+
import AxeAudit from './axe-audit.js';
|
|
10
|
+
//# sourceMappingURL=td-has-header.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Ensure that large tables have `[header]` attributes.
|
|
9
|
+
* See base class in axe-audit.js for audit() implementation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import AxeAudit from './axe-audit.js';
|
|
13
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
14
|
+
|
|
15
|
+
const UIStrings = {
|
|
16
|
+
/** Title of an accesibility audit that evaluates if all large table elements use the headers HTML attribute. This title is descriptive of the successful state and is shown to users when no user action is required. */
|
|
17
|
+
title: '`<td>` elements in a large `<table>` have one or more table headers.',
|
|
18
|
+
/** Title of an accesibility audit that evaluates if all large table elements use the headers HTML attribute. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
|
|
19
|
+
failureTitle: '`<td>` elements in a large `<table>` do not have table headers.',
|
|
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
|
+
description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
|
|
22
|
+
'that `<td>` elements in a large table (3 or more cells in width and height) have an ' +
|
|
23
|
+
'associated table header may improve the experience for screen reader users. ' +
|
|
24
|
+
'[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header).',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
28
|
+
|
|
29
|
+
class TDHasHeader extends AxeAudit {
|
|
30
|
+
/**
|
|
31
|
+
* @return {LH.Audit.Meta}
|
|
32
|
+
*/
|
|
33
|
+
static get meta() {
|
|
34
|
+
return {
|
|
35
|
+
id: 'td-has-header',
|
|
36
|
+
title: str_(UIStrings.title),
|
|
37
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
38
|
+
description: str_(UIStrings.description),
|
|
39
|
+
requiredArtifacts: ['Accessibility'],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default TDHasHeader;
|
|
45
|
+
export {UIStrings};
|
|
@@ -3,6 +3,7 @@ export type LayoutShiftEvent = {
|
|
|
3
3
|
ts: number;
|
|
4
4
|
isMainFrame: boolean;
|
|
5
5
|
weightedScore: number;
|
|
6
|
+
impactedNodes?: LH.Artifacts.TraceImpactedNode[];
|
|
6
7
|
};
|
|
7
8
|
declare const CumulativeLayoutShiftComputed: typeof CumulativeLayoutShift & {
|
|
8
9
|
request: (dependencies: import("../../index.js").Trace, context: import("../../../types/utility-types.js").default.ImmutableObject<{
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {makeComputedArtifact} from '../computed-artifact.js';
|
|
8
8
|
import {ProcessedTrace} from '../processed-trace.js';
|
|
9
9
|
|
|
10
|
-
/** @typedef {{ts: number, isMainFrame: boolean, weightedScore: number}} LayoutShiftEvent */
|
|
10
|
+
/** @typedef {{ts: number, isMainFrame: boolean, weightedScore: number, impactedNodes?: LH.Artifacts.TraceImpactedNode[]}} LayoutShiftEvent */
|
|
11
11
|
|
|
12
12
|
const RECENT_INPUT_WINDOW = 500;
|
|
13
13
|
|
|
@@ -65,6 +65,7 @@ class CumulativeLayoutShift {
|
|
|
65
65
|
ts: event.ts,
|
|
66
66
|
isMainFrame: event.args.data.is_main_frame,
|
|
67
67
|
weightedScore: event.args.data.weighted_score_delta,
|
|
68
|
+
impactedNodes: event.args.data.impacted_nodes,
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -273,6 +273,7 @@ const defaultConfig = {
|
|
|
273
273
|
'accessibility/meta-viewport',
|
|
274
274
|
'accessibility/object-alt',
|
|
275
275
|
'accessibility/tabindex',
|
|
276
|
+
'accessibility/td-has-header',
|
|
276
277
|
'accessibility/td-headers-attr',
|
|
277
278
|
'accessibility/th-has-data-cells',
|
|
278
279
|
'accessibility/valid-lang',
|
|
@@ -537,6 +538,7 @@ const defaultConfig = {
|
|
|
537
538
|
{id: 'meta-viewport', weight: 10, group: 'a11y-best-practices'},
|
|
538
539
|
{id: 'object-alt', weight: 3, group: 'a11y-names-labels'},
|
|
539
540
|
{id: 'tabindex', weight: 3, group: 'a11y-navigation'},
|
|
541
|
+
{id: 'td-has-header', weight: 10, group: 'a11y-tables-lists'},
|
|
540
542
|
{id: 'td-headers-attr', weight: 3, group: 'a11y-tables-lists'},
|
|
541
543
|
{id: 'th-has-data-cells', weight: 3, group: 'a11y-tables-lists'},
|
|
542
544
|
{id: 'valid-lang', weight: 3, group: 'a11y-language'},
|
|
@@ -45,7 +45,7 @@ async function runA11yChecks() {
|
|
|
45
45
|
'meta-viewport': {enabled: true},
|
|
46
46
|
'duplicate-id': {enabled: false},
|
|
47
47
|
'table-fake-caption': {enabled: false},
|
|
48
|
-
'td-has-header': {enabled:
|
|
48
|
+
'td-has-header': {enabled: true},
|
|
49
49
|
'marquee': {enabled: false},
|
|
50
50
|
'area-alt': {enabled: false},
|
|
51
51
|
'html-xml-lang-mismatch': {enabled: false},
|
|
@@ -21,10 +21,10 @@ declare class TraceElements extends FRGatherer {
|
|
|
21
21
|
* We calculate the score per element by taking the 'score' of each layout shift event and
|
|
22
22
|
* distributing it between all the nodes that were shifted, proportianal to the impact region of
|
|
23
23
|
* each shifted element.
|
|
24
|
-
* @param {
|
|
24
|
+
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
25
25
|
* @return {Array<TraceElementData>}
|
|
26
26
|
*/
|
|
27
|
-
static getTopLayoutShiftElements(
|
|
27
|
+
static getTopLayoutShiftElements(processedTrace: LH.Artifacts.ProcessedTrace): Array<TraceElementData>;
|
|
28
28
|
/**
|
|
29
29
|
* @param {LH.Trace} trace
|
|
30
30
|
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
@@ -80,4 +80,5 @@ declare class TraceElements extends FRGatherer {
|
|
|
80
80
|
}
|
|
81
81
|
import FRGatherer from '../base-gatherer.js';
|
|
82
82
|
import Trace from './trace.js';
|
|
83
|
+
import { ProcessedTrace } from '../../computed/processed-trace.js';
|
|
83
84
|
//# sourceMappingURL=trace-elements.d.ts.map
|
|
@@ -22,6 +22,7 @@ import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
|
22
22
|
import {ProcessedNavigation} from '../../computed/processed-navigation.js';
|
|
23
23
|
import {LighthouseError} from '../../lib/lh-error.js';
|
|
24
24
|
import {Responsiveness} from '../../computed/metrics/responsiveness.js';
|
|
25
|
+
import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
|
|
25
26
|
|
|
26
27
|
/** @typedef {{nodeId: number, score?: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
|
|
27
28
|
|
|
@@ -80,34 +81,24 @@ class TraceElements extends FRGatherer {
|
|
|
80
81
|
* We calculate the score per element by taking the 'score' of each layout shift event and
|
|
81
82
|
* distributing it between all the nodes that were shifted, proportianal to the impact region of
|
|
82
83
|
* each shifted element.
|
|
83
|
-
* @param {
|
|
84
|
+
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
84
85
|
* @return {Array<TraceElementData>}
|
|
85
86
|
*/
|
|
86
|
-
static getTopLayoutShiftElements(
|
|
87
|
+
static getTopLayoutShiftElements(processedTrace) {
|
|
87
88
|
/** @type {Map<number, number>} */
|
|
88
89
|
const clsPerNode = new Map();
|
|
89
|
-
const shiftEvents =
|
|
90
|
-
.filter(e => e.name === 'LayoutShift')
|
|
91
|
-
.map(e => e.args?.data);
|
|
92
|
-
const indexFirstEventWithoutInput =
|
|
93
|
-
shiftEvents.findIndex(event => event && !event.had_recent_input);
|
|
94
|
-
|
|
95
|
-
shiftEvents.forEach((event, index) => {
|
|
96
|
-
if (!event || !event.impacted_nodes || !event.score) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
90
|
+
const shiftEvents = CumulativeLayoutShift.getLayoutShiftEvents(processedTrace);
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (event.had_recent_input) return;
|
|
92
|
+
shiftEvents.forEach((event) => {
|
|
93
|
+
if (!event || !event.impactedNodes) {
|
|
94
|
+
return;
|
|
104
95
|
}
|
|
105
96
|
|
|
106
97
|
let totalAreaOfImpact = 0;
|
|
107
98
|
/** @type {Map<number, number>} */
|
|
108
99
|
const pixelsMovedPerNode = new Map();
|
|
109
100
|
|
|
110
|
-
event.
|
|
101
|
+
event.impactedNodes.forEach(node => {
|
|
111
102
|
if (!node.node_id || !node.old_rect || !node.new_rect) {
|
|
112
103
|
return;
|
|
113
104
|
}
|
|
@@ -124,7 +115,7 @@ class TraceElements extends FRGatherer {
|
|
|
124
115
|
|
|
125
116
|
for (const [nodeId, pixelsMoved] of pixelsMovedPerNode.entries()) {
|
|
126
117
|
let clsContribution = clsPerNode.get(nodeId) || 0;
|
|
127
|
-
clsContribution += (pixelsMoved / totalAreaOfImpact) * event.
|
|
118
|
+
clsContribution += (pixelsMoved / totalAreaOfImpact) * event.weightedScore;
|
|
128
119
|
clsPerNode.set(nodeId, clsContribution);
|
|
129
120
|
}
|
|
130
121
|
});
|
|
@@ -272,7 +263,7 @@ class TraceElements extends FRGatherer {
|
|
|
272
263
|
const {mainThreadEvents} = processedTrace;
|
|
273
264
|
|
|
274
265
|
const lcpNodeData = await TraceElements.getLcpElement(trace, context);
|
|
275
|
-
const clsNodeData = TraceElements.getTopLayoutShiftElements(
|
|
266
|
+
const clsNodeData = TraceElements.getTopLayoutShiftElements(processedTrace);
|
|
276
267
|
const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
|
|
277
268
|
const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
|
|
278
269
|
|
package/package.json
CHANGED
|
@@ -362,6 +362,15 @@
|
|
|
362
362
|
"core/audits/accessibility/tabindex.js | title": {
|
|
363
363
|
"message": "No element has a `[tabindex]` value greater than 0"
|
|
364
364
|
},
|
|
365
|
+
"core/audits/accessibility/td-has-header.js | description": {
|
|
366
|
+
"message": "Screen readers have features to make navigating tables easier. Ensuring that `<td>` elements in a large table (3 or more cells in width and height) have an associated table header may improve the experience for screen reader users. [Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header)."
|
|
367
|
+
},
|
|
368
|
+
"core/audits/accessibility/td-has-header.js | failureTitle": {
|
|
369
|
+
"message": "`<td>` elements in a large `<table>` do not have table headers."
|
|
370
|
+
},
|
|
371
|
+
"core/audits/accessibility/td-has-header.js | title": {
|
|
372
|
+
"message": "`<td>` elements in a large `<table>` have one or more table headers."
|
|
373
|
+
},
|
|
365
374
|
"core/audits/accessibility/td-headers-attr.js | description": {
|
|
366
375
|
"message": "Screen readers have features to make navigating tables easier. Ensuring `<td>` cells using the `[headers]` attribute only refer to other cells in the same table may improve the experience for screen reader users. [Learn more about the `headers` attribute](https://dequeuniversity.com/rules/axe/4.7/td-headers-attr)."
|
|
367
376
|
},
|
|
@@ -362,6 +362,15 @@
|
|
|
362
362
|
"core/audits/accessibility/tabindex.js | title": {
|
|
363
363
|
"message": "N̂ó êĺêḿêńt̂ h́âś â `[tabindex]` v́âĺûé ĝŕêát̂ér̂ t́ĥán̂ 0"
|
|
364
364
|
},
|
|
365
|
+
"core/audits/accessibility/td-has-header.js | description": {
|
|
366
|
+
"message": "Ŝćr̂éêń r̂éâd́êŕŝ h́âv́ê f́êát̂úr̂éŝ t́ô ḿâḱê ńâv́îǵât́îńĝ t́âb́l̂éŝ éâśîér̂. Én̂śûŕîńĝ t́ĥát̂ `<td>` él̂ém̂én̂t́ŝ ín̂ á l̂ár̂ǵê t́âb́l̂é (3 ôŕ m̂ór̂é ĉél̂ĺŝ ín̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂) h́âv́ê án̂ áŝśôćîát̂éd̂ t́âb́l̂é ĥéâd́êŕ m̂áŷ ím̂ṕr̂óv̂é t̂h́ê éx̂ṕêŕîén̂ćê f́ôŕ ŝćr̂éêń r̂éâd́êŕ ûśêŕŝ. [Ĺêár̂ń m̂ór̂é âb́ôút̂ t́âb́l̂é ĥéâd́êŕŝ](https://dequeuniversity.com/rules/axe/4.7/td-has-header)."
|
|
367
|
+
},
|
|
368
|
+
"core/audits/accessibility/td-has-header.js | failureTitle": {
|
|
369
|
+
"message": "`<td>` êĺêḿêńt̂ś îń â ĺâŕĝé `<table>` d̂ó n̂ót̂ h́âv́ê t́âb́l̂é ĥéâd́êŕŝ."
|
|
370
|
+
},
|
|
371
|
+
"core/audits/accessibility/td-has-header.js | title": {
|
|
372
|
+
"message": "`<td>` êĺêḿêńt̂ś îń â ĺâŕĝé `<table>` ĥáv̂é ôńê ór̂ ḿôŕê t́âb́l̂é ĥéâd́êŕŝ."
|
|
373
|
+
},
|
|
365
374
|
"core/audits/accessibility/td-headers-attr.js | description": {
|
|
366
375
|
"message": "Ŝćr̂éêń r̂éâd́êŕŝ h́âv́ê f́êát̂úr̂éŝ t́ô ḿâḱê ńâv́îǵât́îńĝ t́âb́l̂éŝ éâśîér̂. Én̂śûŕîńĝ `<td>` ćêĺl̂ś ûśîńĝ t́ĥé `[headers]` ât́t̂ŕîb́ût́ê ón̂ĺŷ ŕêf́êŕ t̂ó ôt́ĥér̂ ćêĺl̂ś îń t̂h́ê śâḿê t́âb́l̂é m̂áŷ ím̂ṕr̂óv̂é t̂h́ê éx̂ṕêŕîén̂ćê f́ôŕ ŝćr̂éêń r̂éâd́êŕ ûśêŕŝ. [Ĺêár̂ń m̂ór̂é âb́ôút̂ t́ĥé `headers` ât́t̂ŕîb́ût́ê](https://dequeuniversity.com/rules/axe/4.7/td-headers-attr)."
|
|
367
376
|
},
|
package/types/artifacts.d.ts
CHANGED
|
@@ -948,6 +948,12 @@ declare module Artifacts {
|
|
|
948
948
|
// Convenience methods.
|
|
949
949
|
isFirstParty: (url: string) => boolean;
|
|
950
950
|
}
|
|
951
|
+
|
|
952
|
+
interface TraceImpactedNode {
|
|
953
|
+
node_id: number;
|
|
954
|
+
old_rect?: Array<number>;
|
|
955
|
+
new_rect?: Array<number>;
|
|
956
|
+
}
|
|
951
957
|
}
|
|
952
958
|
|
|
953
959
|
export interface Trace {
|
|
@@ -1017,11 +1023,7 @@ export interface TraceEvent {
|
|
|
1017
1023
|
nodeId?: number;
|
|
1018
1024
|
DOMNodeId?: number;
|
|
1019
1025
|
imageUrl?: string;
|
|
1020
|
-
impacted_nodes?:
|
|
1021
|
-
node_id: number,
|
|
1022
|
-
old_rect?: Array<number>,
|
|
1023
|
-
new_rect?: Array<number>,
|
|
1024
|
-
}>;
|
|
1026
|
+
impacted_nodes?: Artifacts.TraceImpactedNode[];
|
|
1025
1027
|
score?: number;
|
|
1026
1028
|
weighted_score_delta?: number;
|
|
1027
1029
|
had_recent_input?: boolean;
|