lighthouse 11.1.0-dev.20231002 → 11.1.0-dev.20231004

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 (69) hide show
  1. package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +8 -0
  2. package/core/audits/audit.js +21 -11
  3. package/core/audits/bf-cache.js +1 -0
  4. package/core/audits/bootup-time.js +4 -1
  5. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  6. package/core/audits/byte-efficiency/duplicated-javascript.js +1 -1
  7. package/core/audits/byte-efficiency/efficient-animated-content.js +1 -1
  8. package/core/audits/byte-efficiency/legacy-javascript.js +1 -1
  9. package/core/audits/byte-efficiency/modern-image-formats.js +1 -1
  10. package/core/audits/byte-efficiency/offscreen-images.js +1 -1
  11. package/core/audits/byte-efficiency/render-blocking-resources.js +2 -3
  12. package/core/audits/byte-efficiency/total-byte-weight.js +3 -1
  13. package/core/audits/byte-efficiency/unminified-css.js +1 -1
  14. package/core/audits/byte-efficiency/unminified-javascript.js +1 -1
  15. package/core/audits/byte-efficiency/unused-css-rules.js +1 -1
  16. package/core/audits/byte-efficiency/unused-javascript.js +1 -1
  17. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +3 -8
  18. package/core/audits/byte-efficiency/uses-optimized-images.js +1 -1
  19. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +1 -0
  20. package/core/audits/byte-efficiency/uses-responsive-images.js +1 -1
  21. package/core/audits/byte-efficiency/uses-text-compression.js +1 -1
  22. package/core/audits/csp-xss.js +1 -1
  23. package/core/audits/dobetterweb/dom-size.js +3 -1
  24. package/core/audits/dobetterweb/no-document-write.js +1 -0
  25. package/core/audits/dobetterweb/uses-http2.js +2 -3
  26. package/core/audits/dobetterweb/uses-passive-event-listeners.js +1 -0
  27. package/core/audits/font-display.js +1 -0
  28. package/core/audits/installable-manifest.js +2 -2
  29. package/core/audits/largest-contentful-paint-element.js +3 -2
  30. package/core/audits/layout-shift-elements.js +8 -4
  31. package/core/audits/lcp-lazy-loaded.js +1 -0
  32. package/core/audits/long-tasks.js +8 -1
  33. package/core/audits/mainthread-work-breakdown.js +3 -1
  34. package/core/audits/metrics/cumulative-layout-shift.js +4 -1
  35. package/core/audits/metrics/first-contentful-paint.js +1 -0
  36. package/core/audits/metrics/largest-contentful-paint.js +1 -0
  37. package/core/audits/metrics/speed-index.js +1 -0
  38. package/core/audits/metrics/total-blocking-time.js +1 -0
  39. package/core/audits/prioritize-lcp-image.js +2 -3
  40. package/core/audits/redirects.js +2 -3
  41. package/core/audits/server-response-time.js +2 -0
  42. package/core/audits/third-party-facades.js +1 -0
  43. package/core/audits/third-party-summary.js +5 -1
  44. package/core/audits/unsized-images.js +1 -1
  45. package/core/audits/uses-rel-preconnect.js +2 -3
  46. package/core/audits/uses-rel-preload.js +2 -3
  47. package/core/audits/viewport.js +1 -0
  48. package/core/audits/work-during-interaction.js +6 -2
  49. package/core/gather/session.js +2 -0
  50. package/core/lib/csp-evaluator.js +13 -13
  51. package/dist/report/bundle.esm.js +12 -89
  52. package/dist/report/flow.js +17 -94
  53. package/dist/report/standalone.js +13 -90
  54. package/package.json +4 -4
  55. package/report/assets/styles.css +1 -78
  56. package/report/assets/templates.html +0 -35
  57. package/report/renderer/components.d.ts +2 -2
  58. package/report/renderer/components.js +2 -51
  59. package/report/renderer/performance-category-renderer.d.ts +16 -18
  60. package/report/renderer/performance-category-renderer.js +90 -102
  61. package/shared/localization/locales/en-US.json +14 -14
  62. package/shared/localization/locales/en-XL.json +14 -14
  63. package/shared/util.d.ts +22 -0
  64. package/shared/util.js +31 -0
  65. package/types/audit.d.ts +5 -1
  66. package/types/internal/rxjs.d.ts +3 -0
  67. package/types/lhr/audit-result.d.ts +16 -0
  68. /package/{core/lib → shared}/statistics.d.ts +0 -0
  69. /package/{core/lib → shared}/statistics.js +0 -0
