lighthouse 13.5.0 → 13.6.0-dev.20261027
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 +8 -28
- package/core/config/config.js +1 -2
- package/core/config/constants.d.ts +2 -28
- 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/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 +0 -1
- package/core/lib/i18n/i18n.js +17 -11
- 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 +1 -2
- 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/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
|
@@ -5,19 +5,31 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import BaseGatherer from '../base-gatherer.js';
|
|
8
|
+
import Trace from './trace.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} TraceScript
|
|
12
|
+
* @property {string} scriptId
|
|
13
|
+
* @property {string} [sourceText]
|
|
14
|
+
* @property {string[]} [chunks]
|
|
15
|
+
* @property {string} [url]
|
|
16
|
+
* @property {string} [sourceUrl]
|
|
17
|
+
* @property {number} [executionContextId]
|
|
18
|
+
* @property {string} [frame]
|
|
19
|
+
*/
|
|
8
20
|
|
|
9
21
|
/**
|
|
10
22
|
* @template T, U
|
|
11
23
|
* @param {Array<T>} values
|
|
12
|
-
* @param {(value: T) => Promise<U>} promiseMapper
|
|
24
|
+
* @param {(value: T, index: number) => Promise<U>} promiseMapper
|
|
13
25
|
* @param {boolean} runInSeries
|
|
14
26
|
* @return {Promise<Array<U>>}
|
|
15
27
|
*/
|
|
16
28
|
async function runInSeriesOrParallel(values, promiseMapper, runInSeries) {
|
|
17
29
|
if (runInSeries) {
|
|
18
30
|
const results = [];
|
|
19
|
-
for (
|
|
20
|
-
const result = await promiseMapper(
|
|
31
|
+
for (let i = 0; i < values.length; i++) {
|
|
32
|
+
const result = await promiseMapper(values[i], i);
|
|
21
33
|
results.push(result);
|
|
22
34
|
}
|
|
23
35
|
return results;
|
|
@@ -27,6 +39,121 @@ async function runInSeriesOrParallel(values, promiseMapper, runInSeries) {
|
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Extracts script source text and disambiguating metadata from trace rundown events,
|
|
44
|
+
* grouped by `scriptId`.
|
|
45
|
+
* @param {LH.TraceEvent[]} traceEvents
|
|
46
|
+
* @return {Map<string, TraceScript[]>}
|
|
47
|
+
*/
|
|
48
|
+
function collectTraceScripts(traceEvents) {
|
|
49
|
+
/** @type {Map<string, TraceScript>} */
|
|
50
|
+
const byIsolateAndScriptId = new Map();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {NonNullable<LH.TraceEvent['args']['data']>} data
|
|
54
|
+
* @return {TraceScript}
|
|
55
|
+
*/
|
|
56
|
+
const getOrCreateRecord = (data) => {
|
|
57
|
+
const scriptId = String(data.scriptId);
|
|
58
|
+
const key = `${data.isolate ?? ''}.${scriptId}`;
|
|
59
|
+
let record = byIsolateAndScriptId.get(key);
|
|
60
|
+
if (!record) {
|
|
61
|
+
record = {scriptId};
|
|
62
|
+
byIsolateAndScriptId.set(key, record);
|
|
63
|
+
}
|
|
64
|
+
return record;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
for (const event of traceEvents) {
|
|
68
|
+
const data = event.args?.data;
|
|
69
|
+
if (!data || data.scriptId === undefined) continue;
|
|
70
|
+
|
|
71
|
+
if (event.cat === 'disabled-by-default-devtools.target-rundown') {
|
|
72
|
+
if (typeof data.frame === 'string') {
|
|
73
|
+
getOrCreateRecord(data).frame = data.frame;
|
|
74
|
+
}
|
|
75
|
+
} else if (event.cat === 'disabled-by-default-devtools.v8-source-rundown' &&
|
|
76
|
+
event.name === 'ScriptCatchup') {
|
|
77
|
+
const record = getOrCreateRecord(data);
|
|
78
|
+
if (typeof data.url === 'string') record.url = data.url;
|
|
79
|
+
if (typeof data.sourceUrl === 'string') record.sourceUrl = data.sourceUrl;
|
|
80
|
+
if (typeof data.executionContextId === 'number') {
|
|
81
|
+
record.executionContextId = data.executionContextId;
|
|
82
|
+
}
|
|
83
|
+
} else if (event.cat === 'disabled-by-default-devtools.v8-source-rundown-sources' &&
|
|
84
|
+
typeof data.sourceText === 'string') {
|
|
85
|
+
const record = getOrCreateRecord(data);
|
|
86
|
+
if (event.name === 'ScriptCatchup') {
|
|
87
|
+
record.sourceText = data.sourceText;
|
|
88
|
+
} else if (event.name === 'LargeScriptCatchup') {
|
|
89
|
+
if (!record.chunks || data.splitIndex === 0) record.chunks = [];
|
|
90
|
+
record.chunks.push(data.sourceText);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @type {Map<string, TraceScript[]>} */
|
|
96
|
+
const byScriptId = new Map();
|
|
97
|
+
for (const record of byIsolateAndScriptId.values()) {
|
|
98
|
+
if (record.chunks) record.sourceText = record.chunks.join('');
|
|
99
|
+
if (record.sourceText === undefined) continue;
|
|
100
|
+
|
|
101
|
+
let list = byScriptId.get(record.scriptId);
|
|
102
|
+
if (!list) {
|
|
103
|
+
list = [];
|
|
104
|
+
byScriptId.set(record.scriptId, list);
|
|
105
|
+
}
|
|
106
|
+
list.push(record);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return byScriptId;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {TraceScript} traceScript
|
|
114
|
+
* @param {LH.Crdp.Debugger.ScriptParsedEvent} script
|
|
115
|
+
* @return {boolean}
|
|
116
|
+
*/
|
|
117
|
+
function matchesScriptParsedEvent(traceScript, script) {
|
|
118
|
+
const frameId = script.executionContextAuxData?.frameId;
|
|
119
|
+
if (frameId && traceScript.frame !== undefined && traceScript.frame !== frameId) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const scriptUrl = script.embedderName || script.url;
|
|
123
|
+
if (traceScript.url !== undefined &&
|
|
124
|
+
traceScript.url !== scriptUrl &&
|
|
125
|
+
traceScript.sourceUrl !== script.url) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (script.executionContextId !== undefined &&
|
|
129
|
+
traceScript.executionContextId !== undefined &&
|
|
130
|
+
traceScript.executionContextId !== script.executionContextId) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {LH.Crdp.Debugger.ScriptParsedEvent} script
|
|
138
|
+
* @param {Map<string, TraceScript[]>} traceScriptsByScriptId
|
|
139
|
+
* @return {string|undefined}
|
|
140
|
+
*/
|
|
141
|
+
function findTraceScriptSource(script, traceScriptsByScriptId) {
|
|
142
|
+
const candidates = traceScriptsByScriptId.get(script.scriptId);
|
|
143
|
+
if (!candidates) return undefined;
|
|
144
|
+
|
|
145
|
+
const matches = candidates.filter((candidate) => matchesScriptParsedEvent(candidate, script));
|
|
146
|
+
if (matches.length === 1) return matches[0].sourceText;
|
|
147
|
+
|
|
148
|
+
const frameId = script.executionContextAuxData?.frameId;
|
|
149
|
+
if (matches.length > 1 && frameId) {
|
|
150
|
+
const frameMatches = matches.filter((candidate) => candidate.frame === frameId);
|
|
151
|
+
if (frameMatches.length === 1) return frameMatches[0].sourceText;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
30
157
|
/**
|
|
31
158
|
* Returns true if the script was created via our own calls
|
|
32
159
|
* to Runtime.evaluate.
|
|
@@ -50,18 +177,16 @@ function isLighthouseRuntimeEvaluateScript(script) {
|
|
|
50
177
|
class Scripts extends BaseGatherer {
|
|
51
178
|
static symbol = Symbol('Scripts');
|
|
52
179
|
|
|
53
|
-
/** @type {LH.Gatherer.GathererMeta} */
|
|
180
|
+
/** @type {LH.Gatherer.GathererMeta<'Trace'>} */
|
|
54
181
|
meta = {
|
|
55
182
|
symbol: Scripts.symbol,
|
|
56
183
|
supportedModes: ['timespan', 'navigation'],
|
|
184
|
+
dependencies: {Trace: Trace.symbol},
|
|
57
185
|
};
|
|
58
186
|
|
|
59
187
|
/** @type {LH.Crdp.Debugger.ScriptParsedEvent[]} */
|
|
60
188
|
_scriptParsedEvents = [];
|
|
61
189
|
|
|
62
|
-
/** @type {Array<string | undefined>} */
|
|
63
|
-
_scriptContents = [];
|
|
64
|
-
|
|
65
190
|
constructor() {
|
|
66
191
|
super();
|
|
67
192
|
this.onScriptParsed = this.onScriptParsed.bind(this);
|
|
@@ -89,26 +214,42 @@ class Scripts extends BaseGatherer {
|
|
|
89
214
|
* @param {LH.Gatherer.Context} context
|
|
90
215
|
*/
|
|
91
216
|
async stopInstrumentation(context) {
|
|
217
|
+
const session = context.driver.defaultSession;
|
|
218
|
+
session.off('Debugger.scriptParsed', this.onScriptParsed);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {LH.Gatherer.Context<'Trace'>} context
|
|
223
|
+
* @return {Promise<LH.Artifacts['Scripts']>}
|
|
224
|
+
*/
|
|
225
|
+
async getArtifact(context) {
|
|
92
226
|
const session = context.driver.defaultSession;
|
|
93
227
|
const formFactor = context.baseArtifacts.HostFormFactor;
|
|
228
|
+
const traceScriptsByScriptId = collectTraceScripts(context.dependencies.Trace.traceEvents);
|
|
229
|
+
const traceSources = this._scriptParsedEvents.map(
|
|
230
|
+
(script) => findTraceScriptSource(script, traceScriptsByScriptId)
|
|
231
|
+
);
|
|
94
232
|
|
|
95
|
-
|
|
233
|
+
if (traceSources.some((source) => source === undefined)) {
|
|
234
|
+
// `disableAsyncStacks()` in `prepare.js` runs after `stopInstrumentation` and calls
|
|
235
|
+
// `Debugger.disable`, so re-enable `Debugger` if we need to call `Debugger.getScriptSource`.
|
|
236
|
+
await session.sendCommand('Debugger.enable');
|
|
237
|
+
}
|
|
96
238
|
|
|
97
239
|
// If run on a mobile device, be sensitive to memory limitations and only
|
|
98
240
|
// request one at a time.
|
|
99
|
-
|
|
241
|
+
const scriptContents = await runInSeriesOrParallel(
|
|
100
242
|
this._scriptParsedEvents,
|
|
101
|
-
(
|
|
102
|
-
return
|
|
243
|
+
(script, i) => {
|
|
244
|
+
if (traceSources[i] !== undefined) return Promise.resolve(traceSources[i]);
|
|
245
|
+
return session.sendCommand('Debugger.getScriptSource', {scriptId: script.scriptId})
|
|
103
246
|
.then((resp) => resp.scriptSource)
|
|
104
247
|
.catch(() => undefined);
|
|
105
248
|
},
|
|
106
249
|
formFactor === 'mobile' /* runInSeries */
|
|
107
250
|
);
|
|
108
251
|
await session.sendCommand('Debugger.disable');
|
|
109
|
-
}
|
|
110
252
|
|
|
111
|
-
async getArtifact() {
|
|
112
253
|
/** @type {LH.Artifacts['Scripts']} */
|
|
113
254
|
const scripts = this._scriptParsedEvents.map((event, i) => {
|
|
114
255
|
// 'embedderName' and 'url' are confusingly named, so we rewrite them here.
|
|
@@ -123,10 +264,12 @@ class Scripts extends BaseGatherer {
|
|
|
123
264
|
// embedderName is optional on the protocol because backends like Node may not set it.
|
|
124
265
|
// For our purposes, it is always set. But just in case it isn't... fallback to the url.
|
|
125
266
|
url: event.embedderName || event.url,
|
|
126
|
-
content:
|
|
267
|
+
content: scriptContents[i],
|
|
127
268
|
};
|
|
128
269
|
});
|
|
129
270
|
|
|
271
|
+
this._scriptParsedEvents = [];
|
|
272
|
+
|
|
130
273
|
return scripts;
|
|
131
274
|
}
|
|
132
275
|
}
|
|
@@ -82,6 +82,8 @@ class Stylesheets extends BaseGatherer {
|
|
|
82
82
|
|
|
83
83
|
// Ensure we finish fetching all stylesheet contents before disabling the CSS domain
|
|
84
84
|
const sheets = await Promise.all(this._sheetPromises.values());
|
|
85
|
+
this._sheetPromises.clear();
|
|
86
|
+
this._session = undefined;
|
|
85
87
|
|
|
86
88
|
await session.sendCommand('CSS.disable');
|
|
87
89
|
await session.sendCommand('DOM.disable');
|
|
@@ -21,6 +21,7 @@ import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
|
21
21
|
import {Responsiveness} from '../../computed/metrics/responsiveness.js';
|
|
22
22
|
import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
|
|
23
23
|
import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
24
|
+
import {Helpers} from '../../lib/trace-engine.js';
|
|
24
25
|
import SourceMaps from './source-maps.js';
|
|
25
26
|
|
|
26
27
|
/** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
|
|
@@ -80,7 +81,7 @@ class TraceElements extends BaseGatherer {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
if (navigationId) {
|
|
83
|
-
if (insightSet.navigation
|
|
84
|
+
if (Helpers.Trace.navigationIDForEvent(insightSet.navigation) !== navigationId) {
|
|
84
85
|
return [];
|
|
85
86
|
}
|
|
86
87
|
} else {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export default Trace;
|
|
2
2
|
declare class Trace extends BaseGatherer {
|
|
3
3
|
static getDefaultTraceCategories(): string[];
|
|
4
|
+
/**
|
|
5
|
+
* @param {LH.Gatherer.ProtocolSession} session
|
|
6
|
+
* @param {string} handle
|
|
7
|
+
* @return {Promise<LH.Trace>}
|
|
8
|
+
*/
|
|
9
|
+
static _readTraceFromStream(session: LH.Gatherer.ProtocolSession, handle: string): Promise<LH.Trace>;
|
|
4
10
|
/**
|
|
5
11
|
* @param {LH.Gatherer.ProtocolSession} session
|
|
6
12
|
* @return {Promise<LH.Trace>}
|
|
@@ -73,29 +73,68 @@ class Trace extends BaseGatherer {
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* @param {LH.Gatherer.ProtocolSession} session
|
|
76
|
+
* @param {string} handle
|
|
76
77
|
* @return {Promise<LH.Trace>}
|
|
77
78
|
*/
|
|
78
|
-
static async
|
|
79
|
+
static async _readTraceFromStream(session, handle) {
|
|
79
80
|
/** @type {Array<LH.TraceEvent>} */
|
|
80
81
|
const traceEvents = [];
|
|
82
|
+
const decoder = new TextDecoder();
|
|
83
|
+
let isEof = false;
|
|
84
|
+
let remainder = '';
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
while (!isEof) {
|
|
88
|
+
const response = await session.sendCommand('IO.read', {handle});
|
|
89
|
+
isEof = Boolean(response.eof);
|
|
90
|
+
remainder += response.base64Encoded ?
|
|
91
|
+
decoder.decode(Buffer.from(response.data, 'base64'), {stream: !isEof}) :
|
|
92
|
+
response.data;
|
|
93
|
+
|
|
94
|
+
// Perfetto's `ExportJson` serializes each trace event and trailing `metadata`
|
|
95
|
+
// as compact single-line JSON (with string newlines escaped as `\\n`), emitting
|
|
96
|
+
// raw `,\n` only between top-level elements of `traceEvents`.
|
|
97
|
+
// Maintain the invariant that `remainder` always starts with `{"traceEvents":[`.
|
|
98
|
+
const lastEventSep = remainder.lastIndexOf('},\n');
|
|
99
|
+
if (lastEventSep !== -1) {
|
|
100
|
+
const {traceEvents: batch} = JSON.parse(`${remainder.slice(0, lastEventSep + 1)}]}`);
|
|
101
|
+
for (const event of batch) {
|
|
102
|
+
traceEvents.push(event);
|
|
103
|
+
}
|
|
104
|
+
remainder = `{"traceEvents":[${remainder.slice(lastEventSep + 3)}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
await session.sendCommand('IO.close', {handle});
|
|
109
|
+
}
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
session.on('Tracing.dataCollected', dataListener);
|
|
90
|
-
|
|
91
|
-
return new Promise((resolve, reject) => {
|
|
92
|
-
session.once('Tracing.tracingComplete', _ => {
|
|
93
|
-
session.off('Tracing.dataCollected', dataListener);
|
|
94
|
-
resolve({traceEvents});
|
|
95
|
-
});
|
|
111
|
+
const traceJson = JSON.parse(remainder);
|
|
112
|
+
for (const event of traceJson.traceEvents) {
|
|
113
|
+
traceEvents.push(event);
|
|
114
|
+
}
|
|
115
|
+
traceJson.traceEvents = traceEvents;
|
|
116
|
+
return traceJson;
|
|
117
|
+
}
|
|
96
118
|
|
|
97
|
-
|
|
119
|
+
/**
|
|
120
|
+
* @param {LH.Gatherer.ProtocolSession} session
|
|
121
|
+
* @return {Promise<LH.Trace>}
|
|
122
|
+
*/
|
|
123
|
+
static async endTraceAndCollectEvents(session) {
|
|
124
|
+
/** @type {LH.Crdp.Tracing.TracingCompleteEvent} */
|
|
125
|
+
const completeEvent = await new Promise((resolve, reject) => {
|
|
126
|
+
session.once('Tracing.tracingComplete', resolve);
|
|
127
|
+
session.sendCommand('Tracing.end').catch(err => {
|
|
128
|
+
session.off('Tracing.tracingComplete', resolve);
|
|
129
|
+
reject(err);
|
|
130
|
+
});
|
|
98
131
|
});
|
|
132
|
+
|
|
133
|
+
if (!completeEvent.stream) {
|
|
134
|
+
throw new Error('Tracing.tracingComplete did not return a stream handle');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return Trace._readTraceFromStream(session, completeEvent.stream);
|
|
99
138
|
}
|
|
100
139
|
|
|
101
140
|
static symbol = Symbol('Trace');
|
|
@@ -115,6 +154,7 @@ class Trace extends BaseGatherer {
|
|
|
115
154
|
await driver.defaultSession.sendCommand('Page.enable');
|
|
116
155
|
await driver.defaultSession.sendCommand('Tracing.start', {
|
|
117
156
|
categories: traceCategories.join(','),
|
|
157
|
+
transferMode: 'ReturnAsStream',
|
|
118
158
|
options: 'sampling-frequency=10000', // 1000 is default and too slow.
|
|
119
159
|
});
|
|
120
160
|
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export class ArbitraryEqualityMap {
|
|
7
7
|
/**
|
|
8
|
-
* Determines whether two objects are deeply equal.
|
|
9
|
-
* easy usage by consumers.
|
|
10
|
-
* See https://lodash.com/docs/4.17.5#isEqual.
|
|
8
|
+
* Determines whether two objects are deeply equal.
|
|
11
9
|
* @param {*} objA
|
|
12
10
|
* @param {*} objB
|
|
13
11
|
* @return {boolean}
|
|
@@ -44,4 +42,17 @@ export class ArbitraryEqualityMap {
|
|
|
44
42
|
*/
|
|
45
43
|
_findIndexOf(key: any): number;
|
|
46
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* @license
|
|
47
|
+
* Copyright 2018 Google LLC
|
|
48
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* @param {*} a
|
|
52
|
+
* @param {*} b
|
|
53
|
+
* @param {WeakMap<object, WeakSet<object>>=} seen
|
|
54
|
+
* @return {boolean}
|
|
55
|
+
*/
|
|
56
|
+
declare function deepEquals(a: any, b: any, seen?: WeakMap<object, WeakSet<object>> | undefined): boolean;
|
|
57
|
+
export { deepEquals as isEqual };
|
|
47
58
|
//# sourceMappingURL=arbitrary-equality-map.d.ts.map
|
|
@@ -4,7 +4,116 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @param {*} a
|
|
9
|
+
* @param {*} b
|
|
10
|
+
* @param {WeakMap<object, WeakSet<object>>=} seen
|
|
11
|
+
* @return {boolean}
|
|
12
|
+
*/
|
|
13
|
+
function deepEquals(a, b, seen) {
|
|
14
|
+
if (a === b || (Number.isNaN(a) && Number.isNaN(b))) return true;
|
|
15
|
+
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (a.constructor !== b.constructor) return false;
|
|
19
|
+
|
|
20
|
+
if (a instanceof Date && b instanceof Date) {
|
|
21
|
+
return a.getTime() === b.getTime() || (Number.isNaN(a.getTime()) && Number.isNaN(b.getTime()));
|
|
22
|
+
}
|
|
23
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
24
|
+
return a.source === b.source && a.flags === b.flags;
|
|
25
|
+
}
|
|
26
|
+
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
|
|
27
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
28
|
+
const viewA = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
|
|
29
|
+
const viewB = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
|
30
|
+
for (let i = 0; i < viewA.length; i++) {
|
|
31
|
+
if (viewA[i] !== viewB[i]) return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (a instanceof ArrayBuffer && b instanceof ArrayBuffer) {
|
|
36
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
37
|
+
const viewA = new Uint8Array(a);
|
|
38
|
+
const viewB = new Uint8Array(b);
|
|
39
|
+
for (let i = 0; i < viewA.length; i++) {
|
|
40
|
+
if (viewA[i] !== viewB[i]) return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!seen) {
|
|
46
|
+
seen = new WeakMap();
|
|
47
|
+
}
|
|
48
|
+
let seenB = seen.get(a);
|
|
49
|
+
if (seenB) {
|
|
50
|
+
if (seenB.has(b)) return true;
|
|
51
|
+
} else {
|
|
52
|
+
seenB = new WeakSet();
|
|
53
|
+
seen.set(a, seenB);
|
|
54
|
+
}
|
|
55
|
+
seenB.add(b);
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(a)) {
|
|
58
|
+
const arrB = /** @type {unknown[]} */ (b);
|
|
59
|
+
if (a.length !== arrB.length) return false;
|
|
60
|
+
for (let i = 0; i < a.length; i++) {
|
|
61
|
+
if (!deepEquals(a[i], arrB[i], seen)) return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (a instanceof Map && b instanceof Map) {
|
|
67
|
+
if (a.size !== b.size) return false;
|
|
68
|
+
for (const [k, v] of a) {
|
|
69
|
+
if (!b.has(k) || !deepEquals(v, b.get(k), seen)) return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (a instanceof Set && b instanceof Set) {
|
|
75
|
+
if (a.size !== b.size) return false;
|
|
76
|
+
/** @type {unknown[]} */
|
|
77
|
+
const unmatchedA = [];
|
|
78
|
+
for (const v of a) {
|
|
79
|
+
if (!b.has(v)) {
|
|
80
|
+
if (typeof v !== 'object' || v === null) return false;
|
|
81
|
+
unmatchedA.push(v);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (unmatchedA.length === 0) return true;
|
|
85
|
+
/** @type {unknown[]} */
|
|
86
|
+
const unmatchedB = [];
|
|
87
|
+
for (const v of b) {
|
|
88
|
+
if (!a.has(v)) {
|
|
89
|
+
if (typeof v !== 'object' || v === null) return false;
|
|
90
|
+
unmatchedB.push(v);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (unmatchedA.length !== unmatchedB.length) return false;
|
|
94
|
+
for (const itemA of unmatchedA) {
|
|
95
|
+
const matchIndex = unmatchedB.findIndex(itemB => deepEquals(itemA, itemB, seen));
|
|
96
|
+
if (matchIndex === -1) return false;
|
|
97
|
+
unmatchedB.splice(matchIndex, 1);
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (a instanceof Error && b instanceof Error) {
|
|
103
|
+
if (a.name !== b.name || a.message !== b.message || !deepEquals(a.cause, b.cause, seen)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const keysA = Object.keys(a);
|
|
109
|
+
const keysB = Object.keys(b);
|
|
110
|
+
if (keysA.length !== keysB.length) return false;
|
|
111
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
112
|
+
const key = keysA[i];
|
|
113
|
+
if (!Object.hasOwn(b, key) || !deepEquals(a[key], b[key], seen)) return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
8
117
|
|
|
9
118
|
/**
|
|
10
119
|
* @fileoverview This class is designed to allow maps with arbitrary equality functions.
|
|
@@ -65,16 +174,14 @@ class ArbitraryEqualityMap {
|
|
|
65
174
|
}
|
|
66
175
|
|
|
67
176
|
/**
|
|
68
|
-
* Determines whether two objects are deeply equal.
|
|
69
|
-
* easy usage by consumers.
|
|
70
|
-
* See https://lodash.com/docs/4.17.5#isEqual.
|
|
177
|
+
* Determines whether two objects are deeply equal.
|
|
71
178
|
* @param {*} objA
|
|
72
179
|
* @param {*} objB
|
|
73
180
|
* @return {boolean}
|
|
74
181
|
*/
|
|
75
182
|
static deepEquals(objA, objB) {
|
|
76
|
-
return
|
|
183
|
+
return deepEquals(objA, objB);
|
|
77
184
|
}
|
|
78
185
|
}
|
|
79
186
|
|
|
80
|
-
export {ArbitraryEqualityMap};
|
|
187
|
+
export {ArbitraryEqualityMap, deepEquals as isEqual};
|