lighthouse 9.5.0-dev.20230122 → 9.5.0-dev.20230124

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 (131) hide show
  1. package/.codecov.yml +0 -5
  2. package/core/audits/audit.js +1 -1
  3. package/core/audits/bf-cache.js +1 -1
  4. package/core/audits/byte-efficiency/legacy-javascript.d.ts +1 -0
  5. package/core/audits/byte-efficiency/legacy-javascript.js +7 -3
  6. package/core/audits/dobetterweb/uses-http2.d.ts +5 -2
  7. package/core/audits/dobetterweb/uses-http2.js +12 -7
  8. package/core/audits/screenshot-thumbnails.d.ts +2 -1
  9. package/core/audits/screenshot-thumbnails.js +14 -9
  10. package/core/audits/third-party-facades.d.ts +3 -2
  11. package/core/audits/third-party-facades.js +12 -9
  12. package/core/audits/third-party-summary.d.ts +7 -6
  13. package/core/audits/third-party-summary.js +16 -14
  14. package/core/audits/valid-source-maps.d.ts +5 -3
  15. package/core/audits/valid-source-maps.js +11 -9
  16. package/core/computed/entity-classification.d.ts +29 -0
  17. package/core/computed/entity-classification.js +102 -0
  18. package/core/computed/processed-navigation.d.ts +12 -6
  19. package/core/computed/processed-navigation.js +19 -6
  20. package/core/computed/resource-summary.d.ts +4 -2
  21. package/core/computed/resource-summary.js +8 -5
  22. package/core/lib/url-utils.js +1 -1
  23. package/core/runner.d.ts +10 -0
  24. package/core/runner.js +48 -0
  25. package/core/util.cjs +43 -10
  26. package/core/util.d.cts +114 -2
  27. package/dist/report/bundle.esm.js +858 -883
  28. package/dist/report/flow.js +33 -23
  29. package/dist/report/standalone.js +27 -17
  30. package/flow-report/assets/standalone-flow-template.html +1 -1
  31. package/flow-report/src/common.tsx +1 -1
  32. package/flow-report/src/i18n/i18n.d.ts +103 -100
  33. package/flow-report/src/i18n/i18n.tsx +23 -18
  34. package/flow-report/src/sidebar/sidebar.tsx +3 -3
  35. package/flow-report/src/summary/category.tsx +7 -7
  36. package/flow-report/src/summary/summary.tsx +2 -2
  37. package/flow-report/src/topbar.tsx +4 -28
  38. package/package.json +5 -5
  39. package/readme.md +1 -1
  40. package/report/assets/standalone-template.html +1 -1
  41. package/report/assets/styles.css +3 -3
  42. package/report/assets/templates.html +4 -28
  43. package/report/renderer/category-renderer.js +19 -17
  44. package/report/renderer/components.js +119 -202
  45. package/report/renderer/crc-details-renderer.js +7 -6
  46. package/report/renderer/details-renderer.js +7 -6
  47. package/report/renderer/dom.js +1 -1
  48. package/report/renderer/element-screenshot-renderer.js +3 -3
  49. package/report/renderer/{i18n.d.ts → i18n-formatter.d.ts} +3 -9
  50. package/report/renderer/{i18n.js → i18n-formatter.js} +2 -11
  51. package/report/renderer/performance-category-renderer.js +17 -15
  52. package/report/renderer/pwa-category-renderer.js +5 -4
  53. package/report/renderer/report-globals.d.ts +21 -0
  54. package/report/renderer/report-globals.js +49 -0
  55. package/report/renderer/report-renderer.js +22 -24
  56. package/report/renderer/report-ui-features.js +9 -8
  57. package/report/renderer/report-utils.d.ts +116 -0
  58. package/report/renderer/{util.js → report-utils.js} +100 -415
  59. package/report/renderer/snippet-renderer.js +3 -2
  60. package/report/test/renderer/category-renderer-test.js +10 -5
  61. package/report/test/renderer/crc-details-renderer-test.js +8 -4
  62. package/report/test/renderer/details-renderer-test.js +8 -4
  63. package/report/test/renderer/dom-test.js +8 -4
  64. package/report/test/renderer/element-screenshot-renderer-test.js +9 -5
  65. package/report/test/renderer/{i18n-test.js → i18n-formatter-test.js} +15 -21
  66. package/report/test/renderer/performance-category-renderer-test.js +15 -10
  67. package/report/test/renderer/pwa-category-renderer-test.js +11 -6
  68. package/report/test/renderer/report-renderer-test.js +2 -2
  69. package/report/test/renderer/report-ui-features-test.js +3 -3
  70. package/report/test/renderer/{util-test.js → report-utils-test.js} +37 -235
  71. package/report/test/renderer/snippet-renderer-test.js +8 -4
  72. package/report/test-assets/lhr-3.0.0.json +2332 -0
  73. package/report/test-assets/lhr-4.3.0.json +4754 -0
  74. package/report/test-assets/lhr-5.0.0.json +5994 -0
  75. package/report/test-assets/lhr-6.0.0.json +7222 -0
  76. package/report/test-assets/lhr-8.5.0.json +8679 -0
  77. package/shared/localization/format.js +5 -2
  78. package/shared/localization/locales/ar-XB.json +50 -50
  79. package/shared/localization/locales/ar.json +50 -50
  80. package/shared/localization/locales/bg.json +50 -50
  81. package/shared/localization/locales/ca.json +50 -50
  82. package/shared/localization/locales/cs.json +50 -50
  83. package/shared/localization/locales/da.json +50 -50
  84. package/shared/localization/locales/de.json +50 -50
  85. package/shared/localization/locales/el.json +50 -50
  86. package/shared/localization/locales/en-GB.json +50 -50
  87. package/shared/localization/locales/en-US.json +55 -55
  88. package/shared/localization/locales/en-XA.json +50 -50
  89. package/shared/localization/locales/en-XL.json +55 -55
  90. package/shared/localization/locales/es-419.json +50 -50
  91. package/shared/localization/locales/es.json +50 -50
  92. package/shared/localization/locales/fi.json +50 -50
  93. package/shared/localization/locales/fil.json +50 -50
  94. package/shared/localization/locales/fr.json +50 -50
  95. package/shared/localization/locales/he.json +50 -50
  96. package/shared/localization/locales/hi.json +50 -50
  97. package/shared/localization/locales/hr.json +50 -50
  98. package/shared/localization/locales/hu.json +50 -50
  99. package/shared/localization/locales/id.json +50 -50
  100. package/shared/localization/locales/it.json +50 -50
  101. package/shared/localization/locales/ja.json +50 -50
  102. package/shared/localization/locales/ko.json +50 -50
  103. package/shared/localization/locales/lt.json +50 -50
  104. package/shared/localization/locales/lv.json +50 -50
  105. package/shared/localization/locales/nl.json +50 -50
  106. package/shared/localization/locales/no.json +50 -50
  107. package/shared/localization/locales/pl.json +50 -50
  108. package/shared/localization/locales/pt-PT.json +50 -50
  109. package/shared/localization/locales/pt.json +50 -50
  110. package/shared/localization/locales/ro.json +50 -50
  111. package/shared/localization/locales/ru.json +50 -50
  112. package/shared/localization/locales/sk.json +50 -50
  113. package/shared/localization/locales/sl.json +50 -50
  114. package/shared/localization/locales/sr-Latn.json +50 -50
  115. package/shared/localization/locales/sr.json +50 -50
  116. package/shared/localization/locales/sv.json +50 -50
  117. package/shared/localization/locales/ta.json +50 -50
  118. package/shared/localization/locales/te.json +50 -50
  119. package/shared/localization/locales/th.json +50 -50
  120. package/shared/localization/locales/tr.json +50 -50
  121. package/shared/localization/locales/uk.json +50 -50
  122. package/shared/localization/locales/vi.json +50 -50
  123. package/shared/localization/locales/zh-HK.json +50 -50
  124. package/shared/localization/locales/zh-TW.json +50 -50
  125. package/shared/localization/locales/zh.json +50 -50
  126. package/shared/test/util-test.js +214 -0
  127. package/shared/util.d.ts +122 -0
  128. package/shared/util.js +332 -0
  129. package/types/artifacts.d.ts +14 -0
  130. package/types/lhr/lhr.d.ts +33 -0
  131. package/report/renderer/util.d.ts +0 -230
