lighthouse 13.5.0-dev.20261016 → 13.5.0-dev.20261019

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.
Files changed (88) hide show
  1. package/.agents/skills/update-trace-engine/SKILL.md +154 -0
  2. package/commitlint.config.js +1 -1
  3. package/core/audits/insights/insight-audit.js +3 -2
  4. package/core/computed/metrics/time-to-first-byte.js +5 -3
  5. package/core/computed/navigation-insights.js +3 -2
  6. package/core/computed/trace-engine-result.js +22 -4
  7. package/core/config/config-helpers.js +2 -3
  8. package/core/config/config.js +1 -2
  9. package/core/gather/gatherers/inspector-issues.js +2 -0
  10. package/core/gather/gatherers/js-usage.js +2 -0
  11. package/core/gather/gatherers/scripts.d.ts +16 -3
  12. package/core/gather/gatherers/scripts.js +157 -14
  13. package/core/gather/gatherers/stylesheets.js +2 -0
  14. package/core/gather/gatherers/trace-elements.js +2 -1
  15. package/core/lib/cdt/Common.d.ts +1 -1
  16. package/core/lib/cdt/Common.js +2 -2
  17. package/core/lib/cdt/Platform.d.ts +7 -7
  18. package/core/lib/cdt/Platform.js +11 -8
  19. package/core/lib/cdt/SDK.d.ts +5 -1
  20. package/core/lib/cdt/SDK.js +4 -2
  21. package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
  22. package/core/lib/cdt/generated/ParsedURL.js +3 -9
  23. package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
  24. package/core/lib/cdt/generated/SourceMap.js +7 -15
  25. package/core/lib/i18n/i18n.js +1 -2
  26. package/core/lib/legacy-javascript/legacy-javascript.d.ts +2 -2
  27. package/core/lib/legacy-javascript/legacy-javascript.js +1 -1
  28. package/core/runner.js +2 -2
  29. package/dist/report/flow.js +4 -4
  30. package/package.json +10 -30
  31. package/report/generator/flow-report-assets.js +1 -3
  32. package/report/generator/report-assets.js +1 -2
  33. package/shared/esm-utils.d.ts +0 -8
  34. package/shared/esm-utils.js +0 -18
  35. package/shared/localization/format.js +1 -2
  36. package/shared/localization/locales/ar-XB.json +0 -6
  37. package/shared/localization/locales/ar.json +0 -6
  38. package/shared/localization/locales/bg.json +0 -6
  39. package/shared/localization/locales/ca.json +0 -6
  40. package/shared/localization/locales/cs.json +0 -6
  41. package/shared/localization/locales/da.json +0 -6
  42. package/shared/localization/locales/de.json +0 -6
  43. package/shared/localization/locales/el.json +0 -6
  44. package/shared/localization/locales/en-GB.json +0 -6
  45. package/shared/localization/locales/en-US.json +3 -6
  46. package/shared/localization/locales/en-XL.json +3 -6
  47. package/shared/localization/locales/es-419.json +22 -28
  48. package/shared/localization/locales/es.json +0 -6
  49. package/shared/localization/locales/fi.json +0 -6
  50. package/shared/localization/locales/fil.json +0 -6
  51. package/shared/localization/locales/fr.json +0 -6
  52. package/shared/localization/locales/he.json +127 -133
  53. package/shared/localization/locales/hi.json +0 -6
  54. package/shared/localization/locales/hr.json +0 -6
  55. package/shared/localization/locales/hu.json +0 -6
  56. package/shared/localization/locales/id.json +0 -6
  57. package/shared/localization/locales/it.json +0 -6
  58. package/shared/localization/locales/ja.json +115 -121
  59. package/shared/localization/locales/ko.json +2 -8
  60. package/shared/localization/locales/lt.json +0 -6
  61. package/shared/localization/locales/lv.json +0 -6
  62. package/shared/localization/locales/nl.json +0 -6
  63. package/shared/localization/locales/no.json +0 -6
  64. package/shared/localization/locales/pl.json +0 -6
  65. package/shared/localization/locales/pt-PT.json +0 -6
  66. package/shared/localization/locales/pt.json +0 -6
  67. package/shared/localization/locales/ro.json +3 -9
  68. package/shared/localization/locales/ru.json +3 -9
  69. package/shared/localization/locales/sk.json +0 -6
  70. package/shared/localization/locales/sl.json +0 -6
  71. package/shared/localization/locales/sr-Latn.json +0 -6
  72. package/shared/localization/locales/sr.json +0 -6
  73. package/shared/localization/locales/sv.json +0 -6
  74. package/shared/localization/locales/ta.json +0 -6
  75. package/shared/localization/locales/te.json +0 -6
  76. package/shared/localization/locales/th.json +0 -6
  77. package/shared/localization/locales/tr.json +0 -6
  78. package/shared/localization/locales/uk.json +0 -6
  79. package/shared/localization/locales/vi.json +0 -6
  80. package/shared/localization/locales/zh-HK.json +18 -24
  81. package/shared/localization/locales/zh-TW.json +60 -66
  82. package/shared/localization/locales/zh.json +2 -8
  83. package/shared/root.js +1 -3
  84. package/tsconfig.json +0 -1
  85. package/types/artifacts.d.ts +8 -1
  86. package/core/lib/cdt/package.json +0 -4
  87. package/eslint-local-rules.d.cts +0 -9
  88. package/eslint.config.mjs +0 -242
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: update-trace-engine
3
+ description: Build, validate, publish, and roll @paulirish/trace_engine from devtools-frontend into Lighthouse. Invoke when asked to update or roll the trace_engine package.
4
+ ---
5
+
6
+ # Update `@paulirish/trace_engine`
7
+
8
+ Lighthouse consumes Chrome DevTools' trace processor (`front_end/models/trace`) via the standalone `@paulirish/trace_engine` npm package, built from the `trace-engine-lib` branch of `paulirish/devtools-frontend`.
9
+
10
+ ## What lives where
11
+
12
+ | Location | Purpose | Notes |
13
+ |---|---|---|
14
+ | `devtools-frontend` checkout (branch `trace-engine-lib`) | Source & packaging scripts | Tracks `origin/main` (`https://chromium.googlesource.com/devtools/devtools-frontend`) + packaging scripts in `scripts/trace/`. Common local path: `~/devtools/devtools-frontend` (ask user if not found). |
15
+ | `scripts/trace/prep-trace-engine-package.sh` (in `devtools-frontend`) | Builds `out/TraceEngine` and prepares standalone package | Converts `export const enum` → `export enum` in `front_end/models/trace/**/*.ts`, builds with `gn` + `autoninja`, applies shims from `scripts/trace/replacements/`, extracts insight locale strings, and invokes `copy-build-trace-engine-for-publish.sh`. |
16
+ | `scripts/trace/copy-build-trace-engine-for-publish.sh` (in `devtools-frontend`) | Copies dist to `$HOME/code/trace_engine` and tests | Copies `out/TraceEngine/dist` to `$HOME/code/trace_engine`, runs `npm install` and `npm test` (Mocha tests in `test/test-trace-engine.mjs` + `tsc --build ./tsconfig.json`). |
17
+ | `$HOME/code/trace_engine` | Standalone npm package staging directory | Where `npm version` and `npm publish` are run. |
18
+ | `core/lib/trace-engine.js` (in Lighthouse) | Lighthouse entry point for `@paulirish/trace_engine` | Re-exports `TraceProcessor`, `TraceHandlers`, `Insights`, `Helpers`, and `Types`. |
19
+
20
+ ## Procedure
21
+
22
+ ### 1. Merge `origin/main` into `trace-engine-lib` in `devtools-frontend`
23
+
24
+ 1. Find the previous `devtools-frontend` `main` commit SHA from the most recent `deps: upgrade trace_engine` commit or PR in Lighthouse (or via `git log -n 1 --merges --pretty=format:"%P"` on `trace-engine-lib`).
25
+ 2. In the `devtools-frontend` checkout:
26
+ ```sh
27
+ git checkout trace-engine-lib
28
+ git fetch origin main
29
+ git merge origin/main --no-edit
30
+ ```
31
+ 3. Record the commit range `<old_sha>...<new_sha>` and inspect upstream changes:
32
+ ```sh
33
+ git log --oneline <old_sha>..<new_sha> -- front_end/models/trace front_end/models/cpu_profile
34
+ ```
35
+ 4. If there are merge conflicts in `front_end/models/trace/**/*.ts` due to `export const enum` vs `export enum`, resolve in favor of `origin/main` — `prep-trace-engine-package.sh` automatically converts `export const enum` to `export enum`.
36
+
37
+ ### 2. Build and test the standalone package
38
+
39
+ Ensure `depot_tools` (`gn`, `autoninja`) and Node/npm are on `PATH`, then run:
40
+
41
+ ```sh
42
+ scripts/trace/prep-trace-engine-package.sh
43
+ ```
44
+
45
+ This builds `out/TraceEngine`, populates `$HOME/code/trace_engine`, and runs `npm test` (`mocha` + `tsc --build ./tsconfig.json`).
46
+
47
+ #### Fixing build or test failures in `trace-engine-lib`
48
+
49
+ - **TypeScript (`tsc --build ./tsconfig.json`) errors in `$HOME/code/trace_engine`**:
50
+ - `prep-trace-engine-package.sh` replaces several DevTools modules using `scripts/trace/replacements/` so Lighthouse can bundle `trace_engine` into an IIFE (`esbuild`) and defer localization (`LH.IcuMessage`):
51
+ - `HostRuntime.js`: synchronous stub replacing top-level `await` in `core/platform/HostRuntime.js`.
52
+ - `UIString.d.ts` & `i18n.{js,d.ts}`: types `LocalizedString` as a deferred `{i18nId, values}` object instead of a branded `string`. If a file in `core/platform/` (such as `UserVisibleError.d.ts`) fails type-checking because it expects `LocalizedString` to be assignable to `string` and isn't needed by `models/trace`, stub its `.d.ts` in `prep-trace-engine-package.sh` (`echo 'export {};' > "$dist/core/platform/<File>.d.ts"`).
53
+ - `common.{js,d.ts}`, `sdk.d.ts`, `crux-manager.d.ts`: minimal stubs for `core/common`, `core/sdk`, and `models/crux-manager`. Update these if `models/trace` references new symbols from those modules.
54
+ - **Mocha (`test/test-trace-engine.mjs`) failures**:
55
+ - If an assertion in `scripts/trace/test/test-trace-engine.mjs` fails due to an intentional upstream calculation change in `front_end/models/trace`, update `scripts/trace/test/test-trace-engine.mjs` and re-run `scripts/trace/copy-build-trace-engine-for-publish.sh`.
56
+ - Commit any updated files on `trace-engine-lib` and push to Paul's fork (`git push paulirish trace-engine-lib`) when requested.
57
+
58
+ ### 3. Bump the package version
59
+
60
+ Determine the next patch version (`0.0.X` → `0.0.(X+1)`) from `package.json` in Lighthouse or `npm view @paulirish/trace_engine version --registry https://registry.npmjs.org/`:
61
+
62
+ ```sh
63
+ cd $HOME/code/trace_engine
64
+ npm version v0.0.XXX --no-git-tag-version
65
+ ```
66
+
67
+ ### 4. Validate locally in Lighthouse before publishing
68
+
69
+ Copy the built package into Lighthouse's `node_modules/@paulirish/trace_engine` to validate before publishing to npm:
70
+
71
+ ```sh
72
+ rm -rf node_modules/@paulirish/trace_engine/{models,core,generated,third_party,locales,test}
73
+ cp -rp $HOME/code/trace_engine/{models,core,generated,third_party,locales,package.json,tsconfig.json} node_modules/@paulirish/trace_engine/
74
+ ```
75
+
76
+ Run the Lighthouse validation checks:
77
+
78
+ 1. **Type-checking**:
79
+ ```sh
80
+ yarn type-check
81
+ ```
82
+ Fix any Lighthouse code or JSDoc types broken by upstream `TraceEngine` API/type changes (commonly in `core/audits/insights/`, `core/computed/`, `core/gather/gatherers/trace-elements.js`, or `core/lib/trace-engine.js`).
83
+
84
+ 2. **Bundles**:
85
+ ```sh
86
+ yarn build-devtools && yarn build-lr && yarn build-devtools-mcp
87
+ ```
88
+ Confirms `esbuild` can bundle `trace_engine` into IIFE (`dt-bundle.js`, Lightrider) and ESM without top-level `await` or missing module errors.
89
+
90
+ 3. **Unit tests**:
91
+ ```sh
92
+ yarn mocha core/test/audits/insights/*-test.js core/test/audits/metrics-test.js core/test/audits/predictive-perf-test.js core/test/audits/byte-efficiency/byte-efficiency-audit-test.js core/test/computed/metrics/*-test.js core/test/computed/trace-engine-result-test.js core/test/computed/navigation-insights-test.js core/test/computed/entity-classification-test.js core/test/computed/tbt-impact-tasks-test.js
93
+ INTERNAL_LANTERN_USE_TRACE=1 yarn mocha core/test/computed/metrics/*-test.js core/test/audits/metrics-test.js core/test/audits/predictive-perf-test.js
94
+ ```
95
+ Update test expectations and snapshots (`yarn mocha --update <test-file>`) where upstream changes intentionally affect metrics, insights, or Lantern trace simulations.
96
+
97
+ 4. **Sample JSON, locale strings, flow fixtures, and Lantern baselines**:
98
+ ```sh
99
+ yarn update:sample-json
100
+ bash core/scripts/assert-baseline-flow-result-unchanged.sh
101
+ yarn test-lantern
102
+ ```
103
+ - If `assert-baseline-flow-result-unchanged.sh` fails due to expected changes, update flow fixtures with `yarn update:flow-fixtures`.
104
+ - If `yarn test-lantern` reports expected metric changes on the Lantern dataset (or if `models/trace/lantern` changed upstream), update the Lantern baseline files (`core/test/fixtures/lantern-baseline-computed-values.json` and `core/test/fixtures/lantern-baseline-accuracy.json`):
105
+ ```sh
106
+ FORCE=1 ./core/scripts/lantern/update-baseline-lantern-values.js
107
+ ./core/scripts/lantern/assert-baseline-lantern-values-unchanged.js
108
+ ```
109
+ - Review `git diff core/test/results/sample_v2.json` and `git diff shared/localization/locales/en-US.json` to make sure every change is accounted for by an upstream commit.
110
+
111
+ 5. **Linter**:
112
+ ```sh
113
+ yarn lint --fix
114
+ ```
115
+
116
+ ### 5. Pause for npm publish
117
+
118
+ Once local validation passes, pause and ask the user to publish `$HOME/code/trace_engine` to npm (requires interactive npm authentication):
119
+
120
+ ```sh
121
+ cd $HOME/code/trace_engine
122
+ npm publish --access public --registry https://registry.npmjs.org/
123
+ ```
124
+
125
+ ### 6. Roll `package.json`, `yarn.lock`, and open the PR
126
+
127
+ After the user confirms `@paulirish/trace_engine@0.0.XXX` is published on npm:
128
+
129
+ 1. Update `"@paulirish/trace_engine": "0.0.XXX"` in `package.json` and run `yarn` to update `yarn.lock`.
130
+ 2. Commit all changes (`deps: upgrade trace_engine to 0.0.XXX`).
131
+ 3. Run `yarn diff:sample-json` after committing (note: `assert-strings-collected.sh` runs `git diff --exit-code shared/localization/locales/`, so uncommitted locale changes will cause it to fail).
132
+ 4. Push the branch and open a PR against `GoogleChrome/lighthouse` with title `deps: upgrade trace_engine to 0.0.XXX` and description formatted like [PR #17307](https://github.com/GoogleChrome/lighthouse/pull/17307) (including the collapsible `<details><summary>` block with the full `git log <old_sha>...<new_sha> -- front_end/models/trace` output):
133
+
134
+ ````markdown
135
+ Roll `@paulirish/trace_engine` from `0.0.OLD` to `0.0.NEW` (`<old_sha>...<new_sha>`).
136
+
137
+ https://chromium.googlesource.com/devtools/devtools-frontend.git/+log/<old_sha>..<new_sha>
138
+
139
+ ### Highlights for Lighthouse
140
+
141
+ - **<Category>**:
142
+ - **<Highlight>** ([CL <cl_number>](https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/<cl_number>)): <summary of upstream change and impact on Lighthouse>
143
+
144
+ `git log <old_sha>...<new_sha> -- front_end/models/trace`:
145
+
146
+ <details>
147
+ <summary>Changelog (just front_end/models/trace, mostly relevant to our usage in LH)</summary>
148
+
149
+ ```
150
+ <full output of: git log <old_sha>...<new_sha> -- front_end/models/trace>
151
+ ```
152
+
153
+ </details>
154
+ ````
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- module.exports = {
7
+ export default {
8
8
  extends: ['cz'],
9
9
  rules: {
10
10
  'body-leading-blank': [1, 'always'],
@@ -6,6 +6,7 @@
6
6
 
7
7
  import {NO_NAVIGATION} from '@paulirish/trace_engine/models/trace/types/TraceEvents.js';
8
8
 
9
+ import {Helpers} from '../../lib/trace-engine.js';
9
10
  import {ProcessedTrace} from '../../computed/processed-trace.js';
10
11
  import {TraceEngineResult} from '../../computed/trace-engine-result.js';
11
12
  import {Audit} from '../audit.js';
@@ -28,8 +29,8 @@ async function getInsightSet(artifacts, context) {
28
29
 
29
30
  const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
30
31
  const insights = navigationId ?
31
- [...traceEngineResult.insights.values()]
32
- .find(insightSet => insightSet.navigation?.args.data?.navigationId === navigationId) :
32
+ [...traceEngineResult.insights.values()].find(insightSet =>
33
+ Helpers.Trace.navigationIDForEvent(insightSet.navigation) === navigationId) :
33
34
  traceEngineResult.insights.get(NO_NAVIGATION);
34
35
 
35
36
  return {insights, data: traceEngineResult.data};
@@ -6,6 +6,7 @@
6
6
 
7
7
  import {calculateDocFirstByteTs} from '@paulirish/trace_engine/models/trace/insights/Common.js';
8
8
 
9
+ import {Helpers} from '../../lib/trace-engine.js';
9
10
  import {makeComputedArtifact} from '../computed-artifact.js';
10
11
  import {NavigationMetric} from './navigation-metric.js';
11
12
  import {MainResource} from '../main-resource.js';
@@ -52,15 +53,16 @@ class TimeToFirstByte extends NavigationMetric {
52
53
  await NavigationInsights.request({trace, settings, SourceMaps, HostDPR}, context);
53
54
  const lcpBreakdown = navInsights.model.LCPBreakdown;
54
55
 
56
+ const navigationId = Helpers.Trace.navigationIDForEvent(navInsights.navigation);
57
+
55
58
  // Defer to LCP breakdown, but if there's no LCP fallback to manual calculation.
56
59
  if (lcpBreakdown && !(lcpBreakdown instanceof Error) && lcpBreakdown.subparts) {
57
60
  return {
58
61
  timing: lcpBreakdown.subparts.ttfb.range / 1000,
59
62
  timestamp: lcpBreakdown.subparts.ttfb.max,
60
63
  };
61
- } else if (navInsights.navigation?.args.data?.navigationId) {
62
- const request = traceEngineResult.data.NetworkRequests.byId.get(
63
- navInsights.navigation.args.data.navigationId);
64
+ } else if (navInsights.navigation && navigationId) {
65
+ const request = traceEngineResult.data.NetworkRequests.byId.get(navigationId);
64
66
  if (!request) {
65
67
  throw new Error();
66
68
  }
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import {Helpers} from '../lib/trace-engine.js';
7
8
  import {makeComputedArtifact} from './computed-artifact.js';
8
9
  import {ProcessedTrace} from './processed-trace.js';
9
10
  import {TraceEngineResult} from './trace-engine-result.js';
@@ -26,8 +27,8 @@ class NavigationInsights {
26
27
  const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
27
28
  if (!navigationId) throw new Error('No navigationId found');
28
29
 
29
- const navInsights = [...traceEngineResult.insights.values()]
30
- .find(insightSet => insightSet.navigation?.args.data?.navigationId === navigationId);
30
+ const navInsights = [...traceEngineResult.insights.values()].find(insightSet =>
31
+ Helpers.Trace.navigationIDForEvent(insightSet.navigation) === navigationId);
31
32
  if (!navInsights) throw new Error('No navigations insights found');
32
33
 
33
34
  return navInsights;
@@ -7,6 +7,7 @@
7
7
  import log from 'lighthouse-logger';
8
8
 
9
9
  import * as i18n from '../lib/i18n/i18n.js';
10
+ import * as Lantern from '../lib/lantern/lantern.js';
10
11
  import * as TraceEngine from '../lib/trace-engine.js';
11
12
  import {makeComputedArtifact} from './computed-artifact.js';
12
13
  import {CumulativeLayoutShift} from './metrics/cumulative-layout-shift.js';
@@ -68,8 +69,20 @@ class TraceEngineResult {
68
69
  });
69
70
  if (!processor.data) throw new Error('No data');
70
71
  if (!processor.insights) throw new Error('No insights');
71
- this.localizeInsights(processor.insights);
72
- return {data: processor.data, insights: processor.insights};
72
+ const data = processor.data;
73
+ const insights = processor.insights;
74
+ this.localizeInsights(insights);
75
+
76
+ // Release intermediate handler state retained in module-level TraceHandlers singletons
77
+ // and static Lantern simulation node timings after trace processing completes.
78
+ for (const handler of Object.values(TraceEngine.TraceHandlers)) {
79
+ handler.reset();
80
+ }
81
+ if (!process.env.LANTERN_DEBUG) {
82
+ Lantern.Simulation.Simulator.allNodeTimings.clear();
83
+ }
84
+
85
+ return {data, insights};
73
86
  }
74
87
 
75
88
  /**
@@ -238,8 +251,9 @@ class TraceEngineResult {
238
251
  const layoutShiftEvents = new Set(
239
252
  CumulativeLayoutShift.getLayoutShiftEvents(processedTrace).map(e => e.event));
240
253
 
241
- // Avoid modifying the input array.
242
- const traceEvents = [...data.trace.traceEvents];
254
+ // Avoid modifying the input array, copying only if an event is modified.
255
+ let traceEvents = data.trace.traceEvents;
256
+ let copied = false;
243
257
  for (let i = 0; i < traceEvents.length; i++) {
244
258
  let event = traceEvents[i];
245
259
  if (event.name !== 'LayoutShift') continue;
@@ -247,6 +261,10 @@ class TraceEngineResult {
247
261
 
248
262
  const isConsidered = layoutShiftEvents.has(event);
249
263
  if (event.args.data.had_recent_input && isConsidered) {
264
+ if (!copied) {
265
+ traceEvents = [...traceEvents];
266
+ copied = true;
267
+ }
250
268
  event = JSON.parse(JSON.stringify(event));
251
269
  // @ts-expect-error impossible for data to be missing.
252
270
  event.args.data.had_recent_input = false;
@@ -13,7 +13,6 @@ import ConfigPlugin from './config-plugin.js';
13
13
  import {Runner} from '../runner.js';
14
14
  import * as i18n from '../lib/i18n/i18n.js';
15
15
  import * as validation from './validation.js';
16
- import {getModuleDirectory} from '../../shared/esm-utils.js';
17
16
  import {isEqual} from '../lib/arbitrary-equality-map.js';
18
17
 
19
18
  const require = createRequire(import.meta.url);
@@ -292,7 +291,7 @@ function requireAudit(auditPath, coreAuditList, configDir) {
292
291
  const absolutePath = resolveModulePath(auditPath, configDir, 'audit');
293
292
  if (isBundledEnvironment()) {
294
293
  // Use a relative path so bundler can easily expose it.
295
- requirePath = path.relative(getModuleDirectory(import.meta), absolutePath);
294
+ requirePath = path.relative(import.meta.dirname, absolutePath);
296
295
  } else {
297
296
  requirePath = absolutePath;
298
297
  }
@@ -512,7 +511,7 @@ function resolveModulePath(moduleIdentifier, configDir, category) {
512
511
  const errorString = 'Unable to locate ' + (category ? `${category}: ` : '') +
513
512
  `\`${moduleIdentifier}\`.
514
513
  Tried to resolve the module from these locations:
515
- ${getModuleDirectory(import.meta)}
514
+ ${import.meta.dirname}
516
515
  ${cwdPath}`;
517
516
 
518
517
  if (!configDir) {
@@ -28,11 +28,10 @@ import {
28
28
  mergeConfigFragment,
29
29
  mergeConfigFragmentArrayByKey,
30
30
  } from './config-helpers.js';
31
- import {getModuleDirectory} from '../../shared/esm-utils.js';
32
31
  import * as format from '../../shared/localization/format.js';
33
32
 
34
33
  const defaultConfigPath = path.join(
35
- getModuleDirectory(import.meta),
34
+ import.meta.dirname,
36
35
  '../../config/default-config.js'
37
36
  );
38
37
 
@@ -96,6 +96,8 @@ class InspectorIssues extends BaseGatherer {
96
96
  }
97
97
  }
98
98
 
99
+ this._issues = [];
100
+
99
101
  return artifact;
100
102
  }
101
103
  }
@@ -67,6 +67,8 @@ class JsUsage extends BaseGatherer {
67
67
  usageByScriptId[scriptUsage.scriptId] = scriptUsage;
68
68
  }
69
69
 
70
+ this._scriptUsages = [];
71
+
70
72
  return usageByScriptId;
71
73
  }
72
74
  }
@@ -1,13 +1,22 @@
1
1
  export default Scripts;
2
+ export type TraceScript = {
3
+ scriptId: string;
4
+ sourceText?: string | undefined;
5
+ chunks?: string[] | undefined;
6
+ url?: string | undefined;
7
+ sourceUrl?: string | undefined;
8
+ executionContextId?: number | undefined;
9
+ frame?: string | undefined;
10
+ };
2
11
  /**
3
12
  * @fileoverview Gets JavaScript file contents.
4
13
  */
5
14
  declare class Scripts extends BaseGatherer {
6
15
  static symbol: symbol;
16
+ /** @type {LH.Gatherer.GathererMeta<'Trace'>} */
17
+ meta: LH.Gatherer.GathererMeta<"Trace">;
7
18
  /** @type {LH.Crdp.Debugger.ScriptParsedEvent[]} */
8
19
  _scriptParsedEvents: LH.Crdp.Debugger.ScriptParsedEvent[];
9
- /** @type {Array<string | undefined>} */
10
- _scriptContents: Array<string | undefined>;
11
20
  /**
12
21
  * @param {LH.Crdp.Debugger.ScriptParsedEvent} params
13
22
  */
@@ -20,7 +29,11 @@ declare class Scripts extends BaseGatherer {
20
29
  * @param {LH.Gatherer.Context} context
21
30
  */
22
31
  stopInstrumentation(context: LH.Gatherer.Context): Promise<void>;
23
- getArtifact(): Promise<import("../../index.js").Artifacts.Script[]>;
32
+ /**
33
+ * @param {LH.Gatherer.Context<'Trace'>} context
34
+ * @return {Promise<LH.Artifacts['Scripts']>}
35
+ */
36
+ getArtifact(context: LH.Gatherer.Context<"Trace">): Promise<LH.Artifacts["Scripts"]>;
24
37
  }
25
38
  import BaseGatherer from '../base-gatherer.js';
26
39
  //# sourceMappingURL=scripts.d.ts.map
@@ -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 (const value of values) {
20
- const result = await promiseMapper(value);
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
- session.off('Debugger.scriptParsed', this.onScriptParsed);
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
- this._scriptContents = await runInSeriesOrParallel(
241
+ const scriptContents = await runInSeriesOrParallel(
100
242
  this._scriptParsedEvents,
101
- ({scriptId}) => {
102
- return session.sendCommand('Debugger.getScriptSource', {scriptId})
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: this._scriptContents[i],
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?.args.data?.navigationId !== navigationId) {
84
+ if (Helpers.Trace.navigationIDForEvent(insightSet.navigation) !== navigationId) {
84
85
  return [];
85
86
  }
86
87
  } else {
@@ -1,3 +1,3 @@
1
1
  export { ParsedURL };
2
- import ParsedURL = require("./generated/ParsedURL.js");
2
+ import * as ParsedURL from './generated/ParsedURL.js';
3
3
  //# sourceMappingURL=Common.d.ts.map