lighthouse 9.5.0-dev.20220314 → 9.5.0-dev.20220317

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 (32) hide show
  1. package/lighthouse-cli/test/smokehouse/core-tests.js +2 -0
  2. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  3. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -0
  4. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +11 -13
  5. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +12 -16
  6. package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +10 -10
  7. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +11 -6
  8. package/lighthouse-core/audits/deprecations.js +3 -3
  9. package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +1 -1
  10. package/lighthouse-core/audits/dobetterweb/no-document-write.js +1 -1
  11. package/lighthouse-core/audits/dobetterweb/notification-on-start.js +1 -1
  12. package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +1 -1
  13. package/lighthouse-core/audits/errors-in-console.js +2 -2
  14. package/lighthouse-core/audits/no-unload-listeners.js +7 -15
  15. package/lighthouse-core/audits/script-elements-test-audit.js +30 -0
  16. package/lighthouse-core/audits/script-treemap-data.js +22 -21
  17. package/lighthouse-core/audits/valid-source-maps.js +12 -14
  18. package/lighthouse-core/audits/violation-audit.js +1 -1
  19. package/lighthouse-core/computed/js-bundles.js +11 -11
  20. package/lighthouse-core/computed/module-duplication.js +6 -5
  21. package/lighthouse-core/computed/unused-javascript-summary.js +16 -36
  22. package/lighthouse-core/config/default-config.js +1 -0
  23. package/lighthouse-core/fraggle-rock/config/default-config.js +3 -0
  24. package/lighthouse-core/gather/gatherers/console-messages.js +4 -2
  25. package/lighthouse-core/gather/gatherers/js-usage.js +6 -52
  26. package/lighthouse-core/gather/gatherers/script-elements.js +7 -56
  27. package/lighthouse-core/gather/gatherers/scripts.js +136 -0
  28. package/lighthouse-core/gather/gatherers/source-maps.js +3 -0
  29. package/lighthouse-core/lib/script-helpers.js +22 -0
  30. package/lighthouse-core/lib/url-shim.js +2 -2
  31. package/package.json +3 -2
  32. package/types/artifacts.d.ts +22 -7
@@ -52,6 +52,7 @@ import pwaSvgomg from './test-definitions/pwa-svgomg.js';
52
52
  import redirectsClientPaintServer from './test-definitions/redirects-client-paint-server.js';
53
53
  import redirectsHistoryPushState from './test-definitions/redirects-history-push-state.js';
54
54
  import redirectsMultipleServer from './test-definitions/redirects-multiple-server.js';
55
+ import redirectScripts from './test-definitions/redirects-scripts.js';
55
56
  import redirectsSingleClient from './test-definitions/redirects-single-client.js';
56
57
  import redirectsSingleServer from './test-definitions/redirects-single-server.js';
57
58
  import redirectsSelf from './test-definitions/redirects-self.js';
@@ -112,6 +113,7 @@ const smokeTests = [
112
113
  redirectsClientPaintServer,
113
114
  redirectsHistoryPushState,
114
115
  redirectsMultipleServer,
116
+ redirectScripts,
115
117
  redirectsSingleClient,
116
118
  redirectsSingleServer,
117
119
  redirectsSelf,
@@ -78,8 +78,8 @@ 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',
82
+ '--quiet',
83
83
  ];
84
84
 
