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.
- package/.codecov.yml +0 -5
- package/core/audits/audit.js +1 -1
- package/core/audits/bf-cache.js +1 -1
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +1 -0
- package/core/audits/byte-efficiency/legacy-javascript.js +7 -3
- package/core/audits/dobetterweb/uses-http2.d.ts +5 -2
- package/core/audits/dobetterweb/uses-http2.js +12 -7
- package/core/audits/screenshot-thumbnails.d.ts +2 -1
- package/core/audits/screenshot-thumbnails.js +14 -9
- package/core/audits/third-party-facades.d.ts +3 -2
- package/core/audits/third-party-facades.js +12 -9
- package/core/audits/third-party-summary.d.ts +7 -6
- package/core/audits/third-party-summary.js +16 -14
- package/core/audits/valid-source-maps.d.ts +5 -3
- package/core/audits/valid-source-maps.js +11 -9
- package/core/computed/entity-classification.d.ts +29 -0
- package/core/computed/entity-classification.js +102 -0
- package/core/computed/processed-navigation.d.ts +12 -6
- package/core/computed/processed-navigation.js +19 -6
- package/core/computed/resource-summary.d.ts +4 -2
- package/core/computed/resource-summary.js +8 -5
- package/core/lib/url-utils.js +1 -1
- package/core/runner.d.ts +10 -0
- package/core/runner.js +48 -0
- package/core/util.cjs +43 -10
- package/core/util.d.cts +114 -2
- package/dist/report/bundle.esm.js +858 -883
- package/dist/report/flow.js +33 -23
- package/dist/report/standalone.js +27 -17
- package/flow-report/assets/standalone-flow-template.html +1 -1
- package/flow-report/src/common.tsx +1 -1
- package/flow-report/src/i18n/i18n.d.ts +103 -100
- package/flow-report/src/i18n/i18n.tsx +23 -18
- package/flow-report/src/sidebar/sidebar.tsx +3 -3
- package/flow-report/src/summary/category.tsx +7 -7
- package/flow-report/src/summary/summary.tsx +2 -2
- package/flow-report/src/topbar.tsx +4 -28
- package/package.json +5 -5
- package/readme.md +1 -1
- package/report/assets/standalone-template.html +1 -1
- package/report/assets/styles.css +3 -3
- package/report/assets/templates.html +4 -28
- package/report/renderer/category-renderer.js +19 -17
- package/report/renderer/components.js +119 -202
- package/report/renderer/crc-details-renderer.js +7 -6
- package/report/renderer/details-renderer.js +7 -6
- package/report/renderer/dom.js +1 -1
- package/report/renderer/element-screenshot-renderer.js +3 -3
- package/report/renderer/{i18n.d.ts → i18n-formatter.d.ts} +3 -9
- package/report/renderer/{i18n.js → i18n-formatter.js} +2 -11
- package/report/renderer/performance-category-renderer.js +17 -15
- package/report/renderer/pwa-category-renderer.js +5 -4
- package/report/renderer/report-globals.d.ts +21 -0
- package/report/renderer/report-globals.js +49 -0
- package/report/renderer/report-renderer.js +22 -24
- package/report/renderer/report-ui-features.js +9 -8
- package/report/renderer/report-utils.d.ts +116 -0
- package/report/renderer/{util.js → report-utils.js} +100 -415
- package/report/renderer/snippet-renderer.js +3 -2
- package/report/test/renderer/category-renderer-test.js +10 -5
- package/report/test/renderer/crc-details-renderer-test.js +8 -4
- package/report/test/renderer/details-renderer-test.js +8 -4
- package/report/test/renderer/dom-test.js +8 -4
- package/report/test/renderer/element-screenshot-renderer-test.js +9 -5
- package/report/test/renderer/{i18n-test.js → i18n-formatter-test.js} +15 -21
- package/report/test/renderer/performance-category-renderer-test.js +15 -10
- package/report/test/renderer/pwa-category-renderer-test.js +11 -6
- package/report/test/renderer/report-renderer-test.js +2 -2
- package/report/test/renderer/report-ui-features-test.js +3 -3
- package/report/test/renderer/{util-test.js → report-utils-test.js} +37 -235
- package/report/test/renderer/snippet-renderer-test.js +8 -4
- package/report/test-assets/lhr-3.0.0.json +2332 -0
- package/report/test-assets/lhr-4.3.0.json +4754 -0
- package/report/test-assets/lhr-5.0.0.json +5994 -0
- package/report/test-assets/lhr-6.0.0.json +7222 -0
- package/report/test-assets/lhr-8.5.0.json +8679 -0
- package/shared/localization/format.js +5 -2
- package/shared/localization/locales/ar-XB.json +50 -50
- package/shared/localization/locales/ar.json +50 -50
- package/shared/localization/locales/bg.json +50 -50
- package/shared/localization/locales/ca.json +50 -50
- package/shared/localization/locales/cs.json +50 -50
- package/shared/localization/locales/da.json +50 -50
- package/shared/localization/locales/de.json +50 -50
- package/shared/localization/locales/el.json +50 -50
- package/shared/localization/locales/en-GB.json +50 -50
- package/shared/localization/locales/en-US.json +55 -55
- package/shared/localization/locales/en-XA.json +50 -50
- package/shared/localization/locales/en-XL.json +55 -55
- package/shared/localization/locales/es-419.json +50 -50
- package/shared/localization/locales/es.json +50 -50
- package/shared/localization/locales/fi.json +50 -50
- package/shared/localization/locales/fil.json +50 -50
- package/shared/localization/locales/fr.json +50 -50
- package/shared/localization/locales/he.json +50 -50
- package/shared/localization/locales/hi.json +50 -50
- package/shared/localization/locales/hr.json +50 -50
- package/shared/localization/locales/hu.json +50 -50
- package/shared/localization/locales/id.json +50 -50
- package/shared/localization/locales/it.json +50 -50
- package/shared/localization/locales/ja.json +50 -50
- package/shared/localization/locales/ko.json +50 -50
- package/shared/localization/locales/lt.json +50 -50
- package/shared/localization/locales/lv.json +50 -50
- package/shared/localization/locales/nl.json +50 -50
- package/shared/localization/locales/no.json +50 -50
- package/shared/localization/locales/pl.json +50 -50
- package/shared/localization/locales/pt-PT.json +50 -50
- package/shared/localization/locales/pt.json +50 -50
- package/shared/localization/locales/ro.json +50 -50
- package/shared/localization/locales/ru.json +50 -50
- package/shared/localization/locales/sk.json +50 -50
- package/shared/localization/locales/sl.json +50 -50
- package/shared/localization/locales/sr-Latn.json +50 -50
- package/shared/localization/locales/sr.json +50 -50
- package/shared/localization/locales/sv.json +50 -50
- package/shared/localization/locales/ta.json +50 -50
- package/shared/localization/locales/te.json +50 -50
- package/shared/localization/locales/th.json +50 -50
- package/shared/localization/locales/tr.json +50 -50
- package/shared/localization/locales/uk.json +50 -50
- package/shared/localization/locales/vi.json +50 -50
- package/shared/localization/locales/zh-HK.json +50 -50
- package/shared/localization/locales/zh-TW.json +50 -50
- package/shared/localization/locales/zh.json +50 -50
- package/shared/test/util-test.js +214 -0
- package/shared/util.d.ts +122 -0
- package/shared/util.js +332 -0
- package/types/artifacts.d.ts +14 -0
- package/types/lhr/lhr.d.ts +33 -0
- package/report/renderer/util.d.ts +0 -230
|
@@ -1,77 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license
|
|
3
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
98
|
-
clone.mainDocumentUrl =
|
|
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 =
|
|
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 = `${
|
|
516
|
-
networkThrottling = `${
|
|
517
|
-
`${
|
|
518
|
-
`${
|
|
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() ?
|
|
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 = `${
|
|
532
|
-
networkThrottling = `${
|
|
533
|
-
`${
|
|
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() ?
|
|
233
|
+
summary = isSlow4G() ?
|
|
234
|
+
strings.runtimeSlow4g : strings.runtimeCustom;
|
|
539
235
|
break;
|
|
540
236
|
}
|
|
541
237
|
default:
|
|
542
|
-
summary = cpuThrottling = networkThrottling =
|
|
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 =
|
|
251
|
+
let deviceEmulation = strings.runtimeMobileEmulation;
|
|
556
252
|
if (isScreenEmulationDisabled) {
|
|
557
|
-
deviceEmulation =
|
|
253
|
+
deviceEmulation = strings.runtimeNoEmulation;
|
|
558
254
|
} else if (!isScreenEmulationMobile) {
|
|
559
|
-
deviceEmulation =
|
|
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
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* @param {
|
|
581
|
-
* @
|
|
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
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
*
|
|
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
|
|
622
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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 =
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
352
|
+
/**
|
|
353
|
+
* @param {string} categoryId
|
|
354
|
+
*/
|
|
355
|
+
static isPluginCategory(categoryId) {
|
|
356
|
+
return categoryId.startsWith('lighthouse-plugin-');
|
|
357
|
+
}
|
|
669
358
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
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 '
|
|
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
|
-
} =
|
|
108
|
+
} = Globals.strings;
|
|
108
109
|
dom.find(
|
|
109
110
|
'.lh-snippet__btn-label-collapse',
|
|
110
111
|
header
|