lighthouse 9.5.0-dev.20220313 → 9.5.0-dev.20220316

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 (30) hide show
  1. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +0 -1
  2. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -0
  3. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +9 -8
  4. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +11 -12
  5. package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +9 -10
  6. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +10 -6
  7. package/lighthouse-core/audits/deprecations.js +3 -3
  8. package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +1 -1
  9. package/lighthouse-core/audits/dobetterweb/no-document-write.js +1 -1
  10. package/lighthouse-core/audits/dobetterweb/notification-on-start.js +1 -1
  11. package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +1 -1
  12. package/lighthouse-core/audits/errors-in-console.js +2 -2
  13. package/lighthouse-core/audits/no-unload-listeners.js +7 -15
  14. package/lighthouse-core/audits/script-elements-test-audit.js +30 -0
  15. package/lighthouse-core/audits/script-treemap-data.js +22 -21
  16. package/lighthouse-core/audits/valid-source-maps.js +12 -14
  17. package/lighthouse-core/audits/violation-audit.js +1 -1
  18. package/lighthouse-core/computed/js-bundles.js +11 -11
  19. package/lighthouse-core/computed/module-duplication.js +6 -5
  20. package/lighthouse-core/computed/unused-javascript-summary.js +16 -36
  21. package/lighthouse-core/config/default-config.js +1 -0
  22. package/lighthouse-core/fraggle-rock/config/default-config.js +3 -0
  23. package/lighthouse-core/gather/gatherers/console-messages.js +4 -2
  24. package/lighthouse-core/gather/gatherers/js-usage.js +6 -52
  25. package/lighthouse-core/gather/gatherers/script-elements.js +7 -56
  26. package/lighthouse-core/gather/gatherers/scripts.js +136 -0
  27. package/lighthouse-core/gather/gatherers/source-maps.js +3 -0
  28. package/lighthouse-core/lib/url-shim.js +2 -2
  29. package/package.json +1 -1
  30. package/types/artifacts.d.ts +22 -7
@@ -78,7 +78,6 @@ async function internalRun(url, tmpPath, configJson, options) {
78
78
  '--output=json',
79
79
  `-G=${artifactsDirectory}`,
80
80
  `-A=${artifactsDirectory}`,
81
- '--quiet',
82
81
  '--port=0',
83
82
  ];
84
83
 
