lighthouse 13.4.1 → 13.5.0
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/build-tracker.config.js +1 -1
- package/cli/test/smokehouse/lighthouse-runners/bundle.js +3 -3
- package/cli/test/smokehouse/lighthouse-runners/devtools-mcp.js +2 -1
- 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-conditional-attr.js +1 -1
- package/core/audits/accessibility/aria-deprecated-role.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-prohibited-attr.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/autocomplete-valid.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-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/presentation-role-conflict.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/svg-img-alt.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/agentic/ard-schema.d.ts +19 -0
- package/core/audits/agentic/ard-schema.js +135 -0
- package/core/audits/baseline.d.ts +23 -10
- package/core/audits/deprecations.js +1 -2
- package/core/audits/webmcp-form-coverage.js +3 -1
- package/core/audits/webmcp-registered-tools.d.ts +2 -0
- package/core/audits/webmcp-registered-tools.js +16 -1
- package/core/audits/webmcp-schema-validity.js +1 -1
- package/core/computed/metrics/interactive.js +1 -1
- package/core/computed/metrics/lantern-metric.js +1 -1
- package/core/computed/trace-engine-result.d.ts +3 -3
- package/core/computed/trace-engine-result.js +4 -4
- package/core/computed/unused-javascript-summary.d.ts +0 -1
- package/core/computed/unused-javascript-summary.js +0 -1
- package/core/config/agentic-browsing-config.d.ts +2 -0
- package/core/config/agentic-browsing-config.js +13 -1
- package/core/config/default-config.js +13 -1
- package/core/config/validation.js +6 -0
- package/core/gather/driver/execution-context.js +0 -2
- package/core/gather/driver/navigation.d.ts +1 -0
- package/core/gather/driver/navigation.js +35 -7
- package/core/gather/driver/wait-for-condition.d.ts +5 -7
- package/core/gather/driver/wait-for-condition.js +15 -9
- package/core/gather/fetcher.d.ts +9 -2
- package/core/gather/fetcher.js +30 -4
- package/core/gather/gatherers/accessibility.js +1 -3
- package/core/gather/gatherers/agentic/ard.d.ts +30 -0
- package/core/gather/gatherers/agentic/ard.js +125 -0
- package/core/gather/gatherers/anchor-elements.js +0 -2
- package/core/gather/gatherers/full-page-screenshot.js +0 -4
- package/core/gather/gatherers/iframe-elements.js +0 -2
- package/core/gather/gatherers/image-elements.js +1 -13
- package/core/gather/gatherers/inputs.js +0 -2
- package/core/gather/gatherers/link-elements.js +0 -2
- package/core/gather/gatherers/meta-elements.js +0 -3
- package/core/gather/gatherers/seo/robots-txt.d.ts +1 -0
- package/core/gather/gatherers/seo/robots-txt.js +3 -0
- package/core/gather/gatherers/stacks.d.ts +1 -1
- package/core/gather/gatherers/stacks.js +1 -3
- package/core/gather/gatherers/trace-elements.js +1 -3
- package/core/gather/gatherers/viewport-dimensions.js +0 -2
- package/core/gather/navigation-runner.d.ts +3 -1
- package/core/gather/navigation-runner.js +6 -3
- package/core/gather/session.js +1 -0
- package/core/index.cjs +5 -5
- package/core/index.d.cts +5 -5
- package/core/lib/arbitrary-equality-map.d.ts +2 -2
- package/core/lib/arbitrary-equality-map.js +1 -1
- package/core/lib/baseline/web-features-data.json +52 -28
- package/core/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/deprecation-description.js +1 -1
- package/core/lib/page-functions.d.ts +0 -3
- package/core/lib/page-functions.js +0 -3
- package/core/lib/rect-helpers.js +0 -8
- package/core/runner.js +21 -0
- package/core/user-flow.d.ts +3 -0
- package/core/user-flow.js +12 -0
- package/dist/report/bundle.esm.js +10 -3
- package/dist/report/flow.js +20 -13
- package/dist/report/standalone.js +10 -3
- package/package.json +19 -14
- package/report/assets/styles.css +7 -0
- package/report/renderer/components.js +1 -1
- package/report/renderer/details-renderer.js +7 -5
- package/report/renderer/drop-down-menu.d.ts +2 -2
- package/report/renderer/drop-down-menu.js +1 -1
- package/report/renderer/report-renderer.js +24 -3
- package/report/renderer/report-utils.js +1 -1
- package/report/renderer/snippet-renderer.d.ts +4 -4
- package/report/renderer/snippet-renderer.js +3 -3
- package/report/renderer/text-encoding.js +2 -2
- package/shared/localization/locales/ar-XB.json +111 -72
- package/shared/localization/locales/ar.json +113 -74
- package/shared/localization/locales/bg.json +112 -73
- package/shared/localization/locales/ca.json +112 -73
- package/shared/localization/locales/cs.json +112 -73
- package/shared/localization/locales/da.json +112 -73
- package/shared/localization/locales/de.json +112 -73
- package/shared/localization/locales/el.json +112 -73
- package/shared/localization/locales/en-GB.json +111 -72
- package/shared/localization/locales/en-US.json +102 -66
- package/shared/localization/locales/en-XA.json +111 -72
- package/shared/localization/locales/en-XL.json +102 -66
- package/shared/localization/locales/es-419.json +112 -73
- package/shared/localization/locales/es.json +112 -73
- package/shared/localization/locales/fi.json +112 -73
- package/shared/localization/locales/fil.json +112 -73
- package/shared/localization/locales/fr.json +112 -73
- package/shared/localization/locales/he.json +112 -73
- package/shared/localization/locales/hi.json +112 -73
- package/shared/localization/locales/hr.json +112 -73
- package/shared/localization/locales/hu.json +112 -73
- package/shared/localization/locales/id.json +112 -73
- package/shared/localization/locales/it.json +113 -74
- package/shared/localization/locales/ja.json +112 -73
- package/shared/localization/locales/ko.json +112 -73
- package/shared/localization/locales/lt.json +112 -73
- package/shared/localization/locales/lv.json +112 -73
- package/shared/localization/locales/nl.json +112 -73
- package/shared/localization/locales/no.json +112 -73
- package/shared/localization/locales/pl.json +112 -73
- package/shared/localization/locales/pt-PT.json +112 -73
- package/shared/localization/locales/pt.json +112 -73
- package/shared/localization/locales/ro.json +112 -73
- package/shared/localization/locales/ru.json +112 -73
- package/shared/localization/locales/sk.json +112 -73
- package/shared/localization/locales/sl.json +112 -73
- package/shared/localization/locales/sr-Latn.json +112 -73
- package/shared/localization/locales/sr.json +112 -73
- package/shared/localization/locales/sv.json +124 -85
- package/shared/localization/locales/ta.json +112 -73
- package/shared/localization/locales/te.json +112 -73
- package/shared/localization/locales/th.json +112 -73
- package/shared/localization/locales/tr.json +113 -74
- package/shared/localization/locales/uk.json +112 -73
- package/shared/localization/locales/vi.json +112 -73
- package/shared/localization/locales/zh-HK.json +112 -73
- package/shared/localization/locales/zh-TW.json +113 -74
- package/shared/localization/locales/zh.json +113 -74
- package/shared/util.d.ts +6 -0
- package/shared/util.js +9 -0
- package/third-party/ard/LICENSE +201 -0
- package/third-party/ard/README.md +27 -0
- package/third-party/ard/ard-test.d.ts +2 -0
- package/third-party/ard/ard-test.js +358 -0
- package/third-party/ard/ard.d.ts +34 -0
- package/third-party/ard/ard.js +204 -0
- package/third-party/ard/schema-validator.d.ts +20 -0
- package/third-party/ard/schema-validator.js +1700 -0
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +292 -0
- package/tsconfig.json +1 -0
- package/types/artifacts.d.ts +14 -0
|
@@ -76,7 +76,7 @@ class TraceEngineResult {
|
|
|
76
76
|
*
|
|
77
77
|
* @template {any[]} Args
|
|
78
78
|
* @template {import('../lib/trace-engine.js').DevToolsIcuMessage} Ret
|
|
79
|
-
* @param {ReturnType<i18n.createIcuMessageFn>} str_
|
|
79
|
+
* @param {ReturnType<typeof i18n.createIcuMessageFn>} str_
|
|
80
80
|
* @param {(...args: Args) => Ret} fn
|
|
81
81
|
* @return {(...args: Args) => LH.IcuMessage}
|
|
82
82
|
*/
|
|
@@ -88,7 +88,7 @@ class TraceEngineResult {
|
|
|
88
88
|
* Converts the input parameters given to `i18nString` usages in DevTools to a
|
|
89
89
|
* LH.IcuMessage.
|
|
90
90
|
*
|
|
91
|
-
* @param {ReturnType<i18n.createIcuMessageFn>} str_
|
|
91
|
+
* @param {ReturnType<typeof i18n.createIcuMessageFn>} str_
|
|
92
92
|
* @param {import('../lib/trace-engine.js').DevToolsIcuMessage} traceEngineI18nObject
|
|
93
93
|
* @return {LH.IcuMessage}
|
|
94
94
|
*/
|
|
@@ -121,7 +121,7 @@ class TraceEngineResult {
|
|
|
121
121
|
/**
|
|
122
122
|
* Recursively finds all DevToolsIcuMessage objects and replaces them with LH.IcuMessage.
|
|
123
123
|
*
|
|
124
|
-
* @param {ReturnType<i18n.createIcuMessageFn>} str_
|
|
124
|
+
* @param {ReturnType<typeof i18n.createIcuMessageFn>} str_
|
|
125
125
|
* @param {object} object
|
|
126
126
|
*/
|
|
127
127
|
static localizeObject(str_, object) {
|
|
@@ -133,7 +133,7 @@ class TraceEngineResult {
|
|
|
133
133
|
* @param {Set<object>} seen
|
|
134
134
|
*/
|
|
135
135
|
function recursiveReplaceLocalizableStrings(obj, cb, seen) {
|
|
136
|
-
if (seen.has(
|
|
136
|
+
if (seen.has(obj)) {
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -39,7 +39,6 @@ declare const UnusedJavascriptSummaryComputed: typeof UnusedJavascriptSummary &
|
|
|
39
39
|
* @property {string} scriptId
|
|
40
40
|
* @property {number} wastedBytes
|
|
41
41
|
* @property {number} totalBytes
|
|
42
|
-
* @property {number} wastedBytes
|
|
43
42
|
* @property {number=} wastedPercent
|
|
44
43
|
* @property {Record<string, number>=} sourcesWastedBytes Keyed by file name. Includes (unmapped) key too.
|
|
45
44
|
*/
|
|
@@ -24,7 +24,6 @@ import {makeComputedArtifact} from './computed-artifact.js';
|
|
|
24
24
|
* @property {string} scriptId
|
|
25
25
|
* @property {number} wastedBytes
|
|
26
26
|
* @property {number} totalBytes
|
|
27
|
-
* @property {number} wastedBytes
|
|
28
27
|
* @property {number=} wastedPercent
|
|
29
28
|
* @property {Record<string, number>=} sourcesWastedBytes Keyed by file name. Includes (unmapped) key too.
|
|
30
29
|
*/
|
|
@@ -8,6 +8,8 @@ export namespace UIStrings {
|
|
|
8
8
|
let webmcpGroupDescription: string;
|
|
9
9
|
let agentAccessibilityGroupTitle: string;
|
|
10
10
|
let agentAccessibilityGroupDescription: string;
|
|
11
|
+
let agentDiscoverabilityGroupTitle: string;
|
|
12
|
+
let agentDiscoverabilityGroupDescription: string;
|
|
11
13
|
}
|
|
12
14
|
import * as LH from '../../types/lh.js';
|
|
13
15
|
//# sourceMappingURL=agentic-browsing-config.d.ts.map
|
|
@@ -23,6 +23,11 @@ const UIStrings = {
|
|
|
23
23
|
/** Description of the Agent Accessibility group of audits. */
|
|
24
24
|
agentAccessibilityGroupDescription: 'These audits highlight best practices for improving the ' +
|
|
25
25
|
'accessibility of the website for AI agents.',
|
|
26
|
+
/** Title of the Agent Discoverability group of audits. */
|
|
27
|
+
agentDiscoverabilityGroupTitle: 'Agent Discoverability',
|
|
28
|
+
/** Description of the Agent Discoverability group of audits. */
|
|
29
|
+
agentDiscoverabilityGroupDescription: 'These audits validate that websites expose ' +
|
|
30
|
+
'discoverable resources, documentation, and catalogs for AI agents.',
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -36,11 +41,13 @@ const config = {
|
|
|
36
41
|
'webmcp-form-coverage',
|
|
37
42
|
'webmcp-schema-validity',
|
|
38
43
|
'agentic/llms-txt',
|
|
44
|
+
'agentic/ard-schema',
|
|
39
45
|
],
|
|
40
46
|
artifacts: [
|
|
41
47
|
{id: 'WebMCP', gatherer: 'webmcp'},
|
|
42
48
|
{id: 'WebMcpSchemaIssues', gatherer: 'webmcp-schema'},
|
|
43
49
|
{id: 'LlmsTxt', gatherer: 'agentic/llms-txt'},
|
|
50
|
+
{id: 'AgentResourceDiscovery', gatherer: 'agentic/ard'},
|
|
44
51
|
],
|
|
45
52
|
groups: {
|
|
46
53
|
'webmcp': {
|
|
@@ -51,6 +58,10 @@ const config = {
|
|
|
51
58
|
title: str_(UIStrings.agentAccessibilityGroupTitle),
|
|
52
59
|
description: str_(UIStrings.agentAccessibilityGroupDescription),
|
|
53
60
|
},
|
|
61
|
+
'agent-discoverability': {
|
|
62
|
+
title: str_(UIStrings.agentDiscoverabilityGroupTitle),
|
|
63
|
+
description: str_(UIStrings.agentDiscoverabilityGroupDescription),
|
|
64
|
+
},
|
|
54
65
|
},
|
|
55
66
|
categories: {
|
|
56
67
|
'agentic-browsing': {
|
|
@@ -64,7 +75,8 @@ const config = {
|
|
|
64
75
|
{id: 'webmcp-registered-tools', weight: 1, group: 'webmcp'},
|
|
65
76
|
{id: 'webmcp-schema-validity', weight: 1, group: 'webmcp'},
|
|
66
77
|
{id: 'cumulative-layout-shift', weight: 1, acronym: 'CLS'},
|
|
67
|
-
{id: 'llms-txt', weight: 1, group: 'agent-
|
|
78
|
+
{id: 'llms-txt', weight: 1, group: 'agent-discoverability'},
|
|
79
|
+
{id: 'ard-schema', weight: 1, group: 'agent-discoverability'},
|
|
68
80
|
],
|
|
69
81
|
},
|
|
70
82
|
},
|
|
@@ -115,6 +115,11 @@ const UIStrings = {
|
|
|
115
115
|
/** Description of the Agent Accessibility group of audits. */
|
|
116
116
|
agentAccessibilityGroupDescription: 'These audits highlight best practices for improving the ' +
|
|
117
117
|
'accessibility of the website for AI agents.',
|
|
118
|
+
/** Title of the Agent Discoverability group of audits. */
|
|
119
|
+
agentDiscoverabilityGroupTitle: 'Agent Discoverability',
|
|
120
|
+
/** Description of the Agent Discoverability group of audits. */
|
|
121
|
+
agentDiscoverabilityGroupDescription: 'These audits validate that websites expose discoverable resources, ' +
|
|
122
|
+
'documentation, and catalogs for AI agents.',
|
|
118
123
|
};
|
|
119
124
|
|
|
120
125
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -145,6 +150,7 @@ const defaultConfig = {
|
|
|
145
150
|
{id: 'MetaElements', gatherer: 'meta-elements'},
|
|
146
151
|
{id: 'NetworkUserAgent', gatherer: 'network-user-agent'},
|
|
147
152
|
{id: 'RobotsTxt', gatherer: 'seo/robots-txt'},
|
|
153
|
+
{id: 'AgentResourceDiscovery', gatherer: 'agentic/ard'},
|
|
148
154
|
{id: 'Scripts', gatherer: 'scripts'},
|
|
149
155
|
{id: 'SourceMaps', gatherer: 'source-maps'},
|
|
150
156
|
{id: 'Stacks', gatherer: 'stacks'},
|
|
@@ -302,6 +308,7 @@ const defaultConfig = {
|
|
|
302
308
|
'webmcp-form-coverage',
|
|
303
309
|
'webmcp-schema-validity',
|
|
304
310
|
'agentic/llms-txt',
|
|
311
|
+
'agentic/ard-schema',
|
|
305
312
|
'bf-cache',
|
|
306
313
|
'insights/cache-insight',
|
|
307
314
|
'insights/cls-culprits-insight',
|
|
@@ -397,6 +404,10 @@ const defaultConfig = {
|
|
|
397
404
|
title: str_(UIStrings.agentAccessibilityGroupTitle),
|
|
398
405
|
description: str_(UIStrings.agentAccessibilityGroupDescription),
|
|
399
406
|
},
|
|
407
|
+
'agent-discoverability': {
|
|
408
|
+
title: str_(UIStrings.agentDiscoverabilityGroupTitle),
|
|
409
|
+
description: str_(UIStrings.agentDiscoverabilityGroupDescription),
|
|
410
|
+
},
|
|
400
411
|
// Group for audits that should not be displayed.
|
|
401
412
|
'hidden': {title: ''},
|
|
402
413
|
},
|
|
@@ -643,7 +654,8 @@ const defaultConfig = {
|
|
|
643
654
|
{id: 'webmcp-registered-tools', weight: 1, group: 'webmcp'},
|
|
644
655
|
{id: 'webmcp-schema-validity', weight: 1, group: 'webmcp'},
|
|
645
656
|
{id: 'cumulative-layout-shift', weight: 1, acronym: 'CLS'},
|
|
646
|
-
{id: 'llms-txt', weight: 1, group: 'agent-
|
|
657
|
+
{id: 'llms-txt', weight: 1, group: 'agent-discoverability'},
|
|
658
|
+
{id: 'ard-schema', weight: 1, group: 'agent-discoverability'},
|
|
647
659
|
],
|
|
648
660
|
},
|
|
649
661
|
},
|
|
@@ -8,6 +8,7 @@ import * as LH from '../../types/lh.js';
|
|
|
8
8
|
import {Audit} from '../audits/audit.js';
|
|
9
9
|
import BaseGatherer from '../gather/base-gatherer.js';
|
|
10
10
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
11
|
+
import {Util} from '../../shared/util.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Determines if the artifact dependency direction is valid. The dependency's minimum supported mode
|
|
@@ -37,6 +38,7 @@ function isValidArtifactDependency(dependent, dependency) {
|
|
|
37
38
|
* @param {string} pluginName
|
|
38
39
|
*/
|
|
39
40
|
function assertValidPluginName(config, pluginName) {
|
|
41
|
+
const originalPluginName = pluginName;
|
|
40
42
|
const parts = pluginName.split('/');
|
|
41
43
|
if (parts.length === 2) {
|
|
42
44
|
pluginName = parts[1];
|
|
@@ -48,6 +50,10 @@ function assertValidPluginName(config, pluginName) {
|
|
|
48
50
|
if (config.categories?.[pluginName]) {
|
|
49
51
|
throw new Error(`plugin name '${pluginName}' not allowed because it is the id of a category already found in config`); // eslint-disable-line max-len
|
|
50
52
|
}
|
|
53
|
+
|
|
54
|
+
if (!Util.isPluginCategory(originalPluginName)) {
|
|
55
|
+
throw new Error(`plugin name '${originalPluginName}' has invalid npm package name characters`);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
|
@@ -320,14 +320,12 @@ class ExecutionContext {
|
|
|
320
320
|
*/
|
|
321
321
|
async cacheNativesOnNewDocument() {
|
|
322
322
|
await this.evaluateOnNewDocument(() => {
|
|
323
|
-
/* c8 ignore start */
|
|
324
323
|
window.__nativePromise = window.Promise;
|
|
325
324
|
window.__nativeURL = window.URL;
|
|
326
325
|
window.__nativePerformance = window.performance;
|
|
327
326
|
window.__nativeFetch = window.fetch;
|
|
328
327
|
window.__ElementMatches = window.Element.prototype.matches;
|
|
329
328
|
window.__HTMLElementBoundingClientRect = window.HTMLElement.prototype.getBoundingClientRect;
|
|
330
|
-
/* c8 ignore stop */
|
|
331
329
|
}, {args: []});
|
|
332
330
|
}
|
|
333
331
|
|
|
@@ -39,7 +39,7 @@ const DEFAULT_NETWORK_QUIET_THRESHOLD = 5000;
|
|
|
39
39
|
// Controls how long to wait between longtasks before determining the CPU is idle, off by default
|
|
40
40
|
const DEFAULT_CPU_QUIET_THRESHOLD = 0;
|
|
41
41
|
|
|
42
|
-
/** @typedef {{waitUntil: Array<'fcp'|'load'|'navigated'
|
|
42
|
+
/** @typedef {{waitUntil: Array<'fcp'|'load'|'navigated'>, signal?: AbortSignal} & Partial<LH.Config.Settings>} NavigationOptions */
|
|
43
43
|
|
|
44
44
|
/** @param {NavigationOptions} options */
|
|
45
45
|
function resolveWaitForFullyLoadedOptions(options) {
|
|
@@ -107,23 +107,49 @@ async function gotoURL(driver, requestor, options) {
|
|
|
107
107
|
|
|
108
108
|
/** @type {Array<Promise<{timedOut: boolean}>>} */
|
|
109
109
|
const waitConditionPromises = [];
|
|
110
|
+
/** @type {Array<() => void>} */
|
|
111
|
+
const cleanups = [];
|
|
112
|
+
|
|
113
|
+
/** @type {(event: Event) => void} */
|
|
114
|
+
let onAbort = () => {};
|
|
115
|
+
/** @type {Promise<never>} */
|
|
116
|
+
const abortPromise = new Promise((_, reject) => {
|
|
117
|
+
if (!options.signal) return;
|
|
118
|
+
options.signal.throwIfAborted();
|
|
119
|
+
onAbort = () => reject(new Error('Navigation aborted by signal'));
|
|
120
|
+
options.signal.addEventListener('abort', onAbort, {once: true});
|
|
121
|
+
});
|
|
122
|
+
if (options.signal) cleanups.push(() => options.signal?.removeEventListener('abort', onAbort));
|
|
110
123
|
|
|
111
124
|
if (waitForNavigated) {
|
|
112
|
-
const
|
|
113
|
-
|
|
125
|
+
const {promise, cancel} = waitForFrameNavigated(session);
|
|
126
|
+
cleanups.push(() => cancel());
|
|
127
|
+
waitConditionPromises.push(promise
|
|
128
|
+
.then(() => ({timedOut: false}))
|
|
129
|
+
.catch(() => ({timedOut: false})));
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
if (waitForLoad) {
|
|
117
133
|
const waitOptions = resolveWaitForFullyLoadedOptions(options);
|
|
118
|
-
waitConditionPromises.push(
|
|
134
|
+
waitConditionPromises.push(
|
|
135
|
+
waitForFullyLoaded(session, networkMonitor, waitOptions, options.signal)
|
|
136
|
+
);
|
|
119
137
|
} else if (waitForFcp) {
|
|
120
138
|
throw new Error('Cannot wait for FCP without waiting for page load');
|
|
121
139
|
}
|
|
122
140
|
|
|
123
|
-
const
|
|
141
|
+
const waitConditionsRace = [
|
|
124
142
|
session.onCrashPromise(),
|
|
125
143
|
Promise.all(waitConditionPromises),
|
|
126
|
-
]
|
|
144
|
+
];
|
|
145
|
+
if (options.signal) waitConditionsRace.push(abortPromise);
|
|
146
|
+
|
|
147
|
+
let waitConditions;
|
|
148
|
+
try {
|
|
149
|
+
waitConditions = await Promise.race(waitConditionsRace);
|
|
150
|
+
} finally {
|
|
151
|
+
for (const cleanup of cleanups) cleanup();
|
|
152
|
+
}
|
|
127
153
|
const timedOut = waitConditions.some(condition => condition.timedOut);
|
|
128
154
|
const navigationUrls = await networkMonitor.getNavigationUrls();
|
|
129
155
|
|
|
@@ -142,7 +168,9 @@ async function gotoURL(driver, requestor, options) {
|
|
|
142
168
|
const mainDocumentUrl = navigationUrls.mainDocumentUrl || requestedUrl;
|
|
143
169
|
|
|
144
170
|
// Bring `Page.navigate` errors back into the promise chain. See https://github.com/GoogleChrome/lighthouse/pull/6739.
|
|
145
|
-
await
|
|
171
|
+
await Promise.race(options.signal ?
|
|
172
|
+
[waitForNavigationTriggered, abortPromise] :
|
|
173
|
+
[waitForNavigationTriggered]);
|
|
146
174
|
|
|
147
175
|
if (options.debugNavigation) {
|
|
148
176
|
await waitForUserToContinue(driver);
|
|
@@ -18,7 +18,7 @@ export type WaitOptions = {
|
|
|
18
18
|
waitForCPUIdle: typeof waitForCPUIdle;
|
|
19
19
|
} | undefined;
|
|
20
20
|
};
|
|
21
|
-
/** @typedef {InstanceType<import('./network-monitor.js')['NetworkMonitor']>} NetworkMonitor */
|
|
21
|
+
/** @typedef {InstanceType<typeof import('./network-monitor.js')['NetworkMonitor']>} NetworkMonitor */
|
|
22
22
|
/** @typedef {import('./network-monitor.js').NetworkMonitorEvent} NetworkMonitorEvent */
|
|
23
23
|
/**
|
|
24
24
|
* @template [T=void]
|
|
@@ -40,12 +40,9 @@ export type WaitOptions = {
|
|
|
40
40
|
* Returns a promise that resolves immediately.
|
|
41
41
|
* Used for placeholder conditions that we don't want to start waiting for just yet, but still want
|
|
42
42
|
* to satisfy the same interface.
|
|
43
|
-
* @return {
|
|
43
|
+
* @return {CancellableWait<void>}
|
|
44
44
|
*/
|
|
45
|
-
export function waitForNothing():
|
|
46
|
-
promise: Promise<void>;
|
|
47
|
-
cancel: () => void;
|
|
48
|
-
};
|
|
45
|
+
export function waitForNothing(): CancellableWait<void>;
|
|
49
46
|
/**
|
|
50
47
|
* Returns a promise that resolve when a frame has been navigated.
|
|
51
48
|
* Used for detecting that our about:blank reset has been completed.
|
|
@@ -104,9 +101,10 @@ export function waitForCPUIdle(session: LH.Gatherer.ProtocolSession, waitForCPUQ
|
|
|
104
101
|
* @param {LH.Gatherer.ProtocolSession} session
|
|
105
102
|
* @param {NetworkMonitor} networkMonitor
|
|
106
103
|
* @param {WaitOptions} options
|
|
104
|
+
* @param {AbortSignal} [signal]
|
|
107
105
|
* @return {Promise<{timedOut: boolean}>}
|
|
108
106
|
*/
|
|
109
|
-
export function waitForFullyLoaded(session: LH.Gatherer.ProtocolSession, networkMonitor: NetworkMonitor, options: WaitOptions): Promise<{
|
|
107
|
+
export function waitForFullyLoaded(session: LH.Gatherer.ProtocolSession, networkMonitor: NetworkMonitor, options: WaitOptions, signal?: AbortSignal): Promise<{
|
|
110
108
|
timedOut: boolean;
|
|
111
109
|
}>;
|
|
112
110
|
/**
|
|
@@ -11,7 +11,7 @@ import log from 'lighthouse-logger';
|
|
|
11
11
|
import {LighthouseError} from '../../lib/lh-error.js';
|
|
12
12
|
import {ExecutionContext} from './execution-context.js';
|
|
13
13
|
|
|
14
|
-
/** @typedef {InstanceType<import('./network-monitor.js')['NetworkMonitor']>} NetworkMonitor */
|
|
14
|
+
/** @typedef {InstanceType<typeof import('./network-monitor.js')['NetworkMonitor']>} NetworkMonitor */
|
|
15
15
|
/** @typedef {import('./network-monitor.js').NetworkMonitorEvent} NetworkMonitorEvent */
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -36,7 +36,7 @@ import {ExecutionContext} from './execution-context.js';
|
|
|
36
36
|
* Returns a promise that resolves immediately.
|
|
37
37
|
* Used for placeholder conditions that we don't want to start waiting for just yet, but still want
|
|
38
38
|
* to satisfy the same interface.
|
|
39
|
-
* @return {
|
|
39
|
+
* @return {CancellableWait<void>}
|
|
40
40
|
*/
|
|
41
41
|
function waitForNothing() {
|
|
42
42
|
return {promise: Promise.resolve(), cancel() {}};
|
|
@@ -277,8 +277,6 @@ function waitForCPUIdle(session, waitForCPUQuiet) {
|
|
|
277
277
|
};
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
/* c8 ignore start */
|
|
281
|
-
|
|
282
280
|
/**
|
|
283
281
|
* This function is executed in the page itself when the document is first loaded.
|
|
284
282
|
*
|
|
@@ -338,8 +336,6 @@ function checkTimeSinceLastLongTaskInPage() {
|
|
|
338
336
|
});
|
|
339
337
|
}
|
|
340
338
|
|
|
341
|
-
/* c8 ignore stop */
|
|
342
|
-
|
|
343
339
|
/**
|
|
344
340
|
* Return a promise that resolves `pauseAfterLoadMs` after the load event
|
|
345
341
|
* fires and a method to cancel internal listeners and timeout.
|
|
@@ -422,9 +418,10 @@ const DEFAULT_WAIT_FUNCTIONS = {waitForFcp, waitForLoadEvent, waitForCPUIdle, wa
|
|
|
422
418
|
* @param {LH.Gatherer.ProtocolSession} session
|
|
423
419
|
* @param {NetworkMonitor} networkMonitor
|
|
424
420
|
* @param {WaitOptions} options
|
|
421
|
+
* @param {AbortSignal} [signal]
|
|
425
422
|
* @return {Promise<{timedOut: boolean}>}
|
|
426
423
|
*/
|
|
427
|
-
async function waitForFullyLoaded(session, networkMonitor, options) {
|
|
424
|
+
async function waitForFullyLoaded(session, networkMonitor, options, signal) {
|
|
428
425
|
const {pauseAfterFcpMs, pauseAfterLoadMs, networkQuietThresholdMs,
|
|
429
426
|
cpuQuietThresholdMs, maxWaitForLoadedMs, maxWaitForFcpMs} = options;
|
|
430
427
|
const {waitForFcp, waitForLoadEvent, waitForNetworkIdle, waitForCPUIdle} =
|
|
@@ -528,12 +525,23 @@ async function waitForFullyLoaded(session, networkMonitor, options) {
|
|
|
528
525
|
};
|
|
529
526
|
});
|
|
530
527
|
|
|
528
|
+
/** @type {(event: Event) => void} */
|
|
529
|
+
let onAbort = () => {};
|
|
530
|
+
/** @type {Promise<() => Promise<{timedOut: boolean}>>} */
|
|
531
|
+
const abortPromise = new Promise((resolve) => {
|
|
532
|
+
signal?.throwIfAborted();
|
|
533
|
+
onAbort = () => resolve(async () => ({timedOut: false}));
|
|
534
|
+
signal?.addEventListener('abort', onAbort, {once: true});
|
|
535
|
+
});
|
|
536
|
+
|
|
531
537
|
// Wait for load or timeout and run the cleanup function the winner returns.
|
|
532
538
|
const cleanupFn = await Promise.race([
|
|
533
539
|
loadPromise,
|
|
534
540
|
maxTimeoutPromise,
|
|
541
|
+
abortPromise,
|
|
535
542
|
]);
|
|
536
543
|
|
|
544
|
+
signal?.removeEventListener('abort', onAbort);
|
|
537
545
|
maxTimeoutHandle && clearTimeout(maxTimeoutHandle);
|
|
538
546
|
resolveOnFcp.cancel();
|
|
539
547
|
resolveOnLoadEvent.cancel();
|
|
@@ -547,7 +555,6 @@ async function waitForFullyLoaded(session, networkMonitor, options) {
|
|
|
547
555
|
* @param {LH.Gatherer.Driver} driver
|
|
548
556
|
*/
|
|
549
557
|
function waitForUserToContinue(driver) {
|
|
550
|
-
/* c8 ignore start */
|
|
551
558
|
function createInPagePromise() {
|
|
552
559
|
let resolve = () => {};
|
|
553
560
|
/** @type {Promise<void>} */
|
|
@@ -563,7 +570,6 @@ function waitForUserToContinue(driver) {
|
|
|
563
570
|
window.continueLighthouseRun = resolve;
|
|
564
571
|
return promise;
|
|
565
572
|
}
|
|
566
|
-
/* c8 ignore stop */
|
|
567
573
|
|
|
568
574
|
driver.defaultSession.setNextProtocolTimeout(Infinity);
|
|
569
575
|
return driver.executionContext.evaluate(createInPagePromise, {args: []});
|
package/core/gather/fetcher.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export type FetchResponse = {
|
|
2
2
|
content: string | null;
|
|
3
3
|
status: number | null;
|
|
4
|
+
headers?: Record<string, string> | null;
|
|
4
5
|
};
|
|
5
6
|
/**
|
|
6
7
|
* @fileoverview Fetcher is a utility for making requests to any arbitrary resource,
|
|
7
8
|
* ignoring normal browser constraints such as CORS.
|
|
8
9
|
*/
|
|
9
|
-
/** @typedef {{content: string|null, status: number|null}} FetchResponse */
|
|
10
|
+
/** @typedef {{content: string|null, status: number|null, headers?: Record<string, string>|null}} FetchResponse */
|
|
10
11
|
export class Fetcher {
|
|
11
12
|
/**
|
|
12
13
|
* @param {LH.Gatherer.ProtocolSession} session
|
|
@@ -23,6 +24,11 @@ export class Fetcher {
|
|
|
23
24
|
fetchResource(url: string, options?: {
|
|
24
25
|
timeout: number;
|
|
25
26
|
} | undefined): Promise<FetchResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* @param {Headers|Record<string, string|number|boolean>|null|undefined} rawHeaders
|
|
29
|
+
* @return {Record<string, string>|null}
|
|
30
|
+
*/
|
|
31
|
+
_normalizeHeaders(rawHeaders: Headers | Record<string, string | number | boolean> | null | undefined): Record<string, string> | null;
|
|
26
32
|
/**
|
|
27
33
|
* @param {string} url
|
|
28
34
|
* @return {Promise<FetchResponse>}
|
|
@@ -38,11 +44,12 @@ export class Fetcher {
|
|
|
38
44
|
} | undefined): Promise<string>;
|
|
39
45
|
/**
|
|
40
46
|
* @param {string} url
|
|
41
|
-
* @return {Promise<{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}>}
|
|
47
|
+
* @return {Promise<{stream: LH.Crdp.IO.StreamHandle|null, status: number|null, headers?: Record<string, string>|null}>}
|
|
42
48
|
*/
|
|
43
49
|
_loadNetworkResource(url: string): Promise<{
|
|
44
50
|
stream: LH.Crdp.IO.StreamHandle | null;
|
|
45
51
|
status: number | null;
|
|
52
|
+
headers?: Record<string, string> | null;
|
|
46
53
|
}>;
|
|
47
54
|
/**
|
|
48
55
|
* @param {string} url
|
package/core/gather/fetcher.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as LH from '../../types/lh.js';
|
|
|
10
10
|
* ignoring normal browser constraints such as CORS.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
/** @typedef {{content: string|null, status: number|null}} FetchResponse */
|
|
13
|
+
/** @typedef {{content: string|null, status: number|null, headers?: Record<string, string>|null}} FetchResponse */
|
|
14
14
|
|
|
15
15
|
class Fetcher {
|
|
16
16
|
/**
|
|
@@ -37,6 +37,26 @@ class Fetcher {
|
|
|
37
37
|
return this._fetchResourceOverProtocol(url, options);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @param {Headers|Record<string, string|number|boolean>|null|undefined} rawHeaders
|
|
42
|
+
* @return {Record<string, string>|null}
|
|
43
|
+
*/
|
|
44
|
+
_normalizeHeaders(rawHeaders) {
|
|
45
|
+
if (!rawHeaders) return null;
|
|
46
|
+
|
|
47
|
+
/** @type {Record<string, string>} */
|
|
48
|
+
const headers = {};
|
|
49
|
+
const entries = typeof /** @type {Headers} */ (rawHeaders).entries === 'function' ?
|
|
50
|
+
/** @type {Headers} */ (rawHeaders).entries() :
|
|
51
|
+
Object.entries(rawHeaders);
|
|
52
|
+
|
|
53
|
+
for (const [key, value] of entries) {
|
|
54
|
+
headers[key.toLowerCase()] = String(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return headers;
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
/**
|
|
41
61
|
* @param {string} url
|
|
42
62
|
* @return {Promise<FetchResponse>}
|
|
@@ -49,9 +69,12 @@ class Fetcher {
|
|
|
49
69
|
content = await response.text();
|
|
50
70
|
} catch {}
|
|
51
71
|
|
|
72
|
+
const headers = this._normalizeHeaders(response.headers);
|
|
73
|
+
|
|
52
74
|
return {
|
|
53
75
|
content,
|
|
54
76
|
status: response.status,
|
|
77
|
+
headers,
|
|
55
78
|
};
|
|
56
79
|
}
|
|
57
80
|
|
|
@@ -82,7 +105,7 @@ class Fetcher {
|
|
|
82
105
|
|
|
83
106
|
/**
|
|
84
107
|
* @param {string} url
|
|
85
|
-
* @return {Promise<{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}>}
|
|
108
|
+
* @return {Promise<{stream: LH.Crdp.IO.StreamHandle|null, status: number|null, headers?: Record<string, string>|null}>}
|
|
86
109
|
*/
|
|
87
110
|
async _loadNetworkResource(url) {
|
|
88
111
|
const frameTreeResponse = await this.session.sendCommand('Page.getFrameTree');
|
|
@@ -98,6 +121,7 @@ class Fetcher {
|
|
|
98
121
|
return {
|
|
99
122
|
stream: networkResponse.resource.success ? (networkResponse.resource.stream || null) : null,
|
|
100
123
|
status: networkResponse.resource.httpStatusCode || null,
|
|
124
|
+
headers: networkResponse.resource.headers || null,
|
|
101
125
|
};
|
|
102
126
|
}
|
|
103
127
|
|
|
@@ -110,12 +134,14 @@ class Fetcher {
|
|
|
110
134
|
const startTime = Date.now();
|
|
111
135
|
const response = await this._wrapWithTimeout(this._loadNetworkResource(url), options.timeout);
|
|
112
136
|
|
|
137
|
+
const headers = this._normalizeHeaders(response.headers);
|
|
138
|
+
|
|
113
139
|
const isOk = response.status && response.status >= 200 && response.status <= 299;
|
|
114
|
-
if (!response.stream || !isOk) return {status: response.status, content: null};
|
|
140
|
+
if (!response.stream || !isOk) return {status: response.status, content: null, headers};
|
|
115
141
|
|
|
116
142
|
const timeout = options.timeout - (Date.now() - startTime);
|
|
117
143
|
const content = await this._readIOStream(response.stream, {timeout});
|
|
118
|
-
return {status: response.status, content};
|
|
144
|
+
return {status: response.status, content, headers};
|
|
119
145
|
}
|
|
120
146
|
|
|
121
147
|
/**
|
|
@@ -13,9 +13,8 @@ import {pageFunctions} from '../../lib/page-functions.js';
|
|
|
13
13
|
/**
|
|
14
14
|
* @return {Promise<LH.Artifacts.Accessibility>}
|
|
15
15
|
*/
|
|
16
|
-
/* c8 ignore start */
|
|
17
16
|
async function runA11yChecks() {
|
|
18
|
-
/** @type {import('axe-core/axe')} */
|
|
17
|
+
/** @type {typeof import('axe-core/axe')} */
|
|
19
18
|
// @ts-expect-error - axe defined by axeLibSource
|
|
20
19
|
const axe = window.axe;
|
|
21
20
|
const application = `lighthouse-${Math.random()}`;
|
|
@@ -183,7 +182,6 @@ function createAxeRuleResultArtifact(result) {
|
|
|
183
182
|
description: result.description,
|
|
184
183
|
};
|
|
185
184
|
}
|
|
186
|
-
/* c8 ignore stop */
|
|
187
185
|
|
|
188
186
|
class Accessibility extends BaseGatherer {
|
|
189
187
|
/** @type {LH.Gatherer.GathererMeta} */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export default AgentResourceDiscovery;
|
|
2
|
+
declare class AgentResourceDiscovery extends BaseGatherer {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts['RobotsTxt']|null|undefined} robotsTxt
|
|
5
|
+
* @param {string} finalDisplayedUrl
|
|
6
|
+
* @return {string|null}
|
|
7
|
+
*/
|
|
8
|
+
static getRobotsTxtAgentmap(robotsTxt: LH.Artifacts["RobotsTxt"] | null | undefined, finalDisplayedUrl: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* @param {LH.Gatherer.Context} context
|
|
11
|
+
* @param {string} finalDisplayedUrl
|
|
12
|
+
* @return {Promise<string|null>}
|
|
13
|
+
*/
|
|
14
|
+
static getHtmlLinkFromDom(context: LH.Gatherer.Context, finalDisplayedUrl: string): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* @param {LH.Gatherer.Context} context
|
|
17
|
+
* @param {string} finalDisplayedUrl
|
|
18
|
+
* @return {Promise<string|null>}
|
|
19
|
+
*/
|
|
20
|
+
static getHttpHeaderLink(context: LH.Gatherer.Context, finalDisplayedUrl: string): Promise<string | null>;
|
|
21
|
+
/** @type {LH.Gatherer.GathererMeta<'RobotsTxt'>} */
|
|
22
|
+
meta: LH.Gatherer.GathererMeta<"RobotsTxt">;
|
|
23
|
+
/**
|
|
24
|
+
* @param {LH.Gatherer.Context<'RobotsTxt'>} context
|
|
25
|
+
* @return {Promise<LH.Artifacts['AgentResourceDiscovery']>}
|
|
26
|
+
*/
|
|
27
|
+
getArtifact(context: LH.Gatherer.Context<"RobotsTxt">): Promise<LH.Artifacts["AgentResourceDiscovery"]>;
|
|
28
|
+
}
|
|
29
|
+
import BaseGatherer from '../../base-gatherer.js';
|
|
30
|
+
//# sourceMappingURL=ard.d.ts.map
|