lighthouse 11.7.0-dev.20240408 → 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/CONTRIBUTING.md +2 -2
- package/cli/test/smokehouse/core-tests.js +2 -0
- package/core/audits/accessibility/duplicate-id-active.d.ts +10 -0
- package/core/audits/accessibility/duplicate-id-active.js +42 -0
- package/core/audits/accessibility/target-size.js +1 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +16 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +38 -1
- package/core/audits/byte-efficiency/offscreen-images.d.ts +11 -0
- package/core/audits/byte-efficiency/offscreen-images.js +15 -0
- package/core/audits/dobetterweb/uses-http2.d.ts +13 -0
- package/core/audits/dobetterweb/uses-http2.js +34 -2
- package/core/audits/layout-shift-elements.d.ts +16 -0
- package/core/audits/layout-shift-elements.js +101 -0
- package/core/audits/no-unload-listeners.d.ts +16 -0
- package/core/audits/no-unload-listeners.js +86 -0
- package/core/audits/preload-fonts.d.ts +5 -1
- package/core/audits/preload-fonts.js +10 -1
- package/core/audits/script-elements-test-audit.d.ts +14 -0
- package/core/audits/script-elements-test-audit.js +29 -0
- package/core/audits/seo/is-crawlable.d.ts +1 -2
- package/core/audits/seo/plugins.d.ts +15 -0
- package/core/audits/seo/plugins.js +150 -0
- package/core/audits/seo/tap-targets.d.ts +50 -0
- package/core/audits/seo/tap-targets.js +352 -0
- package/core/audits/uses-rel-preload.d.ts +5 -1
- package/core/audits/uses-rel-preload.js +11 -1
- package/core/audits/work-during-interaction.d.ts +2 -2
- package/core/audits/work-during-interaction.js +2 -2
- package/core/computed/js-bundles.d.ts +1 -1
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +82 -13
- package/core/computed/metrics/lantern-first-contentful-paint.js +178 -15
- package/core/computed/metrics/lantern-first-meaningful-paint.d.ts +12 -13
- package/core/computed/metrics/lantern-first-meaningful-paint.js +49 -10
- package/core/computed/metrics/lantern-interactive.d.ts +21 -13
- package/core/computed/metrics/lantern-interactive.js +84 -11
- package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +25 -13
- package/core/computed/metrics/lantern-largest-contentful-paint.js +80 -12
- package/core/computed/metrics/lantern-max-potential-fid.d.ts +24 -13
- package/core/computed/metrics/lantern-max-potential-fid.js +64 -11
- package/core/computed/metrics/lantern-metric.d.ts +18 -9
- package/core/computed/metrics/lantern-metric.js +31 -13
- package/core/computed/metrics/lantern-speed-index.d.ts +32 -13
- package/core/computed/metrics/lantern-speed-index.js +116 -12
- package/core/computed/metrics/lantern-total-blocking-time.d.ts +25 -13
- package/core/computed/metrics/lantern-total-blocking-time.js +94 -10
- package/core/computed/metrics/metric.js +3 -1
- package/core/computed/module-duplication.d.ts +1 -1
- package/core/computed/page-dependency-graph.js +1 -2
- package/core/config/default-config.js +74 -46
- package/core/config/experimental-config.js +0 -11
- package/core/config/metrics-to-audits.d.ts +19 -0
- package/core/config/metrics-to-audits.js +62 -0
- package/core/gather/gatherers/accessibility.js +3 -1
- package/core/gather/gatherers/global-listeners.d.ts +27 -0
- package/core/gather/gatherers/global-listeners.js +108 -0
- package/core/gather/gatherers/script-elements.d.ts +21 -0
- package/core/gather/gatherers/script-elements.js +100 -0
- package/core/gather/gatherers/seo/embedded-content.d.ts +10 -0
- package/core/gather/gatherers/seo/embedded-content.js +63 -0
- package/core/gather/gatherers/seo/tap-targets.d.ts +21 -0
- package/core/gather/gatherers/seo/tap-targets.js +389 -0
- package/core/gather/gatherers/trace-elements.d.ts +10 -0
- package/core/gather/gatherers/trace-elements.js +19 -0
- package/core/lib/lantern/page-dependency-graph.d.ts +4 -4
- package/core/lib/lantern/page-dependency-graph.js +5 -5
- package/core/lib/lighthouse-compatibility.js +10 -53
- package/dist/report/bundle.esm.js +1 -1
- package/dist/report/flow.js +14 -14
- package/dist/report/standalone.js +1 -1
- package/flow-report/src/summary/category.tsx +12 -10
- package/package.json +1 -1
- package/report/renderer/performance-category-renderer.d.ts +7 -0
- package/report/renderer/performance-category-renderer.js +11 -1
- package/report/renderer/report-utils.js +10 -13
- package/shared/localization/locales/ar-XB.json +64 -1
- package/shared/localization/locales/ar.json +64 -1
- package/shared/localization/locales/bg.json +64 -1
- package/shared/localization/locales/ca.json +64 -1
- package/shared/localization/locales/cs.json +64 -1
- package/shared/localization/locales/da.json +64 -1
- package/shared/localization/locales/de.json +64 -1
- package/shared/localization/locales/el.json +64 -1
- package/shared/localization/locales/en-GB.json +64 -1
- package/shared/localization/locales/en-US.json +65 -2
- package/shared/localization/locales/en-XA.json +64 -1
- package/shared/localization/locales/en-XL.json +65 -2
- package/shared/localization/locales/es-419.json +64 -1
- package/shared/localization/locales/es.json +64 -1
- package/shared/localization/locales/fi.json +64 -1
- package/shared/localization/locales/fil.json +64 -1
- package/shared/localization/locales/fr.json +64 -1
- package/shared/localization/locales/he.json +64 -1
- package/shared/localization/locales/hi.json +64 -1
- package/shared/localization/locales/hr.json +64 -1
- package/shared/localization/locales/hu.json +64 -1
- package/shared/localization/locales/id.json +64 -1
- package/shared/localization/locales/it.json +64 -1
- package/shared/localization/locales/ja.json +64 -1
- package/shared/localization/locales/ko.json +64 -1
- package/shared/localization/locales/lt.json +64 -1
- package/shared/localization/locales/lv.json +64 -1
- package/shared/localization/locales/nl.json +64 -1
- package/shared/localization/locales/no.json +64 -1
- package/shared/localization/locales/pl.json +64 -1
- package/shared/localization/locales/pt-PT.json +64 -1
- package/shared/localization/locales/pt.json +64 -1
- package/shared/localization/locales/ro.json +64 -1
- package/shared/localization/locales/ru.json +64 -1
- package/shared/localization/locales/sk.json +64 -1
- package/shared/localization/locales/sl.json +64 -1
- package/shared/localization/locales/sr-Latn.json +64 -1
- package/shared/localization/locales/sr.json +64 -1
- package/shared/localization/locales/sv.json +64 -1
- package/shared/localization/locales/ta.json +64 -1
- package/shared/localization/locales/te.json +64 -1
- package/shared/localization/locales/th.json +64 -1
- package/shared/localization/locales/tr.json +64 -1
- package/shared/localization/locales/uk.json +64 -1
- package/shared/localization/locales/vi.json +64 -1
- package/shared/localization/locales/zh-HK.json +64 -1
- package/shared/localization/locales/zh-TW.json +64 -1
- package/shared/localization/locales/zh.json +64 -1
- package/tsconfig.json +0 -1
- package/types/artifacts.d.ts +17 -3
- package/types/config.d.ts +1 -0
- package/types/lhr/audit-details.d.ts +4 -9
- package/types/lhr/lhr.d.ts +2 -0
- package/core/lib/lantern/metrics/first-contentful-paint.d.ts +0 -79
- package/core/lib/lantern/metrics/first-contentful-paint.js +0 -200
- package/core/lib/lantern/metrics/first-meaningful-paint.d.ts +0 -6
- package/core/lib/lantern/metrics/first-meaningful-paint.js +0 -66
- package/core/lib/lantern/metrics/interactive.d.ts +0 -26
- package/core/lib/lantern/metrics/interactive.js +0 -112
- package/core/lib/lantern/metrics/largest-contentful-paint.d.ts +0 -19
- package/core/lib/lantern/metrics/largest-contentful-paint.js +0 -108
- package/core/lib/lantern/metrics/max-potential-fid.d.ts +0 -30
- package/core/lib/lantern/metrics/max-potential-fid.js +0 -92
- package/core/lib/lantern/metrics/speed-index.d.ts +0 -38
- package/core/lib/lantern/metrics/speed-index.js +0 -145
- package/core/lib/lantern/metrics/total-blocking-time.d.ts +0 -31
- package/core/lib/lantern/metrics/total-blocking-time.js +0 -128
package/CONTRIBUTING.md
CHANGED
|
@@ -79,8 +79,8 @@ A PR adding or changing a gatherer almost always needs to include the following:
|
|
|
79
79
|
1. **Golden artifacts**: `sample_v2.json` is generated from a set of artifacts that come from running LH against `dbw_tester.html`. Those artifacts likely need to be updated after gatherer changes with `yarn update:sample-artifacts`, but limit to just the artifact being altered if possible. For example:
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
|
-
# update just the
|
|
83
|
-
yarn update:sample-artifacts
|
|
82
|
+
# update just the ScriptElements artifact
|
|
83
|
+
yarn update:sample-artifacts ScriptElements
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
This command works for updating `yarn update:sample-artifacts DevtoolsLog` or `Trace` as well, but the resulting `sample_v2.json` churn may be extensive and you might be better off editing manually.
|
|
@@ -61,6 +61,7 @@ import screenshot from './test-definitions/screenshot.js';
|
|
|
61
61
|
import seoFailing from './test-definitions/seo-failing.js';
|
|
62
62
|
import seoPassing from './test-definitions/seo-passing.js';
|
|
63
63
|
import seoStatus403 from './test-definitions/seo-status-403.js';
|
|
64
|
+
import seoTapTargets from './test-definitions/seo-tap-targets.js';
|
|
64
65
|
import serviceWorkerReloaded from './test-definitions/service-worker-reloaded.js';
|
|
65
66
|
import shiftAttribution from './test-definitions/shift-attribution.js';
|
|
66
67
|
import sourceMaps from './test-definitions/source-maps.js';
|
|
@@ -125,6 +126,7 @@ const smokeTests = [
|
|
|
125
126
|
seoFailing,
|
|
126
127
|
seoPassing,
|
|
127
128
|
seoStatus403,
|
|
129
|
+
seoTapTargets,
|
|
128
130
|
serviceWorkerReloaded,
|
|
129
131
|
shiftAttribution,
|
|
130
132
|
sourceMaps,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default DuplicateIdActive;
|
|
2
|
+
declare class DuplicateIdActive 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=duplicate-id-active.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2019 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Ensures every `id` attribute value of active, focusable elements is unique.
|
|
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 checks if there are any duplicate id HTML attributes on active, focusable elements. This title is descriptive of the successful state and is shown to users when no user action is required. */
|
|
17
|
+
title: '`[id]` attributes on active, focusable elements are unique',
|
|
18
|
+
/** Title of an accesibility audit that checks if there are any duplicate id HTML attributes on active, focusable elements. 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: '`[id]` attributes on active, focusable elements are not unique',
|
|
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: 'All focusable elements must have a unique `id` to ensure that they\'re visible to assistive technologies. [Learn how to fix duplicate `id`s](https://dequeuniversity.com/rules/axe/4.9/duplicate-id-active).',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
25
|
+
|
|
26
|
+
class DuplicateIdActive extends AxeAudit {
|
|
27
|
+
/**
|
|
28
|
+
* @return {LH.Audit.Meta}
|
|
29
|
+
*/
|
|
30
|
+
static get meta() {
|
|
31
|
+
return {
|
|
32
|
+
id: 'duplicate-id-active',
|
|
33
|
+
title: str_(UIStrings.title),
|
|
34
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
35
|
+
description: str_(UIStrings.description),
|
|
36
|
+
requiredArtifacts: ['Accessibility'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default DuplicateIdActive;
|
|
42
|
+
export {UIStrings};
|
|
@@ -35,6 +35,7 @@ class TargetSize extends AxeAudit {
|
|
|
35
35
|
title: str_(UIStrings.title),
|
|
36
36
|
failureTitle: str_(UIStrings.failureTitle),
|
|
37
37
|
description: str_(UIStrings.description),
|
|
38
|
+
scoreDisplayMode: AxeAudit.SCORING_MODES.INFORMATIVE,
|
|
38
39
|
requiredArtifacts: ['Accessibility'],
|
|
39
40
|
};
|
|
40
41
|
}
|
|
@@ -56,6 +56,22 @@ export class ByteEfficiencyAudit extends Audit {
|
|
|
56
56
|
simulationBeforeChanges: LH.Gatherer.Simulation.Result;
|
|
57
57
|
simulationAfterChanges: LH.Gatherer.Simulation.Result;
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Computes the estimated effect of all the byte savings on the maximum of the following:
|
|
61
|
+
*
|
|
62
|
+
* - end time of the last long task in the provided graph
|
|
63
|
+
* - (if includeLoad is true or not provided) end time of the last node in the graph
|
|
64
|
+
*
|
|
65
|
+
* @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
|
|
66
|
+
* @param {Node} graph
|
|
67
|
+
* @param {Simulator} simulator
|
|
68
|
+
* @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
|
|
69
|
+
* @return {number}
|
|
70
|
+
*/
|
|
71
|
+
static computeWasteWithTTIGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: Node, simulator: Simulator, options?: {
|
|
72
|
+
includeLoad?: boolean;
|
|
73
|
+
providedWastedBytesByUrl?: Map<string, number>;
|
|
74
|
+
} | undefined): number;
|
|
59
75
|
/**
|
|
60
76
|
* @param {ByteEfficiencyProduct} result
|
|
61
77
|
* @param {Simulator} simulator
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {Audit} from '../audit.js';
|
|
8
|
+
import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
|
|
8
9
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
9
10
|
import {NetworkRecords} from '../../computed/network-records.js';
|
|
10
11
|
import {LoadSimulator} from '../../computed/load-simulator.js';
|
|
12
|
+
import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
|
|
11
13
|
import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
|
|
12
14
|
import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
|
|
13
15
|
import {LCPImageRecord} from '../../computed/lcp-image-record.js';
|
|
@@ -149,6 +151,37 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
149
151
|
};
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Computes the estimated effect of all the byte savings on the maximum of the following:
|
|
156
|
+
*
|
|
157
|
+
* - end time of the last long task in the provided graph
|
|
158
|
+
* - (if includeLoad is true or not provided) end time of the last node in the graph
|
|
159
|
+
*
|
|
160
|
+
* @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
|
|
161
|
+
* @param {Node} graph
|
|
162
|
+
* @param {Simulator} simulator
|
|
163
|
+
* @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
|
|
164
|
+
* @return {number}
|
|
165
|
+
*/
|
|
166
|
+
static computeWasteWithTTIGraph(results, graph, simulator, options) {
|
|
167
|
+
options = Object.assign({includeLoad: true}, options);
|
|
168
|
+
const {savings: savingsOnOverallLoad, simulationBeforeChanges, simulationAfterChanges} =
|
|
169
|
+
this.computeWasteWithGraph(results, graph, simulator, {
|
|
170
|
+
...options,
|
|
171
|
+
label: 'overallLoad',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const savingsOnTTI =
|
|
175
|
+
LanternInteractive.getLastLongTaskEndTime(simulationBeforeChanges.nodeTimings) -
|
|
176
|
+
LanternInteractive.getLastLongTaskEndTime(simulationAfterChanges.nodeTimings);
|
|
177
|
+
|
|
178
|
+
let savings = savingsOnTTI;
|
|
179
|
+
if (options.includeLoad) savings = Math.max(savings, savingsOnOverallLoad);
|
|
180
|
+
|
|
181
|
+
// Round waste to nearest 10ms
|
|
182
|
+
return Math.round(Math.max(savings, 0) / 10) * 10;
|
|
183
|
+
}
|
|
184
|
+
|
|
152
185
|
/**
|
|
153
186
|
* @param {ByteEfficiencyProduct} result
|
|
154
187
|
* @param {Simulator} simulator
|
|
@@ -171,6 +204,7 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
171
204
|
// This is useful information in the LHR and should be preserved.
|
|
172
205
|
let wastedMs;
|
|
173
206
|
if (metricComputationInput.gatherContext.gatherMode === 'navigation') {
|
|
207
|
+
const graph = await PageDependencyGraph.request(metricComputationInput, context);
|
|
174
208
|
const {
|
|
175
209
|
optimisticGraph: optimisticFCPGraph,
|
|
176
210
|
} = await LanternFirstContentfulPaint.request(metricComputationInput, context);
|
|
@@ -178,6 +212,10 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
178
212
|
optimisticGraph: optimisticLCPGraph,
|
|
179
213
|
} = await LanternLargestContentfulPaint.request(metricComputationInput, context);
|
|
180
214
|
|
|
215
|
+
wastedMs = this.computeWasteWithTTIGraph(results, graph, simulator, {
|
|
216
|
+
providedWastedBytesByUrl: result.wastedBytesByUrl,
|
|
217
|
+
});
|
|
218
|
+
|
|
181
219
|
const {savings: fcpSavings} = this.computeWasteWithGraph(
|
|
182
220
|
results,
|
|
183
221
|
optimisticFCPGraph,
|
|
@@ -205,7 +243,6 @@ class ByteEfficiencyAudit extends Audit {
|
|
|
205
243
|
|
|
206
244
|
metricSavings.FCP = fcpSavings;
|
|
207
245
|
metricSavings.LCP = Math.max(lcpGraphSavings, lcpRecordSavings);
|
|
208
|
-
wastedMs = metricSavings.LCP;
|
|
209
246
|
} else {
|
|
210
247
|
wastedMs = simulator.computeWastedMsFromWastedBytes(wastedBytes);
|
|
211
248
|
}
|
|
@@ -47,6 +47,17 @@ declare class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
47
47
|
* @param {number} interactiveTimestamp
|
|
48
48
|
*/
|
|
49
49
|
static filterObservedResults(images: WasteResult[], interactiveTimestamp: number): WasteResult[];
|
|
50
|
+
/**
|
|
51
|
+
* The default byte efficiency audit will report max(TTI, load), since lazy-loading offscreen
|
|
52
|
+
* images won't reduce the overall time and the wasted bytes are really only "wasted" for TTI,
|
|
53
|
+
* override the function to just look at TTI savings.
|
|
54
|
+
*
|
|
55
|
+
* @param {Array<LH.Audit.ByteEfficiencyItem>} results
|
|
56
|
+
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
57
|
+
* @param {LH.Gatherer.Simulation.Simulator} simulator
|
|
58
|
+
* @return {number}
|
|
59
|
+
*/
|
|
60
|
+
static computeWasteWithTTIGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: LH.Gatherer.Simulation.GraphNode, simulator: LH.Gatherer.Simulation.Simulator): number;
|
|
50
61
|
/**
|
|
51
62
|
* @param {LH.Artifacts} artifacts
|
|
52
63
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -156,6 +156,21 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* The default byte efficiency audit will report max(TTI, load), since lazy-loading offscreen
|
|
161
|
+
* images won't reduce the overall time and the wasted bytes are really only "wasted" for TTI,
|
|
162
|
+
* override the function to just look at TTI savings.
|
|
163
|
+
*
|
|
164
|
+
* @param {Array<LH.Audit.ByteEfficiencyItem>} results
|
|
165
|
+
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
166
|
+
* @param {LH.Gatherer.Simulation.Simulator} simulator
|
|
167
|
+
* @return {number}
|
|
168
|
+
*/
|
|
169
|
+
static computeWasteWithTTIGraph(results, graph, simulator) {
|
|
170
|
+
return super.computeWasteWithTTIGraph(results, graph, simulator,
|
|
171
|
+
{includeLoad: false});
|
|
172
|
+
}
|
|
173
|
+
|
|
159
174
|
/**
|
|
160
175
|
* @param {LH.Artifacts} artifacts
|
|
161
176
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -20,6 +20,19 @@ declare class UsesHTTP2Audit extends Audit {
|
|
|
20
20
|
simulationBefore: LH.Gatherer.Simulation.Result;
|
|
21
21
|
simulationAfter: LH.Gatherer.Simulation.Result;
|
|
22
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Computes the estimated effect all results being converted to use http/2, the max of:
|
|
25
|
+
*
|
|
26
|
+
* - end time of the last long task in the provided graph
|
|
27
|
+
* - end time of the last node in the graph
|
|
28
|
+
* @param {Array<{url: string}>} results
|
|
29
|
+
* @param {Node} graph
|
|
30
|
+
* @param {Simulator} simulator
|
|
31
|
+
* @return {number}
|
|
32
|
+
*/
|
|
33
|
+
static computeWasteWithTTIGraph(results: Array<{
|
|
34
|
+
url: string;
|
|
35
|
+
}>, graph: Node, simulator: Simulator): number;
|
|
23
36
|
/**
|
|
24
37
|
* Determines whether a network request is a "static resource" that would benefit from H2 multiplexing.
|
|
25
38
|
* XHRs, tracking pixels, etc generally don't benefit as much because they aren't requested en-masse
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
import {Audit} from '../audit.js';
|
|
16
16
|
import {EntityClassification} from '../../computed/entity-classification.js';
|
|
17
17
|
import UrlUtils from '../../lib/url-utils.js';
|
|
18
|
+
import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
|
|
18
19
|
import {NetworkRequest} from '../../lib/network-request.js';
|
|
19
20
|
import {NetworkRecords} from '../../computed/network-records.js';
|
|
20
21
|
import {LoadSimulator} from '../../computed/load-simulator.js';
|
|
22
|
+
import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
|
|
21
23
|
import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
|
|
22
24
|
import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
|
|
23
25
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
@@ -115,6 +117,32 @@ class UsesHTTP2Audit extends Audit {
|
|
|
115
117
|
};
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Computes the estimated effect all results being converted to use http/2, the max of:
|
|
122
|
+
*
|
|
123
|
+
* - end time of the last long task in the provided graph
|
|
124
|
+
* - end time of the last node in the graph
|
|
125
|
+
* @param {Array<{url: string}>} results
|
|
126
|
+
* @param {Node} graph
|
|
127
|
+
* @param {Simulator} simulator
|
|
128
|
+
* @return {number}
|
|
129
|
+
*/
|
|
130
|
+
static computeWasteWithTTIGraph(results, graph, simulator) {
|
|
131
|
+
const {savings: savingsOnOverallLoad, simulationBefore, simulationAfter} =
|
|
132
|
+
this.computeWasteWithGraph(results, graph, simulator, {
|
|
133
|
+
label: 'tti',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const savingsOnTTI =
|
|
137
|
+
LanternInteractive.getLastLongTaskEndTime(simulationBefore.nodeTimings) -
|
|
138
|
+
LanternInteractive.getLastLongTaskEndTime(simulationAfter.nodeTimings);
|
|
139
|
+
|
|
140
|
+
const savings = Math.max(savingsOnTTI, savingsOnOverallLoad);
|
|
141
|
+
|
|
142
|
+
// Round waste to nearest 10ms
|
|
143
|
+
return Math.round(Math.max(savings, 0) / 10) * 10;
|
|
144
|
+
}
|
|
145
|
+
|
|
118
146
|
/**
|
|
119
147
|
* Determines whether a network request is a "static resource" that would benefit from H2 multiplexing.
|
|
120
148
|
* XHRs, tracking pixels, etc generally don't benefit as much because they aren't requested en-masse
|
|
@@ -207,6 +235,7 @@ class UsesHTTP2Audit extends Audit {
|
|
|
207
235
|
* @return {Promise<LH.Audit.Product>}
|
|
208
236
|
*/
|
|
209
237
|
static async audit(artifacts, context) {
|
|
238
|
+
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
210
239
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
211
240
|
const URL = artifacts.URL;
|
|
212
241
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
@@ -240,6 +269,7 @@ class UsesHTTP2Audit extends Audit {
|
|
|
240
269
|
devtoolsLog,
|
|
241
270
|
settings,
|
|
242
271
|
};
|
|
272
|
+
const pageGraph = await PageDependencyGraph.request({trace, devtoolsLog, URL}, context);
|
|
243
273
|
const simulator = await LoadSimulator.request(simulatorOptions, context);
|
|
244
274
|
const metricComputationInput = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
245
275
|
|
|
@@ -250,6 +280,8 @@ class UsesHTTP2Audit extends Audit {
|
|
|
250
280
|
pessimisticGraph: lcpGraph,
|
|
251
281
|
} = await LanternLargestContentfulPaint.request(metricComputationInput, context);
|
|
252
282
|
|
|
283
|
+
const wastedMsTti = UsesHTTP2Audit.computeWasteWithTTIGraph(
|
|
284
|
+
resources, pageGraph, simulator);
|
|
253
285
|
const wasteFcp =
|
|
254
286
|
UsesHTTP2Audit.computeWasteWithGraph(resources,
|
|
255
287
|
fcpGraph, simulator, {label: 'fcp'});
|
|
@@ -264,11 +296,11 @@ class UsesHTTP2Audit extends Audit {
|
|
|
264
296
|
];
|
|
265
297
|
|
|
266
298
|
const details = Audit.makeOpportunityDetails(headings, resources,
|
|
267
|
-
{overallSavingsMs:
|
|
299
|
+
{overallSavingsMs: wastedMsTti});
|
|
268
300
|
|
|
269
301
|
return {
|
|
270
302
|
displayValue,
|
|
271
|
-
numericValue:
|
|
303
|
+
numericValue: wastedMsTti,
|
|
272
304
|
numericUnit: 'millisecond',
|
|
273
305
|
score: resources.length ? 0 : 1,
|
|
274
306
|
details,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default LayoutShiftElements;
|
|
2
|
+
declare class LayoutShiftElements extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
export namespace UIStrings {
|
|
11
|
+
const title: string;
|
|
12
|
+
const description: string;
|
|
13
|
+
const columnContribution: string;
|
|
14
|
+
}
|
|
15
|
+
import { Audit } from './audit.js';
|
|
16
|
+
//# sourceMappingURL=layout-shift-elements.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2020 Google LLC
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {Audit} from './audit.js';
|
|
7
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
8
|
+
import {CumulativeLayoutShift as CumulativeLayoutShiftComputed} from '../computed/metrics/cumulative-layout-shift.js';
|
|
9
|
+
import CumulativeLayoutShift from './metrics/cumulative-layout-shift.js';
|
|
10
|
+
|
|
11
|
+
const UIStrings = {
|
|
12
|
+
/** Descriptive title of a diagnostic audit that provides the top elements affected by layout shifts. */
|
|
13
|
+
title: 'Avoid large layout shifts',
|
|
14
|
+
/** Description of a diagnostic audit that provides the top elements affected by layout shifts. "windowing" means the metric value is calculated using the subset of events in a small window of time during the run. "normalization" is a good substitute for "windowing". The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
15
|
+
description: 'These DOM elements were most affected by layout shifts. Some layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)',
|
|
16
|
+
/** Label for a column in a data table; entries in this column will be the amount that the corresponding element affected by layout shifts. */
|
|
17
|
+
columnContribution: 'Layout shift impact',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
21
|
+
|
|
22
|
+
class LayoutShiftElements extends Audit {
|
|
23
|
+
/**
|
|
24
|
+
* @return {LH.Audit.Meta}
|
|
25
|
+
*/
|
|
26
|
+
static get meta() {
|
|
27
|
+
return {
|
|
28
|
+
id: 'layout-shift-elements',
|
|
29
|
+
title: str_(UIStrings.title),
|
|
30
|
+
description: str_(UIStrings.description),
|
|
31
|
+
scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
|
|
32
|
+
guidanceLevel: 2,
|
|
33
|
+
requiredArtifacts: ['traces', 'TraceElements'],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {LH.Artifacts} artifacts
|
|
39
|
+
* @param {LH.Audit.Context} context
|
|
40
|
+
* @return {Promise<LH.Audit.Product>}
|
|
41
|
+
*/
|
|
42
|
+
static async audit(artifacts, context) {
|
|
43
|
+
const {cumulativeLayoutShift: clsSavings, impactByNodeId} =
|
|
44
|
+
await CumulativeLayoutShiftComputed.request(artifacts.traces[Audit.DEFAULT_PASS], context);
|
|
45
|
+
|
|
46
|
+
/** @type {Array<{node: LH.Audit.Details.ItemValue, score: number}>} */
|
|
47
|
+
const clsElementData = artifacts.TraceElements
|
|
48
|
+
.filter(element => element.traceEventType === 'layout-shift-element')
|
|
49
|
+
.map(element => ({
|
|
50
|
+
node: Audit.makeNodeItem(element.node),
|
|
51
|
+
score: impactByNodeId.get(element.nodeId) || 0,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
if (clsElementData.length < impactByNodeId.size) {
|
|
55
|
+
const displayedNodeImpact = clsElementData.reduce((sum, {score}) => sum += score, 0);
|
|
56
|
+
|
|
57
|
+
// This is not necessarily the same as CLS due to normalization.
|
|
58
|
+
const totalNodeImpact = Array.from(impactByNodeId.values())
|
|
59
|
+
.reduce((sum, score) => sum + score);
|
|
60
|
+
|
|
61
|
+
clsElementData.push({
|
|
62
|
+
node: {
|
|
63
|
+
type: 'code',
|
|
64
|
+
value: str_(i18n.UIStrings.otherResourceType),
|
|
65
|
+
},
|
|
66
|
+
score: totalNodeImpact - displayedNodeImpact,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
71
|
+
const headings = [
|
|
72
|
+
{key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
|
|
73
|
+
{key: 'score', valueType: 'numeric',
|
|
74
|
+
granularity: 0.001, label: str_(UIStrings.columnContribution)},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const details = Audit.makeTableDetails(headings, clsElementData);
|
|
78
|
+
|
|
79
|
+
let displayValue;
|
|
80
|
+
if (impactByNodeId.size > 0) {
|
|
81
|
+
displayValue = str_(i18n.UIStrings.displayValueElementsFound,
|
|
82
|
+
{nodeCount: impactByNodeId.size});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const passed = clsSavings <= CumulativeLayoutShift.defaultOptions.p10;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
score: passed ? 1 : 0,
|
|
89
|
+
scoreDisplayMode: passed ? Audit.SCORING_MODES.INFORMATIVE : undefined,
|
|
90
|
+
metricSavings: {
|
|
91
|
+
CLS: clsSavings,
|
|
92
|
+
},
|
|
93
|
+
notApplicable: details.items.length === 0,
|
|
94
|
+
displayValue,
|
|
95
|
+
details,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default LayoutShiftElements;
|
|
101
|
+
export {UIStrings};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default NoUnloadListeners;
|
|
2
|
+
declare class NoUnloadListeners extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
export namespace UIStrings {
|
|
11
|
+
const title: string;
|
|
12
|
+
const failureTitle: string;
|
|
13
|
+
const description: string;
|
|
14
|
+
}
|
|
15
|
+
import { Audit } from './audit.js';
|
|
16
|
+
//# sourceMappingURL=no-unload-listeners.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2020 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Audit} from './audit.js';
|
|
8
|
+
import {JSBundles} from '../computed/js-bundles.js';
|
|
9
|
+
import * as i18n from './../lib/i18n/i18n.js';
|
|
10
|
+
|
|
11
|
+
const UIStrings = {
|
|
12
|
+
/** Descriptive title of a Lighthouse audit that checks if a web page has 'unload' event listeners and finds none. */
|
|
13
|
+
title: 'Avoids `unload` event listeners',
|
|
14
|
+
/** Descriptive title of a Lighthouse audit that checks if a web page has 'unload' event listeners and finds that it is using them. */
|
|
15
|
+
failureTitle: 'Registers an `unload` listener',
|
|
16
|
+
/** Description of a Lighthouse audit that tells the user why pages should not use the 'unload' event. This is displayed after a user expands the section to see more. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
17
|
+
description: 'The `unload` event does not fire reliably and listening for it can prevent browser optimizations like the Back-Forward Cache. Use `pagehide` or `visibilitychange` events instead. [Learn more about unload event listeners](https://web.dev/articles/bfcache#never_use_the_unload_event)',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
21
|
+
|
|
22
|
+
class NoUnloadListeners extends Audit {
|
|
23
|
+
/**
|
|
24
|
+
* @return {LH.Audit.Meta}
|
|
25
|
+
*/
|
|
26
|
+
static get meta() {
|
|
27
|
+
return {
|
|
28
|
+
id: 'no-unload-listeners',
|
|
29
|
+
title: str_(UIStrings.title),
|
|
30
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
31
|
+
description: str_(UIStrings.description),
|
|
32
|
+
requiredArtifacts: ['GlobalListeners', 'SourceMaps', 'Scripts'],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {LH.Artifacts} artifacts
|
|
38
|
+
* @param {LH.Audit.Context} context
|
|
39
|
+
* @return {Promise<LH.Audit.Product>}
|
|
40
|
+
*/
|
|
41
|
+
static async audit(artifacts, context) {
|
|
42
|
+
const unloadListeners = artifacts.GlobalListeners.filter(l => l.type === 'unload');
|
|
43
|
+
if (!unloadListeners.length) {
|
|
44
|
+
return {
|
|
45
|
+
score: 1,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const bundles = await JSBundles.request(artifacts, context);
|
|
50
|
+
|
|
51
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
52
|
+
const headings = [
|
|
53
|
+
{key: 'source', valueType: 'source-location', label: str_(i18n.UIStrings.columnSource)},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/** @type {Array<{source: LH.Audit.Details.ItemValue}>} */
|
|
57
|
+
const tableItems = unloadListeners.map(listener => {
|
|
58
|
+
const {lineNumber, columnNumber} = listener;
|
|
59
|
+
const script = artifacts.Scripts.find(s => s.scriptId === listener.scriptId);
|
|
60
|
+
|
|
61
|
+
// If we can't find a url, still show something so the user can manually
|
|
62
|
+
// look for where an `unload` handler is being created.
|
|
63
|
+
if (!script) {
|
|
64
|
+
return {
|
|
65
|
+
source: {
|
|
66
|
+
type: 'url',
|
|
67
|
+
value: `(unknown):${lineNumber}:${columnNumber}`,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const bundle = bundles.find(bundle => bundle.script.scriptId === script.scriptId);
|
|
73
|
+
return {
|
|
74
|
+
source: Audit.makeSourceLocation(script.url, lineNumber, columnNumber, bundle),
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
score: 0,
|
|
80
|
+
details: Audit.makeTableDetails(headings, tableItems),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default NoUnloadListeners;
|
|
86
|
+
export {UIStrings};
|
|
@@ -14,7 +14,11 @@ declare class PreloadFontsAudit extends Audit {
|
|
|
14
14
|
* @param {LH.Audit.Context} context
|
|
15
15
|
* @return {Promise<LH.Audit.Product>}
|
|
16
16
|
*/
|
|
17
|
-
static
|
|
17
|
+
static audit_(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
18
|
+
/**
|
|
19
|
+
* @return {Promise<LH.Audit.Product>}
|
|
20
|
+
*/
|
|
21
|
+
static audit(): Promise<LH.Audit.Product>;
|
|
18
22
|
}
|
|
19
23
|
export namespace UIStrings {
|
|
20
24
|
const title: string;
|
|
@@ -62,7 +62,7 @@ class PreloadFontsAudit extends Audit {
|
|
|
62
62
|
* @param {LH.Audit.Context} context
|
|
63
63
|
* @return {Promise<LH.Audit.Product>}
|
|
64
64
|
*/
|
|
65
|
-
static async
|
|
65
|
+
static async audit_(artifacts, context) {
|
|
66
66
|
const devtoolsLog = artifacts.devtoolsLogs[this.DEFAULT_PASS];
|
|
67
67
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
68
68
|
|
|
@@ -91,6 +91,15 @@ class PreloadFontsAudit extends Audit {
|
|
|
91
91
|
notApplicable: optionalFontURLs.size === 0,
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @return {Promise<LH.Audit.Product>}
|
|
97
|
+
*/
|
|
98
|
+
static async audit() {
|
|
99
|
+
// Preload advice is on hold until https://github.com/GoogleChrome/lighthouse/issues/11960
|
|
100
|
+
// is resolved.
|
|
101
|
+
return {score: 1, notApplicable: true};
|
|
102
|
+
}
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
export default PreloadFontsAudit;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare namespace _default {
|
|
2
|
+
namespace meta {
|
|
3
|
+
const id: string;
|
|
4
|
+
const title: string;
|
|
5
|
+
const failureTitle: string;
|
|
6
|
+
const description: string;
|
|
7
|
+
const requiredArtifacts: string[];
|
|
8
|
+
}
|
|
9
|
+
function audit(): {
|
|
10
|
+
score: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export default _default;
|
|
14
|
+
//# sourceMappingURL=script-elements-test-audit.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview This is a fake audit used exclusively in smoke tests to force inclusion of ScriptElements artifact.
|
|
9
|
+
* It is included here for complex reasons in the way the bundled smoketests work.
|
|
10
|
+
*
|
|
11
|
+
* The smokehouse configs are evaluated first in the node CLI side (which requires an absolute path using LH_ROOT).
|
|
12
|
+
* The smokehouse configs are then *re-evaluated* in the bundled context for execution by Lighthouse (which *cannot* use an absolute path using LH_ROOT).
|
|
13
|
+
*
|
|
14
|
+
* This mismatch in environment demands that the audit path in the config must be context-aware,
|
|
15
|
+
* yet the require-graph for the config is included before even the CLI knows that it will be using
|
|
16
|
+
* a bundled runner. Rather than force a massive smoketest architecture change, we include a harmless,
|
|
17
|
+
* test-only audit in our core list instead.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
meta: {
|
|
22
|
+
id: 'script-elements-test-audit',
|
|
23
|
+
title: 'ScriptElements',
|
|
24
|
+
failureTitle: 'ScriptElements',
|
|
25
|
+
description: 'Audit to force the inclusion of ScriptElements artifact',
|
|
26
|
+
requiredArtifacts: ['ScriptElements'],
|
|
27
|
+
},
|
|
28
|
+
audit: () => ({score: 1}),
|
|
29
|
+
};
|
|
@@ -6,12 +6,11 @@ declare class IsCrawlable extends Audit {
|
|
|
6
6
|
static handleMetaElement(metaElement: LH.Artifacts.MetaElement): {
|
|
7
7
|
source: {
|
|
8
8
|
snippet: string;
|
|
9
|
-
/** @type {Array<string|undefined>} */
|
|
10
9
|
type: "node";
|
|
11
10
|
lhId?: string | undefined;
|
|
12
11
|
path?: string | undefined;
|
|
13
12
|
selector?: string | undefined;
|
|
14
|
-
boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect | undefined;
|
|
13
|
+
boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect | undefined;
|
|
15
14
|
nodeLabel?: string | undefined;
|
|
16
15
|
};
|
|
17
16
|
} | undefined;
|