85
85
  if (useFraggleRock) {
@@ -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) {
@@ -11,8 +11,8 @@
11
11
 
12
12
  const ByteEfficiencyAudit = require('./byte-efficiency-audit.js');
13
13
  const ModuleDuplication = require('../../computed/module-duplication.js');
14
- const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
15
14
  const i18n = require('../../lib/i18n/i18n.js');
15
+ const {getRequestForScript} = require('../../lib/script-helpers.js');
16
16
 
17
17
  const UIStrings = {
18
18
  /** Imperative title of a Lighthouse audit that tells the user to remove duplicate JavaScript from their code. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -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;
@@ -130,7 +130,6 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
130
130
  context.options?.ignoreThresholdInBytes || IGNORE_THRESHOLD_IN_BYTES;
131
131
  const duplication =
132
132
  await DuplicatedJavascript._getDuplicationGroupedByNodeModules(artifacts, context);
133
- const mainDocumentRecord = NetworkAnalyzer.findOptionalMainDocument(networkRecords);
134
133
 
135
134
  /** @type {Map<string, number>} */
136
135
  const transferRatioByUrl = new Map();
@@ -157,22 +156,21 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
157
156
  let wastedBytesTotal = 0;
158
157
  for (let i = 0; i < sourceDatas.length; i++) {
159
158
  const sourceData = sourceDatas[i];
160
- const url = sourceData.scriptUrl;
159
+ const scriptId = sourceData.scriptId;
160
+ const script = artifacts.Scripts.find(script => script.scriptId === scriptId);
161
+ const url = script?.url || '';
161
162
 
162
163
  /** @type {number|undefined} */
163
164
  let transferRatio = transferRatioByUrl.get(url);
164
165
  if (transferRatio === undefined) {
165
- const networkRecord = url === artifacts.URL.finalUrl ?
166
- mainDocumentRecord :
167
- networkRecords.find(n => n.url === url);
166
+ const networkRecord = getRequestForScript(networkRecords, script);
168
167
 
169
- const script = artifacts.ScriptElements.find(script => script.src === url);
170
- if (!script || script.content === null) {
168
+ if (!script || script.length === undefined) {
171
169
  // This should never happen because we found the wasted bytes from bundles, which required contents in a ScriptElement.
172
170
  continue;
173
171
  }
174
172
 
175
- const contentLength = script.content.length;
173
+ const contentLength = script.length;
176
174
  transferRatio = DuplicatedJavascript._estimateTransferRatio(networkRecord, contentLength);
177
175
  transferRatioByUrl.set(url, transferRatio);
178
176
  }
@@ -22,7 +22,7 @@ const ByteEfficiencyAudit = require('./byte-efficiency-audit.js');
22
22
  const JsBundles = require('../../computed/js-bundles.js');
23
23
  const i18n = require('../../lib/i18n/i18n.js');
24
24
  const thirdPartyWeb = require('../../lib/third-party-web.js');
25
- const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
25
+ const {getRequestForScript} = require('../../lib/script-helpers.js');
26
26
 
27
27
  const UIStrings = {
28
28
  /** Title of a Lighthouse audit that tells the user about legacy polyfills and transforms used on the page. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -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;
@@ -373,17 +372,14 @@ class LegacyJavascript extends ByteEfficiencyAudit {
373
372
  let transferRatio = transferRatioByUrl.get(url);
374
373
  if (transferRatio !== undefined) return transferRatio;
375
374
 
376
- const mainDocumentRecord = NetworkAnalyzer.findOptionalMainDocument(networkRecords);
377
- const networkRecord = url === artifacts.URL.finalUrl ?
378
- mainDocumentRecord :
379
- networkRecords.find(n => n.url === url);
380
- const script = artifacts.ScriptElements.find(script => script.src === url);
375
+ const script = artifacts.Scripts.find(script => script.url === url);
376
+ const networkRecord = getRequestForScript(networkRecords, script);
381
377
 
382
378
  if (!script || script.content === null) {
383
379
  // Can't find content, so just use 1.
384
380
  transferRatio = 1;
385
381
  } else {
386
- const contentLength = script.content.length;
382
+ const contentLength = script.length || 0;
387
383
  const transferSize =
388
384
  ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength, 'Script');
389
385
  transferRatio = transferSize / contentLength;
@@ -415,7 +411,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
415
411
  const transferRatioByUrl = new Map();
416
412
 
417
413
  const urlToMatchResults =
418
- this.detectAcrossScripts(matcher, artifacts.ScriptElements, networkRecords, bundles);
414
+ this.detectAcrossScripts(matcher, artifacts.Scripts, networkRecords, bundles);
419
415
  for (const [url, matches] of urlToMatchResults.entries()) {
420
416
  const transferRatio = await this.estimateTransferRatioForScript(
421
417
  transferRatioByUrl, url, artifacts, networkRecords);
@@ -432,7 +428,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
432
428
  totalBytes: 0,
433
429
  };
434
430
 
435
- const bundle = bundles.find(bundle => bundle.script.src === url);
431
+ const bundle = bundles.find(bundle => bundle.script.url === url); // TODO: scriptId
436
432
  for (const match of matches) {
437
433
  const {name, line, column} = match;
438
434
  /** @type {SubItem} */
@@ -8,6 +8,7 @@
8
8
  const ByteEfficiencyAudit = require('./byte-efficiency-audit.js');
9
9
  const i18n = require('../../lib/i18n/i18n.js');
10
10
  const computeTokenLength = require('../../lib/minification-estimator.js').computeJSTokenLength;
11
+ const {getRequestForScript} = require('../../lib/script-helpers.js');
11
12
 
12
13
  const UIStrings = {
13
14
  /** Imperative title of a Lighthouse audit that tells the user to minify the page’s JS code to reduce file size. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -42,7 +43,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
42
43
  title: str_(UIStrings.title),
43
44
  description: str_(UIStrings.description),
44
45
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
45
- requiredArtifacts: ['ScriptElements', 'devtoolsLogs', 'traces', 'GatherContext'],
46
+ requiredArtifacts: ['Scripts', 'devtoolsLogs', 'traces', 'GatherContext', 'URL'],
46
47
  };
47
48
  }
48
49
 
@@ -78,15 +79,15 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
78
79
  /** @type {Array<LH.Audit.ByteEfficiencyItem>} */
79
80
  const items = [];
80
81
  const warnings = [];
81
- for (const {requestId, src, content} of artifacts.ScriptElements) {
82
- if (!content) continue;
82
+ for (const script of artifacts.Scripts) {
83
+ if (!script.content) continue;
83
84
 
84
- const networkRecord = networkRecords.find(record => record.requestId === requestId);
85
- const displayUrl = !src || !networkRecord ?
86
- `inline: ${content.substr(0, 40)}...` :
87
- networkRecord.url;
85
+ const networkRecord = getRequestForScript(networkRecords, script);
86
+ const displayUrl = script.name === artifacts.URL.finalUrl ?
87
+ `inline: ${script.content.substring(0, 40)}...` :
88
+ script.url;
88
89
  try {
89
- const result = UnminifiedJavaScript.computeWaste(content, displayUrl, networkRecord);
90
+ const result = UnminifiedJavaScript.computeWaste(script.content, displayUrl, networkRecord);
90
91
  // If the ratio is minimal, the file is likely already minified, so ignore it.
91
92
  // If the total number of bytes to be saved is quite small, it's also safe to ignore.
92
93
  if (result.wastedPercent < IGNORE_THRESHOLD_IN_PERCENT ||
@@ -94,8 +95,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
94
95
  !Number.isFinite(result.wastedBytes)) continue;
95
96
  items.push(result);
96
97
  } catch (err) {
97
- const url = networkRecord ? networkRecord.url : '?';
98
- warnings.push(`Unable to process script ${url}: ${err.message}`);
98
+ warnings.push(`Unable to process script ${script.url}: ${err.message}`);
99
99
  }
100
100
  }
101
101
 
@@ -9,6 +9,7 @@ const ByteEfficiencyAudit = require('./byte-efficiency-audit.js');
9
9
  const UnusedJavaScriptSummary = require('../../computed/unused-javascript-summary.js');
10
10
  const JsBundles = require('../../computed/js-bundles.js');
11
11
  const i18n = require('../../lib/i18n/i18n.js');
12
+ const {getRequestForScript} = require('../../lib/script-helpers.js');
12
13
 
13
14
  const UIStrings = {
14
15
  /** Imperative title of a Lighthouse audit that tells the user to reduce JavaScript that is never evaluated during page load. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -67,7 +68,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
67
68
  title: str_(UIStrings.title),
68
69
  description: str_(UIStrings.description),
69
70
  scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
70
- requiredArtifacts: ['JsUsage', 'ScriptElements', 'SourceMaps', 'GatherContext',
71
+ requiredArtifacts: ['JsUsage', 'Scripts', 'SourceMaps', 'GatherContext',
71
72
  'devtoolsLogs', 'traces'],
72
73
  };
73
74
  }
@@ -86,12 +87,16 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
86
87
  } = context.options || {};
87
88
 
88
89
  const items = [];
89
- for (const [url, scriptCoverages] of Object.entries(artifacts.JsUsage)) {
90
- const networkRecord = networkRecords.find(record => record.url === url);
90
+ for (const [scriptId, scriptCoverage] of Object.entries(artifacts.JsUsage)) {
91
+ const script = artifacts.Scripts.find(s => s.scriptId === scriptId);
92
+ if (!script) continue; // This should never happen.
93
+
94
+ const networkRecord = getRequestForScript(networkRecords, script);
91
95
  if (!networkRecord) continue;
92
- const bundle = bundles.find(b => b.script.src === url);
96
+
97
+ const bundle = bundles.find(b => b.script.scriptId === scriptId);
93
98
  const unusedJsSummary =
94
- await UnusedJavaScriptSummary.request({url, scriptCoverages, bundle}, context);
99
+ await UnusedJavaScriptSummary.request({scriptId, scriptCoverage, bundle}, context);
95
100
  if (unusedJsSummary.wastedBytes === 0 || unusedJsSummary.totalBytes === 0) continue;
96
101
 
97
102
  const transfer = ByteEfficiencyAudit
@@ -99,7 +104,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
99
104
  const transferRatio = transfer / unusedJsSummary.totalBytes;
100
105
  /** @type {LH.Audit.ByteEfficiencyItem} */
101
106
  const item = {
102
- url: unusedJsSummary.url,
107
+ url: script.url,
103
108
  totalBytes: Math.round(transferRatio * unusedJsSummary.totalBytes),
104
109
  wastedBytes: Math.round(transferRatio * unusedJsSummary.wastedBytes),
105
110
  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)