@@ -158,6 +158,7 @@ class UnusedBytes extends Audit {
158
158
 
159
159
  const simulationBeforeChanges = simulator.simulate(graph, {label: beforeLabel});
160
160
 
161
+ // TODO: change this to wastedBytesByScriptId
161
162
  const wastedBytesByUrl = options.providedWastedBytesByUrl || new Map();
162
163
  if (!options.providedWastedBytesByUrl) {
163
164
  for (const {url, wastedBytes} of results) {
@@ -48,7 +48,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
48
48
  title: str_(UIStrings.title),
49
49
  description: str_(UIStrings.description),
50
50
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
51
- requiredArtifacts: ['devtoolsLogs', 'traces', 'SourceMaps', 'ScriptElements',
51
+ requiredArtifacts: ['devtoolsLogs', 'traces', 'SourceMaps', 'Scripts',
52
52
  'GatherContext', 'URL'],
53
53
  };
54
54
  }
@@ -85,10 +85,10 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
85
85
 
86
86
  const normalizedSource = 'node_modules/' + DuplicatedJavascript._getNodeModuleName(source);
87
87
  const aggregatedSourceDatas = groupedDuplication.get(normalizedSource) || [];
88
- for (const {scriptUrl, resourceSize} of sourceDatas) {
89
- let sourceData = aggregatedSourceDatas.find(d => d.scriptUrl === scriptUrl);
88
+ for (const {scriptId, scriptUrl, resourceSize} of sourceDatas) {
89
+ let sourceData = aggregatedSourceDatas.find(d => d.scriptId === scriptId);
90
90
  if (!sourceData) {
91
- sourceData = {scriptUrl, resourceSize: 0};
91
+ sourceData = {scriptId, scriptUrl, resourceSize: 0};
92
92
  aggregatedSourceDatas.push(sourceData);
93
93
  }
94
94
  sourceData.resourceSize += resourceSize;
@@ -157,7 +157,9 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
157
157
  let wastedBytesTotal = 0;
158
158
  for (let i = 0; i < sourceDatas.length; i++) {
159
159
  const sourceData = sourceDatas[i];
160
- const url = sourceData.scriptUrl;
160
+ const scriptId = sourceData.scriptId;
161
+ const script = artifacts.Scripts.find(script => script.scriptId === scriptId);
162
+ const url = script?.url || '';
161
163
 
162
164
  /** @type {number|undefined} */
163
165
  let transferRatio = transferRatioByUrl.get(url);
@@ -166,13 +168,12 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
166
168
  mainDocumentRecord :
167
169
  networkRecords.find(n => n.url === url);
168
170
 
169
- const script = artifacts.ScriptElements.find(script => script.src === url);
170
- if (!script || script.content === null) {
171
+ if (!script || script.length === undefined) {
171
172
  // This should never happen because we found the wasted bytes from bundles, which required contents in a ScriptElement.
172
173
  continue;
173
174
  }
174
175
 
175
- const contentLength = script.content.length;
176
+ const contentLength = script.length;
176
177
  transferRatio = DuplicatedJavascript._estimateTransferRatio(networkRecord, contentLength);
177
178
  transferRatioByUrl.set(url, transferRatio);
178
179
  }
@@ -109,7 +109,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
109
109
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
110
110
  description: str_(UIStrings.description),
111
111
  title: str_(UIStrings.title),
112
- requiredArtifacts: ['devtoolsLogs', 'traces', 'ScriptElements', 'SourceMaps',
112
+ requiredArtifacts: ['devtoolsLogs', 'traces', 'Scripts', 'SourceMaps',
113
113
  'GatherContext', 'URL'],
114
114
  };
115
115
  }
@@ -274,7 +274,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
274
274
  * Returns a collection of match results grouped by script url.
275
275
  *
276
276
  * @param {CodePatternMatcher} matcher
277
- * @param {LH.GathererArtifacts['ScriptElements']} scripts
277
+ * @param {LH.Artifacts['Scripts']} scripts
278
278
  * @param {LH.Artifacts.NetworkRequest[]} networkRecords
279
279
  * @param {LH.Artifacts.Bundle[]} bundles
280
280
  * @return {Map<string, PatternMatchResult[]>}
@@ -284,16 +284,14 @@ class LegacyJavascript extends ByteEfficiencyAudit {
284
284
  const urlToMatchResults = new Map();
285
285
  const polyfillData = this.getPolyfillData();
286
286
 
287
- for (const {requestId, content} of Object.values(scripts)) {
287
+ for (const {scriptId, url, content} of Object.values(scripts)) {
288
288
  if (!content) continue;
289
- const networkRecord = networkRecords.find(record => record.requestId === requestId);
290
- if (!networkRecord) continue;
291
289
 
292
290
  // Start with pattern matching against the downloaded script.
293
291
  const matches = matcher.match(content);
294
292
 
295
293
  // If it's a bundle with source maps, add in the polyfill modules by name too.
296
- const bundle = bundles.find(b => b.script.src === networkRecord.url);
294
+ const bundle = bundles.find(b => b.script.scriptId === scriptId);
297
295
  if (bundle) {
298
296
  for (const {coreJs2Module, coreJs3Module, name} of polyfillData) {
299
297
  // Skip if the pattern matching found a match for this polyfill.
@@ -313,7 +311,8 @@ class LegacyJavascript extends ByteEfficiencyAudit {
313
311
  }
314
312
 
315
313
  if (!matches.length) continue;
316
- urlToMatchResults.set(networkRecord.url, matches);
314
+ // TODO: scriptId
315
+ urlToMatchResults.set(url, matches);
317
316
  }
318
317
 
319
318
  return urlToMatchResults;
@@ -374,16 +373,16 @@ class LegacyJavascript extends ByteEfficiencyAudit {
374
373
  if (transferRatio !== undefined) return transferRatio;
375
374
 
376
375
  const mainDocumentRecord = NetworkAnalyzer.findOptionalMainDocument(networkRecords);
376
+ const script = artifacts.Scripts.find(script => script.url === url);
377
377
  const networkRecord = url === artifacts.URL.finalUrl ?
378
378
  mainDocumentRecord :
379
- networkRecords.find(n => n.url === url);
380
- const script = artifacts.ScriptElements.find(script => script.src === url);
379
+ networkRecords.find(n => n.url === script?.url);
381
380
 
382
381
  if (!script || script.content === null) {
383
382
  // Can't find content, so just use 1.
384
383
  transferRatio = 1;
385
384
  } else {
386
- const contentLength = script.content.length;
385
+ const contentLength = script.length || 0;
387
386
  const transferSize =
388
387
  ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength, 'Script');
389
388
  transferRatio = transferSize / contentLength;
@@ -415,7 +414,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
415
414
  const transferRatioByUrl = new Map();
416
415
 
417
416
  const urlToMatchResults =
418
- this.detectAcrossScripts(matcher, artifacts.ScriptElements, networkRecords, bundles);
417
+ this.detectAcrossScripts(matcher, artifacts.Scripts, networkRecords, bundles);
419
418
  for (const [url, matches] of urlToMatchResults.entries()) {
420
419
  const transferRatio = await this.estimateTransferRatioForScript(
421
420
  transferRatioByUrl, url, artifacts, networkRecords);
@@ -432,7 +431,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
432
431
  totalBytes: 0,
433
432
  };
434
433
 
435
- const bundle = bundles.find(bundle => bundle.script.src === url);
434
+ const bundle = bundles.find(bundle => bundle.script.url === url); // TODO: scriptId
436
435
  for (const match of matches) {
437
436
  const {name, line, column} = match;
438
437
  /** @type {SubItem} */
@@ -42,7 +42,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
42
42
  title: str_(UIStrings.title),
43
43
  description: str_(UIStrings.description),
44
44
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
45
- requiredArtifacts: ['ScriptElements', 'devtoolsLogs', 'traces', 'GatherContext'],
45
+ requiredArtifacts: ['Scripts', 'devtoolsLogs', 'traces', 'GatherContext', 'URL'],
46
46
  };
47
47
  }
48
48
 
@@ -78,15 +78,15 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
78
78
  /** @type {Array<LH.Audit.ByteEfficiencyItem>} */
79
79
  const items = [];
80
80
  const warnings = [];
81
- for (const {requestId, src, content} of artifacts.ScriptElements) {
82
- if (!content) continue;
81
+ for (const script of artifacts.Scripts) {
82
+ if (!script.content) continue;
83
83
 
84
- const networkRecord = networkRecords.find(record => record.requestId === requestId);
85
- const displayUrl = !src || !networkRecord ?
86
- `inline: ${content.substr(0, 40)}...` :
87
- networkRecord.url;
84
+ const networkRecord = networkRecords.find(record => record.url === script.url);
85
+ const displayUrl = script.name === artifacts.URL.finalUrl ?
86
+ `inline: ${script.content.substring(0, 40)}...` :
87
+ script.url;
88
88
  try {
89
- const result = UnminifiedJavaScript.computeWaste(content, displayUrl, networkRecord);
89
+ const result = UnminifiedJavaScript.computeWaste(script.content, displayUrl, networkRecord);
90
90
  // If the ratio is minimal, the file is likely already minified, so ignore it.
91
91
  // If the total number of bytes to be saved is quite small, it's also safe to ignore.
92
92
  if (result.wastedPercent < IGNORE_THRESHOLD_IN_PERCENT ||
@@ -94,8 +94,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
94
94
  !Number.isFinite(result.wastedBytes)) continue;
95
95
  items.push(result);
96
96
  } catch (err) {
97
- const url = networkRecord ? networkRecord.url : '?';
98
- warnings.push(`Unable to process script ${url}: ${err.message}`);
97
+ warnings.push(`Unable to process script ${script.url}: ${err.message}`);
99
98
  }
100
99
  }
101
100
 
@@ -67,7 +67,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
67
67
  title: str_(UIStrings.title),
68
68
  description: str_(UIStrings.description),
69
69
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
70
- requiredArtifacts: ['JsUsage', 'ScriptElements', 'SourceMaps', 'GatherContext',
70
+ requiredArtifacts: ['JsUsage', 'Scripts', 'SourceMaps', 'GatherContext',
71
71
  'devtoolsLogs', 'traces'],
72
72
  };
73
73
  }
@@ -86,12 +86,16 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
86
86
  } = context.options || {};
87
87
 
88
88
  const items = [];
89
- for (const [url, scriptCoverages] of Object.entries(artifacts.JsUsage)) {
90
- const networkRecord = networkRecords.find(record => record.url === url);
89
+ for (const [scriptId, scriptCoverage] of Object.entries(artifacts.JsUsage)) {
90
+ const script = artifacts.Scripts.find(s => s.scriptId === scriptId);
91
+ if (!script) continue; // This should never happen.
92
+
93
+ const networkRecord = networkRecords.find(record => record.url === script.url);
91
94
  if (!networkRecord) continue;
92
- const bundle = bundles.find(b => b.script.src === url);
95
+
96
+ const bundle = bundles.find(b => b.script.scriptId === scriptId);
93
97
  const unusedJsSummary =
94
- await UnusedJavaScriptSummary.request({url, scriptCoverages, bundle}, context);
98
+ await UnusedJavaScriptSummary.request({scriptId, scriptCoverage, bundle}, context);
95
99
  if (unusedJsSummary.wastedBytes === 0 || unusedJsSummary.totalBytes === 0) continue;
96
100
 
97
101
  const transfer = ByteEfficiencyAudit
@@ -99,7 +103,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
99
103
  const transferRatio = transfer / unusedJsSummary.totalBytes;
100
104
  /** @type {LH.Audit.ByteEfficiencyItem} */
101
105
  const item = {
102
- url: unusedJsSummary.url,
106
+ url: script.url,
103
107
  totalBytes: Math.round(transferRatio * unusedJsSummary.totalBytes),
104
108
  wastedBytes: Math.round(transferRatio * unusedJsSummary.wastedBytes),
105
109
  wastedPercent: unusedJsSummary.wastedPercent,
@@ -48,7 +48,7 @@ class Deprecations extends Audit {
48
48
  title: str_(UIStrings.title),
49
49
  failureTitle: str_(UIStrings.failureTitle),
50
50
  description: str_(UIStrings.description),
51
- requiredArtifacts: ['ConsoleMessages', 'InspectorIssues', 'SourceMaps', 'ScriptElements'],
51
+ requiredArtifacts: ['ConsoleMessages', 'InspectorIssues', 'SourceMaps', 'Scripts'],
52
52
  };
53
53
  }
54
54
 
@@ -65,8 +65,8 @@ class Deprecations extends Audit {
65
65
  if (artifacts.InspectorIssues.deprecationIssue.length) {
66
66
  deprecations = artifacts.InspectorIssues.deprecationIssue
67
67
  .map(deprecation => {
68
- const {url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
69
- const bundle = bundles.find(bundle => bundle.script.src === url);
68
+ const {scriptId, url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
69
+ const bundle = bundles.find(bundle => bundle.script.scriptId === scriptId);
70
70
  return {
71
71
  value: deprecation.message || '',
72
72
  // Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed, but we use 0-indexed.
@@ -38,7 +38,7 @@ class GeolocationOnStart extends ViolationAudit {
38
38
  failureTitle: str_(UIStrings.failureTitle),
39
39
  description: str_(UIStrings.description),
40
40
  supportedModes: ['navigation'],
41
- requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
41
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
42
42
  };
43
43
  }
44
44
 
@@ -54,7 +54,7 @@ class NoDocWriteAudit extends ViolationAudit {
54
54
  title: str_(UIStrings.title),
55
55
  failureTitle: str_(UIStrings.failureTitle),
56
56
  description: str_(UIStrings.description),
57
- requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
57
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
58
58
  };
59
59
  }
60
60
 
@@ -38,7 +38,7 @@ class NotificationOnStart extends ViolationAudit {
38
38
  failureTitle: str_(UIStrings.failureTitle),
39
39
  description: str_(UIStrings.description),
40
40
  supportedModes: ['navigation'],
41
- requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
41
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
42
42
  };
43
43
  }
44
44
 
@@ -37,7 +37,7 @@ class PassiveEventsAudit extends ViolationAudit {
37
37
  title: str_(UIStrings.title),
38
38
  failureTitle: str_(UIStrings.failureTitle),
39
39
  description: str_(UIStrings.description),
40
- requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
40
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
41
41
  };
42
42
  }
43
43
 
@@ -40,7 +40,7 @@ class ErrorLogs extends Audit {
40
40
  title: str_(UIStrings.title),
41
41
  failureTitle: str_(UIStrings.failureTitle),
42
42
  description: str_(UIStrings.description),
43
- requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
43
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
44
44
  };
45
45
  }
46
46
 
@@ -87,7 +87,7 @@ class ErrorLogs extends Audit {
87
87
  const consoleRows = artifacts.ConsoleMessages
88
88
  .filter(item => item.level === 'error')
89
89
  .map(item => {
90
- const bundle = bundles.find(bundle => bundle.script.src === item.url);
90
+ const bundle = bundles.find(bundle => bundle.script.scriptId === item.scriptId);
91
91
  return {
92
92
  source: item.source,
93
93
  description: item.text,
@@ -30,7 +30,7 @@ class NoUnloadListeners extends Audit {
30
30
  title: str_(UIStrings.title),
31
31
  failureTitle: str_(UIStrings.failureTitle),
32
32
  description: str_(UIStrings.description),
33
- requiredArtifacts: ['GlobalListeners', 'JsUsage', 'SourceMaps', 'ScriptElements'],
33
+ requiredArtifacts: ['GlobalListeners', 'SourceMaps', 'Scripts'],
34
34
  };
35
35
  }
36
36
 
@@ -54,33 +54,25 @@ class NoUnloadListeners extends Audit {
54
54
  {key: 'source', itemType: 'source-location', text: str_(i18n.UIStrings.columnSource)},
55
55
  ];
56
56
 
57
- // Look up scriptId to script URL via the JsUsage artifact.
58
- /** @type {Map<string, string>} */
59
- const scriptIdToUrl = new Map();
60
- for (const [url, usages] of Object.entries(artifacts.JsUsage)) {
61
- for (const usage of usages) {
62
- scriptIdToUrl.set(usage.scriptId, url);
63
- }
64
- }
65
-
66
57
  /** @type {Array<{source: LH.Audit.Details.ItemValue}>} */
67
58
  const tableItems = unloadListeners.map(listener => {
68
- const url = scriptIdToUrl.get(listener.scriptId);
59
+ const {lineNumber, columnNumber} = listener;
60
+ const script = artifacts.Scripts.find(s => s.scriptId === listener.scriptId);
69
61
 
70
62
  // If we can't find a url, still show something so the user can manually
71
63
  // look for where an `unload` handler is being created.
72
- if (!url) {
64
+ if (!script) {
73
65
  return {
74
66
  source: {
75
67
  type: 'url',
76
- value: '(unknown)',
68
+ value: `(unknown):${lineNumber}:${columnNumber}`,
77
69
  },
78
70
  };
79
71
  }
80
72
 
81
- const bundle = bundles.find(bundle => bundle.script.src === url);
73
+ const bundle = bundles.find(bundle => bundle.script.scriptId === script.scriptId);
82
74
  return {
83
- source: Audit.makeSourceLocation(url, listener.lineNumber, listener.columnNumber, bundle),
75
+ source: Audit.makeSourceLocation(script.url, lineNumber, columnNumber, bundle),
84
76
  };
85
77
  });
86
78
 
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+ 'use strict';
7
+
8
+ /**
9
+ * @fileoverview This is a fake audit used exclusively in smoke tests to force inclusion of ScriptElements artifact.
10
+ * It is included here for complex reasons in the way the bundled smoketests work.
11
+ *
12
+ * The smokehouse configs are evaluated first in the node CLI side (which requires an absolute path using LH_ROOT).
13
+ * The smokehouse configs are then *re-evaluated* in the bundled context for execution by Lighthouse (which *cannot* use an absolute path using LH_ROOT).
14
+ *
15
+ * This mismatch in environment demands that the audit path in the config must be context-aware,
16
+ * yet the require-graph for the config is included before even the CLI knows that it will be using
17
+ * a bundled runner. Rather than force a massive smoketest architecture change, we include a harmless,
18
+ * test-only audit in our core list instead.
19
+ */
20
+
21
+ module.exports = {
22
+ meta: {
23
+ id: 'script-elements-test-audit',
24
+ title: 'ScriptElements',
25
+ failureTitle: 'ScriptElements',
26
+ description: 'Audit to force the inclusion of ScriptElements artifact',
27
+ requiredArtifacts: ['ScriptElements'],
28
+ },
29
+ audit: () => ({score: 1}),
30
+ };
@@ -31,7 +31,7 @@ class ScriptTreemapDataAudit extends Audit {
31
31
  title: 'Script Treemap Data',
32
32
  description: 'Used for treemap app',
33
33
  requiredArtifacts:
34
- ['traces', 'devtoolsLogs', 'SourceMaps', 'ScriptElements', 'JsUsage', 'URL'],
34
+ ['traces', 'devtoolsLogs', 'SourceMaps', 'Scripts', 'JsUsage', 'URL'],
35
35
  };
36
36
  }
37
37
 
@@ -167,11 +167,10 @@ class ScriptTreemapDataAudit extends Audit {
167
167
  const nodes = [];
168
168
 
169
169
  let inlineScriptLength = 0;
170
- for (const scriptElement of artifacts.ScriptElements) {
171
- // No src means script is inline.
172
- // Combine these ScriptElements so that inline scripts show up as a single root node.
173
- if (!scriptElement.src) {
174
- inlineScriptLength += (scriptElement.content || '').length;
170
+ for (const script of artifacts.Scripts) {
171
+ // Combine so that inline scripts show up as a single root node.
172
+ if (script.url === artifacts.URL.finalUrl) {
173
+ inlineScriptLength += (script.content || '').length;
175
174
  }
176
175
  }
177
176
  if (inlineScriptLength) {
@@ -185,25 +184,28 @@ class ScriptTreemapDataAudit extends Audit {
185
184
  const bundles = await JsBundles.request(artifacts, context);
186
185
  const duplicationByPath = await ModuleDuplication.request(artifacts, context);
187
186
 
188
- for (const scriptElement of artifacts.ScriptElements) {
189
- if (!scriptElement.src) continue;
187
+ for (const script of artifacts.Scripts) {
188
+ if (script.url === artifacts.URL.finalUrl) continue; // Handled above.
190
189
 
191
- const name = scriptElement.src;
192
- const bundle = bundles.find(bundle => scriptElement.src === bundle.script.src);
193
- const scriptCoverages = artifacts.JsUsage[scriptElement.src] || [];
194
- if (!bundle && scriptCoverages.length === 0) {
190
+ const name = script.url;
191
+ const bundle = bundles.find(bundle => script.scriptId === bundle.script.scriptId);
192
+ const scriptCoverage = /** @type {Omit<LH.Crdp.Profiler.ScriptCoverage, "url"> | undefined} */
193
+ (artifacts.JsUsage[script.scriptId]);
194
+ if (!bundle && !scriptCoverage) {
195
195
  // No bundle and no coverage information, so simply make a single node
196
196
  // detailing how big the script is.
197
197
 
198
198
  nodes.push({
199
199
  name,
200
- resourceBytes: scriptElement.content?.length || 0,
200
+ resourceBytes: script.length || 0,
201
201
  });
202
202
  continue;
203
203
  }
204
204
 
205
- const unusedJavascriptSummary = await UnusedJavaScriptSummary.request(
206
- {url: scriptElement.src, scriptCoverages, bundle}, context);
205
+ const unusedJavascriptSummary = scriptCoverage ?
206
+ await UnusedJavaScriptSummary.request(
207
+ {scriptId: script.scriptId, scriptCoverage, bundle}, context) :
208
+ undefined;
207
209
 
208
210
  /** @type {LH.Treemap.Node} */
209
211
  let node;
@@ -218,7 +220,7 @@ class ScriptTreemapDataAudit extends Audit {
218
220
  resourceBytes: bundle.sizes.files[source],
219
221
  };
220
222
 
221
- if (unusedJavascriptSummary.sourcesWastedBytes) {
223
+ if (unusedJavascriptSummary?.sourcesWastedBytes) {
222
224
  sourceData.unusedBytes = unusedJavascriptSummary.sourcesWastedBytes[source];
223
225
  }
224
226
 
@@ -241,20 +243,19 @@ class ScriptTreemapDataAudit extends Audit {
241
243
  const sourceData = {
242
244
  resourceBytes: bundle.sizes.unmappedBytes,
243
245
  };
244
- if (unusedJavascriptSummary.sourcesWastedBytes) {
246
+ if (unusedJavascriptSummary?.sourcesWastedBytes) {
245
247
  sourceData.unusedBytes = unusedJavascriptSummary.sourcesWastedBytes['(unmapped)'];
246
248
  }
247
249
  sourcesData['(unmapped)'] = sourceData;
248
250
  }
249
251
 
250
- node = this.makeScriptNode(scriptElement.src, bundle.rawMap.sourceRoot || '', sourcesData);
252
+ node = this.makeScriptNode(script.url, bundle.rawMap.sourceRoot || '', sourcesData);
251
253
  } else {
252
254
  // No valid source map for this script, so we can only produce a single node.
253
-
254
255
  node = {
255
256
  name,
256
- resourceBytes: unusedJavascriptSummary.totalBytes,
257
- unusedBytes: unusedJavascriptSummary.wastedBytes,
257
+ resourceBytes: unusedJavascriptSummary?.totalBytes ?? script.length ?? 0,
258
+ unusedBytes: unusedJavascriptSummary?.wastedBytes,
258
259
  };
259
260
  }
260
261
 
@@ -44,22 +44,22 @@ class ValidSourceMaps extends Audit {
44
44
  title: str_(UIStrings.title),
45
45
  failureTitle: str_(UIStrings.failureTitle),
46
46
  description: str_(UIStrings.description),
47
- requiredArtifacts: ['ScriptElements', 'SourceMaps', 'URL'],
47
+ requiredArtifacts: ['Scripts', 'SourceMaps', 'URL'],
48
48
  };
49
49
  }
50
50
 
51
51
  /**
52
52
  * Returns true if the size of the script exceeds a static threshold.
53
- * @param {LH.Artifacts.ScriptElement} scriptElement
53
+ * @param {LH.Artifacts.Script} script
54
54
  * @param {string} finalURL
55
55
  * @return {boolean}
56
56
  */
57
- static isLargeFirstPartyJS(scriptElement, finalURL) {
58
- if (scriptElement.content === null) return false;
57
+ static isLargeFirstPartyJS(script, finalURL) {
58
+ if (!script.length) return false;
59
59
 
60
- const isLargeJS = scriptElement.content.length >= LARGE_JS_BYTE_THRESHOLD;
61
- const isFirstPartyJS = scriptElement.src ?
62
- thirdPartyWeb.isFirstParty(scriptElement.src, thirdPartyWeb.getEntity(finalURL)) : false;
60
+ const isLargeJS = script.length >= LARGE_JS_BYTE_THRESHOLD;
61
+ const isFirstPartyJS = script.url ?
62
+ thirdPartyWeb.isFirstParty(script.url, thirdPartyWeb.getEntity(finalURL)) : false;
63
63
 
64
64
  return isLargeJS && isFirstPartyJS;
65
65
  }
@@ -75,16 +75,14 @@ class ValidSourceMaps extends Audit {
75
75
 
76
76
  let missingMapsForLargeFirstPartyFile = false;
77
77
  const results = [];
78
- for (const scriptElement of artifacts.ScriptElements) {
79
- if (!scriptElement.src) continue; // TODO: inline scripts, how do they work?
80
-
81
- const sourceMap = SourceMaps.find(m => m.scriptUrl === scriptElement.src);
78
+ for (const script of artifacts.Scripts) {
79
+ const sourceMap = SourceMaps.find(m => m.scriptId === script.scriptId);
82
80
  const errors = [];
83
- const isLargeFirstParty = this.isLargeFirstPartyJS(scriptElement, artifacts.URL.finalUrl);
81
+ const isLargeFirstParty = this.isLargeFirstPartyJS(script, artifacts.URL.finalUrl);
84
82
 
85
83
  if (isLargeFirstParty && (!sourceMap || !sourceMap.map)) {
86
84
  missingMapsForLargeFirstPartyFile = true;
87
- isMissingMapForLargeFirstPartyScriptUrl.add(scriptElement.src);
85
+ isMissingMapForLargeFirstPartyScriptUrl.add(script.url);
88
86
  errors.push({error: str_(UIStrings.missingSourceMapErrorMessage)});
89
87
  }
90
88
 
@@ -107,7 +105,7 @@ class ValidSourceMaps extends Audit {
107
105
 
108
106
  if (sourceMap || errors.length) {
109
107
  results.push({
110
- scriptUrl: scriptElement.src,
108
+ scriptUrl: script.url,
111
109
  sourceMapUrl: sourceMap?.sourceMapUrl,
112
110
  subItems: {
113
111
  type: /** @type {const} */ ('subitems'),
@@ -31,7 +31,7 @@ class ViolationAudit extends Audit {
31
31
  return artifacts.ConsoleMessages
32
32
  .filter(entry => entry.url && entry.source === 'violation' && pattern.test(entry.text))
33
33
  .map(entry => {
34
- const bundle = bundles.find(bundle => bundle.script.src === entry.url);
34
+ const bundle = bundles.find(bundle => bundle.script.scriptId === entry.scriptId);
35
35
  return Audit.makeSourceLocationFromConsoleMessage(entry, bundle);
36
36
  })
37
37
  .filter(filterUndefined)
@@ -12,14 +12,15 @@ const SDK = require('../lib/cdt/SDK.js');
12
12
  /**
13
13
  * Calculate the number of bytes contributed by each source file
14
14
  * @param {LH.Artifacts.Bundle['map']} map
15
+ * @param {number} contentLength
15
16
  * @param {string} content
16
17
  * @return {LH.Artifacts.Bundle['sizes']}
17
18
  */
18
- function computeGeneratedFileSizes(map, content) {
19
+ function computeGeneratedFileSizes(map, contentLength, content) {
19
20
  const lines = content.split('\n');
20
21
  /** @type {Record<string, number>} */
21
22
  const files = {};
22
- const totalBytes = content.length;
23
+ const totalBytes = contentLength;
23
24
  let unmappedBytes = totalBytes;
24
25
 
25
26
  // @ts-expect-error: This function is added in SDK.js. This will eventually be added to CDT.
@@ -78,10 +79,10 @@ function computeGeneratedFileSizes(map, content) {
78
79
 
79
80
  class JSBundles {
80
81
  /**
81
- * @param {Pick<LH.Artifacts, 'SourceMaps'|'ScriptElements'>} artifacts
82
+ * @param {Pick<LH.Artifacts, 'SourceMaps'|'Scripts'>} artifacts
82
83
  */
83
84
  static async compute_(artifacts) {
84
- const {SourceMaps, ScriptElements} = artifacts;
85
+ const {SourceMaps, Scripts} = artifacts;
85
86
 
86
87
  /** @type {LH.Artifacts.Bundle[]} */
87
88
  const bundles = [];
@@ -89,23 +90,22 @@ class JSBundles {
89
90
  // Collate map and script, compute file sizes.
90
91
  for (const SourceMap of SourceMaps) {
91
92
  if (!SourceMap.map) continue;
92
- const {scriptUrl, map: rawMap} = SourceMap;
93
+ const {scriptId, map: rawMap} = SourceMap;
93
94
 
94
95
  if (!rawMap.mappings) continue;
95
96
 
96
- const scriptElement = ScriptElements.find(s => s.src === scriptUrl);
97
- if (!scriptElement) continue;
97
+ const script = Scripts.find(s => s.scriptId === scriptId);
98
+ if (!script) continue;
98
99
 
99
100
  const compiledUrl = SourceMap.scriptUrl || 'compiled.js';
100
101
  const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map';
101
102
  const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMap);
102
103
 
103
- const content = scriptElement?.content ? scriptElement.content : '';
104
- const sizes = computeGeneratedFileSizes(map, content);
104
+ const sizes = computeGeneratedFileSizes(map, script.length || 0, script.content || '');
105
105
 
106
106
  const bundle = {
107
107
  rawMap,
108
- script: scriptElement,
108
+ script,
109
109
  map,
110
110
  sizes,
111
111
  };
@@ -116,4 +116,4 @@ class JSBundles {
116
116
  }
117
117
  }
118
118
 
119
- module.exports = makeComputedArtifact(JSBundles, ['ScriptElements', 'SourceMaps']);
119
+ module.exports = makeComputedArtifact(JSBundles, ['Scripts', 'SourceMaps']);