lighthouse 13.5.0 → 13.6.0-dev.20261028
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/.agents/skills/update-ard-port/SKILL.md +171 -0
- package/.agents/skills/update-trace-engine/SKILL.md +154 -0
- package/cli/bin.d.ts +2 -2
- package/cli/bin.js +5 -4
- package/cli/cli-flags.d.ts +16 -1
- package/cli/cli-flags.js +53 -2
- package/cli/printer.js +5 -8
- package/cli/run.d.ts +5 -5
- package/cli/run.js +97 -47
- package/cli/sentry-prompt.js +3 -4
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +1 -1
- package/cli/test/smokehouse/config/exclusions.js +3 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
- package/cli/test/smokehouse/report-assert-test.js +65 -0
- package/cli/test/smokehouse/report-assert.js +24 -39
- package/cli/test/smokehouse/smokehouse-test.d.ts +2 -0
- package/cli/test/smokehouse/smokehouse-test.js +120 -0
- package/cli/test/smokehouse/smokehouse.js +37 -15
- package/cli/test/smokehouse/version-check-test.js +5 -0
- package/cli/test/smokehouse/version-check.d.ts +1 -1
- package/cli/test/smokehouse/version-check.js +1 -1
- package/commitlint.config.js +1 -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 +13 -0
- package/core/audits/agentic/ard-schema.js +81 -33
- package/core/audits/agentic/llms-txt.js +5 -2
- package/core/audits/baseline.d.ts +29 -7
- package/core/audits/baseline.js +19 -6
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
- package/core/audits/insights/insight-audit.d.ts +9 -0
- package/core/audits/insights/insight-audit.js +35 -28
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/screenshot-thumbnails.d.ts +0 -13
- package/core/audits/screenshot-thumbnails.js +0 -45
- package/core/audits/script-treemap-data.js +26 -6
- package/core/audits/seo/robots-txt.js +3 -8
- package/core/audits/unsized-images.d.ts +0 -7
- package/core/audits/unsized-images.js +7 -26
- package/core/computed/lcp-image-record.d.ts +2 -23
- package/core/computed/lcp-image-record.js +3 -67
- package/core/computed/lcp-network-record.d.ts +24 -0
- package/core/computed/lcp-network-record.js +74 -0
- package/core/computed/metrics/lantern-metric.js +1 -1
- package/core/computed/metrics/lcp-breakdown.js +6 -6
- package/core/computed/metrics/time-to-first-byte.js +5 -3
- package/core/computed/navigation-insights.js +3 -2
- package/core/computed/page-dependency-graph.js +55 -5
- package/core/computed/speedline.js +20 -5
- package/core/computed/trace-engine-result.js +25 -5
- package/core/computed/unused-css.js +15 -2
- package/core/config/config-helpers.d.ts +5 -0
- package/core/config/config-helpers.js +17 -28
- package/core/config/config-plugin.d.ts +7 -0
- package/core/config/config-plugin.js +49 -0
- package/core/config/config.js +10 -2
- package/core/config/constants.d.ts +2 -28
- package/core/config/filters.js +1 -0
- package/core/config/lr-desktop-config.js +13 -0
- package/core/config/lr-mobile-config.js +13 -0
- package/core/gather/base-artifacts.js +7 -1
- package/core/gather/driver/storage.js +3 -2
- package/core/gather/gatherers/agentic/ard.d.ts +59 -4
- package/core/gather/gatherers/agentic/ard.js +218 -56
- package/core/gather/gatherers/full-page-screenshot.js +2 -2
- package/core/gather/gatherers/image-elements.d.ts +7 -0
- package/core/gather/gatherers/image-elements.js +75 -24
- package/core/gather/gatherers/inspector-issues.js +2 -0
- package/core/gather/gatherers/js-usage.js +2 -0
- package/core/gather/gatherers/scripts.d.ts +16 -3
- package/core/gather/gatherers/scripts.js +157 -14
- package/core/gather/gatherers/stylesheets.js +2 -0
- package/core/gather/gatherers/trace-elements.js +2 -1
- package/core/gather/gatherers/trace.d.ts +6 -0
- package/core/gather/gatherers/trace.js +56 -16
- package/core/index.d.ts +2 -0
- package/core/index.js +2 -0
- package/core/lib/arbitrary-equality-map.d.ts +14 -3
- package/core/lib/arbitrary-equality-map.js +113 -6
- package/core/lib/baseline/web-features-data.json +32 -11
- package/core/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/bf-cache-strings.js +237 -254
- package/core/lib/cdt/Common.d.ts +1 -1
- package/core/lib/cdt/Common.js +2 -2
- package/core/lib/cdt/Platform.d.ts +7 -7
- package/core/lib/cdt/Platform.js +11 -8
- package/core/lib/cdt/SDK.d.ts +5 -1
- package/core/lib/cdt/SDK.js +4 -2
- package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
- package/core/lib/cdt/generated/ParsedURL.js +3 -9
- package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
- package/core/lib/cdt/generated/SourceMap.js +7 -15
- package/core/lib/deprecations-strings.d.ts +93 -88
- package/core/lib/deprecations-strings.js +20 -14
- package/core/lib/i18n/i18n.d.ts +2 -1
- package/core/lib/i18n/i18n.js +25 -13
- package/core/lib/legacy-javascript/legacy-javascript.d.ts +3 -2
- package/core/lib/legacy-javascript/legacy-javascript.js +235 -111
- package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
- package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
- package/core/lib/minification-estimator.js +10 -10
- package/core/lib/network-request.d.ts +2 -0
- package/core/lib/network-request.js +21 -0
- package/core/lib/page-functions.js +2 -5
- package/core/lib/sentry.d.ts +1 -0
- package/core/lib/sentry.js +3 -0
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/core/runner.js +9 -3
- package/dist/report/bundle.esm.js +10 -10
- package/dist/report/flow.js +13 -13
- package/dist/report/standalone.js +5 -5
- package/flow-report/src/common.tsx +5 -2
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/flow-report/src/summary/summary.tsx +9 -1
- package/flow-report/src/util.d.ts +1 -1
- package/package.json +62 -98
- package/readme.md +13 -18
- package/report/generator/file-namer.d.ts +1 -1
- package/report/generator/file-namer.js +5 -4
- package/report/generator/flow-report-assets.js +1 -3
- package/report/generator/report-assets.js +1 -2
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/details-renderer.js +3 -2
- package/report/renderer/dom.d.ts +8 -2
- package/report/renderer/dom.js +29 -1
- package/report/renderer/element-screenshot-renderer.js +3 -0
- package/report/renderer/performance-category-renderer.js +3 -1
- package/report/renderer/report-renderer.js +4 -0
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/text-encoding.js +2 -2
- package/report/types/augment-dom.d.ts +4 -0
- package/shared/esm-utils.d.ts +0 -8
- package/shared/esm-utils.js +0 -18
- package/shared/localization/format.js +5 -3
- package/shared/localization/locales/ar-XB.json +157 -142
- package/shared/localization/locales/ar.json +157 -142
- package/shared/localization/locales/bg.json +154 -139
- package/shared/localization/locales/ca.json +157 -142
- package/shared/localization/locales/cs.json +148 -133
- package/shared/localization/locales/da.json +153 -138
- package/shared/localization/locales/de.json +152 -137
- package/shared/localization/locales/el.json +153 -138
- package/shared/localization/locales/en-GB.json +160 -145
- package/shared/localization/locales/en-US.json +239 -218
- package/shared/localization/locales/en-XL.json +239 -218
- package/shared/localization/locales/es-419.json +150 -135
- package/shared/localization/locales/es.json +149 -134
- package/shared/localization/locales/fi.json +151 -136
- package/shared/localization/locales/fil.json +154 -139
- package/shared/localization/locales/fr.json +150 -135
- package/shared/localization/locales/he.json +160 -145
- package/shared/localization/locales/hi.json +156 -141
- package/shared/localization/locales/hr.json +154 -139
- package/shared/localization/locales/hu.json +111 -96
- package/shared/localization/locales/id.json +153 -138
- package/shared/localization/locales/it.json +153 -138
- package/shared/localization/locales/ja.json +150 -135
- package/shared/localization/locales/ko.json +42 -27
- package/shared/localization/locales/lt.json +152 -137
- package/shared/localization/locales/lv.json +93 -78
- package/shared/localization/locales/nl.json +149 -134
- package/shared/localization/locales/no.json +152 -137
- package/shared/localization/locales/pl.json +153 -138
- package/shared/localization/locales/pt-PT.json +152 -137
- package/shared/localization/locales/pt.json +157 -142
- package/shared/localization/locales/ro.json +149 -134
- package/shared/localization/locales/ru.json +76 -61
- package/shared/localization/locales/sk.json +153 -138
- package/shared/localization/locales/sl.json +149 -134
- package/shared/localization/locales/sr-Latn.json +151 -136
- package/shared/localization/locales/sr.json +151 -136
- package/shared/localization/locales/sv.json +152 -137
- package/shared/localization/locales/ta.json +156 -141
- package/shared/localization/locales/te.json +157 -142
- package/shared/localization/locales/th.json +111 -96
- package/shared/localization/locales/tr.json +153 -138
- package/shared/localization/locales/uk.json +149 -134
- package/shared/localization/locales/vi.json +162 -147
- package/shared/localization/locales/zh-HK.json +150 -135
- package/shared/localization/locales/zh-TW.json +154 -139
- package/shared/localization/locales/zh.json +152 -137
- package/shared/localization/swap-locale.js +10 -5
- package/shared/root.js +1 -3
- package/third-party/ard/README.md +19 -10
- package/third-party/ard/ard-test.js +189 -266
- package/third-party/ard/ard.d.ts +18 -1
- package/third-party/ard/ard.js +282 -45
- package/third-party/ard/schema-validator.d.ts +14 -5
- package/third-party/ard/schema-validator.js +921 -1177
- package/third-party/ard/spec/schemas/ard-entry.schema.json +176 -0
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +43 -7
- package/types/config.d.ts +6 -1
- package/types/externs.d.ts +4 -0
- package/types/internal/test.d.ts +1 -0
- package/build-tracker.config.js +0 -16
- package/core/lib/cdt/package.json +0 -4
- package/eslint-local-rules.d.cts +0 -9
- package/eslint.config.mjs +0 -242
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
- package/third-party/esbuild-plugins-polyfills/LICENSE +0 -3
- package/third-party/esbuild-plugins-polyfills/README.md +0 -7
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.d.ts +0 -9
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.js +0 -129
- package/types/internal/lookup-closest-locale.d.ts +0 -13
|
@@ -1,31 +1,4 @@
|
|
|
1
|
-
export const throttling:
|
|
2
|
-
DEVTOOLS_RTT_ADJUSTMENT_FACTOR: number;
|
|
3
|
-
DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR: number;
|
|
4
|
-
mobileSlow4G: {
|
|
5
|
-
rttMs: number;
|
|
6
|
-
throughputKbps: number;
|
|
7
|
-
requestLatencyMs: number;
|
|
8
|
-
downloadThroughputKbps: number;
|
|
9
|
-
uploadThroughputKbps: number;
|
|
10
|
-
cpuSlowdownMultiplier: number;
|
|
11
|
-
};
|
|
12
|
-
mobileRegular3G: {
|
|
13
|
-
rttMs: number;
|
|
14
|
-
throughputKbps: number;
|
|
15
|
-
requestLatencyMs: number;
|
|
16
|
-
downloadThroughputKbps: number;
|
|
17
|
-
uploadThroughputKbps: number;
|
|
18
|
-
cpuSlowdownMultiplier: number;
|
|
19
|
-
};
|
|
20
|
-
desktopDense4G: {
|
|
21
|
-
rttMs: number;
|
|
22
|
-
throughputKbps: number;
|
|
23
|
-
cpuSlowdownMultiplier: number;
|
|
24
|
-
requestLatencyMs: number;
|
|
25
|
-
downloadThroughputKbps: number;
|
|
26
|
-
uploadThroughputKbps: number;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
1
|
+
export const throttling: Lantern.Simulation.ThrottlingSettings;
|
|
29
2
|
export namespace screenEmulationMetrics {
|
|
30
3
|
export { MOTOGPOWER_EMULATION_METRICS as mobile };
|
|
31
4
|
export { DESKTOP_EMULATION_METRICS as desktop };
|
|
@@ -42,6 +15,7 @@ export namespace nonSimulatedSettingsOverrides {
|
|
|
42
15
|
let networkQuietThresholdMs: number;
|
|
43
16
|
let cpuQuietThresholdMs: number;
|
|
44
17
|
}
|
|
18
|
+
import * as Lantern from '../lib/lantern/lantern.js';
|
|
45
19
|
/**
|
|
46
20
|
* @type {Required<LH.SharedFlagsSettings['screenEmulation']>}
|
|
47
21
|
*/
|
package/core/config/filters.js
CHANGED
|
@@ -69,6 +69,7 @@ function filterArtifactsByAvailableAudits(artifacts, audits) {
|
|
|
69
69
|
/** @type {Set<string>} */
|
|
70
70
|
const artifactIdsToKeep = new Set([
|
|
71
71
|
...filterResistantArtifactIds,
|
|
72
|
+
...artifacts.filter(artifact => artifact.alwaysGather).map(artifact => artifact.id),
|
|
72
73
|
...audits.flatMap(audit => audit.implementation.meta.requiredArtifacts),
|
|
73
74
|
]);
|
|
74
75
|
|
|
@@ -24,6 +24,19 @@ const config = {
|
|
|
24
24
|
'bf-cache',
|
|
25
25
|
],
|
|
26
26
|
},
|
|
27
|
+
audits: [
|
|
28
|
+
// Lightrider does not support WebSocket connections.
|
|
29
|
+
// See https://github.com/GoogleChrome/lighthouse/issues/12459
|
|
30
|
+
{
|
|
31
|
+
path: 'errors-in-console',
|
|
32
|
+
options: {
|
|
33
|
+
ignoredPatterns: [
|
|
34
|
+
'ERR_BLOCKED_BY_CLIENT.Inspector',
|
|
35
|
+
/^WebSocket connection to 'wss?:\/\/.*' failed:/,
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
27
40
|
};
|
|
28
41
|
|
|
29
42
|
export default config;
|
|
@@ -24,6 +24,19 @@ const config = {
|
|
|
24
24
|
'bf-cache',
|
|
25
25
|
],
|
|
26
26
|
},
|
|
27
|
+
audits: [
|
|
28
|
+
// Lightrider does not support WebSocket connections.
|
|
29
|
+
// See https://github.com/GoogleChrome/lighthouse/issues/12459
|
|
30
|
+
{
|
|
31
|
+
path: 'errors-in-console',
|
|
32
|
+
options: {
|
|
33
|
+
ignoredPatterns: [
|
|
34
|
+
'ERR_BLOCKED_BY_CLIENT.Inspector',
|
|
35
|
+
/^WebSocket connection to 'wss?:\/\/.*' failed:/,
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
27
40
|
};
|
|
28
41
|
|
|
29
42
|
export default config;
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import log from 'lighthouse-logger';
|
|
8
|
-
import {isEqual} from 'lodash-es';
|
|
9
8
|
|
|
10
9
|
import {
|
|
11
10
|
getBrowserVersion, getBenchmarkIndex, getEnvironmentWarnings,
|
|
12
11
|
getDevicePixelRatio,
|
|
13
12
|
} from './driver/environment.js';
|
|
13
|
+
import {isEqual} from '../lib/arbitrary-equality-map.js';
|
|
14
|
+
import {Sentry} from '../lib/sentry.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @param {LH.Config.ResolvedConfig} resolvedConfig
|
|
@@ -23,6 +24,11 @@ async function getBaseArtifacts(resolvedConfig, driver, context) {
|
|
|
23
24
|
const {userAgent, product} = await getBrowserVersion(driver.defaultSession);
|
|
24
25
|
const HostDPR = await getDevicePixelRatio(driver.executionContext);
|
|
25
26
|
|
|
27
|
+
const [name, version] = product.split('/');
|
|
28
|
+
if (name && version) {
|
|
29
|
+
Sentry.setContext('browser', {name, version});
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
return {
|
|
27
33
|
// Meta artifacts.
|
|
28
34
|
fetchTime: new Date().toJSON(),
|
|
@@ -41,13 +41,14 @@ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
|
41
41
|
* @return {Promise<LH.IcuMessage[]>}
|
|
42
42
|
*/
|
|
43
43
|
async function clearDataForOrigin(session, url, clearStorageTypes) {
|
|
44
|
+
const origin = new URL(url).origin;
|
|
45
|
+
if (origin === 'null') return [];
|
|
46
|
+
|
|
44
47
|
const status = {msg: 'Cleaning origin data', id: 'lh:storage:clearDataForOrigin'};
|
|
45
48
|
log.time(status);
|
|
46
49
|
|
|
47
50
|
const warnings = [];
|
|
48
51
|
|
|
49
|
-
const origin = new URL(url).origin;
|
|
50
|
-
|
|
51
52
|
const typesToClear = clearStorageTypes.join(',');
|
|
52
53
|
|
|
53
54
|
// `Storage.clearDataForOrigin` is one of our PROTOCOL_TIMEOUT culprits and this command is also
|
|
@@ -1,26 +1,81 @@
|
|
|
1
1
|
export default AgentResourceDiscovery;
|
|
2
|
+
export type ArdLinks = {
|
|
3
|
+
ard: string | null;
|
|
4
|
+
legacy: string | null;
|
|
5
|
+
};
|
|
6
|
+
export type DiscoverySignals = LH.Artifacts["AgentResourceDiscovery"]["discoverySignals"];
|
|
7
|
+
export type FetchResult = {
|
|
8
|
+
status: number | null;
|
|
9
|
+
content: string | null;
|
|
10
|
+
headers: Record<string, string> | null;
|
|
11
|
+
errorMessage?: string | undefined;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A location to try when resolving the manifest. `advertised` locations are ones the site
|
|
15
|
+
* points at (robots.txt, link tags, Link headers); the well-known paths are probed by
|
|
16
|
+
* Lighthouse, so a failure there is not the site's fault and is not reported.
|
|
17
|
+
*/
|
|
18
|
+
export type ManifestCandidate = {
|
|
19
|
+
source: LH.Artifacts.ArdDiscoverySource;
|
|
20
|
+
url: string | null;
|
|
21
|
+
advertised: boolean;
|
|
22
|
+
};
|
|
2
23
|
declare class AgentResourceDiscovery extends BaseGatherer {
|
|
3
24
|
/**
|
|
25
|
+
* Returns the absolute URL from the first `Agentmap:` directive in robots.txt, if any.
|
|
4
26
|
* @param {LH.Artifacts['RobotsTxt']|null|undefined} robotsTxt
|
|
5
27
|
* @param {string} finalDisplayedUrl
|
|
6
28
|
* @return {string|null}
|
|
7
29
|
*/
|
|
8
30
|
static getRobotsTxtAgentmap(robotsTxt: LH.Artifacts["RobotsTxt"] | null | undefined, finalDisplayedUrl: string): string | null;
|
|
9
31
|
/**
|
|
32
|
+
* Finds `<link rel="ard">` and `<link rel="ai-catalog">` in the page.
|
|
10
33
|
* @param {LH.Gatherer.Context} context
|
|
11
34
|
* @param {string} finalDisplayedUrl
|
|
12
|
-
* @return {Promise<
|
|
35
|
+
* @return {Promise<ArdLinks>}
|
|
13
36
|
*/
|
|
14
|
-
static
|
|
37
|
+
static getHtmlLinksFromDom(context: LH.Gatherer.Context, finalDisplayedUrl: string): Promise<ArdLinks>;
|
|
15
38
|
/**
|
|
39
|
+
* Finds `rel="ard"` and `rel="ai-catalog"` in the main document's HTTP `Link` header.
|
|
16
40
|
* @param {LH.Gatherer.Context} context
|
|
17
41
|
* @param {string} finalDisplayedUrl
|
|
18
|
-
* @return {Promise<
|
|
42
|
+
* @return {Promise<ArdLinks>}
|
|
19
43
|
*/
|
|
20
|
-
static
|
|
44
|
+
static getHttpHeaderLinks(context: LH.Gatherer.Context, finalDisplayedUrl: string): Promise<ArdLinks>;
|
|
45
|
+
/**
|
|
46
|
+
* Fetches a candidate manifest URL. Never throws; network errors are returned as a
|
|
47
|
+
* null status with an `errorMessage`.
|
|
48
|
+
* @param {LH.Gatherer.Context} context
|
|
49
|
+
* @param {string} url
|
|
50
|
+
* @return {Promise<FetchResult>}
|
|
51
|
+
*/
|
|
52
|
+
static fetchManifest(context: LH.Gatherer.Context, url: string): Promise<FetchResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Tries each candidate in order and returns the first that loads. Advertised candidates
|
|
55
|
+
* that fail are recorded in `failedSources` and skipped. If nothing loads, the result
|
|
56
|
+
* describes the first advertised location that failed, or else the `/.well-known/ard.json`
|
|
57
|
+
* path consumers are required to fetch.
|
|
58
|
+
* @param {LH.Gatherer.Context} context
|
|
59
|
+
* @param {ManifestCandidate[]} candidates
|
|
60
|
+
* @return {Promise<{
|
|
61
|
+
* discoverySource: LH.Artifacts.ArdDiscoverySource,
|
|
62
|
+
* catalogUrl: string,
|
|
63
|
+
* fetchResult: FetchResult,
|
|
64
|
+
* failedSources: LH.Artifacts['AgentResourceDiscovery']['failedSources'],
|
|
65
|
+
* }>}
|
|
66
|
+
*/
|
|
67
|
+
static resolveManifest(context: LH.Gatherer.Context, candidates: ManifestCandidate[]): Promise<{
|
|
68
|
+
discoverySource: LH.Artifacts.ArdDiscoverySource;
|
|
69
|
+
catalogUrl: string;
|
|
70
|
+
fetchResult: FetchResult;
|
|
71
|
+
failedSources: LH.Artifacts["AgentResourceDiscovery"]["failedSources"];
|
|
72
|
+
}>;
|
|
21
73
|
/** @type {LH.Gatherer.GathererMeta<'RobotsTxt'>} */
|
|
22
74
|
meta: LH.Gatherer.GathererMeta<"RobotsTxt">;
|
|
23
75
|
/**
|
|
76
|
+
* Resolves the manifest following ARD §5.1: explicit ARD signals first, then the
|
|
77
|
+
* `/.well-known/ard.json` path consumers MUST fetch, and only then the predecessor
|
|
78
|
+
* `ai-catalog` relation and well-known path, which consumers MAY consult.
|
|
24
79
|
* @param {LH.Gatherer.Context<'RobotsTxt'>} context
|
|
25
80
|
* @return {Promise<LH.Artifacts['AgentResourceDiscovery']>}
|
|
26
81
|
*/
|
|
@@ -4,17 +4,112 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Gatherer that discovers and fetches a site's Agentic Resource Discovery (ARD)
|
|
9
|
+
* manifest. It follows the discovery mechanisms in ARD §5.1 and also consults the predecessor
|
|
10
|
+
* `ai-catalog` names so that sites that have not yet migrated are still audited.
|
|
11
|
+
* @see https://agenticresourcediscovery.org/spec/
|
|
12
|
+
*/
|
|
13
|
+
|
|
7
14
|
import log from 'lighthouse-logger';
|
|
8
15
|
import LinkHeader from 'http-link-header';
|
|
9
16
|
|
|
10
17
|
import BaseGatherer from '../../base-gatherer.js';
|
|
11
18
|
import RobotsTxt from '../seo/robots-txt.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Link relations for ARD entry sources. `ai-catalog` is the predecessor relation, which
|
|
22
|
+
* consumers MAY consult but are not required to (ARD §5.1).
|
|
23
|
+
*/
|
|
24
|
+
const REL_ARD = 'ard';
|
|
25
|
+
const REL_LEGACY = 'ai-catalog';
|
|
26
|
+
|
|
27
|
+
const WELL_KNOWN_PATH = '/.well-known/ard.json';
|
|
28
|
+
const LEGACY_WELL_KNOWN_PATH = '/.well-known/ai-catalog.json';
|
|
29
|
+
|
|
30
|
+
const HTTP_OK = 200;
|
|
31
|
+
|
|
32
|
+
/** @typedef {{ard: string|null, legacy: string|null}} ArdLinks */
|
|
33
|
+
|
|
34
|
+
/** @typedef {LH.Artifacts['AgentResourceDiscovery']['discoverySignals']} DiscoverySignals */
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef FetchResult
|
|
38
|
+
* @property {number|null} status
|
|
39
|
+
* @property {string|null} content
|
|
40
|
+
* @property {Record<string, string>|null} headers
|
|
41
|
+
* @property {string} [errorMessage]
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A location to try when resolving the manifest. `advertised` locations are ones the site
|
|
46
|
+
* points at (robots.txt, link tags, Link headers); the well-known paths are probed by
|
|
47
|
+
* Lighthouse, so a failure there is not the site's fault and is not reported.
|
|
48
|
+
* @typedef ManifestCandidate
|
|
49
|
+
* @property {LH.Artifacts.ArdDiscoverySource} source
|
|
50
|
+
* @property {string|null} url
|
|
51
|
+
* @property {boolean} advertised
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/** @type {ReadonlyArray<LH.Artifacts.ArdDiscoverySource>} */
|
|
55
|
+
const SOURCE_PRIORITY = [
|
|
56
|
+
'robotsTxtAgentmap',
|
|
57
|
+
'htmlLink',
|
|
58
|
+
'httpHeaderLink',
|
|
59
|
+
'wellKnown',
|
|
60
|
+
'legacyHtmlLink',
|
|
61
|
+
'legacyHttpHeaderLink',
|
|
62
|
+
'legacyWellKnown',
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/** @type {Set<LH.Artifacts.ArdDiscoverySource>} */
|
|
66
|
+
const PROBED_SOURCES = new Set(['wellKnown', 'legacyWellKnown']);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {DiscoverySignals} discoverySignals
|
|
70
|
+
* @return {ManifestCandidate[]}
|
|
71
|
+
*/
|
|
72
|
+
function getManifestCandidates(discoverySignals) {
|
|
73
|
+
return SOURCE_PRIORITY.map(source => ({
|
|
74
|
+
source,
|
|
75
|
+
url: discoverySignals[source],
|
|
76
|
+
advertised: !PROBED_SOURCES.has(source),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolves `href` against `baseUrl`, returning null for empty or unparseable values.
|
|
82
|
+
* @param {string|null|undefined} href
|
|
83
|
+
* @param {string} baseUrl
|
|
84
|
+
* @return {string|null}
|
|
85
|
+
*/
|
|
86
|
+
function resolveUrl(href, baseUrl) {
|
|
87
|
+
if (!href) return null;
|
|
88
|
+
try {
|
|
89
|
+
return new URL(href, baseUrl).href;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Runs in the page. Returns the `href` of the first `<link>` for each relation, or null.
|
|
97
|
+
* @param {string} relArd
|
|
98
|
+
* @param {string} relLegacy
|
|
99
|
+
* @return {ArdLinks}
|
|
100
|
+
*/
|
|
101
|
+
function getArdLinksInDOM(relArd, relLegacy) {
|
|
102
|
+
/** @param {string} rel */
|
|
103
|
+
const getHref = rel => {
|
|
104
|
+
const link = document.querySelector(`link[rel~="${rel}" i]`);
|
|
105
|
+
return link instanceof HTMLLinkElement ? link.href : null;
|
|
106
|
+
};
|
|
107
|
+
return {ard: getHref(relArd), legacy: getHref(relLegacy)};
|
|
16
108
|
}
|
|
17
109
|
|
|
110
|
+
/** @type {ArdLinks} */
|
|
111
|
+
const NO_LINKS = {ard: null, legacy: null};
|
|
112
|
+
|
|
18
113
|
class AgentResourceDiscovery extends BaseGatherer {
|
|
19
114
|
/** @type {LH.Gatherer.GathererMeta<'RobotsTxt'>} */
|
|
20
115
|
meta = {
|
|
@@ -23,6 +118,7 @@ class AgentResourceDiscovery extends BaseGatherer {
|
|
|
23
118
|
};
|
|
24
119
|
|
|
25
120
|
/**
|
|
121
|
+
* Returns the absolute URL from the first `Agentmap:` directive in robots.txt, if any.
|
|
26
122
|
* @param {LH.Artifacts['RobotsTxt']|null|undefined} robotsTxt
|
|
27
123
|
* @param {string} finalDisplayedUrl
|
|
28
124
|
* @return {string|null}
|
|
@@ -31,93 +127,159 @@ class AgentResourceDiscovery extends BaseGatherer {
|
|
|
31
127
|
if (!robotsTxt?.content) return null;
|
|
32
128
|
const match = robotsTxt.content.match(/^\s*Agentmap:\s*(\S+)/im);
|
|
33
129
|
if (!match) return null;
|
|
34
|
-
|
|
35
|
-
return new URL(match[1], finalDisplayedUrl).href;
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
130
|
+
return resolveUrl(match[1], finalDisplayedUrl);
|
|
39
131
|
}
|
|
40
132
|
|
|
41
133
|
/**
|
|
134
|
+
* Finds `<link rel="ard">` and `<link rel="ai-catalog">` in the page.
|
|
42
135
|
* @param {LH.Gatherer.Context} context
|
|
43
136
|
* @param {string} finalDisplayedUrl
|
|
44
|
-
* @return {Promise<
|
|
137
|
+
* @return {Promise<ArdLinks>}
|
|
45
138
|
*/
|
|
46
|
-
static async
|
|
139
|
+
static async getHtmlLinksFromDom(context, finalDisplayedUrl) {
|
|
47
140
|
try {
|
|
48
|
-
const
|
|
49
|
-
args: [],
|
|
141
|
+
const links = await context.driver.executionContext.evaluate(getArdLinksInDOM, {
|
|
142
|
+
args: [REL_ARD, REL_LEGACY],
|
|
50
143
|
useIsolation: true,
|
|
51
|
-
deps: [pageFunctions.getNodeDetails],
|
|
52
144
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
145
|
+
return {
|
|
146
|
+
ard: resolveUrl(links?.ard, finalDisplayedUrl),
|
|
147
|
+
legacy: resolveUrl(links?.legacy, finalDisplayedUrl),
|
|
148
|
+
};
|
|
149
|
+
} catch (err) {
|
|
150
|
+
log.verbose('AgentResourceDiscovery', `Could not read link tags: ${err.message}`);
|
|
151
|
+
return NO_LINKS;
|
|
57
152
|
}
|
|
58
153
|
}
|
|
59
154
|
|
|
60
155
|
/**
|
|
156
|
+
* Finds `rel="ard"` and `rel="ai-catalog"` in the main document's HTTP `Link` header.
|
|
61
157
|
* @param {LH.Gatherer.Context} context
|
|
62
158
|
* @param {string} finalDisplayedUrl
|
|
63
|
-
* @return {Promise<
|
|
159
|
+
* @return {Promise<ArdLinks>}
|
|
64
160
|
*/
|
|
65
|
-
static async
|
|
66
|
-
|
|
67
|
-
if (context.gatherMode !== 'navigation') return null;
|
|
161
|
+
static async getHttpHeaderLinks(context, finalDisplayedUrl) {
|
|
162
|
+
if (context.gatherMode !== 'navigation') return NO_LINKS;
|
|
68
163
|
|
|
69
164
|
try {
|
|
70
165
|
const mainResourceResponse = await context.driver.fetcher.fetchResource(finalDisplayedUrl);
|
|
71
|
-
const linkHeader = mainResourceResponse.headers?.
|
|
72
|
-
if (!linkHeader) return
|
|
166
|
+
const linkHeader = mainResourceResponse.headers?.link;
|
|
167
|
+
if (!linkHeader) return NO_LINKS;
|
|
168
|
+
|
|
169
|
+
const parsed = LinkHeader.parse(linkHeader.replace(/\n/g, ','));
|
|
170
|
+
/** @param {string} rel */
|
|
171
|
+
const getUri = rel =>
|
|
172
|
+
resolveUrl(parsed.refs.find(ref => ref.rel?.toLowerCase() === rel)?.uri, finalDisplayedUrl);
|
|
173
|
+
return {ard: getUri(REL_ARD), legacy: getUri(REL_LEGACY)};
|
|
174
|
+
} catch (err) {
|
|
175
|
+
log.verbose('AgentResourceDiscovery', `Could not read Link header: ${err.message}`);
|
|
176
|
+
return NO_LINKS;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Fetches a candidate manifest URL. Never throws; network errors are returned as a
|
|
182
|
+
* null status with an `errorMessage`.
|
|
183
|
+
* @param {LH.Gatherer.Context} context
|
|
184
|
+
* @param {string} url
|
|
185
|
+
* @return {Promise<FetchResult>}
|
|
186
|
+
*/
|
|
187
|
+
static async fetchManifest(context, url) {
|
|
188
|
+
try {
|
|
189
|
+
const {status, content, headers} = await context.driver.fetcher.fetchResource(url);
|
|
190
|
+
return {status, content, headers: headers || null};
|
|
191
|
+
} catch (err) {
|
|
192
|
+
log.error('AgentResourceDiscovery', err);
|
|
193
|
+
return {status: null, content: null, headers: null, errorMessage: err.message};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
73
196
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Tries each candidate in order and returns the first that loads. Advertised candidates
|
|
199
|
+
* that fail are recorded in `failedSources` and skipped. If nothing loads, the result
|
|
200
|
+
* describes the first advertised location that failed, or else the `/.well-known/ard.json`
|
|
201
|
+
* path consumers are required to fetch.
|
|
202
|
+
* @param {LH.Gatherer.Context} context
|
|
203
|
+
* @param {ManifestCandidate[]} candidates
|
|
204
|
+
* @return {Promise<{
|
|
205
|
+
* discoverySource: LH.Artifacts.ArdDiscoverySource,
|
|
206
|
+
* catalogUrl: string,
|
|
207
|
+
* fetchResult: FetchResult,
|
|
208
|
+
* failedSources: LH.Artifacts['AgentResourceDiscovery']['failedSources'],
|
|
209
|
+
* }>}
|
|
210
|
+
*/
|
|
211
|
+
static async resolveManifest(context, candidates) {
|
|
212
|
+
/** @type {Array<{source: LH.Artifacts.ArdDiscoverySource, url: string, advertised: boolean, fetchResult: FetchResult}>} */
|
|
213
|
+
const attempts = [];
|
|
214
|
+
const failedSources = () => attempts
|
|
215
|
+
.filter(attempt => attempt.advertised)
|
|
216
|
+
.map(({source, url, fetchResult}) => ({
|
|
217
|
+
source,
|
|
218
|
+
url,
|
|
219
|
+
status: fetchResult.status,
|
|
220
|
+
errorMessage: fetchResult.errorMessage,
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
for (const {source, url, advertised} of candidates) {
|
|
224
|
+
if (!url || attempts.some(attempt => attempt.url === url)) continue;
|
|
77
225
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
226
|
+
const fetchResult = await AgentResourceDiscovery.fetchManifest(context, url);
|
|
227
|
+
if (fetchResult.status === HTTP_OK && fetchResult.content) {
|
|
228
|
+
return {discoverySource: source, catalogUrl: url, fetchResult,
|
|
229
|
+
failedSources: failedSources()};
|
|
230
|
+
}
|
|
231
|
+
attempts.push({source, url, advertised, fetchResult});
|
|
81
232
|
}
|
|
233
|
+
|
|
234
|
+
const fallback = attempts.find(attempt => attempt.advertised) ||
|
|
235
|
+
attempts.find(attempt => attempt.source === 'wellKnown');
|
|
236
|
+
if (!fallback) throw new Error('ARD discovery attempted no candidates');
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
discoverySource: fallback.source,
|
|
240
|
+
catalogUrl: fallback.url,
|
|
241
|
+
fetchResult: fallback.fetchResult,
|
|
242
|
+
failedSources: failedSources(),
|
|
243
|
+
};
|
|
82
244
|
}
|
|
83
245
|
|
|
84
246
|
/**
|
|
247
|
+
* Resolves the manifest following ARD §5.1: explicit ARD signals first, then the
|
|
248
|
+
* `/.well-known/ard.json` path consumers MUST fetch, and only then the predecessor
|
|
249
|
+
* `ai-catalog` relation and well-known path, which consumers MAY consult.
|
|
85
250
|
* @param {LH.Gatherer.Context<'RobotsTxt'>} context
|
|
86
251
|
* @return {Promise<LH.Artifacts['AgentResourceDiscovery']>}
|
|
87
252
|
*/
|
|
88
253
|
async getArtifact(context) {
|
|
89
254
|
const {finalDisplayedUrl} = context.baseArtifacts.URL;
|
|
90
|
-
const robotsTxt = context.dependencies.RobotsTxt;
|
|
91
255
|
const robotsTxtAgentmap = AgentResourceDiscovery.getRobotsTxtAgentmap(
|
|
92
|
-
|
|
93
|
-
const [
|
|
94
|
-
AgentResourceDiscovery.
|
|
95
|
-
AgentResourceDiscovery.
|
|
256
|
+
context.dependencies.RobotsTxt, finalDisplayedUrl);
|
|
257
|
+
const [htmlLinks, httpHeaderLinks] = await Promise.all([
|
|
258
|
+
AgentResourceDiscovery.getHtmlLinksFromDom(context, finalDisplayedUrl),
|
|
259
|
+
AgentResourceDiscovery.getHttpHeaderLinks(context, finalDisplayedUrl),
|
|
96
260
|
]);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
261
|
+
|
|
262
|
+
/** @type {DiscoverySignals} */
|
|
263
|
+
const discoverySignals = {
|
|
264
|
+
robotsTxtAgentmap,
|
|
265
|
+
htmlLink: htmlLinks.ard,
|
|
266
|
+
httpHeaderLink: httpHeaderLinks.ard,
|
|
267
|
+
legacyHtmlLink: htmlLinks.legacy,
|
|
268
|
+
legacyHttpHeaderLink: httpHeaderLinks.legacy,
|
|
269
|
+
wellKnown: new URL(WELL_KNOWN_PATH, finalDisplayedUrl).href,
|
|
270
|
+
legacyWellKnown: new URL(LEGACY_WELL_KNOWN_PATH, finalDisplayedUrl).href,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const candidates = getManifestCandidates(discoverySignals);
|
|
274
|
+
const {discoverySource, catalogUrl, fetchResult, failedSources} =
|
|
275
|
+
await AgentResourceDiscovery.resolveManifest(context, candidates);
|
|
108
276
|
|
|
109
277
|
return {
|
|
110
|
-
|
|
111
|
-
content: fetchResult.content,
|
|
112
|
-
headers: fetchResult.headers || null,
|
|
278
|
+
...fetchResult,
|
|
113
279
|
catalogUrl,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
httpHeaderLink,
|
|
118
|
-
wellKnown,
|
|
119
|
-
},
|
|
120
|
-
errorMessage,
|
|
280
|
+
discoverySource,
|
|
281
|
+
discoverySignals,
|
|
282
|
+
failedSources,
|
|
121
283
|
};
|
|
122
284
|
}
|
|
123
285
|
}
|
|
@@ -95,7 +95,7 @@ class FullPageScreenshot extends BaseGatherer {
|
|
|
95
95
|
mobile: deviceMetrics.mobile,
|
|
96
96
|
deviceScaleFactor: 1,
|
|
97
97
|
height,
|
|
98
|
-
width:
|
|
98
|
+
width: deviceMetrics.width,
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
// Now that the viewport is taller, give the page some time to fetch new resources that
|
|
@@ -235,7 +235,7 @@ class FullPageScreenshot extends BaseGatherer {
|
|
|
235
235
|
mobile: deviceMetrics.mobile,
|
|
236
236
|
deviceScaleFactor: deviceMetrics.deviceScaleFactor,
|
|
237
237
|
height: deviceMetrics.height,
|
|
238
|
-
width:
|
|
238
|
+
width: deviceMetrics.width,
|
|
239
239
|
});
|
|
240
240
|
}
|
|
241
241
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export default ImageElements;
|
|
2
2
|
declare class ImageElements extends BaseGatherer {
|
|
3
|
+
/**
|
|
4
|
+
* An img size attribute prevents layout shifts if it is a non-negative integer (incl zero!).
|
|
5
|
+
* @url https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes
|
|
6
|
+
* @param {string | null} attrValue
|
|
7
|
+
* @return {boolean}
|
|
8
|
+
*/
|
|
9
|
+
static doesHtmlAttrProvideExplicitSize(attrValue: string | null): boolean;
|
|
3
10
|
/** @type {Map<string, {naturalWidth: number, naturalHeight: number}>} */
|
|
4
11
|
_naturalSizeCache: Map<string, {
|
|
5
12
|
naturalWidth: number;
|