lighthouse 12.6.1 → 12.7.0-dev.20250628
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/cli/test/smokehouse/core-tests.js +2 -0
- package/cli/test/smokehouse/frontends/lib.js +0 -2
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +1 -3
- package/cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
- package/cli/test/smokehouse/smokehouse.js +0 -2
- package/core/audits/audit.d.ts +7 -0
- package/core/audits/audit.js +17 -2
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -8
- package/core/audits/byte-efficiency/legacy-javascript.js +0 -1
- package/core/audits/byte-efficiency/render-blocking-resources.js +1 -1
- package/core/audits/deprecations.js +0 -2
- package/core/audits/insights/cache-insight.js +2 -1
- package/core/audits/insights/cls-culprits-insight.js +6 -7
- package/core/audits/insights/{interaction-to-next-paint-insight.d.ts → inp-breakdown-insight.d.ts} +3 -3
- package/core/audits/insights/{interaction-to-next-paint-insight.js → inp-breakdown-insight.js} +10 -10
- package/core/audits/insights/insight-audit.d.ts +7 -4
- package/core/audits/insights/insight-audit.js +48 -7
- package/core/audits/insights/lcp-breakdown-insight.d.ts +16 -0
- package/core/audits/insights/{lcp-phases-insight.js → lcp-breakdown-insight.js} +19 -19
- package/core/audits/insights/modern-http-insight.js +0 -2
- package/core/audits/insights/network-dependency-tree-insight.js +85 -8
- package/core/audits/insights/render-blocking-insight.js +1 -1
- package/core/audits/layout-shifts.js +5 -4
- package/core/audits/seo/crawlable-anchors.js +27 -0
- package/core/audits/seo/link-text.js +130 -83
- package/core/audits/third-party-cookies.js +0 -2
- package/core/audits/valid-source-maps.js +0 -2
- package/core/computed/js-bundles.js +0 -1
- package/core/computed/metrics/cumulative-layout-shift.js +1 -1
- package/core/computed/metrics/lantern-metric.js +4 -3
- package/core/computed/metrics/timing-summary.js +4 -1
- package/core/computed/page-dependency-graph.js +3 -3
- package/core/computed/trace-engine-result.js +1 -2
- package/core/config/config.js +1 -1
- package/core/config/default-config.js +4 -4
- package/core/config/experimental-config.js +2 -2
- package/core/config/filters.js +7 -0
- package/core/config/validation.js +4 -0
- package/core/gather/base-artifacts.js +3 -0
- package/core/gather/driver/environment.d.ts +6 -0
- package/core/gather/driver/environment.js +17 -0
- package/core/gather/driver/execution-context.d.ts +3 -1
- package/core/gather/driver/execution-context.js +3 -1
- package/core/gather/driver/navigation.js +1 -3
- package/core/gather/driver/wait-for-condition.js +0 -1
- package/core/gather/fetcher.js +0 -2
- package/core/gather/gatherers/accessibility.js +1 -1
- package/core/gather/gatherers/anchor-elements.js +61 -2
- package/core/gather/gatherers/cache-contents.js +0 -2
- package/core/gather/gatherers/css-usage.js +3 -1
- package/core/gather/gatherers/dobetterweb/doctype.js +0 -2
- package/core/gather/gatherers/dobetterweb/domstats.js +1 -1
- package/core/gather/gatherers/full-page-screenshot.js +1 -1
- package/core/gather/gatherers/image-elements.js +1 -1
- package/core/gather/gatherers/inspector-issues.js +1 -1
- package/core/gather/gatherers/link-elements.js +1 -1
- package/core/gather/gatherers/stacks.js +0 -1
- package/core/gather/gatherers/stylesheets.js +3 -1
- package/core/gather/gatherers/trace-elements.d.ts +3 -4
- package/core/gather/gatherers/trace-elements.js +13 -43
- package/core/gather/gatherers/viewport-dimensions.js +0 -2
- package/core/index.cjs +0 -1
- package/core/index.d.cts +5 -0
- package/core/lib/asset-saver.d.ts +1 -1
- package/core/lib/asset-saver.js +20 -8
- package/core/lib/bf-cache-strings.js +4 -1
- package/core/lib/deprecations-strings.d.ts +51 -47
- package/core/lib/deprecations-strings.js +14 -8
- package/core/lib/i18n/i18n.js +0 -2
- package/core/lib/lantern-trace-saver.js +1 -1
- package/core/lib/lh-error.js +0 -1
- package/core/lib/manifest-parser.js +0 -2
- package/core/lib/minify-devtoolslog.js +0 -2
- package/core/lib/sentry.d.ts +1 -1
- package/core/lib/sentry.js +2 -2
- package/core/runner.js +11 -8
- package/core/scoring.d.ts +186 -15
- package/core/scripts/manual-chrome-launcher.js +0 -1
- package/dist/report/bundle.esm.js +14 -12
- package/dist/report/flow.js +18 -16
- package/dist/report/standalone.js +15 -13
- package/eslint.config.mjs +242 -0
- package/flow-report/src/common.tsx +1 -0
- package/flow-report/src/i18n/i18n.tsx +1 -0
- package/flow-report/src/util.ts +2 -0
- package/package.json +23 -19
- package/readme.md +3 -2
- package/report/assets/styles.css +11 -9
- package/report/assets/templates.html +1 -1
- package/report/generator/file-namer.d.ts +5 -0
- package/report/generator/file-namer.js +1 -1
- package/report/generator/flow-report-assets.js +1 -1
- package/report/generator/report-assets.js +1 -1
- package/report/generator/report-generator.js +3 -3
- package/report/renderer/api.js +1 -0
- package/report/renderer/components.js +2 -2
- package/report/renderer/details-renderer.d.ts +5 -0
- package/report/renderer/details-renderer.js +35 -3
- package/report/renderer/dom.d.ts +2 -0
- package/report/renderer/dom.js +6 -0
- package/report/renderer/i18n-formatter.js +2 -1
- package/report/renderer/performance-category-renderer.js +2 -2
- package/report/renderer/report-renderer.js +1 -0
- package/report/renderer/report-ui-features.d.ts +1 -0
- package/report/renderer/report-ui-features.js +16 -0
- package/report/renderer/report-utils.js +3 -2
- package/report/renderer/text-encoding.js +0 -2
- package/report/renderer/topbar-features.js +1 -1
- package/report/types/report-renderer.d.ts +5 -0
- package/shared/localization/locales/ar-XB.json +57 -69
- package/shared/localization/locales/ar.json +57 -69
- package/shared/localization/locales/bg.json +57 -69
- package/shared/localization/locales/ca.json +57 -69
- package/shared/localization/locales/cs.json +57 -69
- package/shared/localization/locales/da.json +57 -69
- package/shared/localization/locales/de.json +57 -69
- package/shared/localization/locales/el.json +57 -69
- package/shared/localization/locales/en-GB.json +57 -69
- package/shared/localization/locales/en-US.json +73 -61
- package/shared/localization/locales/en-XL.json +73 -61
- package/shared/localization/locales/es-419.json +57 -69
- package/shared/localization/locales/es.json +56 -68
- package/shared/localization/locales/fi.json +57 -69
- package/shared/localization/locales/fil.json +57 -69
- package/shared/localization/locales/fr.json +57 -69
- package/shared/localization/locales/he.json +57 -69
- package/shared/localization/locales/hi.json +57 -69
- package/shared/localization/locales/hr.json +57 -69
- package/shared/localization/locales/hu.json +56 -68
- package/shared/localization/locales/id.json +57 -69
- package/shared/localization/locales/it.json +56 -68
- package/shared/localization/locales/ja.json +57 -69
- package/shared/localization/locales/ko.json +57 -69
- package/shared/localization/locales/lt.json +57 -69
- package/shared/localization/locales/lv.json +57 -69
- package/shared/localization/locales/nl.json +57 -69
- package/shared/localization/locales/no.json +57 -69
- package/shared/localization/locales/pl.json +56 -68
- package/shared/localization/locales/pt-PT.json +57 -69
- package/shared/localization/locales/pt.json +57 -69
- package/shared/localization/locales/ro.json +57 -69
- package/shared/localization/locales/ru.json +58 -70
- package/shared/localization/locales/sk.json +57 -69
- package/shared/localization/locales/sl.json +56 -68
- package/shared/localization/locales/sr-Latn.json +57 -69
- package/shared/localization/locales/sr.json +57 -69
- package/shared/localization/locales/sv.json +57 -69
- package/shared/localization/locales/ta.json +57 -69
- package/shared/localization/locales/te.json +57 -69
- package/shared/localization/locales/th.json +56 -68
- package/shared/localization/locales/tr.json +57 -69
- package/shared/localization/locales/uk.json +57 -69
- package/shared/localization/locales/vi.json +57 -69
- package/shared/localization/locales/zh-HK.json +57 -69
- package/shared/localization/locales/zh-TW.json +56 -68
- package/shared/localization/locales/zh.json +57 -69
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/types/artifacts.d.ts +8 -1
- package/types/internal/test.d.ts +1 -1
- package/types/lhr/audit-details.d.ts +13 -3
- package/types/lhr/lhr.d.ts +8 -1
- package/core/audits/insights/lcp-phases-insight.d.ts +0 -16
package/core/config/filters.js
CHANGED
|
@@ -13,6 +13,7 @@ const baseArtifactKeySource = {
|
|
|
13
13
|
fetchTime: '',
|
|
14
14
|
LighthouseRunWarnings: '',
|
|
15
15
|
BenchmarkIndex: '',
|
|
16
|
+
HostDPR: '',
|
|
16
17
|
settings: '',
|
|
17
18
|
Timing: '',
|
|
18
19
|
URL: '',
|
|
@@ -263,6 +264,12 @@ function filterConfigByGatherMode(resolvedConfig, mode) {
|
|
|
263
264
|
*/
|
|
264
265
|
function filterConfigByExplicitFilters(resolvedConfig, filters) {
|
|
265
266
|
const {onlyAudits, onlyCategories, skipAudits} = filters;
|
|
267
|
+
if (onlyAudits && !onlyAudits.length) {
|
|
268
|
+
throw new Error(`onlyAudits cannot be an empty array.`);
|
|
269
|
+
}
|
|
270
|
+
if (onlyCategories && !onlyCategories.length) {
|
|
271
|
+
throw new Error(`onlyCategories cannot be an empty array.`);
|
|
272
|
+
}
|
|
266
273
|
|
|
267
274
|
warnOnUnknownOnlyCategories(resolvedConfig.categories, onlyCategories);
|
|
268
275
|
|
|
@@ -36,6 +36,10 @@ function isValidArtifactDependency(dependent, dependency) {
|
|
|
36
36
|
* @param {string} pluginName
|
|
37
37
|
*/
|
|
38
38
|
function assertValidPluginName(config, pluginName) {
|
|
39
|
+
const parts = pluginName.split('/');
|
|
40
|
+
if (parts.length === 2) {
|
|
41
|
+
pluginName = parts[1];
|
|
42
|
+
}
|
|
39
43
|
if (!pluginName.startsWith('lighthouse-plugin-')) {
|
|
40
44
|
throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
|
|
41
45
|
}
|
|
@@ -9,6 +9,7 @@ import {isEqual} from 'lodash-es';
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
getBrowserVersion, getBenchmarkIndex, getEnvironmentWarnings,
|
|
12
|
+
getDevicePixelRatio,
|
|
12
13
|
} from './driver/environment.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
async function getBaseArtifacts(resolvedConfig, driver, context) {
|
|
21
22
|
const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
|
|
22
23
|
const {userAgent, product} = await getBrowserVersion(driver.defaultSession);
|
|
24
|
+
const HostDPR = await getDevicePixelRatio(driver.executionContext);
|
|
23
25
|
|
|
24
26
|
return {
|
|
25
27
|
// Meta artifacts.
|
|
@@ -29,6 +31,7 @@ async function getBaseArtifacts(resolvedConfig, driver, context) {
|
|
|
29
31
|
settings: resolvedConfig.settings,
|
|
30
32
|
// Environment artifacts that can always be computed.
|
|
31
33
|
BenchmarkIndex,
|
|
34
|
+
HostDPR,
|
|
32
35
|
HostUserAgent: userAgent,
|
|
33
36
|
HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
|
|
34
37
|
'mobile' : 'desktop',
|
|
@@ -14,6 +14,12 @@ export function getBrowserVersion(session: LH.Gatherer.ProtocolSession): Promise
|
|
|
14
14
|
* @return {Promise<number>}
|
|
15
15
|
*/
|
|
16
16
|
export function getBenchmarkIndex(executionContext: LH.Gatherer.Driver["executionContext"]): Promise<number>;
|
|
17
|
+
/**
|
|
18
|
+
* Computes the observed DPR.
|
|
19
|
+
* @param {LH.Gatherer.Driver['executionContext']} executionContext
|
|
20
|
+
* @return {Promise<number>}
|
|
21
|
+
*/
|
|
22
|
+
export function getDevicePixelRatio(executionContext: LH.Gatherer.Driver["executionContext"]): Promise<number>;
|
|
17
23
|
/**
|
|
18
24
|
* Returns a warning if the host device appeared to be underpowered according to BenchmarkIndex.
|
|
19
25
|
*
|
|
@@ -58,6 +58,22 @@ async function getBenchmarkIndex(executionContext) {
|
|
|
58
58
|
return indexVal;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Computes the observed DPR.
|
|
63
|
+
* @param {LH.Gatherer.Driver['executionContext']} executionContext
|
|
64
|
+
* @return {Promise<number>}
|
|
65
|
+
*/
|
|
66
|
+
async function getDevicePixelRatio(executionContext) {
|
|
67
|
+
const status = {msg: 'Host device pixel ratio', id: 'lh:gather:getDevicePixelRatio'};
|
|
68
|
+
log.time(status);
|
|
69
|
+
// eslint-disable-next-line no-undef
|
|
70
|
+
const indexVal = await executionContext.evaluate(() => devicePixelRatio, {
|
|
71
|
+
args: [],
|
|
72
|
+
});
|
|
73
|
+
log.timeEnd(status);
|
|
74
|
+
return indexVal;
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
/**
|
|
62
78
|
* Returns a warning if the host device appeared to be underpowered according to BenchmarkIndex.
|
|
63
79
|
*
|
|
@@ -100,6 +116,7 @@ export {
|
|
|
100
116
|
UIStrings,
|
|
101
117
|
getBrowserVersion,
|
|
102
118
|
getBenchmarkIndex,
|
|
119
|
+
getDevicePixelRatio,
|
|
103
120
|
getSlowHostCpuWarning,
|
|
104
121
|
getEnvironmentWarnings,
|
|
105
122
|
};
|
|
@@ -58,11 +58,13 @@ export class ExecutionContext {
|
|
|
58
58
|
*/
|
|
59
59
|
_evaluateInContext(expression: string, contextId: number | undefined, timeout: number): Promise<any>;
|
|
60
60
|
/**
|
|
61
|
-
* Note: Prefer `evaluate` instead.
|
|
62
61
|
* Evaluate an expression in the context of the current page. If useIsolation is true, the expression
|
|
63
62
|
* will be evaluated in a content script that has access to the page's DOM but whose JavaScript state
|
|
64
63
|
* is completely separate.
|
|
65
64
|
* Returns a promise that resolves on the expression's value.
|
|
65
|
+
*
|
|
66
|
+
* @deprecated Use `evaluate` instead! It has a better API, and unlike `evaluateAsync` doesn't sometimes
|
|
67
|
+
* execute invalid code.
|
|
66
68
|
* @param {string} expression
|
|
67
69
|
* @param {{useIsolation?: boolean}=} options
|
|
68
70
|
* @return {Promise<*>}
|
|
@@ -151,11 +151,13 @@ class ExecutionContext {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
|
-
* Note: Prefer `evaluate` instead.
|
|
155
154
|
* Evaluate an expression in the context of the current page. If useIsolation is true, the expression
|
|
156
155
|
* will be evaluated in a content script that has access to the page's DOM but whose JavaScript state
|
|
157
156
|
* is completely separate.
|
|
158
157
|
* Returns a promise that resolves on the expression's value.
|
|
158
|
+
*
|
|
159
|
+
* @deprecated Use `evaluate` instead! It has a better API, and unlike `evaluateAsync` doesn't sometimes
|
|
160
|
+
* execute invalid code.
|
|
159
161
|
* @param {string} expression
|
|
160
162
|
* @param {{useIsolation?: boolean}=} options
|
|
161
163
|
* @return {Promise<*>}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import log from 'lighthouse-logger';
|
|
8
8
|
|
|
9
|
-
import {waitForFullyLoaded, waitForFrameNavigated, waitForUserToContinue} from './wait-for-condition.js';
|
|
9
|
+
import {waitForFullyLoaded, waitForFrameNavigated, waitForUserToContinue} from './wait-for-condition.js';
|
|
10
10
|
import * as constants from '../../config/constants.js';
|
|
11
11
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
12
12
|
import UrlUtils from '../../lib/url-utils.js';
|
|
@@ -43,7 +43,6 @@ const DEFAULT_CPU_QUIET_THRESHOLD = 0;
|
|
|
43
43
|
|
|
44
44
|
/** @param {NavigationOptions} options */
|
|
45
45
|
function resolveWaitForFullyLoadedOptions(options) {
|
|
46
|
-
/* eslint-disable max-len */
|
|
47
46
|
let {pauseAfterFcpMs, pauseAfterLoadMs, networkQuietThresholdMs, cpuQuietThresholdMs} = options;
|
|
48
47
|
let maxWaitMs = options.maxWaitForLoad;
|
|
49
48
|
let maxFCPMs = options.maxWaitForFcp;
|
|
@@ -56,7 +55,6 @@ function resolveWaitForFullyLoadedOptions(options) {
|
|
|
56
55
|
if (typeof cpuQuietThresholdMs !== 'number') cpuQuietThresholdMs = DEFAULT_CPU_QUIET_THRESHOLD;
|
|
57
56
|
if (typeof maxWaitMs !== 'number') maxWaitMs = constants.defaultSettings.maxWaitForLoad;
|
|
58
57
|
if (typeof maxFCPMs !== 'number') maxFCPMs = constants.defaultSettings.maxWaitForFcp;
|
|
59
|
-
/* eslint-enable max-len */
|
|
60
58
|
|
|
61
59
|
if (!options.waitUntil.includes('fcp')) maxFCPMs = undefined;
|
|
62
60
|
|
|
@@ -138,7 +138,6 @@ function waitForNetworkIdle(session, networkMonitor, networkQuietOptions) {
|
|
|
138
138
|
/** @type {Promise<void>} */
|
|
139
139
|
const promise = new Promise((resolve, reject) => {
|
|
140
140
|
const onIdle = () => {
|
|
141
|
-
// eslint-disable-next-line no-use-before-define
|
|
142
141
|
networkMonitor.once(busyEvent, onBusy);
|
|
143
142
|
idleTimeout = setTimeout(() => {
|
|
144
143
|
cancel();
|
package/core/gather/fetcher.js
CHANGED
|
@@ -10,8 +10,6 @@ import BaseGatherer from '../base-gatherer.js';
|
|
|
10
10
|
import {pageFunctions} from '../../lib/page-functions.js';
|
|
11
11
|
import {resolveDevtoolsNodePathToObjectId} from '../driver/dom.js';
|
|
12
12
|
|
|
13
|
-
/* eslint-env browser, node */
|
|
14
|
-
|
|
15
13
|
/**
|
|
16
14
|
* Function that is stringified and run in the page to collect anchor elements.
|
|
17
15
|
* Additional complexity is introduced because anchors can be HTML or SVG elements.
|
|
@@ -38,10 +36,49 @@ function collectAnchorElements() {
|
|
|
38
36
|
return onclick.slice(0, 1024);
|
|
39
37
|
}
|
|
40
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {HTMLElement|SVGElement} node
|
|
41
|
+
* @return {string|null}
|
|
42
|
+
*/
|
|
43
|
+
function getLangOfInnerText(node) {
|
|
44
|
+
let curNodeLang = null;
|
|
45
|
+
|
|
46
|
+
// If we find multiple languages within this element, return null.
|
|
47
|
+
for (const child of node.querySelectorAll('*')) {
|
|
48
|
+
if (!child.textContent) continue;
|
|
49
|
+
|
|
50
|
+
const childLang = child.closest('[lang]')?.getAttribute('lang');
|
|
51
|
+
if (!childLang) continue;
|
|
52
|
+
|
|
53
|
+
if (!curNodeLang) {
|
|
54
|
+
curNodeLang = childLang;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (curNodeLang.split('-')[0] !== childLang.split('-')[0]) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return curNodeLang ?? node.closest('[lang]')?.getAttribute('lang') ?? null;
|
|
64
|
+
}
|
|
65
|
+
|
|
41
66
|
/** @type {Array<HTMLAnchorElement|SVGAElement>} */
|
|
42
67
|
// @ts-expect-error - put into scope via stringification
|
|
43
68
|
const anchorElements = getElementsInDocument('a'); // eslint-disable-line no-undef
|
|
44
69
|
|
|
70
|
+
// Check, if document has only one lang attribute in opening html or in body tag. If so,
|
|
71
|
+
// there is no need to run the `getLangOfInnerText()` function with multiple
|
|
72
|
+
// possible DOM traversals
|
|
73
|
+
/** @type {Array<HTMLElement|SVGElement>} */
|
|
74
|
+
// @ts-expect-error - put into scope via stringification
|
|
75
|
+
const langElements = getElementsInDocument('[lang]'); // eslint-disable-line no-undef
|
|
76
|
+
const documentHasSingleLang = langElements.length === 1 &&
|
|
77
|
+
(langElements[0].nodeName === 'BODY' || langElements[0].nodeName === 'HTML');
|
|
78
|
+
const singleLang = documentHasSingleLang ? langElements[0].getAttribute('lang') : null;
|
|
79
|
+
|
|
80
|
+
// TODO: consider Content-Language.
|
|
81
|
+
|
|
45
82
|
return anchorElements.map(node => {
|
|
46
83
|
if (node instanceof HTMLAnchorElement) {
|
|
47
84
|
return {
|
|
@@ -51,9 +88,11 @@ function collectAnchorElements() {
|
|
|
51
88
|
role: node.getAttribute('role') || '',
|
|
52
89
|
name: node.name,
|
|
53
90
|
text: node.innerText, // we don't want to return hidden text, so use innerText
|
|
91
|
+
textLang: singleLang ?? getLangOfInnerText(node) ?? undefined,
|
|
54
92
|
rel: node.rel,
|
|
55
93
|
target: node.target,
|
|
56
94
|
id: node.getAttribute('id') || '',
|
|
95
|
+
attributeNames: node.getAttributeNames(),
|
|
57
96
|
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
58
97
|
node: getNodeDetails(node),
|
|
59
98
|
};
|
|
@@ -65,9 +104,11 @@ function collectAnchorElements() {
|
|
|
65
104
|
onclick: getTruncatedOnclick(node),
|
|
66
105
|
role: node.getAttribute('role') || '',
|
|
67
106
|
text: node.textContent || '',
|
|
107
|
+
textLang: singleLang ?? getLangOfInnerText(node) ?? undefined,
|
|
68
108
|
rel: '',
|
|
69
109
|
target: node.target.baseVal || '',
|
|
70
110
|
id: node.getAttribute('id') || '',
|
|
111
|
+
attributeNames: node.getAttributeNames(),
|
|
71
112
|
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
72
113
|
node: getNodeDetails(node),
|
|
73
114
|
};
|
|
@@ -119,9 +160,27 @@ class AnchorElements extends BaseGatherer {
|
|
|
119
160
|
const anchorsWithEventListeners = anchors.map(async anchor => {
|
|
120
161
|
const listeners = await getEventListeners(session, anchor.node.devtoolsNodePath);
|
|
121
162
|
|
|
163
|
+
/** @type {Set<{type: string}>} */
|
|
164
|
+
const ancestorListeners = new Set();
|
|
165
|
+
const splitPath = anchor.node.devtoolsNodePath.split(',');
|
|
166
|
+
const ancestorListenerPromises = [];
|
|
167
|
+
while (splitPath.length >= 2) {
|
|
168
|
+
splitPath.length -= 2;
|
|
169
|
+
const path = splitPath.join(',');
|
|
170
|
+
const promise = getEventListeners(session, path).then(listeners => {
|
|
171
|
+
for (const listener of listeners) {
|
|
172
|
+
ancestorListeners.add(listener);
|
|
173
|
+
}
|
|
174
|
+
}).catch(() => {});
|
|
175
|
+
ancestorListenerPromises.push(promise);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await Promise.all(ancestorListenerPromises);
|
|
179
|
+
|
|
122
180
|
return {
|
|
123
181
|
...anchor,
|
|
124
182
|
listeners,
|
|
183
|
+
ancestorListeners: Array.from(ancestorListeners),
|
|
125
184
|
};
|
|
126
185
|
});
|
|
127
186
|
|
|
@@ -30,7 +30,9 @@ class CSSUsage extends BaseGatherer {
|
|
|
30
30
|
|
|
31
31
|
// Force style to recompute.
|
|
32
32
|
// Doesn't appear to be necessary in newer versions of Chrome.
|
|
33
|
-
await executionContext.
|
|
33
|
+
await executionContext.evaluate(() => window.getComputedStyle(document.body), {
|
|
34
|
+
args: [],
|
|
35
|
+
});
|
|
34
36
|
|
|
35
37
|
const {ruleUsage} = await session.sendCommand('CSS.stopRuleUsageTracking');
|
|
36
38
|
await session.sendCommand('CSS.disable');
|
|
@@ -15,7 +15,7 @@ import BaseGatherer from '../base-gatherer.js';
|
|
|
15
15
|
import {pageFunctions} from '../../lib/page-functions.js';
|
|
16
16
|
import * as FontSize from './seo/font-size.js';
|
|
17
17
|
|
|
18
|
-
/* global
|
|
18
|
+
/* global getElementsInDocument, getNodeDetails */
|
|
19
19
|
|
|
20
20
|
/** @param {Element} element */
|
|
21
21
|
/* c8 ignore start */
|
|
@@ -81,7 +81,7 @@ class InspectorIssues extends BaseGatherer {
|
|
|
81
81
|
propertyRuleIssue: [],
|
|
82
82
|
quirksModeIssue: [],
|
|
83
83
|
cookieIssue: [],
|
|
84
|
-
|
|
84
|
+
elementAccessibilityIssue: [],
|
|
85
85
|
sharedArrayBufferIssue: [],
|
|
86
86
|
sharedDictionaryIssue: [],
|
|
87
87
|
stylesheetLoadingIssue: [],
|
|
@@ -13,7 +13,7 @@ import {MainResource} from '../../computed/main-resource.js';
|
|
|
13
13
|
import {Util} from '../../../shared/util.js';
|
|
14
14
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
15
15
|
|
|
16
|
-
/* globals
|
|
16
|
+
/* globals getNodeDetails */
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @fileoverview
|
|
@@ -74,7 +74,9 @@ class Stylesheets extends BaseGatherer {
|
|
|
74
74
|
|
|
75
75
|
// Force style to recompute.
|
|
76
76
|
// Doesn't appear to be necessary in newer versions of Chrome.
|
|
77
|
-
await executionContext.
|
|
77
|
+
await executionContext.evaluate(() => window.getComputedStyle(document.body), {
|
|
78
|
+
args: [],
|
|
79
|
+
});
|
|
78
80
|
|
|
79
81
|
session.off('CSS.styleSheetAdded', this._onStylesheetAdded);
|
|
80
82
|
|
|
@@ -23,21 +23,20 @@ declare class TraceElements extends BaseGatherer {
|
|
|
23
23
|
*
|
|
24
24
|
* @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
|
|
25
25
|
* @param {Map<number, number>} impactByNodeId
|
|
26
|
-
* @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
|
|
27
26
|
* @return {number|undefined}
|
|
28
27
|
*/
|
|
29
|
-
static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number
|
|
28
|
+
static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>): number | undefined;
|
|
30
29
|
/**
|
|
31
30
|
* This function finds the top (up to 15) layout shifts on the page, and returns
|
|
32
31
|
* the id of the largest impacted node of each shift, along with any related nodes
|
|
33
32
|
* that may have caused the shift.
|
|
34
33
|
*
|
|
35
34
|
* @param {LH.Trace} trace
|
|
36
|
-
* @param {LH.Artifacts.TraceEngineResult['
|
|
35
|
+
* @param {LH.Artifacts.TraceEngineResult['parsedTrace']} traceEngineResult
|
|
37
36
|
* @param {LH.Gatherer.Context} context
|
|
38
37
|
* @return {Promise<Array<{nodeId: number}>>}
|
|
39
38
|
*/
|
|
40
|
-
static getTopLayoutShifts(trace: LH.Trace, traceEngineResult: LH.Artifacts.TraceEngineResult["
|
|
39
|
+
static getTopLayoutShifts(trace: LH.Trace, traceEngineResult: LH.Artifacts.TraceEngineResult["parsedTrace"], context: LH.Gatherer.Context): Promise<Array<{
|
|
41
40
|
nodeId: number;
|
|
42
41
|
}>>;
|
|
43
42
|
/**
|
|
@@ -35,7 +35,7 @@ const MAX_LAYOUT_SHIFTS = 15;
|
|
|
35
35
|
*/
|
|
36
36
|
/* c8 ignore start */
|
|
37
37
|
function getNodeDetailsData() {
|
|
38
|
-
const elem = this.nodeType === document.ELEMENT_NODE ? this : this.parentElement;
|
|
38
|
+
const elem = this.nodeType === document.ELEMENT_NODE ? this : this.parentElement;
|
|
39
39
|
let traceElement;
|
|
40
40
|
if (elem) {
|
|
41
41
|
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
@@ -152,49 +152,19 @@ class TraceElements extends BaseGatherer {
|
|
|
152
152
|
*
|
|
153
153
|
* @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
|
|
154
154
|
* @param {Map<number, number>} impactByNodeId
|
|
155
|
-
* @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
|
|
156
155
|
* @return {number|undefined}
|
|
157
156
|
*/
|
|
158
|
-
static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
biggestImpactNodeScore = impactScore;
|
|
167
|
-
}
|
|
157
|
+
static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId) {
|
|
158
|
+
let biggestImpactNodeId;
|
|
159
|
+
let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
|
|
160
|
+
for (const node of impactedNodes) {
|
|
161
|
+
const impactScore = impactByNodeId.get(node.node_id);
|
|
162
|
+
if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
|
|
163
|
+
biggestImpactNodeId = node.node_id;
|
|
164
|
+
biggestImpactNodeScore = impactScore;
|
|
168
165
|
}
|
|
169
|
-
return biggestImpactNodeId;
|
|
170
|
-
} catch (err) {
|
|
171
|
-
// See https://github.com/GoogleChrome/lighthouse/issues/15870
|
|
172
|
-
// `impactedNodes` should always be an array here, but it can randomly be something else for
|
|
173
|
-
// currently unknown reasons. This exception handling will help us identify what
|
|
174
|
-
// `impactedNodes` really is and also prevent the error from being fatal.
|
|
175
|
-
|
|
176
|
-
// It's possible `impactedNodes` is not JSON serializable, so let's add more supplemental
|
|
177
|
-
// fields just in case.
|
|
178
|
-
const impactedNodesType = typeof impactedNodes;
|
|
179
|
-
const impactedNodesClassName = impactedNodes?.constructor?.name;
|
|
180
|
-
|
|
181
|
-
let impactedNodesJson;
|
|
182
|
-
let eventJson;
|
|
183
|
-
try {
|
|
184
|
-
impactedNodesJson = JSON.parse(JSON.stringify(impactedNodes));
|
|
185
|
-
eventJson = JSON.parse(JSON.stringify(event));
|
|
186
|
-
} catch {}
|
|
187
|
-
|
|
188
|
-
Sentry.captureException(err, {
|
|
189
|
-
extra: {
|
|
190
|
-
impactedNodes: impactedNodesJson,
|
|
191
|
-
event: eventJson,
|
|
192
|
-
impactedNodesType,
|
|
193
|
-
impactedNodesClassName,
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
|
-
return;
|
|
197
166
|
}
|
|
167
|
+
return biggestImpactNodeId;
|
|
198
168
|
}
|
|
199
169
|
|
|
200
170
|
/**
|
|
@@ -203,7 +173,7 @@ class TraceElements extends BaseGatherer {
|
|
|
203
173
|
* that may have caused the shift.
|
|
204
174
|
*
|
|
205
175
|
* @param {LH.Trace} trace
|
|
206
|
-
* @param {LH.Artifacts.TraceEngineResult['
|
|
176
|
+
* @param {LH.Artifacts.TraceEngineResult['parsedTrace']} traceEngineResult
|
|
207
177
|
* @param {LH.Gatherer.Context} context
|
|
208
178
|
* @return {Promise<Array<{nodeId: number}>>}
|
|
209
179
|
*/
|
|
@@ -222,7 +192,7 @@ class TraceElements extends BaseGatherer {
|
|
|
222
192
|
const nodeIds = [];
|
|
223
193
|
const impactedNodes = event.args.data.impacted_nodes || [];
|
|
224
194
|
const biggestImpactedNodeId =
|
|
225
|
-
this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId
|
|
195
|
+
this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId);
|
|
226
196
|
if (biggestImpactedNodeId !== undefined) {
|
|
227
197
|
nodeIds.push(biggestImpactedNodeId);
|
|
228
198
|
}
|
|
@@ -399,7 +369,7 @@ class TraceElements extends BaseGatherer {
|
|
|
399
369
|
traceEngineResult, navigationId);
|
|
400
370
|
const lcpNodeData = await TraceElements.getLcpElement(trace, context);
|
|
401
371
|
const shiftsData = await TraceElements.getTopLayoutShifts(
|
|
402
|
-
trace, traceEngineResult.
|
|
372
|
+
trace, traceEngineResult.parsedTrace, context);
|
|
403
373
|
const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
|
|
404
374
|
const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
|
|
405
375
|
|
package/core/index.cjs
CHANGED
package/core/index.d.cts
CHANGED
|
@@ -102,5 +102,5 @@ export function normalizeTimingEntries(timings: LH.Result.MeasureEntry[]): void;
|
|
|
102
102
|
/**
|
|
103
103
|
* @param {LH.Result} lhr
|
|
104
104
|
*/
|
|
105
|
-
export function
|
|
105
|
+
export function elideLhrErrorStacks(lhr: LH.Result): void;
|
|
106
106
|
//# sourceMappingURL=asset-saver.d.ts.map
|
package/core/lib/asset-saver.js
CHANGED
|
@@ -515,19 +515,31 @@ function normalizeTimingEntries(timings) {
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
/**
|
|
518
|
-
* @param {
|
|
518
|
+
* @param {string} errorStack
|
|
519
|
+
* @return {string}
|
|
519
520
|
*/
|
|
520
|
-
function
|
|
521
|
+
function elideErrorStack(errorStack) {
|
|
521
522
|
const baseCallFrameUrl = url.pathToFileURL(LH_ROOT);
|
|
523
|
+
return errorStack
|
|
524
|
+
// Make paths relative to the repo root.
|
|
525
|
+
.replaceAll(baseCallFrameUrl.pathname, '')
|
|
526
|
+
// Remove line/col info.
|
|
527
|
+
.replaceAll(/:\d+:\d+/g, '');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* @param {LH.Result} lhr
|
|
532
|
+
*/
|
|
533
|
+
function elideLhrErrorStacks(lhr) {
|
|
522
534
|
for (const auditResult of Object.values(lhr.audits)) {
|
|
523
535
|
if (auditResult.errorStack) {
|
|
524
|
-
auditResult.errorStack = auditResult.errorStack
|
|
525
|
-
// Make paths relative to the repo root.
|
|
526
|
-
.replaceAll(baseCallFrameUrl.pathname, '')
|
|
527
|
-
// Remove line/col info.
|
|
528
|
-
.replaceAll(/:\d+:\d+/g, '');
|
|
536
|
+
auditResult.errorStack = elideErrorStack(auditResult.errorStack);
|
|
529
537
|
}
|
|
530
538
|
}
|
|
539
|
+
|
|
540
|
+
if (lhr.runtimeError?.errorStack) {
|
|
541
|
+
lhr.runtimeError.errorStack = elideErrorStack(lhr.runtimeError.errorStack);
|
|
542
|
+
}
|
|
531
543
|
}
|
|
532
544
|
|
|
533
545
|
export {
|
|
@@ -543,5 +555,5 @@ export {
|
|
|
543
555
|
saveLanternNetworkData,
|
|
544
556
|
stringifyReplacer,
|
|
545
557
|
normalizeTimingEntries,
|
|
546
|
-
|
|
558
|
+
elideLhrErrorStacks,
|
|
547
559
|
};
|
|
@@ -704,7 +704,10 @@ const NotRestoredReasonDescription = {
|
|
|
704
704
|
WebViewSafeBrowsingAllowlistChanged: {name: ('WebViewSafeBrowsingAllowlistChanged')},
|
|
705
705
|
WebViewDocumentStartJavascriptChanged: {name: ('WebViewDocumentStartJavascriptChanged')},
|
|
706
706
|
CacheControlNoStoreDeviceBoundSessionTerminated: {name: str_(UIStrings.cacheControlNoStore)},
|
|
707
|
-
|
|
707
|
+
CacheLimitPrunedOnModerateMemoryPressure:
|
|
708
|
+
{name: ('CacheLimitPrunedOnModerateMemoryPressure')},
|
|
709
|
+
CacheLimitPrunedOnCriticalMemoryPressure:
|
|
710
|
+
{name: ('CacheLimitPrunedOnCriticalMemoryPressure')},
|
|
708
711
|
};
|
|
709
712
|
|
|
710
713
|
export {
|