@@ -1,77 +1,16 @@
1
1
  /**
2
- * @license
3
- * Copyright 2017 The Lighthouse Authors. All Rights Reserved.
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS-IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
2
+ * @license Copyright 2023 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.
16
5
  */
17
6
 
18
- /** @template T @typedef {import('./i18n').I18n<T>} I18n */
7
+ import {Util} from '../../shared/util.js';
8
+ import {Globals} from './report-globals.js';
19
9
 
20
- const ELLIPSIS = '\u2026';
21
- const NBSP = '\xa0';
22
- const PASS_THRESHOLD = 0.9;
23
10
  const SCREENSHOT_PREFIX = 'data:image/jpeg;base64,';
11
+ const RATINGS = Util.RATINGS;
24
12
 
25
- const RATINGS = {
26
- PASS: {label: 'pass', minScore: PASS_THRESHOLD},
27
- AVERAGE: {label: 'average', minScore: 0.5},
28
- FAIL: {label: 'fail'},
29
- ERROR: {label: 'error'},
30
- };
31
-
32
- // 25 most used tld plus one domains (aka public suffixes) from http archive.
33
- // @see https://github.com/GoogleChrome/lighthouse/pull/5065#discussion_r191926212
34
- // The canonical list is https://publicsuffix.org/learn/ but we're only using subset to conserve bytes
35
- const listOfTlds = [
36
- 'com', 'co', 'gov', 'edu', 'ac', 'org', 'go', 'gob', 'or', 'net', 'in', 'ne', 'nic', 'gouv',
37
- 'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on',
38
- ];
39
-
40
- class Util {
41
- /** @type {I18n<typeof UIStrings>} */
42
- // @ts-expect-error: Is set in report renderer.
43
- static i18n = null;
44
-
45
- static get PASS_THRESHOLD() {
46
- return PASS_THRESHOLD;
47
- }
48
-
49
- static get MS_DISPLAY_VALUE() {
50
- return `%10d${NBSP}ms`;
51
- }
52
-
53
- /**
54
- * If LHR is older than 10.0 it will not have the `finalDisplayedUrl` property.
55
- * Old LHRs should have the `finalUrl` property which will work fine for the report.
56
- *
57
- * @param {LH.Result} lhr
58
- */
59
- static getFinalDisplayedUrl(lhr) {
60
- if (lhr.finalDisplayedUrl) return lhr.finalDisplayedUrl;
61
- if (lhr.finalUrl) return lhr.finalUrl;
62
- throw new Error('Could not determine final displayed URL');
63
- }
64
-
65
- /**
66
- * If LHR is older than 10.0 it will not have the `mainDocumentUrl` property.
67
- * Old LHRs should have the `finalUrl` property which is the same as `mainDocumentUrl`.
68
- *
69
- * @param {LH.Result} lhr
70
- */
71
- static getMainDocumentUrl(lhr) {
72
- return lhr.mainDocumentUrl || lhr.finalUrl;
73
- }
74
-
13
+ class ReportUtils {
75
14
  /**
76
15
  * Returns a new LHR that's reshaped for slightly better ergonomics within the report rendereer.
77
16
  * Also, sets up the localized UI strings used within renderer and makes changes to old LHRs to be
@@ -94,8 +33,8 @@ class Util {
94
33
  clone.configSettings.formFactor = clone.configSettings.emulatedFormFactor;
95
34
  }
96
35
 
97
- clone.finalDisplayedUrl = this.getFinalDisplayedUrl(clone);
98
- clone.mainDocumentUrl = this.getMainDocumentUrl(clone);
36
+ clone.finalDisplayedUrl = Util.getFinalDisplayedUrl(clone);
37
+ clone.mainDocumentUrl = Util.getMainDocumentUrl(clone);
99
38
 
100
39
  for (const audit of Object.values(clone.audits)) {
101
40
  // Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
@@ -150,6 +89,10 @@ class Util {
150
89
  }
151
90
  }
152
91
  }
92
+
93
+ // TODO: convert printf-style displayValue.
94
+ // Added: #5099, v3
95
+ // Removed: #6767, v4
153
96
  }
154
97
  }
155
98
 
@@ -212,6 +155,20 @@ class Util {
212
155
  });
213
156
  }
214
157
 
158
+ // Add some minimal stuff so older reports still work.
159
+ if (!clone.environment) {
160
+ // @ts-expect-error
161
+ clone.environment = {benchmarkIndex: 0};
162
+ }
163
+ if (!clone.configSettings.screenEmulation) {
164
+ // @ts-expect-error
165
+ clone.configSettings.screenEmulation = {};
166
+ }
167
+ if (!clone.i18n) {
168
+ // @ts-expect-error
169
+ clone.i18n = {};
170
+ }
171
+
215
172
  // In 10.0, full-page-screenshot became a top-level property on the LHR.
216
173
  if (clone.audits['full-page-screenshot']) {
217
174
  const details = /** @type {LH.Result.FullPageScreenshot=} */ (
@@ -230,270 +187,6 @@ class Util {
230
187
  return clone;
231
188
  }
232
189
 
233
- /**
234
- * @param {LH.Result} lhr
235
- * @return {LH.Result.FullPageScreenshot=}
236
- */
237
- static getFullPageScreenshot(lhr) {
238
- if (lhr.fullPageScreenshot) {
239
- return lhr.fullPageScreenshot;
240
- }
241
-
242
- // Prior to 10.0.
243
- const details = /** @type {LH.Result.FullPageScreenshot=} */ (
244
- lhr.audits['full-page-screenshot']?.details);
245
- return details;
246
- }
247
-
248
- /**
249
- * Used to determine if the "passed" for the purposes of showing up in the "failed" or "passed"
250
- * sections of the report.
251
- *
252
- * @param {{score: (number|null), scoreDisplayMode: string}} audit
253
- * @return {boolean}
254
- */
255
- static showAsPassed(audit) {
256
- switch (audit.scoreDisplayMode) {
257
- case 'manual':
258
- case 'notApplicable':
259
- return true;
260
- case 'error':
261
- case 'informative':
262
- return false;
263
- case 'numeric':
264
- case 'binary':
265
- default:
266
- return Number(audit.score) >= RATINGS.PASS.minScore;
267
- }
268
- }
269
-
270
- /**
271
- * Convert a score to a rating label.
272
- * TODO: Return `'error'` for `score === null && !scoreDisplayMode`.
273
- *
274
- * @param {number|null} score
275
- * @param {string=} scoreDisplayMode
276
- * @return {string}
277
- */
278
- static calculateRating(score, scoreDisplayMode) {
279
- // Handle edge cases first, manual and not applicable receive 'pass', errored audits receive 'error'
280
- if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'notApplicable') {
281
- return RATINGS.PASS.label;
282
- } else if (scoreDisplayMode === 'error') {
283
- return RATINGS.ERROR.label;
284
- } else if (score === null) {
285
- return RATINGS.FAIL.label;
286
- }
287
-
288
- // At this point, we're rating a standard binary/numeric audit
289
- let rating = RATINGS.FAIL.label;
290
- if (score >= RATINGS.PASS.minScore) {
291
- rating = RATINGS.PASS.label;
292
- } else if (score >= RATINGS.AVERAGE.minScore) {
293
- rating = RATINGS.AVERAGE.label;
294
- }
295
- return rating;
296
- }
297
-
298
- /**
299
- * Split a string by markdown code spans (enclosed in `backticks`), splitting
300
- * into segments that were enclosed in backticks (marked as `isCode === true`)
301
- * and those that outside the backticks (`isCode === false`).
302
- * @param {string} text
303
- * @return {Array<{isCode: true, text: string}|{isCode: false, text: string}>}
304
- */
305
- static splitMarkdownCodeSpans(text) {
306
- /** @type {Array<{isCode: true, text: string}|{isCode: false, text: string}>} */
307
- const segments = [];
308
-
309
- // Split on backticked code spans.
310
- const parts = text.split(/`(.*?)`/g);
311
- for (let i = 0; i < parts.length; i ++) {
312
- const text = parts[i];
313
-
314
- // Empty strings are an artifact of splitting, not meaningful.
315
- if (!text) continue;
316
-
317
- // Alternates between plain text and code segments.
318
- const isCode = i % 2 !== 0;
319
- segments.push({
320
- isCode,
321
- text,
322
- });
323
- }
324
-
325
- return segments;
326
- }
327
-
328
- /**
329
- * Split a string on markdown links (e.g. [some link](https://...)) into
330
- * segments of plain text that weren't part of a link (marked as
331
- * `isLink === false`), and segments with text content and a URL that did make
332
- * up a link (marked as `isLink === true`).
333
- * @param {string} text
334
- * @return {Array<{isLink: true, text: string, linkHref: string}|{isLink: false, text: string}>}
335
- */
336
- static splitMarkdownLink(text) {
337
- /** @type {Array<{isLink: true, text: string, linkHref: string}|{isLink: false, text: string}>} */
338
- const segments = [];
339
-
340
- const parts = text.split(/\[([^\]]+?)\]\((https?:\/\/.*?)\)/g);
341
- while (parts.length) {
342
- // Shift off the same number of elements as the pre-split and capture groups.
343
- const [preambleText, linkText, linkHref] = parts.splice(0, 3);
344
-
345
- if (preambleText) { // Skip empty text as it's an artifact of splitting, not meaningful.
346
- segments.push({
347
- isLink: false,
348
- text: preambleText,
349
- });
350
- }
351
-
352
- // Append link if there are any.
353
- if (linkText && linkHref) {
354
- segments.push({
355
- isLink: true,
356
- text: linkText,
357
- linkHref,
358
- });
359
- }
360
- }
361
-
362
- return segments;
363
- }
364
-
365
- /**
366
- * @param {URL} parsedUrl
367
- * @param {{numPathParts?: number, preserveQuery?: boolean, preserveHost?: boolean}=} options
368
- * @return {string}
369
- */
370
- static getURLDisplayName(parsedUrl, options) {
371
- // Closure optional properties aren't optional in tsc, so fallback needs undefined values.
372
- options = options || {numPathParts: undefined, preserveQuery: undefined,
373
- preserveHost: undefined};
374
- const numPathParts = options.numPathParts !== undefined ? options.numPathParts : 2;
375
- const preserveQuery = options.preserveQuery !== undefined ? options.preserveQuery : true;
376
- const preserveHost = options.preserveHost || false;
377
-
378
- let name;
379
-
380
- if (parsedUrl.protocol === 'about:' || parsedUrl.protocol === 'data:') {
381
- // Handle 'about:*' and 'data:*' URLs specially since they have no path.
382
- name = parsedUrl.href;
383
- } else {
384
- name = parsedUrl.pathname;
385
- const parts = name.split('/').filter(part => part.length);
386
- if (numPathParts && parts.length > numPathParts) {
387
- name = ELLIPSIS + parts.slice(-1 * numPathParts).join('/');
388
- }
389
-
390
- if (preserveHost) {
391
- name = `${parsedUrl.host}/${name.replace(/^\//, '')}`;
392
- }
393
- if (preserveQuery) {
394
- name = `${name}${parsedUrl.search}`;
395
- }
396
- }
397
-
398
- const MAX_LENGTH = 64;
399
- if (parsedUrl.protocol !== 'data:') {
400
- // Always elide hexadecimal hash
401
- name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
402
- // Also elide other hash-like mixed-case strings
403
- name = name.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,
404
- `$1${ELLIPSIS}`);
405
- // Also elide long number sequences
406
- name = name.replace(/(\d{3})\d{6,}/g, `$1${ELLIPSIS}`);
407
- // Merge any adjacent ellipses
408
- name = name.replace(/\u2026+/g, ELLIPSIS);
409
-
410
- // Elide query params first
411
- if (name.length > MAX_LENGTH && name.includes('?')) {
412
- // Try to leave the first query parameter intact
413
- name = name.replace(/\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`);
414
-
415
- // Remove it all if it's still too long
416
- if (name.length > MAX_LENGTH) {
417
- name = name.replace(/\?.*/, `?${ELLIPSIS}`);
418
- }
419
- }
420
- }
421
-
422
- // Elide too long names next
423
- if (name.length > MAX_LENGTH) {
424
- const dotIndex = name.lastIndexOf('.');
425
- if (dotIndex >= 0) {
426
- name = name.slice(0, MAX_LENGTH - 1 - (name.length - dotIndex)) +
427
- // Show file extension
428
- `${ELLIPSIS}${name.slice(dotIndex)}`;
429
- } else {
430
- name = name.slice(0, MAX_LENGTH - 1) + ELLIPSIS;
431
- }
432
- }
433
-
434
- return name;
435
- }
436
-
437
- /**
438
- * Split a URL into a file, hostname and origin for easy display.
439
- * @param {string} url
440
- * @return {{file: string, hostname: string, origin: string}}
441
- */
442
- static parseURL(url) {
443
- const parsedUrl = new URL(url);
444
- return {
445
- file: Util.getURLDisplayName(parsedUrl),
446
- hostname: parsedUrl.hostname,
447
- origin: parsedUrl.origin,
448
- };
449
- }
450
-
451
- /**
452
- * @param {string|URL} value
453
- * @return {!URL}
454
- */
455
- static createOrReturnURL(value) {
456
- if (value instanceof URL) {
457
- return value;
458
- }
459
-
460
- return new URL(value);
461
- }
462
-
463
- /**
464
- * Gets the tld of a domain
465
- *
466
- * @param {string} hostname
467
- * @return {string} tld
468
- */
469
- static getTld(hostname) {
470
- const tlds = hostname.split('.').slice(-2);
471
-
472
- if (!listOfTlds.includes(tlds[0])) {
473
- return `.${tlds[tlds.length - 1]}`;
474
- }
475
-
476
- return `.${tlds.join('.')}`;
477
- }
478
-
479
- /**
480
- * Returns a primary domain for provided hostname (e.g. www.example.com -> example.com).
481
- * @param {string|URL} url hostname or URL object
482
- * @return {string}
483
- */
484
- static getRootDomain(url) {
485
- const hostname = Util.createOrReturnURL(url).hostname;
486
- const tld = Util.getTld(hostname);
487
-
488
- // tld is .com or .co.uk which means we means that length is 1 to big
489
- // .com => 2 & .co.uk => 3
490
- const splitTld = tld.split('.');
491
-
492
- // get TLD + root domain
493
- return hostname.split('.').slice(-splitTld.length).join('.');
494
- }
495
-
496
-
497
190
  /**
498
191
  * @param {LH.Result['configSettings']} settings
499
192
  * @return {!{deviceEmulation: string, screenEmulation?: string, networkThrottling: string, cpuThrottling: string, summary: string}}
@@ -504,42 +197,45 @@ class Util {
504
197
  let summary;
505
198
 
506
199
  const throttling = settings.throttling;
200
+ const i18n = Globals.i18n;
201
+ const strings = Globals.strings;
507
202
 
508
203
  switch (settings.throttlingMethod) {
509
204
  case 'provided':
510
- summary = networkThrottling = cpuThrottling = Util.i18n.strings.throttlingProvided;
205
+ summary = networkThrottling = cpuThrottling = strings.throttlingProvided;
511
206
  break;
512
207
  case 'devtools': {
513
208
  const {cpuSlowdownMultiplier, requestLatencyMs} = throttling;
514
209
  // eslint-disable-next-line max-len
515
- cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`;
516
- networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` +
517
- `${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` +
518
- `${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`;
210
+ cpuThrottling = `${i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`;
211
+ networkThrottling = `${i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` +
212
+ `${i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` +
213
+ `${i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`;
519
214
 
520
215
  const isSlow4G = () => {
521
216
  return requestLatencyMs === 150 * 3.75 &&
522
217
  throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 &&
523
218
  throttling.uploadThroughputKbps === 750 * 0.9;
524
219
  };
525
- summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
220
+ summary = isSlow4G() ? strings.runtimeSlow4g : strings.runtimeCustom;
526
221
  break;
527
222
  }
528
223
  case 'simulate': {
529
224
  const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling;
530
225
  // eslint-disable-next-line max-len
531
- cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
532
- networkThrottling = `${Util.i18n.formatMilliseconds(rttMs)} TCP RTT, ` +
533
- `${Util.i18n.formatKbps(throughputKbps)} throughput (Simulated)`;
226
+ cpuThrottling = `${i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
227
+ networkThrottling = `${i18n.formatMilliseconds(rttMs)} TCP RTT, ` +
228
+ `${i18n.formatKbps(throughputKbps)} throughput (Simulated)`;
534
229
 
535
230
  const isSlow4G = () => {
536
231
  return rttMs === 150 && throughputKbps === 1.6 * 1024;
537
232
  };
538
- summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
233
+ summary = isSlow4G() ?
234
+ strings.runtimeSlow4g : strings.runtimeCustom;
539
235
  break;
540
236
  }
541
237
  default:
542
- summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown;
238
+ summary = cpuThrottling = networkThrottling = strings.runtimeUnknown;
543
239
  }
544
240
 
545
241
  // devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation,
@@ -552,11 +248,11 @@ class Util {
552
248
  settings.formFactor === 'mobile' :
553
249
  settings.screenEmulation.mobile;
554
250
 
555
- let deviceEmulation = Util.i18n.strings.runtimeMobileEmulation;
251
+ let deviceEmulation = strings.runtimeMobileEmulation;
556
252
  if (isScreenEmulationDisabled) {
557
- deviceEmulation = Util.i18n.strings.runtimeNoEmulation;
253
+ deviceEmulation = strings.runtimeNoEmulation;
558
254
  } else if (!isScreenEmulationMobile) {
559
- deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation;
255
+ deviceEmulation = strings.runtimeDesktopEmulation;
560
256
  }
561
257
 
562
258
  const screenEmulation = isScreenEmulationDisabled ?
@@ -574,59 +270,53 @@ class Util {
574
270
  }
575
271
 
576
272
  /**
577
- * Returns only lines that are near a message, or the first few lines if there are
578
- * no line messages.
579
- * @param {LH.Audit.Details.SnippetValue['lines']} lines
580
- * @param {LH.Audit.Details.SnippetValue['lineMessages']} lineMessages
581
- * @param {number} surroundingLineCount Number of lines to include before and after
582
- * the message. If this is e.g. 2 this function might return 5 lines.
273
+ * Used to determine if the "passed" for the purposes of showing up in the "failed" or "passed"
274
+ * sections of the report.
275
+ *
276
+ * @param {{score: (number|null), scoreDisplayMode: string}} audit
277
+ * @return {boolean}
583
278
  */
584
- static filterRelevantLines(lines, lineMessages, surroundingLineCount) {
585
- if (lineMessages.length === 0) {
586
- // no lines with messages, just return the first bunch of lines
587
- return lines.slice(0, surroundingLineCount * 2 + 1);
279
+ static showAsPassed(audit) {
280
+ switch (audit.scoreDisplayMode) {
281
+ case 'manual':
282
+ case 'notApplicable':
283
+ return true;
284
+ case 'error':
285
+ case 'informative':
286
+ return false;
287
+ case 'numeric':
288
+ case 'binary':
289
+ default:
290
+ return Number(audit.score) >= RATINGS.PASS.minScore;
588
291
  }
589
-
590
- const minGapSize = 3;
591
- const lineNumbersToKeep = new Set();
592
- // Sort messages so we can check lineNumbersToKeep to see how big the gap to
593
- // the previous line is.
594
- lineMessages = lineMessages.sort((a, b) => (a.lineNumber || 0) - (b.lineNumber || 0));
595
- lineMessages.forEach(({lineNumber}) => {
596
- let firstSurroundingLineNumber = lineNumber - surroundingLineCount;
597
- let lastSurroundingLineNumber = lineNumber + surroundingLineCount;
598
-
599
- while (firstSurroundingLineNumber < 1) {
600
- // make sure we still show (surroundingLineCount * 2 + 1) lines in total
601
- firstSurroundingLineNumber++;
602
- lastSurroundingLineNumber++;
603
- }
604
- // If only a few lines would be omitted normally then we prefer to include
605
- // extra lines to avoid the tiny gap
606
- if (lineNumbersToKeep.has(firstSurroundingLineNumber - minGapSize - 1)) {
607
- firstSurroundingLineNumber -= minGapSize;
608
- }
609
- for (let i = firstSurroundingLineNumber; i <= lastSurroundingLineNumber; i++) {
610
- const surroundingLineNumber = i;
611
- lineNumbersToKeep.add(surroundingLineNumber);
612
- }
613
- });
614
-
615
- return lines.filter(line => lineNumbersToKeep.has(line.lineNumber));
616
292
  }
617
293
 
618
294
  /**
619
- * @param {string} categoryId
295
+ * Convert a score to a rating label.
296
+ * TODO: Return `'error'` for `score === null && !scoreDisplayMode`.
297
+ *
298
+ * @param {number|null} score
299
+ * @param {string=} scoreDisplayMode
300
+ * @return {string}
620
301
  */
621
- static isPluginCategory(categoryId) {
622
- return categoryId.startsWith('lighthouse-plugin-');
623
- }
302
+ static calculateRating(score, scoreDisplayMode) {
303
+ // Handle edge cases first, manual and not applicable receive 'pass', errored audits receive 'error'
304
+ if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'notApplicable') {
305
+ return RATINGS.PASS.label;
306
+ } else if (scoreDisplayMode === 'error') {
307
+ return RATINGS.ERROR.label;
308
+ } else if (score === null) {
309
+ return RATINGS.FAIL.label;
310
+ }
624
311
 
625
- /**
626
- * @param {LH.Result.GatherMode} gatherMode
627
- */
628
- static shouldDisplayAsFraction(gatherMode) {
629
- return gatherMode === 'timespan' || gatherMode === 'snapshot';
312
+ // At this point, we're rating a standard binary/numeric audit
313
+ let rating = RATINGS.FAIL.label;
314
+ if (score >= RATINGS.PASS.minScore) {
315
+ rating = RATINGS.PASS.label;
316
+ } else if (score >= RATINGS.AVERAGE.minScore) {
317
+ rating = RATINGS.AVERAGE.label;
318
+ }
319
+ return rating;
630
320
  }
631
321
 
632
322
  /**
@@ -638,7 +328,7 @@ class Util {
638
328
  let numInformative = 0;
639
329
  let totalWeight = 0;
640
330
  for (const auditRef of category.auditRefs) {
641
- const auditPassed = Util.showAsPassed(auditRef.result);
331
+ const auditPassed = ReportUtils.showAsPassed(auditRef.result);
642
332
 
643
333
  // Don't count the audit if it's manual, N/A, or isn't displayed.
644
334
  if (auditRef.group === 'hidden' ||
@@ -658,25 +348,21 @@ class Util {
658
348
  }
659
349
  return {numPassed, numPassableAudits, numInformative, totalWeight};
660
350
  }
661
- }
662
351
 
663
- /**
664
- * Some parts of the report renderer require data found on the LHR. Instead of wiring it
665
- * through, we have this global.
666
- * @type {LH.ReportResult | null}
667
- */
668
- Util.reportJson = null;
352
+ /**
353
+ * @param {string} categoryId
354
+ */
355
+ static isPluginCategory(categoryId) {
356
+ return categoryId.startsWith('lighthouse-plugin-');
357
+ }
669
358
 
670
- let svgSuffix = 0;
671
- /**
672
- * An always-increasing counter for making unique SVG ID suffixes.
673
- */
674
- Util.getUniqueSuffix = () => {
675
- return svgSuffix++;
676
- };
677
- Util.resetUniqueSuffix = () => {
678
- svgSuffix = 0;
679
- };
359
+ /**
360
+ * @param {LH.Result.GatherMode} gatherMode
361
+ */
362
+ static shouldDisplayAsFraction(gatherMode) {
363
+ return gatherMode === 'timespan' || gatherMode === 'snapshot';
364
+ }
365
+ }
680
366
 
681
367
  /**
682
368
  * Report-renderer-specific strings.
@@ -761,7 +447,7 @@ const UIStrings = {
761
447
  /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */
762
448
  runtimeSettingsUANetwork: 'User agent (network)',
763
449
  /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */
764
- runtimeSettingsBenchmark: 'CPU/Memory Power',
450
+ runtimeSettingsBenchmark: 'Unthrottled CPU/Memory Power',
765
451
  /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */
766
452
  runtimeSettingsAxeVersion: 'Axe version',
767
453
  /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */
@@ -800,9 +486,8 @@ const UIStrings = {
800
486
  /** Label indicating that Lighthouse throttled the page using custom throttling settings. */
801
487
  runtimeCustom: 'Custom throttling',
802
488
  };
803
- Util.UIStrings = UIStrings;
804
489
 
805
490
  export {
806
- Util,
491
+ ReportUtils,
807
492
  UIStrings,
808
493
  };
@@ -7,7 +7,8 @@
7
7
  /** @typedef {import('./details-renderer').DetailsRenderer} DetailsRenderer */
8
8
  /** @typedef {import('./dom').DOM} DOM */
9
9
 
10
- import {Util} from './util.js';
10
+ import {Util} from '../../shared/util.js';
11
+ import {Globals} from './report-globals.js';
11
12
 
12
13
  /** @enum {number} */
13
14
  const LineVisibility = {
@@ -104,7 +105,7 @@ export class SnippetRenderer {
104
105
  const {
105
106
  snippetCollapseButtonLabel,
106
107
  snippetExpandButtonLabel,
107
- } = Util.i18n.strings;
108
+ } = Globals.strings;
108
109
  dom.find(
109
110
  '.lh-snippet__btn-label-collapse',
110
111
  header