@@ -20,6 +20,10 @@ exports[`getAssertionReport works (multiple failing) 1`] = `
20
20
  \\"numericValue\\": 0.13570762803819444,
21
21
  \\"numericUnit\\": \\"unitless\\",
22
22
  \\"displayValue\\": \\"0.136\\",
23
+ \\"scoringOptions\\": {
24
+ \\"p10\\": 0.1,
25
+ \\"median\\": 0.25
26
+ },
23
27
  \\"details\\": {
24
28
  \\"type\\": \\"debugdata\\",
25
29
  \\"items\\": [
@@ -46,6 +50,10 @@ exports[`getAssertionReport works (trivial failing) 1`] = `
46
50
  \\"numericValue\\": 0.13570762803819444,
47
51
  \\"numericUnit\\": \\"unitless\\",
48
52
  \\"displayValue\\": \\"0.136\\",
53
+ \\"scoringOptions\\": {
54
+ \\"p10\\": 0.1,
55
+ \\"median\\": 0.25
56
+ },
49
57
  \\"details\\": {
50
58
  \\"type\\": \\"debugdata\\",
51
59
  \\"items\\": [
@@ -6,7 +6,6 @@
6
6
 
7
7
  import * as LH from '../../types/lh.js';
8
8
  import {isUnderTest} from '../lib/lh-env.js';
9
- import * as statistics from '../lib/statistics.js';
10
9
  import {Util} from '../../shared/util.js';
11
10
 
12
11
  const DEFAULT_PASS = 'defaultPass';
@@ -50,6 +49,7 @@ class Audit {
50
49
  static get SCORING_MODES() {
51
50
  return {
52
51
  NUMERIC: 'numeric',
52
+ METRIC_SAVINGS: 'metricSavings',
53
53
  BINARY: 'binary',
54
54
  MANUAL: 'manual',
55
55
  INFORMATIVE: 'informative',
@@ -105,14 +105,7 @@ class Audit {
105
105
  * @return {number}
106
106
  */
107
107
  static computeLogNormalScore(controlPoints, value) {
108
- let percentile = statistics.getLogNormalScore(controlPoints, value);
109
- // Add a boost to scores of 90+, linearly ramping from 0 at 0.9 to half a
110
- // point (0.005) at 1. Expands scores in (0.9, 1] to (0.9, 1.005], so more top
111
- // scores will be a perfect 1 after the two-digit `Math.floor()` rounding below.
112
- if (percentile > 0.9) { // getLogNormalScore ensures `percentile` can't exceed 1.
113
- percentile += 0.05 * (percentile - 0.9);
114
- }
115
- return Math.floor(percentile * 100) / 100;
108
+ return Util.computeLogNormalScore(controlPoints, value);
116
109
  }
117
110
 
118
111
  /**
@@ -329,7 +322,8 @@ class Audit {
329
322
  */
330
323
  static _normalizeAuditScore(score, scoreDisplayMode, auditId) {
331
324
  if (scoreDisplayMode !== Audit.SCORING_MODES.BINARY &&
332
- scoreDisplayMode !== Audit.SCORING_MODES.NUMERIC) {
325
+ scoreDisplayMode !== Audit.SCORING_MODES.NUMERIC &&
326
+ scoreDisplayMode !== Audit.SCORING_MODES.METRIC_SAVINGS) {
333
327
  return null;
334
328
  }
335
329
 
@@ -371,6 +365,7 @@ class Audit {
371
365
 
372
366
  // Default to binary scoring.
373
367
  let scoreDisplayMode = audit.meta.scoreDisplayMode || Audit.SCORING_MODES.BINARY;
368
+ let score = product.score;
374
369
 
375
370
  // But override if product contents require it.
376
371
  if (product.errorMessage !== undefined) {
@@ -379,9 +374,21 @@ class Audit {
379
374
  } else if (product.notApplicable) {
380
375
  // Audit was determined to not apply to the page.
381
376
  scoreDisplayMode = Audit.SCORING_MODES.NOT_APPLICABLE;
377
+ } else if (product.scoreDisplayMode) {
378
+ scoreDisplayMode = product.scoreDisplayMode;
379
+ }
380
+
381
+ if (scoreDisplayMode === Audit.SCORING_MODES.METRIC_SAVINGS) {
382
+ if (score && score >= Util.PASS_THRESHOLD) {
383
+ score = 1;
384
+ } else if (Object.values(product.metricSavings || {}).some(v => v)) {
385
+ score = 0;
386
+ } else {
387
+ score = 0.5;
388
+ }
382
389
  }
383
390
 
384
- const score = Audit._normalizeAuditScore(product.score, scoreDisplayMode, audit.meta.id);
391
+ score = Audit._normalizeAuditScore(score, scoreDisplayMode, audit.meta.id);
385
392
 
386
393
  let auditTitle = audit.meta.title;
387
394
  if (audit.meta.failureTitle) {
@@ -411,8 +418,11 @@ class Audit {
411
418
  errorMessage: product.errorMessage,
412
419
  errorStack: product.errorStack,
413
420
  warnings: product.warnings,
421
+ scoringOptions: product.scoringOptions,
422
+ metricSavings: product.metricSavings,
414
423
 
415
424
  details: product.details,
425
+ guidanceLevel: audit.meta.guidanceLevel,
416
426
  };
417
427
  }
418
428
 
@@ -61,6 +61,7 @@ class BFCache extends Audit {
61
61
  supportedModes: ['navigation', 'timespan'],
62
62
  guidanceLevel: 2,
63
63
  requiredArtifacts: ['BFCacheFailures'],
64
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
64
65
  };
65
66
  }
66
67
 
@@ -14,6 +14,7 @@ import {MainThreadTasks} from '../computed/main-thread-tasks.js';
14
14
  import {getExecutionTimingsByURL} from '../lib/tracehouse/task-summary.js';
15
15
  import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';
16
16
  import {Sentry} from '../lib/sentry.js';
17
+ import {Util} from '../../shared/util.js';
17
18
 
18
19
  const UIStrings = {
19
20
  /** Title of a diagnostic audit that provides detail on the time spent executing javascript files during the load. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
@@ -47,7 +48,7 @@ class BootupTime extends Audit {
47
48
  title: str_(UIStrings.title),
48
49
  failureTitle: str_(UIStrings.failureTitle),
49
50
  description: str_(UIStrings.description),
50
- scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
51
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
51
52
  guidanceLevel: 1,
52
53
  requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
53
54
  };
@@ -169,6 +170,8 @@ class BootupTime extends Audit {
169
170
 
170
171
  return {
171
172
  score,
173
+ scoreDisplayMode: score >= Util.PASS_THRESHOLD ? Audit.SCORING_MODES.INFORMATIVE : undefined,
174
+ notApplicable: !results.length,
172
175
  numericValue: totalBootupTime,
173
176
  numericUnit: 'millisecond',
174
177
  displayValue: totalBootupTime > 0 ?
@@ -309,7 +309,7 @@ class ByteEfficiencyAudit extends Audit {
309
309
  displayValue,
310
310
  numericValue: wastedMs,
311
311
  numericUnit: 'millisecond',
312
- score: ByteEfficiencyAudit.scoreForWastedMs(wastedMs),
312
+ score: results.length ? 0 : 1,
313
313
  details,
314
314
  metricSavings,
315
315
  };
@@ -46,7 +46,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
46
46
  id: 'duplicated-javascript',
47
47
  title: str_(UIStrings.title),
48
48
  description: str_(UIStrings.description),
49
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
49
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
50
50
  guidanceLevel: 2,
51
51
  requiredArtifacts: ['devtoolsLogs', 'traces', 'SourceMaps', 'Scripts',
52
52
  'GatherContext', 'URL'],
@@ -36,7 +36,7 @@ class EfficientAnimatedContent extends ByteEfficiencyAudit {
36
36
  id: 'efficient-animated-content',
37
37
  title: str_(UIStrings.title),
38
38
  description: str_(UIStrings.description),
39
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
39
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
40
40
  guidanceLevel: 3,
41
41
  requiredArtifacts: ['devtoolsLogs', 'traces', 'GatherContext', 'URL'],
42
42
  };
@@ -115,7 +115,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
115
115
  static get meta() {
116
116
  return {
117
117
  id: 'legacy-javascript',
118
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
118
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
119
119
  description: str_(UIStrings.description),
120
120
  title: str_(UIStrings.title),
121
121
  guidanceLevel: 2,
@@ -34,7 +34,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
34
34
  id: 'modern-image-formats',
35
35
  title: str_(UIStrings.title),
36
36
  description: str_(UIStrings.description),
37
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
37
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
38
38
  guidanceLevel: 3,
39
39
  requiredArtifacts: ['OptimizedImages', 'devtoolsLogs', 'traces', 'URL', 'GatherContext',
40
40
  'ImageElements'],
@@ -48,7 +48,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
48
48
  id: 'offscreen-images',
49
49
  title: str_(UIStrings.title),
50
50
  description: str_(UIStrings.description),
51
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
51
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
52
52
  supportedModes: ['navigation'],
53
53
  guidanceLevel: 2,
54
54
  requiredArtifacts: ['ImageElements', 'ViewportDimensions', 'GatherContext', 'devtoolsLogs',
@@ -12,7 +12,6 @@
12
12
  import {Audit} from '../audit.js';
13
13
  import * as i18n from '../../lib/i18n/i18n.js';
14
14
  import {BaseNode} from '../../lib/dependency-graph/base-node.js';
15
- import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
16
15
  import {UnusedCSS} from '../../computed/unused-css.js';
17
16
  import {NetworkRequest} from '../../lib/network-request.js';
18
17
  import {ProcessedNavigation} from '../../computed/processed-navigation.js';
@@ -113,7 +112,7 @@ class RenderBlockingResources extends Audit {
113
112
  id: 'render-blocking-resources',
114
113
  title: str_(UIStrings.title),
115
114
  supportedModes: ['navigation'],
116
- scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
115
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
117
116
  description: str_(UIStrings.description),
118
117
  guidanceLevel: 2,
119
118
  // TODO: look into adding an `optionalArtifacts` property that captures the non-required nature
@@ -296,7 +295,7 @@ class RenderBlockingResources extends Audit {
296
295
 
297
296
  return {
298
297
  displayValue,
299
- score: ByteEfficiencyAudit.scoreForWastedMs(wastedMs),
298
+ score: results.length ? 0 : 1,
300
299
  numericValue: wastedMs,
301
300
  numericUnit: 'millisecond',
302
301
  details,
@@ -8,6 +8,7 @@ import {Audit} from '../audit.js';
8
8
  import * as i18n from '../../lib/i18n/i18n.js';
9
9
  import {NetworkRequest} from '../../lib/network-request.js';
10
10
  import {NetworkRecords} from '../../computed/network-records.js';
11
+ import {Util} from '../../../shared/util.js';
11
12
 
12
13
  const UIStrings = {
13
14
  /** Title of a diagnostic audit that provides detail on large network resources required during page load. 'Payloads' is roughly equivalent to 'resources'. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
@@ -34,7 +35,7 @@ class TotalByteWeight extends Audit {
34
35
  title: str_(UIStrings.title),
35
36
  failureTitle: str_(UIStrings.failureTitle),
36
37
  description: str_(UIStrings.description),
37
- scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
38
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
38
39
  guidanceLevel: 1,
39
40
  requiredArtifacts: ['devtoolsLogs'],
40
41
  };
@@ -98,6 +99,7 @@ class TotalByteWeight extends Audit {
98
99
 
99
100
  return {
100
101
  score,
102
+ scoreDisplayMode: score >= Util.PASS_THRESHOLD ? Audit.SCORING_MODES.INFORMATIVE : undefined,
101
103
  numericValue: totalBytes,
102
104
  numericUnit: 'byte',
103
105
  displayValue: str_(UIStrings.displayValue, {totalBytes}),
@@ -34,7 +34,7 @@ class UnminifiedCSS extends ByteEfficiencyAudit {
34
34
  id: 'unminified-css',
35
35
  title: str_(UIStrings.title),
36
36
  description: str_(UIStrings.description),
37
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
37
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
38
38
  guidanceLevel: 3,
39
39
  requiredArtifacts: ['CSSUsage', 'devtoolsLogs', 'traces', 'URL', 'GatherContext'],
40
40
  };
@@ -42,7 +42,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
42
42
  id: 'unminified-javascript',
43
43
  title: str_(UIStrings.title),
44
44
  description: str_(UIStrings.description),
45
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
45
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
46
46
  guidanceLevel: 3,
47
47
  requiredArtifacts: ['Scripts', 'devtoolsLogs', 'traces', 'GatherContext', 'URL'],
48
48
  };
@@ -32,7 +32,7 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
32
32
  id: 'unused-css-rules',
33
33
  title: str_(UIStrings.title),
34
34
  description: str_(UIStrings.description),
35
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
35
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
36
36
  guidanceLevel: 2,
37
37
  requiredArtifacts: ['CSSUsage', 'URL', 'devtoolsLogs', 'traces', 'GatherContext'],
38
38
  };
@@ -66,7 +66,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
66
66
  id: 'unused-javascript',
67
67
  title: str_(UIStrings.title),
68
68
  description: str_(UIStrings.description),
69
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
69
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
70
70
  guidanceLevel: 2,
71
71
  requiredArtifacts: ['JsUsage', 'Scripts', 'SourceMaps', 'GatherContext',
72
72
  'devtoolsLogs', 'traces', 'URL'],
@@ -9,7 +9,7 @@ import parseCacheControl from 'parse-cache-control';
9
9
  import {Audit} from '../audit.js';
10
10
  import {NetworkRequest} from '../../lib/network-request.js';
11
11
  import UrlUtils from '../../lib/url-utils.js';
12
- import {linearInterpolation} from '../../lib/statistics.js';
12
+ import {linearInterpolation} from '../../../shared/statistics.js';
13
13
  import * as i18n from '../../lib/i18n/i18n.js';
14
14
  import {NetworkRecords} from '../../computed/network-records.js';
15
15
 
@@ -44,7 +44,7 @@ class CacheHeaders extends Audit {
44
44
  title: str_(UIStrings.title),
45
45
  failureTitle: str_(UIStrings.failureTitle),
46
46
  description: str_(UIStrings.description),
47
- scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
47
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
48
48
  guidanceLevel: 3,
49
49
  requiredArtifacts: ['devtoolsLogs'],
50
50
  };
@@ -266,11 +266,6 @@ class CacheHeaders extends Audit {
266
266
  a.url.localeCompare(b.url);
267
267
  });
268
268
 
269
- const score = Audit.computeLogNormalScore(
270
- {p10: context.options.p10, median: context.options.median},
271
- totalWastedBytes
272
- );
273
-
274
269
  /** @type {LH.Audit.Details.Table['headings']} */
275
270
  const headings = [
276
271
  {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
@@ -285,7 +280,7 @@ class CacheHeaders extends Audit {
285
280
  {wastedBytes: totalWastedBytes, sortedBy: ['totalBytes'], skipSumming: ['cacheLifetimeMs']});
286
281
 
287
282
  return {
288
- score,
283
+ score: results.length ? 0 : 1,
289
284
  numericValue: totalWastedBytes,
290
285
  numericUnit: 'byte',
291
286
  displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
@@ -34,7 +34,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
34
34
  id: 'uses-optimized-images',
35
35
  title: str_(UIStrings.title),
36
36
  description: str_(UIStrings.description),
37
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
37
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
38
38
  guidanceLevel: 2,
39
39
  requiredArtifacts: ['OptimizedImages', 'ImageElements', 'GatherContext', 'devtoolsLogs',
40
40
  'traces', 'URL'],
@@ -42,6 +42,7 @@ class UsesResponsiveImagesSnapshot extends Audit {
42
42
  title: str_(UIStrings.title),
43
43
  failureTitle: str_(UIStrings.failureTitle),
44
44
  description: UsesResponsiveImages.str_(UsesResponsiveImages.UIStrings.description),
45
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
45
46
  supportedModes: ['snapshot'],
46
47
  guidanceLevel: 2,
47
48
  requiredArtifacts: ['ImageElements', 'ViewportDimensions'],
@@ -45,7 +45,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
45
45
  id: 'uses-responsive-images',
46
46
  title: str_(UIStrings.title),
47
47
  description: str_(UIStrings.description),
48
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
48
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
49
49
  guidanceLevel: 2,
50
50
  requiredArtifacts: ['ImageElements', 'ViewportDimensions', 'GatherContext',
51
51
  'devtoolsLogs', 'traces', 'URL'],
@@ -36,7 +36,7 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit {
36
36
  id: 'uses-text-compression',
37
37
  title: str_(UIStrings.title),
38
38
  description: str_(UIStrings.description),
39
- scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC,
39
+ scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.METRIC_SAVINGS,
40
40
  guidanceLevel: 3,
41
41
  requiredArtifacts: ['ResponseCompression', 'GatherContext', 'devtoolsLogs', 'traces', 'URL'],
42
42
  };
@@ -21,7 +21,7 @@ const UIStrings = {
21
21
  /** Summary text for the results of a Lighthouse audit that evaluates the security of a page's CSP. This is displayed if no CSP is being enforced. "CSP" stands for "Content Security Policy". "CSP" does not need to be translated. */
22
22
  noCsp: 'No CSP found in enforcement mode',
23
23
  /** Message shown when one or more CSPs are defined in a <meta> tag. Shown in a table with a list of other CSP bypasses and warnings. "CSP" stands for "Content Security Policy". "CSP" and "HTTP" do not need to be translated. */
24
- metaTagMessage: 'The page contains a CSP defined in a <meta> tag. ' +
24
+ metaTagMessage: 'The page contains a CSP defined in a `<meta>` tag. ' +
25
25
  'Consider moving the CSP to an HTTP header or ' +
26
26
  'defining another strict CSP in an HTTP header.',
27
27
  /** Label for a column in a data table; entries will be a directive of a CSP. "CSP" stands for "Content Security Policy". */
@@ -14,6 +14,7 @@
14
14
  import {Audit} from '../audit.js';
15
15
  import * as i18n from '../../lib/i18n/i18n.js';
16
16
  import {TBTImpactTasks} from '../../computed/tbt-impact-tasks.js';
17
+ import {Util} from '../../../shared/util.js';
17
18
 
18
19
  const UIStrings = {
19
20
  /** Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
@@ -53,7 +54,7 @@ class DOMSize extends Audit {
53
54
  title: str_(UIStrings.title),
54
55
  failureTitle: str_(UIStrings.failureTitle),
55
56
  description: str_(UIStrings.description),
56
- scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
57
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
57
58
  guidanceLevel: 1,
58
59
  requiredArtifacts: ['DOMStats', 'URL', 'GatherContext'],
59
60
  __internalOptionalArtifacts: ['traces', 'devtoolsLogs'],
@@ -167,6 +168,7 @@ class DOMSize extends Audit {
167
168
 
168
169
  return {
169
170
  score,
171
+ scoreDisplayMode: score >= Util.PASS_THRESHOLD ? Audit.SCORING_MODES.INFORMATIVE : undefined,
170
172
  numericValue: stats.totalBodyElements,
171
173
  numericUnit: 'element',
172
174
  displayValue: str_(UIStrings.displayValue, {itemCount: stats.totalBodyElements}),
@@ -55,6 +55,7 @@ class NoDocWriteAudit extends ViolationAudit {
55
55
  description: str_(UIStrings.description),
56
56
  guidanceLevel: 2,
57
57
  requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
58
+ scoreDisplayMode: ViolationAudit.SCORING_MODES.METRIC_SAVINGS,
58
59
  };
59
60
  }
60
61
 
@@ -15,7 +15,6 @@
15
15
  import {Audit} from '../audit.js';
16
16
  import {EntityClassification} from '../../computed/entity-classification.js';
17
17
  import UrlUtils from '../../lib/url-utils.js';
18
- import {ByteEfficiencyAudit} from '../byte-efficiency/byte-efficiency-audit.js';
19
18
  import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
20
19
  import {NetworkRequest} from '../../lib/network-request.js';
21
20
  import {NetworkRecords} from '../../computed/network-records.js';
@@ -61,7 +60,7 @@ class UsesHTTP2Audit extends Audit {
61
60
  id: 'uses-http2',
62
61
  title: str_(UIStrings.title),
63
62
  description: str_(UIStrings.description),
64
- scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
63
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
65
64
  guidanceLevel: 3,
66
65
  supportedModes: ['timespan', 'navigation'],
67
66
  requiredArtifacts: ['URL', 'devtoolsLogs', 'traces', 'GatherContext'],
@@ -300,7 +299,7 @@ class UsesHTTP2Audit extends Audit {
300
299
  displayValue,
301
300
  numericValue: wastedMsTti,
302
301
  numericUnit: 'millisecond',
303
- score: ByteEfficiencyAudit.scoreForWastedMs(wastedMsTti),
302
+ score: resources.length ? 0 : 1,
304
303
  details,
305
304
  metricSavings: {LCP: wasteLcp.savings, FCP: wasteFcp.savings},
306
305
  };
@@ -38,6 +38,7 @@ class PassiveEventsAudit extends ViolationAudit {
38
38
  description: str_(UIStrings.description),
39
39
  guidanceLevel: 3,
40
40
  requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'Scripts'],
41
+ scoreDisplayMode: ViolationAudit.SCORING_MODES.METRIC_SAVINGS,
41
42
  };
42
43
  }
43
44
 
@@ -51,6 +51,7 @@ class FontDisplay extends Audit {
51
51
  supportedModes: ['navigation'],
52
52
  guidanceLevel: 3,
53
53
  requiredArtifacts: ['devtoolsLogs', 'CSSUsage', 'URL'],
54
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
54
55
  };
55
56
  }
56
57
 
@@ -39,9 +39,9 @@ const UIStrings = {
39
39
  /** Error message explaining that the provided manifest URL is invalid. */
40
40
  'start-url-not-valid': `Manifest start URL is not valid`,
41
41
  /** Error message explaining that the provided manifest does not contain a name or short_name field. */
42
- 'manifest-missing-name-or-short-name': `Manifest does not contain a 'name' or 'short_name' field`,
42
+ 'manifest-missing-name-or-short-name': 'Manifest does not contain a `name` or `short_name` field',
43
43
  /** Error message explaining that the manifest display property must be one of 'standalone', 'fullscreen', or 'minimal-ui'. */
44
- 'manifest-display-not-supported': `Manifest 'display' property must be one of 'standalone', 'fullscreen', or 'minimal-ui'`,
44
+ 'manifest-display-not-supported': 'Manifest `display` property must be one of `standalone`, `fullscreen`, or `minimal-ui`',
45
45
  /** Error message explaining that the manifest could not be fetched, might be empty, or could not be parsed. */
46
46
  'manifest-empty': `Manifest could not be fetched, is empty, or could not be parsed`,
47
47
  /**
@@ -46,7 +46,7 @@ class LargestContentfulPaintElement extends Audit {
46
46
  id: 'largest-contentful-paint-element',
47
47
  title: str_(UIStrings.title),
48
48
  description: str_(UIStrings.description),
49
- scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
49
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
50
50
  guidanceLevel: 1,
51
51
  supportedModes: ['navigation'],
52
52
  requiredArtifacts:
@@ -162,7 +162,8 @@ class LargestContentfulPaintElement extends Audit {
162
162
  const lcpSavings = Math.max(0, metricLcp - idealLcp);
163
163
 
164
164
  return {
165
- score: 1,
165
+ score: lcpSavings ? 0 : 1,
166
+ scoreDisplayMode: lcpSavings ? undefined : Audit.SCORING_MODES.INFORMATIVE,
166
167
  displayValue,
167
168
  details,
168
169
  metricSavings: {
@@ -5,7 +5,8 @@
5
5
 
6
6
  import {Audit} from './audit.js';
7
7
  import * as i18n from '../lib/i18n/i18n.js';
8
- import {CumulativeLayoutShift} from '../computed/metrics/cumulative-layout-shift.js';
8
+ import {CumulativeLayoutShift as CumulativeLayoutShiftComputed} from '../computed/metrics/cumulative-layout-shift.js';
9
+ import CumulativeLayoutShift from './metrics/cumulative-layout-shift.js';
9
10
 
10
11
  const UIStrings = {
11
12
  /** Descriptive title of a diagnostic audit that provides up to the top five elements contributing to Cumulative Layout Shift. */
@@ -27,7 +28,7 @@ class LayoutShiftElements extends Audit {
27
28
  id: 'layout-shift-elements',
28
29
  title: str_(UIStrings.title),
29
30
  description: str_(UIStrings.description),
30
- scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
31
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
31
32
  guidanceLevel: 2,
32
33
  requiredArtifacts: ['traces', 'TraceElements'],
33
34
  };
@@ -64,10 +65,13 @@ class LayoutShiftElements extends Audit {
64
65
  }
65
66
 
66
67
  const {cumulativeLayoutShift: clsSavings} =
67
- await CumulativeLayoutShift.request(artifacts.traces[Audit.DEFAULT_PASS], context);
68
+ await CumulativeLayoutShiftComputed.request(artifacts.traces[Audit.DEFAULT_PASS], context);
69
+
70
+ const passed = clsSavings <= CumulativeLayoutShift.defaultOptions.p10;
68
71
 
69
72
  return {
70
- score: 1,
73
+ score: passed ? 1 : 0,
74
+ scoreDisplayMode: passed ? Audit.SCORING_MODES.INFORMATIVE : undefined,
71
75
  metricSavings: {
72
76
  CLS: clsSavings,
73
77
  },
@@ -33,6 +33,7 @@ class LargestContentfulPaintLazyLoaded extends Audit {
33
33
  failureTitle: str_(UIStrings.failureTitle),
34
34
  description: str_(UIStrings.description),
35
35
  supportedModes: ['navigation'],
36
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
36
37
  guidanceLevel: 3,
37
38
  requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements',
38
39
  'traces', 'devtoolsLogs', 'GatherContext', 'URL'],
@@ -11,6 +11,7 @@ import {MainThreadTasks} from '../computed/main-thread-tasks.js';
11
11
  import {PageDependencyGraph} from '../computed/page-dependency-graph.js';
12
12
  import {LoadSimulator} from '../computed/load-simulator.js';
13
13
  import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/task-summary.js';
14
+ import {TotalBlockingTime} from '../computed/metrics/total-blocking-time.js';
14
15
 
15
16
  /** We don't always have timing data for short tasks, if we're missing timing data. Treat it as though it were 0ms. */
16
17
  const DEFAULT_TIMING = {startTime: 0, endTime: 0, duration: 0};
@@ -66,8 +67,8 @@ class LongTasks extends Audit {
66
67
  scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
67
68
  title: str_(UIStrings.title),
68
69
  description: str_(UIStrings.description),
70
+ requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
69
71
  guidanceLevel: 1,
70
- requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
71
72
  };
72
73
  }
73
74
 
@@ -180,6 +181,9 @@ class LongTasks extends Audit {
180
181
  const devtoolsLog = artifacts.devtoolsLogs[LongTasks.DEFAULT_PASS];
181
182
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
182
183
 
184
+ const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
185
+ const tbtResult = await TotalBlockingTime.request(metricComputationData, context);
186
+
183
187
  /** @type {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} */
184
188
  let taskTimingsByEvent;
185
189
 
@@ -245,6 +249,9 @@ class LongTasks extends Audit {
245
249
  notApplicable: results.length === 0,
246
250
  details: tableDetails,
247
251
  displayValue,
252
+ metricSavings: {
253
+ TBT: tbtResult.timing,
254
+ },
248
255
  };
249
256
  }
250
257
  }
@@ -16,6 +16,7 @@ import * as i18n from '../lib/i18n/i18n.js';
16
16
  import {MainThreadTasks} from '../computed/main-thread-tasks.js';
17
17
  import {TotalBlockingTime} from '../computed/metrics/total-blocking-time.js';
18
18
  import {Sentry} from '../lib/sentry.js';
19
+ import {Util} from '../../shared/util.js';
19
20
 
20
21
  const UIStrings = {
21
22
  /** Title of a diagnostic audit that provides detail on the main thread work the browser did to load the page. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
@@ -44,7 +45,7 @@ class MainThreadWorkBreakdown extends Audit {
44
45
  title: str_(UIStrings.title),
45
46
  failureTitle: str_(UIStrings.failureTitle),
46
47
  description: str_(UIStrings.description),
47
- scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
48
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
48
49
  guidanceLevel: 1,
49
50
  requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
50
51
  };
@@ -141,6 +142,7 @@ class MainThreadWorkBreakdown extends Audit {
141
142
 
142
143
  return {
143
144
  score,
145
+ scoreDisplayMode: score >= Util.PASS_THRESHOLD ? Audit.SCORING_MODES.INFORMATIVE : undefined,
144
146
  numericValue: totalExecutionTime,
145
147
  numericUnit: 'millisecond',
146
148
  displayValue: str_(i18n.UIStrings.seconds, {timeInMs: totalExecutionTime}),
@@ -62,11 +62,14 @@ class CumulativeLayoutShift extends Audit {
62
62
  items: [rest],
63
63
  };
64
64
 
65
+ const scoringOptions = {p10: context.options.p10, median: context.options.median};
66
+
65
67
  return {
66
68
  score: Audit.computeLogNormalScore(
67
- {p10: context.options.p10, median: context.options.median},
69
+ scoringOptions,
68
70
  cumulativeLayoutShift
69
71
  ),
72
+ scoringOptions,
70
73
  numericValue: cumulativeLayoutShift,
71
74
  numericUnit: 'unitless',
72
75
  displayValue: cumulativeLayoutShift.toLocaleString(context.settings.locale),
@@ -74,6 +74,7 @@ class FirstContentfulPaint extends Audit {
74
74
  options.scoring,
75
75
  metricResult.timing
76
76
  ),
77
+ scoringOptions: options.scoring,
77
78
  numericValue: metricResult.timing,
78
79
  numericUnit: 'millisecond',
79
80
  displayValue: str_(i18n.UIStrings.seconds, {timeInMs: metricResult.timing}),
@@ -83,6 +83,7 @@ class LargestContentfulPaint extends Audit {
83
83
  options.scoring,
84
84
  metricResult.timing
85
85
  ),
86
+ scoringOptions: options.scoring,
86
87
  numericValue: metricResult.timing,
87
88
  numericUnit: 'millisecond',
88
89
  displayValue: str_(i18n.UIStrings.seconds, {timeInMs: metricResult.timing}),