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.
@@ -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: false},
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 {Array<LH.TraceEvent>} mainThreadEvents
24
+ * @param {LH.Artifacts.ProcessedTrace} processedTrace
25
25
  * @return {Array<TraceElementData>}
26
26
  */
27
- static getTopLayoutShiftElements(mainThreadEvents: Array<LH.TraceEvent>): Array<TraceElementData>;
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 {Array<LH.TraceEvent>} mainThreadEvents
84
+ * @param {LH.Artifacts.ProcessedTrace} processedTrace
84
85
  * @return {Array<TraceElementData>}
85
86
  */
86
- static getTopLayoutShiftElements(mainThreadEvents) {
87
+ static getTopLayoutShiftElements(processedTrace) {
87
88
  /** @type {Map<number, number>} */
88
89
  const clsPerNode = new Map();
89
- const shiftEvents = mainThreadEvents
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
- // Ignore events with input, unless it's one of the initial events.
101
- // See comment in computed/metrics/cumulative-layout-shift.js.
102
- if (indexFirstEventWithoutInput !== -1 && index >= indexFirstEventWithoutInput) {
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.impacted_nodes.forEach(node => {
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.score;
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(mainThreadEvents);
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "10.2.0-dev.20230510",
4
+ "version": "10.2.0-dev.20230512",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -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
  },
@@ -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?: Array<{
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;