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
|
@@ -15,14 +15,13 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
/** @
|
|
18
|
+
/** @typedef {import('../types/lhr/audit-details').default.SnippetValue} SnippetValue */
|
|
19
19
|
|
|
20
20
|
const ELLIPSIS = '\u2026';
|
|
21
21
|
const NBSP = '\xa0';
|
|
22
22
|
const PASS_THRESHOLD = 0.9;
|
|
23
|
-
const SCREENSHOT_PREFIX = 'data:image/jpeg;base64,';
|
|
24
23
|
|
|
25
|
-
const RATINGS = {
|
|
24
|
+
const RATINGS$1 = {
|
|
26
25
|
PASS: {label: 'pass', minScore: PASS_THRESHOLD},
|
|
27
26
|
AVERAGE: {label: 'average', minScore: 0.5},
|
|
28
27
|
FAIL: {label: 'fail'},
|
|
@@ -38,9 +37,9 @@ const listOfTlds = [
|
|
|
38
37
|
];
|
|
39
38
|
|
|
40
39
|
class Util {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
static get RATINGS() {
|
|
41
|
+
return RATINGS$1;
|
|
42
|
+
}
|
|
44
43
|
|
|
45
44
|
static get PASS_THRESHOLD() {
|
|
46
45
|
return PASS_THRESHOLD;
|
|
@@ -72,164 +71,6 @@ class Util {
|
|
|
72
71
|
return lhr.mainDocumentUrl || lhr.finalUrl;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
/**
|
|
76
|
-
* Returns a new LHR that's reshaped for slightly better ergonomics within the report rendereer.
|
|
77
|
-
* Also, sets up the localized UI strings used within renderer and makes changes to old LHRs to be
|
|
78
|
-
* compatible with current renderer.
|
|
79
|
-
* The LHR passed in is not mutated.
|
|
80
|
-
* TODO(team): we all agree the LHR shape change is technical debt we should fix
|
|
81
|
-
* @param {LH.Result} result
|
|
82
|
-
* @return {LH.ReportResult}
|
|
83
|
-
*/
|
|
84
|
-
static prepareReportResult(result) {
|
|
85
|
-
// If any mutations happen to the report within the renderers, we want the original object untouched
|
|
86
|
-
const clone = /** @type {LH.ReportResult} */ (JSON.parse(JSON.stringify(result)));
|
|
87
|
-
|
|
88
|
-
// If LHR is older (≤3.0.3), it has no locale setting. Set default.
|
|
89
|
-
if (!clone.configSettings.locale) {
|
|
90
|
-
clone.configSettings.locale = 'en';
|
|
91
|
-
}
|
|
92
|
-
if (!clone.configSettings.formFactor) {
|
|
93
|
-
// @ts-expect-error fallback handling for emulatedFormFactor
|
|
94
|
-
clone.configSettings.formFactor = clone.configSettings.emulatedFormFactor;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
clone.finalDisplayedUrl = this.getFinalDisplayedUrl(clone);
|
|
98
|
-
clone.mainDocumentUrl = this.getMainDocumentUrl(clone);
|
|
99
|
-
|
|
100
|
-
for (const audit of Object.values(clone.audits)) {
|
|
101
|
-
// Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
|
|
102
|
-
// into 'notApplicable' (LHR ≥4.0).
|
|
103
|
-
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
104
|
-
// eslint-disable-next-line max-len
|
|
105
|
-
if (audit.scoreDisplayMode === 'not_applicable' || audit.scoreDisplayMode === 'not-applicable') {
|
|
106
|
-
audit.scoreDisplayMode = 'notApplicable';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (audit.details) {
|
|
110
|
-
// Turn `auditDetails.type` of undefined (LHR <4.2) and 'diagnostic' (LHR <5.0)
|
|
111
|
-
// into 'debugdata' (LHR ≥5.0).
|
|
112
|
-
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
113
|
-
if (audit.details.type === undefined || audit.details.type === 'diagnostic') {
|
|
114
|
-
// @ts-expect-error details is of type never.
|
|
115
|
-
audit.details.type = 'debugdata';
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Add the jpg data URL prefix to filmstrip screenshots without them (LHR <5.0).
|
|
119
|
-
if (audit.details.type === 'filmstrip') {
|
|
120
|
-
for (const screenshot of audit.details.items) {
|
|
121
|
-
if (!screenshot.data.startsWith(SCREENSHOT_PREFIX)) {
|
|
122
|
-
screenshot.data = SCREENSHOT_PREFIX + screenshot.data;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Circa 10.0, table items were refactored.
|
|
128
|
-
if (audit.details.type === 'table') {
|
|
129
|
-
for (const heading of audit.details.headings) {
|
|
130
|
-
/** @type {{itemType: LH.Audit.Details.ItemValueType|undefined, text: string|undefined}} */
|
|
131
|
-
// @ts-expect-error
|
|
132
|
-
const {itemType, text} = heading;
|
|
133
|
-
if (itemType !== undefined) {
|
|
134
|
-
heading.valueType = itemType;
|
|
135
|
-
// @ts-expect-error
|
|
136
|
-
delete heading.itemType;
|
|
137
|
-
}
|
|
138
|
-
if (text !== undefined) {
|
|
139
|
-
heading.label = text;
|
|
140
|
-
// @ts-expect-error
|
|
141
|
-
delete heading.text;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// @ts-expect-error
|
|
145
|
-
const subItemsItemType = heading.subItemsHeading?.itemType;
|
|
146
|
-
if (heading.subItemsHeading && subItemsItemType !== undefined) {
|
|
147
|
-
heading.subItemsHeading.valueType = subItemsItemType;
|
|
148
|
-
// @ts-expect-error
|
|
149
|
-
delete heading.subItemsHeading.itemType;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// For convenience, smoosh all AuditResults into their auditRef (which has just weight & group)
|
|
157
|
-
if (typeof clone.categories !== 'object') throw new Error('No categories provided.');
|
|
158
|
-
|
|
159
|
-
/** @type {Map<string, Array<LH.ReportResult.AuditRef>>} */
|
|
160
|
-
const relevantAuditToMetricsMap = new Map();
|
|
161
|
-
|
|
162
|
-
// This backcompat converts old LHRs (<9.0.0) to use the new "hidden" group.
|
|
163
|
-
// Old LHRs used "no group" to identify audits that should be hidden in performance instead of the "hidden" group.
|
|
164
|
-
// Newer LHRs use "no group" to identify opportunities and diagnostics whose groups are assigned by details type.
|
|
165
|
-
const [majorVersion] = clone.lighthouseVersion.split('.').map(Number);
|
|
166
|
-
const perfCategory = clone.categories['performance'];
|
|
167
|
-
if (majorVersion < 9 && perfCategory) {
|
|
168
|
-
if (!clone.categoryGroups) clone.categoryGroups = {};
|
|
169
|
-
clone.categoryGroups['hidden'] = {title: ''};
|
|
170
|
-
for (const auditRef of perfCategory.auditRefs) {
|
|
171
|
-
if (!auditRef.group) {
|
|
172
|
-
auditRef.group = 'hidden';
|
|
173
|
-
} else if (['load-opportunities', 'diagnostics'].includes(auditRef.group)) {
|
|
174
|
-
delete auditRef.group;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
for (const category of Object.values(clone.categories)) {
|
|
180
|
-
// Make basic lookup table for relevantAudits
|
|
181
|
-
category.auditRefs.forEach(metricRef => {
|
|
182
|
-
if (!metricRef.relevantAudits) return;
|
|
183
|
-
metricRef.relevantAudits.forEach(auditId => {
|
|
184
|
-
const arr = relevantAuditToMetricsMap.get(auditId) || [];
|
|
185
|
-
arr.push(metricRef);
|
|
186
|
-
relevantAuditToMetricsMap.set(auditId, arr);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
category.auditRefs.forEach(auditRef => {
|
|
191
|
-
const result = clone.audits[auditRef.id];
|
|
192
|
-
auditRef.result = result;
|
|
193
|
-
|
|
194
|
-
// Attach any relevantMetric auditRefs
|
|
195
|
-
if (relevantAuditToMetricsMap.has(auditRef.id)) {
|
|
196
|
-
auditRef.relevantMetrics = relevantAuditToMetricsMap.get(auditRef.id);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// attach the stackpacks to the auditRef object
|
|
200
|
-
if (clone.stackPacks) {
|
|
201
|
-
clone.stackPacks.forEach(pack => {
|
|
202
|
-
if (pack.descriptions[auditRef.id]) {
|
|
203
|
-
auditRef.stackPacks = auditRef.stackPacks || [];
|
|
204
|
-
auditRef.stackPacks.push({
|
|
205
|
-
title: pack.title,
|
|
206
|
-
iconDataURL: pack.iconDataURL,
|
|
207
|
-
description: pack.descriptions[auditRef.id],
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// In 10.0, full-page-screenshot became a top-level property on the LHR.
|
|
216
|
-
if (clone.audits['full-page-screenshot']) {
|
|
217
|
-
const details = /** @type {LH.Result.FullPageScreenshot=} */ (
|
|
218
|
-
clone.audits['full-page-screenshot'].details);
|
|
219
|
-
if (details) {
|
|
220
|
-
clone.fullPageScreenshot = {
|
|
221
|
-
screenshot: details.screenshot,
|
|
222
|
-
nodes: details.nodes,
|
|
223
|
-
};
|
|
224
|
-
} else {
|
|
225
|
-
clone.fullPageScreenshot = null;
|
|
226
|
-
}
|
|
227
|
-
delete clone.audits['full-page-screenshot'];
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return clone;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
74
|
/**
|
|
234
75
|
* @param {LH.Result} lhr
|
|
235
76
|
* @return {LH.Result.FullPageScreenshot=}
|
|
@@ -245,56 +86,6 @@ class Util {
|
|
|
245
86
|
return details;
|
|
246
87
|
}
|
|
247
88
|
|
|
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
89
|
/**
|
|
299
90
|
* Split a string by markdown code spans (enclosed in `backticks`), splitting
|
|
300
91
|
* into segments that were enclosed in backticks (marked as `isCode === true`)
|
|
@@ -493,113 +284,33 @@ class Util {
|
|
|
493
284
|
return hostname.split('.').slice(-splitTld.length).join('.');
|
|
494
285
|
}
|
|
495
286
|
|
|
496
|
-
|
|
497
287
|
/**
|
|
498
|
-
*
|
|
499
|
-
*
|
|
288
|
+
* Returns only lines that are near a message, or the first few lines if there are
|
|
289
|
+
* no line messages.
|
|
290
|
+
* @param {SnippetValue['lines']} lines
|
|
291
|
+
* @param {SnippetValue['lineMessages']} lineMessages
|
|
292
|
+
* @param {number} surroundingLineCount Number of lines to include before and after
|
|
293
|
+
* the message. If this is e.g. 2 this function might return 5 lines.
|
|
500
294
|
*/
|
|
501
|
-
static
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const throttling = settings.throttling;
|
|
295
|
+
static filterRelevantLines(lines, lineMessages, surroundingLineCount) {
|
|
296
|
+
if (lineMessages.length === 0) {
|
|
297
|
+
// no lines with messages, just return the first bunch of lines
|
|
298
|
+
return lines.slice(0, surroundingLineCount * 2 + 1);
|
|
299
|
+
}
|
|
507
300
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` +
|
|
517
|
-
`${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` +
|
|
518
|
-
`${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`;
|
|
301
|
+
const minGapSize = 3;
|
|
302
|
+
const lineNumbersToKeep = new Set();
|
|
303
|
+
// Sort messages so we can check lineNumbersToKeep to see how big the gap to
|
|
304
|
+
// the previous line is.
|
|
305
|
+
lineMessages = lineMessages.sort((a, b) => (a.lineNumber || 0) - (b.lineNumber || 0));
|
|
306
|
+
lineMessages.forEach(({lineNumber}) => {
|
|
307
|
+
let firstSurroundingLineNumber = lineNumber - surroundingLineCount;
|
|
308
|
+
let lastSurroundingLineNumber = lineNumber + surroundingLineCount;
|
|
519
309
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
};
|
|
525
|
-
summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
case 'simulate': {
|
|
529
|
-
const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling;
|
|
530
|
-
// 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)`;
|
|
534
|
-
|
|
535
|
-
const isSlow4G = () => {
|
|
536
|
-
return rttMs === 150 && throughputKbps === 1.6 * 1024;
|
|
537
|
-
};
|
|
538
|
-
summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
|
-
default:
|
|
542
|
-
summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation,
|
|
546
|
-
// because we handle the emulation outside of Lighthouse. Since the screen truly is emulated
|
|
547
|
-
// as a mobile device, ignore `.disabled` in devtools and just check the form factor
|
|
548
|
-
const isScreenEmulationDisabled = settings.channel === 'devtools' ?
|
|
549
|
-
false :
|
|
550
|
-
settings.screenEmulation.disabled;
|
|
551
|
-
const isScreenEmulationMobile = settings.channel === 'devtools' ?
|
|
552
|
-
settings.formFactor === 'mobile' :
|
|
553
|
-
settings.screenEmulation.mobile;
|
|
554
|
-
|
|
555
|
-
let deviceEmulation = Util.i18n.strings.runtimeMobileEmulation;
|
|
556
|
-
if (isScreenEmulationDisabled) {
|
|
557
|
-
deviceEmulation = Util.i18n.strings.runtimeNoEmulation;
|
|
558
|
-
} else if (!isScreenEmulationMobile) {
|
|
559
|
-
deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const screenEmulation = isScreenEmulationDisabled ?
|
|
563
|
-
undefined :
|
|
564
|
-
// eslint-disable-next-line max-len
|
|
565
|
-
`${settings.screenEmulation.width}x${settings.screenEmulation.height}, DPR ${settings.screenEmulation.deviceScaleFactor}`;
|
|
566
|
-
|
|
567
|
-
return {
|
|
568
|
-
deviceEmulation,
|
|
569
|
-
screenEmulation,
|
|
570
|
-
cpuThrottling,
|
|
571
|
-
networkThrottling,
|
|
572
|
-
summary,
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
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.
|
|
583
|
-
*/
|
|
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);
|
|
588
|
-
}
|
|
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++;
|
|
310
|
+
while (firstSurroundingLineNumber < 1) {
|
|
311
|
+
// make sure we still show (surroundingLineCount * 2 + 1) lines in total
|
|
312
|
+
firstSurroundingLineNumber++;
|
|
313
|
+
lastSurroundingLineNumber++;
|
|
603
314
|
}
|
|
604
315
|
// If only a few lines would be omitted normally then we prefer to include
|
|
605
316
|
// extra lines to avoid the tiny gap
|
|
@@ -614,194 +325,8 @@ class Util {
|
|
|
614
325
|
|
|
615
326
|
return lines.filter(line => lineNumbersToKeep.has(line.lineNumber));
|
|
616
327
|
}
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* @param {string} categoryId
|
|
620
|
-
*/
|
|
621
|
-
static isPluginCategory(categoryId) {
|
|
622
|
-
return categoryId.startsWith('lighthouse-plugin-');
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
* @param {LH.Result.GatherMode} gatherMode
|
|
627
|
-
*/
|
|
628
|
-
static shouldDisplayAsFraction(gatherMode) {
|
|
629
|
-
return gatherMode === 'timespan' || gatherMode === 'snapshot';
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* @param {LH.ReportResult.Category} category
|
|
634
|
-
*/
|
|
635
|
-
static calculateCategoryFraction(category) {
|
|
636
|
-
let numPassableAudits = 0;
|
|
637
|
-
let numPassed = 0;
|
|
638
|
-
let numInformative = 0;
|
|
639
|
-
let totalWeight = 0;
|
|
640
|
-
for (const auditRef of category.auditRefs) {
|
|
641
|
-
const auditPassed = Util.showAsPassed(auditRef.result);
|
|
642
|
-
|
|
643
|
-
// Don't count the audit if it's manual, N/A, or isn't displayed.
|
|
644
|
-
if (auditRef.group === 'hidden' ||
|
|
645
|
-
auditRef.result.scoreDisplayMode === 'manual' ||
|
|
646
|
-
auditRef.result.scoreDisplayMode === 'notApplicable') {
|
|
647
|
-
continue;
|
|
648
|
-
} else if (auditRef.result.scoreDisplayMode === 'informative') {
|
|
649
|
-
if (!auditPassed) {
|
|
650
|
-
++numInformative;
|
|
651
|
-
}
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
++numPassableAudits;
|
|
656
|
-
totalWeight += auditRef.weight;
|
|
657
|
-
if (auditPassed) numPassed++;
|
|
658
|
-
}
|
|
659
|
-
return {numPassed, numPassableAudits, numInformative, totalWeight};
|
|
660
|
-
}
|
|
661
328
|
}
|
|
662
329
|
|
|
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;
|
|
669
|
-
|
|
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
|
-
};
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* Report-renderer-specific strings.
|
|
683
|
-
*/
|
|
684
|
-
const UIStrings = {
|
|
685
|
-
/** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */
|
|
686
|
-
varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.',
|
|
687
|
-
/** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */
|
|
688
|
-
calculatorLink: 'See calculator.',
|
|
689
|
-
/** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */
|
|
690
|
-
showRelevantAudits: 'Show audits relevant to:',
|
|
691
|
-
/** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */
|
|
692
|
-
opportunityResourceColumnLabel: 'Opportunity',
|
|
693
|
-
/** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */
|
|
694
|
-
opportunitySavingsColumnLabel: 'Estimated Savings',
|
|
695
|
-
|
|
696
|
-
/** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */
|
|
697
|
-
errorMissingAuditInfo: 'Report error: no audit information',
|
|
698
|
-
/** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */
|
|
699
|
-
errorLabel: 'Error!',
|
|
700
|
-
/** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */
|
|
701
|
-
warningHeader: 'Warnings: ',
|
|
702
|
-
/** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */
|
|
703
|
-
warningAuditsGroupTitle: 'Passed audits but with warnings',
|
|
704
|
-
/** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
|
|
705
|
-
passedAuditsGroupTitle: 'Passed audits',
|
|
706
|
-
/** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
|
|
707
|
-
notApplicableAuditsGroupTitle: 'Not applicable',
|
|
708
|
-
/** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
|
|
709
|
-
manualAuditsGroupTitle: 'Additional items to manually check',
|
|
710
|
-
|
|
711
|
-
/** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */
|
|
712
|
-
toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:',
|
|
713
|
-
|
|
714
|
-
/** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */
|
|
715
|
-
crcInitialNavigation: 'Initial Navigation',
|
|
716
|
-
/** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */
|
|
717
|
-
crcLongestDurationLabel: 'Maximum critical path latency:',
|
|
718
|
-
|
|
719
|
-
/** Label for button that shows all lines of the snippet when clicked */
|
|
720
|
-
snippetExpandButtonLabel: 'Expand snippet',
|
|
721
|
-
/** Label for button that only shows a few lines of the snippet when clicked */
|
|
722
|
-
snippetCollapseButtonLabel: 'Collapse snippet',
|
|
723
|
-
|
|
724
|
-
/** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */
|
|
725
|
-
lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.',
|
|
726
|
-
/** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */
|
|
727
|
-
labDataTitle: 'Lab Data',
|
|
728
|
-
|
|
729
|
-
/** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */
|
|
730
|
-
thirdPartyResourcesLabel: 'Show 3rd-party resources',
|
|
731
|
-
/** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */
|
|
732
|
-
viewTreemapLabel: 'View Treemap',
|
|
733
|
-
/** This label is for a button that will show the user a trace of the page. */
|
|
734
|
-
viewTraceLabel: 'View Trace',
|
|
735
|
-
/** This label is for a button that will show the user a trace of the page. */
|
|
736
|
-
viewOriginalTraceLabel: 'View Original Trace',
|
|
737
|
-
|
|
738
|
-
/** Option in a dropdown menu that opens a small, summary report in a print dialog. */
|
|
739
|
-
dropdownPrintSummary: 'Print Summary',
|
|
740
|
-
/** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */
|
|
741
|
-
dropdownPrintExpanded: 'Print Expanded',
|
|
742
|
-
/** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */
|
|
743
|
-
dropdownCopyJSON: 'Copy JSON',
|
|
744
|
-
/** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */
|
|
745
|
-
dropdownSaveHTML: 'Save as HTML',
|
|
746
|
-
/** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */
|
|
747
|
-
dropdownSaveJSON: 'Save as JSON',
|
|
748
|
-
/** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */
|
|
749
|
-
dropdownViewer: 'Open in Viewer',
|
|
750
|
-
/** Option in a dropdown menu that saves the current report as a new GitHub Gist. */
|
|
751
|
-
dropdownSaveGist: 'Save as Gist',
|
|
752
|
-
/** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */
|
|
753
|
-
dropdownDarkTheme: 'Toggle Dark Theme',
|
|
754
|
-
|
|
755
|
-
/** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */
|
|
756
|
-
runtimeSettingsDevice: 'Device',
|
|
757
|
-
/** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */
|
|
758
|
-
runtimeSettingsNetworkThrottling: 'Network throttling',
|
|
759
|
-
/** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/
|
|
760
|
-
runtimeSettingsCPUThrottling: 'CPU throttling',
|
|
761
|
-
/** 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
|
-
runtimeSettingsUANetwork: 'User agent (network)',
|
|
763
|
-
/** 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',
|
|
765
|
-
/** 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
|
-
runtimeSettingsAxeVersion: 'Axe version',
|
|
767
|
-
/** 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' */
|
|
768
|
-
runtimeSettingsScreenEmulation: 'Screen emulation',
|
|
769
|
-
|
|
770
|
-
/** Label for button to create an issue against the Lighthouse GitHub project. */
|
|
771
|
-
footerIssue: 'File an issue',
|
|
772
|
-
|
|
773
|
-
/** Descriptive explanation for emulation setting when no device emulation is set. */
|
|
774
|
-
runtimeNoEmulation: 'No emulation',
|
|
775
|
-
/** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */
|
|
776
|
-
runtimeMobileEmulation: 'Emulated Moto G4',
|
|
777
|
-
/** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */
|
|
778
|
-
runtimeDesktopEmulation: 'Emulated Desktop',
|
|
779
|
-
/** Descriptive explanation for a runtime setting that is set to an unknown value. */
|
|
780
|
-
runtimeUnknown: 'Unknown',
|
|
781
|
-
/** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */
|
|
782
|
-
runtimeSingleLoad: 'Single page load',
|
|
783
|
-
/** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */
|
|
784
|
-
runtimeAnalysisWindow: 'Initial page load',
|
|
785
|
-
/** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */
|
|
786
|
-
runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len
|
|
787
|
-
|
|
788
|
-
/** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */
|
|
789
|
-
throttlingProvided: 'Provided by environment',
|
|
790
|
-
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
|
|
791
|
-
show: 'Show',
|
|
792
|
-
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
|
|
793
|
-
hide: 'Hide',
|
|
794
|
-
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
|
|
795
|
-
expandView: 'Expand view',
|
|
796
|
-
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
|
|
797
|
-
collapseView: 'Collapse view',
|
|
798
|
-
/** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */
|
|
799
|
-
runtimeSlow4g: 'Slow 4G throttling',
|
|
800
|
-
/** Label indicating that Lighthouse throttled the page using custom throttling settings. */
|
|
801
|
-
runtimeCustom: 'Custom throttling',
|
|
802
|
-
};
|
|
803
|
-
Util.UIStrings = UIStrings;
|
|
804
|
-
|
|
805
330
|
// auto-generated by build/build-report-components.js
|
|
806
331
|
|
|
807
332
|
/** @typedef {import('./dom.js').DOM} DOM */
|
|
@@ -1324,7 +849,7 @@ function createSnippetLineComponent(dom) {
|
|
|
1324
849
|
function createStylesComponent(dom) {
|
|
1325
850
|
const el0 = dom.createFragment();
|
|
1326
851
|
const el1 = dom.createElement("style");
|
|
1327
|
-
el1.append("/**\n * @license\n * Copyright 2017 The Lighthouse Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS-IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n Naming convention:\n\n If a variable is used for a specific component: --{component}-{property name}-{modifier}\n\n Both {component} and {property name} should be kebab-case. If the target is the entire page,\n use 'report' for the component. The property name should not be abbreviated. Use the\n property name the variable is intended for - if it's used for multiple, a common descriptor\n is fine (ex: 'size' for a variable applied to 'width' and 'height'). If a variable is shared\n across multiple components, either create more variables or just drop the \"{component}-\"\n part of the name. Append any modifiers at the end (ex: 'big', 'dark').\n\n For colors: --color-{hue}-{intensity}\n\n {intensity} is the Material Design tag - 700, A700, etc.\n*/\n.lh-vars {\n /* Palette using Material Design Colors\n * https://www.materialui.co/colors */\n --color-amber-50: #FFF8E1;\n --color-blue-200: #90CAF9;\n --color-blue-900: #0D47A1;\n --color-blue-A700: #2962FF;\n --color-blue-primary: #06f;\n --color-cyan-500: #00BCD4;\n --color-gray-100: #F5F5F5;\n --color-gray-300: #CFCFCF;\n --color-gray-200: #E0E0E0;\n --color-gray-400: #BDBDBD;\n --color-gray-50: #FAFAFA;\n --color-gray-500: #9E9E9E;\n --color-gray-600: #757575;\n --color-gray-700: #616161;\n --color-gray-800: #424242;\n --color-gray-900: #212121;\n --color-gray: #000000;\n --color-green-700: #080;\n --color-green: #0c6;\n --color-lime-400: #D3E156;\n --color-orange-50: #FFF3E0;\n --color-orange-700: #C33300;\n --color-orange: #fa3;\n --color-red-700: #c00;\n --color-red: #f33;\n --color-teal-600: #00897B;\n --color-white: #FFFFFF;\n\n /* Context-specific colors */\n --color-average-secondary: var(--color-orange-700);\n --color-average: var(--color-orange);\n --color-fail-secondary: var(--color-red-700);\n --color-fail: var(--color-red);\n --color-hover: var(--color-gray-50);\n --color-informative: var(--color-blue-900);\n --color-pass-secondary: var(--color-green-700);\n --color-pass: var(--color-green);\n --color-not-applicable: var(--color-gray-600);\n\n /* Component variables */\n --audit-description-padding-left: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right));\n --audit-explanation-line-height: 16px;\n --audit-group-margin-bottom: calc(var(--default-padding) * 6);\n --audit-group-padding-vertical: 8px;\n --audit-margin-horizontal: 5px;\n --audit-padding-vertical: 8px;\n --category-padding: calc(var(--default-padding) * 6) var(--edge-gap-padding) calc(var(--default-padding) * 4);\n --chevron-line-stroke: var(--color-gray-600);\n --chevron-size: 12px;\n --default-padding: 8px;\n --edge-gap-padding: calc(var(--default-padding) * 4);\n --env-item-background-color: var(--color-gray-100);\n --env-item-font-size: 28px;\n --env-item-line-height: 36px;\n --env-item-padding: 10px 0px;\n --env-name-min-width: 220px;\n --footer-padding-vertical: 16px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 48px;\n --gauge-circle-size-sm: 32px;\n --gauge-label-font-size-big: 18px;\n --gauge-label-font-size: var(--report-font-size-secondary);\n --gauge-label-line-height-big: 24px;\n --gauge-label-line-height: var(--report-line-height-secondary);\n --gauge-percentage-font-size-big: 38px;\n --gauge-percentage-font-size: var(--report-font-size-secondary);\n --gauge-wrapper-width: 120px;\n --header-line-height: 24px;\n --highlighter-background-color: var(--report-text-color);\n --icon-square-size: calc(var(--score-icon-size) * 0.88);\n --image-preview-size: 48px;\n --link-color: var(--color-blue-primary);\n --locale-selector-background-color: var(--color-white);\n --metric-toggle-lines-fill: #7F7F7F;\n --metric-value-font-size: calc(var(--report-font-size) * 1.8);\n --metrics-toggle-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-white);\n --plugin-badge-size-big: calc(var(--gauge-circle-size-big) / 2.7);\n --plugin-badge-size: calc(var(--gauge-circle-size) / 2.7);\n --plugin-icon-size: 65%;\n --pwa-icon-margin: 0 var(--default-padding);\n --pwa-icon-size: var(--topbar-logo-size);\n --report-background-color: #fff;\n --report-border-color-secondary: #ebebeb;\n --report-font-family-monospace: 'Roboto Mono', 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: Roboto, Helvetica, Arial, sans-serif;\n --report-font-size: 14px;\n --report-font-size-secondary: 12px;\n --report-icon-size: var(--score-icon-background-size);\n --report-line-height: 24px;\n --report-line-height-secondary: 20px;\n --report-monospace-font-size: calc(var(--report-font-size) * 0.85);\n --report-text-color-secondary: var(--color-gray-800);\n --report-text-color: var(--color-gray-900);\n --report-content-max-width: calc(60 * var(--report-font-size)); /* defaults to 840px */\n --report-content-min-width: 360px;\n --report-content-max-width-minus-edge-gap: calc(var(--report-content-max-width) - var(--edge-gap-padding) * 2);\n --score-container-padding: 8px;\n --score-icon-background-size: 24px;\n --score-icon-margin-left: 6px;\n --score-icon-margin-right: 14px;\n --score-icon-margin: 0 var(--score-icon-margin-right) 0 var(--score-icon-margin-left);\n --score-icon-size: 12px;\n --score-icon-size-big: 16px;\n --screenshot-overlay-background: rgba(0, 0, 0, 0.3);\n --section-padding-vertical: calc(var(--default-padding) * 6);\n --snippet-background-color: var(--color-gray-50);\n --snippet-color: #0938C2;\n --sparkline-height: 5px;\n --stackpack-padding-horizontal: 10px;\n --sticky-header-background-color: var(--report-background-color);\n --table-higlight-background-color: hsla(210, 17%, 77%, 0.1);\n --tools-icon-color: var(--color-gray-600);\n --topbar-background-color: var(--color-white);\n --topbar-height: 32px;\n --topbar-logo-size: 24px;\n --topbar-padding: 0 8px;\n --toplevel-warning-background-color: hsla(30, 100%, 75%, 10%);\n --toplevel-warning-message-text-color: var(--color-average-secondary);\n --toplevel-warning-padding: 18px;\n --toplevel-warning-text-color: var(--report-text-color);\n\n /* SVGs */\n --plugin-icon-url-dark: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\" viewBox=\"0 0 24 24\" fill=\"%23FFFFFF\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z\"/></svg>');\n --plugin-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\" viewBox=\"0 0 24 24\" fill=\"%23757575\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z\"/></svg>');\n\n --pass-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\"><title>check</title><path fill=\"%23178239\" d=\"M24 4C12.95 4 4 12.95 4 24c0 11.04 8.95 20 20 20 11.04 0 20-8.96 20-20 0-11.05-8.96-20-20-20zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z\"/></svg>');\n --average-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\"><title>info</title><path fill=\"%23E67700\" d=\"M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z\"/></svg>');\n --fail-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\"><title>warn</title><path fill=\"%23C7221F\" d=\"M2 42h44L24 4 2 42zm24-6h-4v-4h4v4zm0-8h-4v-8h4v8z\"/></svg>');\n\n --pwa-installable-gray-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"nonzero\"><circle fill=\"%23DAE0E3\" cx=\"12\" cy=\"12\" r=\"12\"/><path d=\"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z\" fill=\"%23FFF\"/></g></svg>');\n --pwa-optimized-gray-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"evenodd\"><rect fill=\"%23DAE0E3\" width=\"24\" height=\"24\" rx=\"12\"/><path fill=\"%23FFF\" d=\"M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z\"/><path d=\"M5 5h14v14H5z\"/></g></svg>');\n\n --pwa-installable-gray-url-dark: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"nonzero\"><circle fill=\"%23424242\" cx=\"12\" cy=\"12\" r=\"12\"/><path d=\"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z\" fill=\"%23FFF\"/></g></svg>');\n --pwa-optimized-gray-url-dark: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"evenodd\"><rect fill=\"%23424242\" width=\"24\" height=\"24\" rx=\"12\"/><path fill=\"%23FFF\" d=\"M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z\"/><path d=\"M5 5h14v14H5z\"/></g></svg>');\n\n --pwa-installable-color-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"nonzero\" fill=\"none\"><circle fill=\"%230CCE6B\" cx=\"12\" cy=\"12\" r=\"12\"/><path d=\"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z\" fill=\"%23FFF\"/></g></svg>');\n --pwa-optimized-color-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"evenodd\"><rect fill=\"%230CCE6B\" width=\"24\" height=\"24\" rx=\"12\"/><path d=\"M5 5h14v14H5z\"/><path fill=\"%23FFF\" d=\"M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z\"/></g></svg>');\n\n --swap-locale-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#000000\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\"/><path d=\"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z\"/></svg>');\n}\n\n@media not print {\n .lh-dark {\n /* Pallete */\n --color-gray-200: var(--color-gray-800);\n --color-gray-300: #616161;\n --color-gray-400: var(--color-gray-600);\n --color-gray-700: var(--color-gray-400);\n --color-gray-50: #757575;\n --color-gray-600: var(--color-gray-500);\n --color-green-700: var(--color-green);\n --color-orange-700: var(--color-orange);\n --color-red-700: var(--color-red);\n --color-teal-600: var(--color-cyan-500);\n\n /* Context-specific colors */\n --color-hover: rgba(0, 0, 0, 0.2);\n --color-informative: var(--color-blue-200);\n\n /* Component variables */\n --env-item-background-color: #393535;\n --link-color: var(--color-blue-200);\n --locale-selector-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-gray-800);\n --report-background-color: var(--color-gray-900);\n --report-border-color-secondary: var(--color-gray-200);\n --report-text-color-secondary: var(--color-gray-400);\n --report-text-color: var(--color-gray-100);\n --snippet-color: var(--color-cyan-500);\n --topbar-background-color: var(--color-gray);\n --toplevel-warning-background-color: hsl(33deg 14% 18%);\n --toplevel-warning-message-text-color: var(--color-orange-700);\n --toplevel-warning-text-color: var(--color-gray-100);\n\n /* SVGs */\n --plugin-icon-url: var(--plugin-icon-url-dark);\n --pwa-installable-gray-url: var(--pwa-installable-gray-url-dark);\n --pwa-optimized-gray-url: var(--pwa-optimized-gray-url-dark);\n }\n}\n\n@media only screen and (max-width: 480px) {\n .lh-vars {\n --audit-group-margin-bottom: 20px;\n --edge-gap-padding: var(--default-padding);\n --env-name-min-width: 120px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 72px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 112px;\n --header-padding: 16px 0 16px 0;\n --image-preview-size: 24px;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-size: 14px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --topbar-height: 28px;\n --topbar-logo-size: 20px;\n }\n\n /* Not enough space to adequately show the relative savings bars. */\n .lh-sparkline {\n display: none;\n }\n}\n\n.lh-vars.lh-devtools {\n --audit-explanation-line-height: 14px;\n --audit-group-margin-bottom: 20px;\n --audit-group-padding-vertical: 12px;\n --audit-padding-vertical: 4px;\n --category-padding: 12px;\n --default-padding: 12px;\n --env-name-min-width: 120px;\n --footer-padding-vertical: 8px;\n --gauge-circle-size-big: 72px;\n --gauge-circle-size: 64px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 97px;\n --header-line-height: 20px;\n --header-padding: 16px 0 16px 0;\n --screenshot-overlay-background: transparent;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-family-monospace: 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n --report-font-size: 12px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --section-padding-vertical: 8px;\n}\n\n.lh-devtools.lh-root {\n height: 100%;\n}\n.lh-devtools.lh-root img {\n /* Override devtools default 'min-width: 0' so svg without size in a flexbox isn't collapsed. */\n min-width: auto;\n}\n.lh-devtools .lh-container {\n overflow-y: scroll;\n height: calc(100% - var(--topbar-height));\n}\n@media print {\n .lh-devtools .lh-container {\n overflow: unset;\n }\n}\n.lh-devtools .lh-sticky-header {\n /* This is normally the height of the topbar, but we want it to stick to the top of our scroll container .lh-container` */\n top: 0;\n}\n.lh-devtools .lh-element-screenshot__overlay {\n position: absolute;\n}\n\n@keyframes fadeIn {\n 0% { opacity: 0;}\n 100% { opacity: 0.6;}\n}\n\n.lh-root *, .lh-root *::before, .lh-root *::after {\n box-sizing: border-box;\n}\n\n.lh-root {\n font-family: var(--report-font-family);\n font-size: var(--report-font-size);\n margin: 0;\n line-height: var(--report-line-height);\n background: var(--report-background-color);\n color: var(--report-text-color);\n}\n\n.lh-root :focus-visible {\n outline: -webkit-focus-ring-color auto 3px;\n}\n.lh-root summary:focus {\n outline: none;\n box-shadow: 0 0 0 1px hsl(217, 89%, 61%);\n}\n\n.lh-root [hidden] {\n display: none !important;\n}\n\n.lh-root pre {\n margin: 0;\n}\n\n.lh-root details > summary {\n cursor: pointer;\n}\n\n.lh-hidden {\n display: none !important;\n}\n\n.lh-container {\n /*\n Text wrapping in the report is so much FUN!\n We have a `word-break: break-word;` globally here to prevent a few common scenarios, namely\n long non-breakable text (usually URLs) found in:\n 1. The footer\n 2. .lh-node (outerHTML)\n 3. .lh-code\n\n With that sorted, the next challenge is appropriate column sizing and text wrapping inside our\n .lh-details tables. Even more fun.\n * We don't want table headers (\"Potential Savings (ms)\") to wrap or their column values, but\n we'd be happy for the URL column to wrap if the URLs are particularly long.\n * We want the narrow columns to remain narrow, providing the most column width for URL\n * We don't want the table to extend past 100% width.\n * Long URLs in the URL column can wrap. Util.getURLDisplayName maxes them out at 64 characters,\n but they do not get any overflow:ellipsis treatment.\n */\n word-break: break-word;\n}\n\n.lh-audit-group a,\n.lh-category-header__description a,\n.lh-audit__description a,\n.lh-warnings a,\n.lh-footer a,\n.lh-table-column--link a {\n color: var(--link-color);\n}\n\n.lh-audit__description, .lh-audit__stackpack {\n --inner-audit-padding-right: var(--stackpack-padding-horizontal);\n padding-left: var(--audit-description-padding-left);\n padding-right: var(--inner-audit-padding-right);\n padding-top: 8px;\n padding-bottom: 8px;\n}\n\n.lh-details {\n margin-top: var(--default-padding);\n margin-bottom: var(--default-padding);\n margin-left: var(--audit-description-padding-left);\n /* whatever the .lh-details side margins are */\n width: 100%;\n}\n\n.lh-audit__stackpack {\n display: flex;\n align-items: center;\n}\n\n.lh-audit__stackpack__img {\n max-width: 30px;\n margin-right: var(--default-padding)\n}\n\n/* Report header */\n\n.lh-report-icon {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n cursor: pointer;\n}\n.lh-report-icon[disabled] {\n opacity: 0.3;\n pointer-events: none;\n}\n\n.lh-report-icon::before {\n content: \"\";\n margin: 4px;\n background-repeat: no-repeat;\n width: var(--report-icon-size);\n height: var(--report-icon-size);\n opacity: 0.7;\n display: inline-block;\n vertical-align: middle;\n}\n.lh-report-icon:hover::before {\n opacity: 1;\n}\n.lh-dark .lh-report-icon::before {\n filter: invert(1);\n}\n.lh-report-icon--print::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z\"/><path fill=\"none\" d=\"M0 0h24v24H0z\"/></svg>');\n}\n.lh-report-icon--copy::before {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\"/></svg>');\n}\n.lh-report-icon--open::before {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z\"/></svg>');\n}\n.lh-report-icon--download::before {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>');\n}\n.lh-report-icon--dark::before {\n background-image:url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 100 125\"><path d=\"M50 23.587c-16.27 0-22.799 12.574-22.799 21.417 0 12.917 10.117 22.451 12.436 32.471h20.726c2.32-10.02 12.436-19.554 12.436-32.471 0-8.843-6.528-21.417-22.799-21.417zM39.637 87.161c0 3.001 1.18 4.181 4.181 4.181h.426l.41 1.231C45.278 94.449 46.042 95 48.019 95h3.963c1.978 0 2.74-.551 3.365-2.427l.409-1.231h.427c3.002 0 4.18-1.18 4.18-4.181V80.91H39.637v6.251zM50 18.265c1.26 0 2.072-.814 2.072-2.073v-9.12C52.072 5.813 51.26 5 50 5c-1.259 0-2.072.813-2.072 2.073v9.12c0 1.259.813 2.072 2.072 2.072zM68.313 23.727c.994.774 2.135.634 2.91-.357l5.614-7.187c.776-.992.636-2.135-.356-2.909-.992-.776-2.135-.636-2.91.357l-5.613 7.186c-.778.993-.636 2.135.355 2.91zM91.157 36.373c-.306-1.222-1.291-1.815-2.513-1.51l-8.85 2.207c-1.222.305-1.814 1.29-1.51 2.512.305 1.223 1.291 1.814 2.513 1.51l8.849-2.206c1.223-.305 1.816-1.291 1.511-2.513zM86.757 60.48l-8.331-3.709c-1.15-.512-2.225-.099-2.736 1.052-.512 1.151-.1 2.224 1.051 2.737l8.33 3.707c1.15.514 2.225.101 2.736-1.05.513-1.149.1-2.223-1.05-2.737zM28.779 23.37c.775.992 1.917 1.131 2.909.357.992-.776 1.132-1.917.357-2.91l-5.615-7.186c-.775-.992-1.917-1.132-2.909-.357s-1.131 1.917-.356 2.909l5.614 7.187zM21.715 39.583c.305-1.223-.288-2.208-1.51-2.513l-8.849-2.207c-1.222-.303-2.208.289-2.513 1.511-.303 1.222.288 2.207 1.511 2.512l8.848 2.206c1.222.304 2.208-.287 2.513-1.509zM21.575 56.771l-8.331 3.711c-1.151.511-1.563 1.586-1.05 2.735.511 1.151 1.586 1.563 2.736 1.052l8.331-3.711c1.151-.511 1.563-1.586 1.05-2.735-.512-1.15-1.585-1.562-2.736-1.052z\"/></svg>');\n}\n.lh-report-icon--treemap::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"black\"><path d=\"M3 5v14h19V5H3zm2 2h15v4H5V7zm0 10v-4h4v4H5zm6 0v-4h9v4h-9z\"/></svg>');\n}\n.lh-report-icon--date::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 11h2v2H7v-2zm14-5v14a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h1V2h2v2h8V2h2v2h1a2 2 0 012 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z\"/></svg>');\n}\n.lh-report-icon--devices::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M4 6h18V4H4a2 2 0 00-2 2v11H0v3h14v-3H4V6zm19 2h-6a1 1 0 00-1 1v10c0 .6.5 1 1 1h6c.6 0 1-.5 1-1V9c0-.6-.5-1-1-1zm-1 9h-4v-7h4v7z\"/></svg>');\n}\n.lh-report-icon--world::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm7 6h-3c-.3-1.3-.8-2.5-1.4-3.6A8 8 0 0 1 18.9 8zm-7-4a14 14 0 0 1 2 4h-4a14 14 0 0 1 2-4zM4.3 14a8.2 8.2 0 0 1 0-4h3.3a16.5 16.5 0 0 0 0 4H4.3zm.8 2h3a14 14 0 0 0 1.3 3.6A8 8 0 0 1 5.1 16zm3-8H5a8 8 0 0 1 4.3-3.6L8 8zM12 20a14 14 0 0 1-2-4h4a14 14 0 0 1-2 4zm2.3-6H9.7a14.7 14.7 0 0 1 0-4h4.6a14.6 14.6 0 0 1 0 4zm.3 5.6c.6-1.2 1-2.4 1.4-3.6h3a8 8 0 0 1-4.4 3.6zm1.8-5.6a16.5 16.5 0 0 0 0-4h3.3a8.2 8.2 0 0 1 0 4h-3.3z\"/></svg>');\n}\n.lh-report-icon--stopwatch::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.1-6.6L20.5 6l-1.4-1.4L17.7 6A9 9 0 0 0 3 13a9 9 0 1 0 16-5.6zm-7 12.6a7 7 0 1 1 0-14 7 7 0 0 1 0 14z\"/></svg>');\n}\n.lh-report-icon--networkspeed::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M15.9 5c-.2 0-.3 0-.4.2v.2L10.1 17a2 2 0 0 0-.2 1 2 2 0 0 0 4 .4l2.4-12.9c0-.3-.2-.5-.5-.5zM1 9l2 2c2.9-2.9 6.8-4 10.5-3.6l1.2-2.7C10 3.8 4.7 5.3 1 9zm20 2 2-2a15.4 15.4 0 0 0-5.6-3.6L17 8.2c1.5.7 2.9 1.6 4.1 2.8zm-4 4 2-2a9.9 9.9 0 0 0-2.7-1.9l-.5 3 1.2.9zM5 13l2 2a7.1 7.1 0 0 1 4-2l1.3-2.9C9.7 10.1 7 11 5 13z\"/></svg>');\n}\n.lh-report-icon--samples-one::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><circle cx=\"7\" cy=\"14\" r=\"3\"/><path d=\"M7 18a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm4-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm5.6 17.6a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/></svg>');\n}\n.lh-report-icon--samples-many::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 18a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm4-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm5.6 17.6a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/><circle cx=\"7\" cy=\"14\" r=\"3\"/><circle cx=\"11\" cy=\"6\" r=\"3\"/></svg>');\n}\n.lh-report-icon--chrome::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-50 -50 562 562\"><path d=\"M256 25.6v25.6a204 204 0 0 1 144.8 60 204 204 0 0 1 60 144.8 204 204 0 0 1-60 144.8 204 204 0 0 1-144.8 60 204 204 0 0 1-144.8-60 204 204 0 0 1-60-144.8 204 204 0 0 1 60-144.8 204 204 0 0 1 144.8-60V0a256 256 0 1 0 0 512 256 256 0 0 0 0-512v25.6z\"/><path d=\"M256 179.2v25.6a51.3 51.3 0 0 1 0 102.4 51.3 51.3 0 0 1 0-102.4v-51.2a102.3 102.3 0 1 0-.1 204.7 102.3 102.3 0 0 0 .1-204.7v25.6z\"/><path d=\"M256 204.8h217.6a25.6 25.6 0 0 0 0-51.2H256a25.6 25.6 0 0 0 0 51.2m44.3 76.8L191.5 470.1a25.6 25.6 0 1 0 44.4 25.6l108.8-188.5a25.6 25.6 0 1 0-44.4-25.6m-88.6 0L102.9 93.2a25.7 25.7 0 0 0-35-9.4 25.7 25.7 0 0 0-9.4 35l108.8 188.5a25.7 25.7 0 0 0 35 9.4 25.9 25.9 0 0 0 9.4-35.1\"/></svg>');\n}\n\n\n\n.lh-buttons {\n display: flex;\n flex-wrap: wrap;\n margin: var(--default-padding) 0;\n}\n.lh-button {\n height: 32px;\n border: 1px solid var(--report-border-color-secondary);\n border-radius: 3px;\n color: var(--link-color);\n background-color: var(--report-background-color);\n margin: 5px;\n}\n\n.lh-button:first-of-type {\n margin-left: 0;\n}\n\n/* Node */\n.lh-node__snippet {\n font-family: var(--report-font-family-monospace);\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n line-height: 20px;\n}\n\n/* Score */\n\n.lh-audit__score-icon {\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n margin: var(--score-icon-margin);\n}\n\n.lh-audit--pass .lh-audit__display-text {\n color: var(--color-pass-secondary);\n}\n.lh-audit--pass .lh-audit__score-icon,\n.lh-scorescale-range--pass::before {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-audit__display-text {\n color: var(--color-average-secondary);\n}\n.lh-audit--average .lh-audit__score-icon,\n.lh-scorescale-range--average::before {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-audit--fail .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n.lh-audit--fail .lh-audit__score-icon,\n.lh-audit--error .lh-audit__score-icon,\n.lh-scorescale-range--fail::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-audit--manual .lh-audit__display-text,\n.lh-audit--notapplicable .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n.lh-audit--manual .lh-audit__score-icon,\n.lh-audit--notapplicable .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n background: none;\n}\n\n.lh-audit--informative .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n\n.lh-audit--informative .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n}\n\n.lh-audit__description,\n.lh-audit__stackpack {\n color: var(--report-text-color-secondary);\n}\n.lh-audit__adorn {\n border: 1px solid slategray;\n border-radius: 3px;\n margin: 0 3px;\n padding: 0 2px;\n line-height: 1.1;\n display: inline-block;\n font-size: 90%;\n}\n\n.lh-category-header__description {\n text-align: center;\n color: var(--color-gray-700);\n margin: 0px auto;\n max-width: 400px;\n}\n\n\n.lh-audit__display-text,\n.lh-load-opportunity__sparkline,\n.lh-chevron-container {\n margin: 0 var(--audit-margin-horizontal);\n}\n.lh-chevron-container {\n margin-right: 0;\n}\n\n.lh-audit__title-and-text {\n flex: 1;\n}\n\n.lh-audit__title-and-text code {\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n}\n\n/* Prepend display text with em dash separator. But not in Opportunities. */\n.lh-audit__display-text:not(:empty):before {\n content: '—';\n margin-right: var(--audit-margin-horizontal);\n}\n.lh-audit-group.lh-audit-group--load-opportunities .lh-audit__display-text:not(:empty):before {\n display: none;\n}\n\n/* Expandable Details (Audit Groups, Audits) */\n.lh-audit__header {\n display: flex;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-audit--load-opportunity .lh-audit__header {\n display: block;\n}\n\n\n.lh-metricfilter {\n display: grid;\n justify-content: end;\n align-items: center;\n grid-auto-flow: column;\n gap: 4px;\n color: var(--color-gray-700);\n}\n\n.lh-metricfilter__radio {\n position: absolute;\n left: -9999px;\n}\n.lh-metricfilter input[type='radio']:focus-visible + label {\n outline: -webkit-focus-ring-color auto 1px;\n}\n\n.lh-metricfilter__label {\n display: inline-flex;\n padding: 0 4px;\n height: 16px;\n text-decoration: underline;\n align-items: center;\n cursor: pointer;\n font-size: 90%;\n}\n\n.lh-metricfilter__label--active {\n background: var(--color-blue-primary);\n color: var(--color-white);\n border-radius: 3px;\n text-decoration: none;\n}\n/* Give the 'All' choice a more muted display */\n.lh-metricfilter__label--active[for=\"metric-All\"] {\n background-color: var(--color-blue-200) !important;\n color: black !important;\n}\n\n.lh-metricfilter__text {\n margin-right: 8px;\n}\n\n/* If audits are filtered, hide the itemcount for Passed Audits… */\n.lh-category--filtered .lh-audit-group .lh-audit-group__itemcount {\n display: none;\n}\n\n\n.lh-audit__header:hover {\n background-color: var(--color-hover);\n}\n\n/* We want to hide the browser's default arrow marker on summary elements. Admittedly, it's complicated. */\n.lh-root details > summary {\n /* Blink 89+ and Firefox will hide the arrow when display is changed from (new) default of `list-item` to block. https://chromestatus.com/feature/6730096436051968*/\n display: block;\n}\n/* Safari and Blink <=88 require using the -webkit-details-marker selector */\n.lh-root details > summary::-webkit-details-marker {\n display: none;\n}\n\n/* Perf Metric */\n\n.lh-metrics-container {\n display: grid;\n grid-auto-rows: 1fr;\n grid-template-columns: 1fr 1fr;\n grid-column-gap: var(--report-line-height);\n margin-bottom: var(--default-padding);\n}\n\n.lh-metric {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(-n+2) {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-metric__innerwrap {\n display: grid;\n /**\n * Icon -- Metric Name\n * -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 1fr;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-metric__details {\n order: -1;\n}\n\n.lh-metric__title {\n flex: 1;\n}\n\n.lh-calclink {\n padding-left: calc(1ex / 3);\n}\n\n.lh-metric__description {\n display: none;\n grid-column-start: 2;\n grid-column-end: 4;\n color: var(--report-text-color-secondary);\n}\n\n.lh-metric__value {\n font-size: var(--metric-value-font-size);\n margin: calc(var(--default-padding) / 2) 0;\n white-space: nowrap; /* No wrapping between metric value and the icon */\n grid-column-start: 2;\n}\n\n\n@media screen and (max-width: 535px) {\n .lh-metrics-container {\n display: block;\n }\n\n .lh-metric {\n border-bottom: none !important;\n }\n .lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(1) {\n border-bottom: 1px solid var(--report-border-color-secondary) !important;\n }\n\n /* Change the grid to 3 columns for narrow viewport. */\n .lh-metric__innerwrap {\n /**\n * Icon -- Metric Name -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 2fr 1fr;\n }\n .lh-metric__value {\n justify-self: end;\n grid-column-start: unset;\n }\n}\n\n/* No-JS toggle switch */\n/* Keep this selector sync'd w/ `magicSelector` in report-ui-features-test.js */\n .lh-metrics-toggle__input:checked ~ .lh-metrics-container .lh-metric__description {\n display: block;\n}\n\n/* TODO get rid of the SVGS and clean up these some more */\n.lh-metrics-toggle__input {\n opacity: 0;\n position: absolute;\n right: 0;\n top: 0px;\n}\n\n.lh-metrics-toggle__input + div > label > .lh-metrics-toggle__labeltext--hide,\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--show {\n display: none;\n}\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--hide {\n display: inline;\n}\n.lh-metrics-toggle__input:focus + div > label {\n outline: -webkit-focus-ring-color auto 3px;\n}\n\n.lh-metrics-toggle__label {\n cursor: pointer;\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n/* Pushes the metric description toggle button to the right. */\n.lh-audit-group--metrics .lh-audit-group__header {\n display: flex;\n justify-content: space-between;\n}\n\n.lh-metric__icon,\n.lh-scorescale-range::before {\n content: '';\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n display: inline-block;\n margin: var(--score-icon-margin);\n}\n\n.lh-metric--pass .lh-metric__value {\n color: var(--color-pass-secondary);\n}\n.lh-metric--pass .lh-metric__icon {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-metric--average .lh-metric__value {\n color: var(--color-average-secondary);\n}\n.lh-metric--average .lh-metric__icon {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-metric--fail .lh-metric__value {\n color: var(--color-fail-secondary);\n}\n.lh-metric--fail .lh-metric__icon,\n.lh-metric--error .lh-metric__icon {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-metric--error .lh-metric__value,\n.lh-metric--error .lh-metric__description {\n color: var(--color-fail-secondary);\n}\n\n/* Perf load opportunity */\n\n.lh-load-opportunity__cols {\n display: flex;\n align-items: flex-start;\n}\n\n.lh-load-opportunity__header .lh-load-opportunity__col {\n color: var(--color-gray-600);\n display: unset;\n line-height: calc(2.3 * var(--report-font-size));\n}\n\n.lh-load-opportunity__col {\n display: flex;\n}\n\n.lh-load-opportunity__col--one {\n flex: 5;\n align-items: center;\n margin-right: 2px;\n}\n.lh-load-opportunity__col--two {\n flex: 4;\n text-align: right;\n}\n\n.lh-audit--load-opportunity .lh-audit__display-text {\n text-align: right;\n flex: 0 0 calc(4 * var(--report-font-size));\n}\n\n\n/* Sparkline */\n\n.lh-load-opportunity__sparkline {\n flex: 1;\n margin-top: calc((var(--report-line-height) - var(--sparkline-height)) / 2);\n}\n\n.lh-sparkline {\n height: var(--sparkline-height);\n width: 100%;\n}\n\n.lh-sparkline__bar {\n height: 100%;\n float: right;\n}\n\n.lh-audit--pass .lh-sparkline__bar {\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-sparkline__bar {\n background: var(--color-average);\n}\n\n.lh-audit--fail .lh-sparkline__bar {\n background: var(--color-fail);\n}\n\n/* Filmstrip */\n\n.lh-filmstrip-container {\n /* smaller gap between metrics and filmstrip */\n margin: -8px auto 0 auto;\n}\n\n.lh-filmstrip {\n display: grid;\n justify-content: space-between;\n padding-bottom: var(--default-padding);\n width: 100%;\n grid-template-columns: repeat(auto-fit, 60px);\n}\n\n.lh-filmstrip__frame {\n text-align: right;\n position: relative;\n}\n\n.lh-filmstrip__thumbnail {\n border: 1px solid var(--report-border-color-secondary);\n max-height: 100px;\n max-width: 60px;\n}\n\n/* Audit */\n\n.lh-audit {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n/* Apply border-top to just the first audit. */\n.lh-audit {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n.lh-audit ~ .lh-audit {\n border-top: none;\n}\n\n\n.lh-audit--error .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n\n/* Audit Group */\n\n.lh-audit-group {\n margin-bottom: var(--audit-group-margin-bottom);\n position: relative;\n}\n.lh-audit-group--metrics {\n margin-bottom: calc(var(--audit-group-margin-bottom) / 2);\n}\n\n.lh-audit-group__header::before {\n /* By default, groups don't get an icon */\n content: none;\n width: var(--pwa-icon-size);\n height: var(--pwa-icon-size);\n margin: var(--pwa-icon-margin);\n display: inline-block;\n vertical-align: middle;\n}\n\n/* Style the \"over budget\" columns red. */\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(4),\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(5),\n.lh-audit-group--budgets #timing-budget tbody tr td:nth-child(3) {\n color: var(--color-red-700);\n}\n\n/* Align the \"over budget request count\" text to be close to the \"over budget bytes\" column. */\n.lh-audit-group--budgets .lh-table tbody tr td:nth-child(4){\n text-align: right;\n}\n\n.lh-audit-group--budgets .lh-details--budget {\n width: 100%;\n margin: 0 0 var(--default-padding);\n}\n\n.lh-audit-group--pwa-installable .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-installable-gray-url);\n}\n.lh-audit-group--pwa-optimized .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-optimized-gray-url);\n}\n.lh-audit-group--pwa-installable.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-installable-color-url);\n}\n.lh-audit-group--pwa-optimized.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-optimized-color-url);\n}\n\n.lh-audit-group--metrics .lh-audit-group__summary {\n margin-top: 0;\n margin-bottom: 0;\n}\n\n.lh-audit-group__summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.lh-audit-group__header .lh-chevron {\n margin-top: calc((var(--report-line-height) - 5px) / 2);\n}\n\n.lh-audit-group__header {\n letter-spacing: 0.8px;\n padding: var(--default-padding);\n padding-left: 0;\n}\n\n.lh-audit-group__header, .lh-audit-group__summary {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n.lh-audit-group__title {\n text-transform: uppercase;\n font-weight: 500;\n}\n\n.lh-audit-group__itemcount {\n color: var(--color-gray-600);\n}\n\n.lh-audit-group__footer {\n color: var(--color-gray-600);\n display: block;\n margin-top: var(--default-padding);\n}\n\n.lh-details,\n.lh-category-header__description,\n.lh-load-opportunity__header,\n.lh-audit-group__footer {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n}\n\n.lh-audit-explanation {\n margin: var(--audit-padding-vertical) 0 calc(var(--audit-padding-vertical) / 2) var(--audit-margin-horizontal);\n line-height: var(--audit-explanation-line-height);\n display: inline-block;\n}\n\n.lh-audit--fail .lh-audit-explanation {\n color: var(--color-fail-secondary);\n}\n\n/* Report */\n.lh-list > :not(:last-child) {\n margin-bottom: calc(var(--default-padding) * 2);\n}\n\n.lh-header-container {\n display: block;\n margin: 0 auto;\n position: relative;\n word-wrap: break-word;\n}\n\n.lh-header-container .lh-scores-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n\n\n.lh-report {\n min-width: var(--report-content-min-width);\n}\n\n.lh-exception {\n font-size: large;\n}\n\n.lh-code {\n white-space: normal;\n margin-top: 0;\n font-size: var(--report-monospace-font-size);\n}\n\n.lh-warnings {\n --item-margin: calc(var(--report-line-height) / 6);\n color: var(--color-average-secondary);\n margin: var(--audit-padding-vertical) 0;\n padding: var(--default-padding)\n var(--default-padding)\n var(--default-padding)\n calc(var(--audit-description-padding-left));\n background-color: var(--toplevel-warning-background-color);\n}\n.lh-warnings span {\n font-weight: bold;\n}\n\n.lh-warnings--toplevel {\n --item-margin: calc(var(--header-line-height) / 4);\n color: var(--toplevel-warning-text-color);\n margin-left: auto;\n margin-right: auto;\n max-width: var(--report-content-max-width-minus-edge-gap);\n padding: var(--toplevel-warning-padding);\n border-radius: 8px;\n}\n\n.lh-warnings__msg {\n color: var(--toplevel-warning-message-text-color);\n margin: 0;\n}\n\n.lh-warnings ul {\n margin: 0;\n}\n.lh-warnings li {\n margin: var(--item-margin) 0;\n}\n.lh-warnings li:last-of-type {\n margin-bottom: 0;\n}\n\n.lh-scores-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n}\n.lh-scores-header__solo {\n padding: 0;\n border: 0;\n}\n\n/* Gauge */\n\n.lh-gauge__wrapper--pass {\n color: var(--color-pass-secondary);\n fill: var(--color-pass);\n stroke: var(--color-pass);\n}\n\n.lh-gauge__wrapper--average {\n color: var(--color-average-secondary);\n fill: var(--color-average);\n stroke: var(--color-average);\n}\n\n.lh-gauge__wrapper--fail {\n color: var(--color-fail-secondary);\n fill: var(--color-fail);\n stroke: var(--color-fail);\n}\n\n.lh-gauge__wrapper--not-applicable {\n color: var(--color-not-applicable);\n fill: var(--color-not-applicable);\n stroke: var(--color-not-applicable);\n}\n\n.lh-fraction__wrapper .lh-fraction__content::before {\n content: '';\n height: var(--score-icon-size);\n width: var(--score-icon-size);\n margin: var(--score-icon-margin);\n display: inline-block;\n}\n.lh-fraction__wrapper--pass .lh-fraction__content {\n color: var(--color-pass-secondary);\n}\n.lh-fraction__wrapper--pass .lh-fraction__background {\n background-color: var(--color-pass);\n}\n.lh-fraction__wrapper--pass .lh-fraction__content::before {\n background-color: var(--color-pass);\n border-radius: 50%;\n}\n.lh-fraction__wrapper--average .lh-fraction__content {\n color: var(--color-average-secondary);\n}\n.lh-fraction__wrapper--average .lh-fraction__background,\n.lh-fraction__wrapper--average .lh-fraction__content::before {\n background-color: var(--color-average);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content {\n color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__background {\n background-color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n.lh-fraction__wrapper--null .lh-fraction__content {\n color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__background {\n background-color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__content::before {\n border-radius: 50%;\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-700);\n}\n\n.lh-fraction__background {\n position: absolute;\n height: 100%;\n width: 100%;\n border-radius: calc(var(--gauge-circle-size) / 2);\n opacity: 0.1;\n z-index: -1;\n}\n\n.lh-fraction__content-wrapper {\n height: var(--gauge-circle-size);\n display: flex;\n align-items: center;\n}\n\n.lh-fraction__content {\n display: flex;\n position: relative;\n align-items: center;\n justify-content: center;\n font-size: calc(0.3 * var(--gauge-circle-size));\n line-height: calc(0.4 * var(--gauge-circle-size));\n width: max-content;\n min-width: calc(1.5 * var(--gauge-circle-size));\n padding: calc(0.1 * var(--gauge-circle-size)) calc(0.2 * var(--gauge-circle-size));\n --score-icon-size: calc(0.21 * var(--gauge-circle-size));\n --score-icon-margin: 0 calc(0.15 * var(--gauge-circle-size)) 0 0;\n}\n\n.lh-gauge {\n stroke-linecap: round;\n width: var(--gauge-circle-size);\n height: var(--gauge-circle-size);\n}\n\n.lh-category .lh-gauge {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n.lh-gauge-base {\n opacity: 0.1;\n}\n\n.lh-gauge-arc {\n fill: none;\n transform-origin: 50% 50%;\n animation: load-gauge var(--transition-length) ease both;\n animation-delay: 250ms;\n}\n\n.lh-gauge__svg-wrapper {\n position: relative;\n height: var(--gauge-circle-size);\n}\n.lh-category .lh-gauge__svg-wrapper,\n.lh-category .lh-fraction__wrapper {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n/* The plugin badge overlay */\n.lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size);\n height: var(--plugin-badge-size);\n background-color: var(--plugin-badge-background-color);\n background-image: var(--plugin-icon-url);\n background-repeat: no-repeat;\n background-size: var(--plugin-icon-size);\n background-position: 58% 50%;\n content: \"\";\n position: absolute;\n right: -6px;\n bottom: 0px;\n display: block;\n z-index: 100;\n box-shadow: 0 0 4px rgba(0,0,0,.2);\n border-radius: 25%;\n}\n.lh-category .lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size-big);\n height: var(--plugin-badge-size-big);\n}\n\n@keyframes load-gauge {\n from { stroke-dasharray: 0 352; }\n}\n\n.lh-gauge__percentage {\n width: 100%;\n height: var(--gauge-circle-size);\n position: absolute;\n font-family: var(--report-font-family-monospace);\n font-size: calc(var(--gauge-circle-size) * 0.34 + 1.3px);\n line-height: 0;\n text-align: center;\n top: calc(var(--score-container-padding) + var(--gauge-circle-size) / 2);\n}\n\n.lh-category .lh-gauge__percentage {\n --gauge-circle-size: var(--gauge-circle-size-big);\n --gauge-percentage-font-size: var(--gauge-percentage-font-size-big);\n}\n\n.lh-gauge__wrapper,\n.lh-fraction__wrapper {\n position: relative;\n display: flex;\n align-items: center;\n flex-direction: column;\n text-decoration: none;\n padding: var(--score-container-padding);\n\n --transition-length: 1s;\n\n /* Contain the layout style paint & layers during animation*/\n contain: content;\n will-change: opacity; /* Only using for layer promotion */\n}\n\n.lh-gauge__label,\n.lh-fraction__label {\n font-size: var(--gauge-label-font-size);\n font-weight: 500;\n line-height: var(--gauge-label-line-height);\n margin-top: 10px;\n text-align: center;\n color: var(--report-text-color);\n word-break: keep-all;\n}\n\n/* TODO(#8185) use more BEM (.lh-gauge__label--big) instead of relying on descendant selector */\n.lh-category .lh-gauge__label,\n.lh-category .lh-fraction__label {\n --gauge-label-font-size: var(--gauge-label-font-size-big);\n --gauge-label-line-height: var(--gauge-label-line-height-big);\n margin-top: 14px;\n}\n\n.lh-scores-header .lh-gauge__wrapper,\n.lh-scores-header .lh-fraction__wrapper,\n.lh-scores-header .lh-gauge--pwa__wrapper,\n.lh-sticky-header .lh-gauge__wrapper,\n.lh-sticky-header .lh-fraction__wrapper,\n.lh-sticky-header .lh-gauge--pwa__wrapper {\n width: var(--gauge-wrapper-width);\n}\n\n.lh-scorescale {\n display: inline-flex;\n\n gap: calc(var(--default-padding) * 4);\n margin: 16px auto 0 auto;\n font-size: var(--report-font-size-secondary);\n color: var(--color-gray-700);\n\n}\n\n.lh-scorescale-range {\n display: flex;\n align-items: center;\n font-family: var(--report-font-family-monospace);\n white-space: nowrap;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale {\n border: 0;\n display: flex;\n justify-content: center;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale-range {\n font-family: unset;\n font-size: 12px;\n}\n\n.lh-scorescale-wrap {\n display: contents;\n}\n\n/* Hide category score gauages if it's a single category report */\n.lh-header--solo-category .lh-scores-wrapper {\n display: none;\n}\n\n\n.lh-categories {\n width: 100%;\n}\n\n.lh-category {\n padding: var(--category-padding);\n max-width: var(--report-content-max-width);\n margin: 0 auto;\n\n --sticky-header-height: calc(var(--gauge-circle-size-sm) + var(--score-container-padding) * 2);\n --topbar-plus-sticky-header: calc(var(--topbar-height) + var(--sticky-header-height));\n scroll-margin-top: var(--topbar-plus-sticky-header);\n\n /* Faster recalc style & layout of the report. https://web.dev/content-visibility/ */\n content-visibility: auto;\n contain-intrinsic-size: 1000px;\n}\n\n.lh-category-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n.lh-category-wrapper:last-of-type {\n border-bottom: 0;\n}\n\n.lh-category-header {\n margin-bottom: var(--section-padding-vertical);\n}\n\n.lh-category-header .lh-score__gauge {\n max-width: 400px;\n width: auto;\n margin: 0px auto;\n}\n\n.lh-category-header__finalscreenshot {\n display: grid;\n grid-template: none / 1fr 1px 1fr;\n justify-items: center;\n align-items: center;\n gap: var(--report-line-height);\n min-height: 288px;\n margin-bottom: var(--default-padding);\n}\n\n.lh-final-ss-image {\n /* constrain the size of the image to not be too large */\n max-height: calc(var(--gauge-circle-size-big) * 2.8);\n max-width: calc(var(--gauge-circle-size-big) * 3.5);\n border: 1px solid var(--color-gray-200);\n padding: 4px;\n border-radius: 3px;\n display: block;\n}\n\n.lh-category-headercol--separator {\n background: var(--color-gray-200);\n width: 1px;\n height: var(--gauge-circle-size-big);\n}\n\n@media screen and (max-width: 780px) {\n .lh-category-header__finalscreenshot {\n grid-template: 1fr 1fr / none\n }\n .lh-category-headercol--separator {\n display: none;\n }\n}\n\n\n/* 964 fits the min-width of the filmstrip */\n@media screen and (max-width: 964px) {\n .lh-report {\n margin-left: 0;\n width: 100%;\n }\n}\n\n@media print {\n body {\n -webkit-print-color-adjust: exact; /* print background colors */\n }\n .lh-container {\n display: block;\n }\n .lh-report {\n margin-left: 0;\n padding-top: 0;\n }\n .lh-categories {\n margin-top: 0;\n }\n}\n\n.lh-table {\n position: relative;\n border-collapse: separate;\n border-spacing: 0;\n /* Can't assign padding to table, so shorten the width instead. */\n width: calc(100% - var(--audit-description-padding-left) - var(--stackpack-padding-horizontal));\n border: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-table thead th {\n position: sticky;\n top: calc(var(--topbar-plus-sticky-header) + 1em);\n z-index: 1;\n background-color: var(--report-background-color);\n border-bottom: 1px solid var(--report-border-color-secondary);\n font-weight: normal;\n color: var(--color-gray-600);\n /* See text-wrapping comment on .lh-container. */\n word-break: normal;\n}\n\n.lh-row--even {\n background-color: var(--table-higlight-background-color);\n}\n.lh-row--hidden {\n display: none;\n}\n\n.lh-table th,\n.lh-table td {\n padding: var(--default-padding);\n}\n\n.lh-table tr {\n vertical-align: middle;\n}\n\n/* Looks unnecessary, but mostly for keeping the <th>s left-aligned */\n.lh-table-column--text,\n.lh-table-column--source-location,\n.lh-table-column--url,\n/* .lh-table-column--thumbnail, */\n/* .lh-table-column--empty,*/\n.lh-table-column--code,\n.lh-table-column--node {\n text-align: left;\n}\n\n.lh-table-column--code {\n min-width: 100px;\n}\n\n.lh-table-column--bytes,\n.lh-table-column--timespanMs,\n.lh-table-column--ms,\n.lh-table-column--numeric {\n text-align: right;\n word-break: normal;\n}\n\n\n\n.lh-table .lh-table-column--thumbnail {\n width: var(--image-preview-size);\n}\n\n.lh-table-column--url {\n min-width: 250px;\n}\n\n.lh-table-column--text {\n min-width: 80px;\n}\n\n/* Keep columns narrow if they follow the URL column */\n/* 12% was determined to be a decent narrow width, but wide enough for column headings */\n.lh-table-column--url + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--ms,\n.lh-table-column--url + .lh-table-column--ms + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--timespanMs {\n width: 12%;\n}\n\n.lh-text__url-host {\n display: inline;\n}\n\n.lh-text__url-host {\n margin-left: calc(var(--report-font-size) / 2);\n opacity: 0.6;\n font-size: 90%\n}\n\n.lh-thumbnail {\n object-fit: cover;\n width: var(--image-preview-size);\n height: var(--image-preview-size);\n display: block;\n}\n\n.lh-unknown pre {\n overflow: scroll;\n border: solid 1px var(--color-gray-200);\n}\n\n.lh-text__url > a {\n color: inherit;\n text-decoration: none;\n}\n\n.lh-text__url > a:hover {\n text-decoration: underline dotted #999;\n}\n\n.lh-sub-item-row {\n margin-left: 20px;\n margin-bottom: 0;\n color: var(--color-gray-700);\n}\n.lh-sub-item-row td {\n padding-top: 4px;\n padding-bottom: 4px;\n padding-left: 20px;\n}\n\n/* Chevron\n https://codepen.io/paulirish/pen/LmzEmK\n */\n.lh-chevron {\n --chevron-angle: 42deg;\n /* Edge doesn't support transform: rotate(calc(...)), so we define it here */\n --chevron-angle-right: -42deg;\n width: var(--chevron-size);\n height: var(--chevron-size);\n margin-top: calc((var(--report-line-height) - 12px) / 2);\n}\n\n.lh-chevron__lines {\n transition: transform 0.4s;\n transform: translateY(var(--report-line-height));\n}\n.lh-chevron__line {\n stroke: var(--chevron-line-stroke);\n stroke-width: var(--chevron-size);\n stroke-linecap: square;\n transform-origin: 50%;\n transform: rotate(var(--chevron-angle));\n transition: transform 300ms, stroke 300ms;\n}\n\n.lh-expandable-details .lh-chevron__line-right,\n.lh-expandable-details[open] .lh-chevron__line-left {\n transform: rotate(var(--chevron-angle-right));\n}\n\n.lh-expandable-details[open] .lh-chevron__line-right {\n transform: rotate(var(--chevron-angle));\n}\n\n\n.lh-expandable-details[open] .lh-chevron__lines {\n transform: translateY(calc(var(--chevron-size) * -1));\n}\n\n.lh-expandable-details[open] {\n animation: 300ms openDetails forwards;\n padding-bottom: var(--default-padding);\n}\n\n@keyframes openDetails {\n from {\n outline: 1px solid var(--report-background-color);\n }\n to {\n outline: 1px solid;\n box-shadow: 0 2px 4px rgba(0, 0, 0, .24);\n }\n}\n\n@media screen and (max-width: 780px) {\n /* no black outline if we're not confident the entire table can be displayed within bounds */\n .lh-expandable-details[open] {\n animation: none;\n }\n}\n\n.lh-expandable-details[open] summary, details.lh-clump > summary {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\ndetails.lh-clump[open] > summary {\n border-bottom-width: 0;\n}\n\n\n\ndetails .lh-clump-toggletext--hide,\ndetails[open] .lh-clump-toggletext--show { display: none; }\ndetails[open] .lh-clump-toggletext--hide { display: block;}\n\n\n/* Tooltip */\n.lh-tooltip-boundary {\n position: relative;\n}\n\n.lh-tooltip {\n position: absolute;\n display: none; /* Don't retain these layers when not needed */\n opacity: 0;\n background: #ffffff;\n white-space: pre-line; /* Render newlines in the text */\n min-width: 246px;\n max-width: 275px;\n padding: 15px;\n border-radius: 5px;\n text-align: initial;\n line-height: 1.4;\n}\n/* shrink tooltips to not be cutoff on left edge of narrow viewports\n 45vw is chosen to be ~= width of the left column of metrics\n*/\n@media screen and (max-width: 535px) {\n .lh-tooltip {\n min-width: 45vw;\n padding: 3vw;\n }\n}\n\n.lh-tooltip-boundary:hover .lh-tooltip {\n display: block;\n animation: fadeInTooltip 250ms;\n animation-fill-mode: forwards;\n animation-delay: 850ms;\n bottom: 100%;\n z-index: 1;\n will-change: opacity;\n right: 0;\n pointer-events: none;\n}\n\n.lh-tooltip::before {\n content: \"\";\n border: solid transparent;\n border-bottom-color: #fff;\n border-width: 10px;\n position: absolute;\n bottom: -20px;\n right: 6px;\n transform: rotate(180deg);\n pointer-events: none;\n}\n\n@keyframes fadeInTooltip {\n 0% { opacity: 0; }\n 75% { opacity: 1; }\n 100% { opacity: 1; filter: drop-shadow(1px 0px 1px #aaa) drop-shadow(0px 2px 4px hsla(206, 6%, 25%, 0.15)); pointer-events: auto; }\n}\n\n/* Element screenshot */\n.lh-element-screenshot {\n float: left;\n margin-right: 20px;\n}\n.lh-element-screenshot__content {\n overflow: hidden;\n min-width: 110px;\n display: flex;\n justify-content: center;\n background-color: var(--report-background-color);\n}\n.lh-element-screenshot__image {\n position: relative;\n /* Set by ElementScreenshotRenderer.installFullPageScreenshotCssVariable */\n background-image: var(--element-screenshot-url);\n outline: 2px solid #777;\n background-color: white;\n background-repeat: no-repeat;\n}\n.lh-element-screenshot__mask {\n position: absolute;\n background: #555;\n opacity: 0.8;\n}\n.lh-element-screenshot__element-marker {\n position: absolute;\n outline: 2px solid var(--color-lime-400);\n}\n.lh-element-screenshot__overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 2000; /* .lh-topbar is 1000 */\n background: var(--screenshot-overlay-background);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: zoom-out;\n}\n\n.lh-element-screenshot__overlay .lh-element-screenshot {\n margin-right: 0; /* clearing margin used in thumbnail case */\n outline: 1px solid var(--color-gray-700);\n}\n\n.lh-screenshot-overlay--enabled .lh-element-screenshot {\n cursor: zoom-out;\n}\n.lh-screenshot-overlay--enabled .lh-node .lh-element-screenshot {\n cursor: zoom-in;\n}\n\n\n.lh-meta__items {\n --meta-icon-size: calc(var(--report-icon-size) * 0.667);\n padding: var(--default-padding);\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n background-color: var(--env-item-background-color);\n border-radius: 3px;\n margin: 0 0 var(--default-padding) 0;\n font-size: 12px;\n column-gap: var(--default-padding);\n color: var(--color-gray-700);\n}\n\n.lh-meta__item {\n display: block;\n list-style-type: none;\n position: relative;\n padding: 0 0 0 calc(var(--meta-icon-size) + var(--default-padding) * 2);\n cursor: unset; /* disable pointer cursor from report-icon */\n}\n\n.lh-meta__item.lh-tooltip-boundary {\n text-decoration: dotted underline var(--color-gray-500);\n cursor: help;\n}\n\n.lh-meta__item.lh-report-icon::before {\n position: absolute;\n left: var(--default-padding);\n width: var(--meta-icon-size);\n height: var(--meta-icon-size);\n}\n\n.lh-meta__item.lh-report-icon:hover::before {\n opacity: 0.7;\n}\n\n.lh-meta__item .lh-tooltip {\n color: var(--color-gray-800);\n}\n\n.lh-meta__item .lh-tooltip::before {\n right: auto; /* Set the tooltip arrow to the leftside */\n left: 6px;\n}\n\n/* Change the grid for narrow viewport. */\n@media screen and (max-width: 640px) {\n .lh-meta__items {\n grid-template-columns: 1fr 1fr;\n }\n}\n@media screen and (max-width: 535px) {\n .lh-meta__items {\n display: block;\n }\n}\n\n\n/*# sourceURL=report-styles.css */\n");
|
|
852
|
+
el1.append("/**\n * @license\n * Copyright 2017 The Lighthouse Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS-IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n Naming convention:\n\n If a variable is used for a specific component: --{component}-{property name}-{modifier}\n\n Both {component} and {property name} should be kebab-case. If the target is the entire page,\n use 'report' for the component. The property name should not be abbreviated. Use the\n property name the variable is intended for - if it's used for multiple, a common descriptor\n is fine (ex: 'size' for a variable applied to 'width' and 'height'). If a variable is shared\n across multiple components, either create more variables or just drop the \"{component}-\"\n part of the name. Append any modifiers at the end (ex: 'big', 'dark').\n\n For colors: --color-{hue}-{intensity}\n\n {intensity} is the Material Design tag - 700, A700, etc.\n*/\n.lh-vars {\n /* Palette using Material Design Colors\n * https://www.materialui.co/colors */\n --color-amber-50: #FFF8E1;\n --color-blue-200: #90CAF9;\n --color-blue-900: #0D47A1;\n --color-blue-A700: #2962FF;\n --color-blue-primary: #06f;\n --color-cyan-500: #00BCD4;\n --color-gray-100: #F5F5F5;\n --color-gray-300: #CFCFCF;\n --color-gray-200: #E0E0E0;\n --color-gray-400: #BDBDBD;\n --color-gray-50: #FAFAFA;\n --color-gray-500: #9E9E9E;\n --color-gray-600: #757575;\n --color-gray-700: #616161;\n --color-gray-800: #424242;\n --color-gray-900: #212121;\n --color-gray: #000000;\n --color-green-700: #080;\n --color-green: #0c6;\n --color-lime-400: #D3E156;\n --color-orange-50: #FFF3E0;\n --color-orange-700: #C33300;\n --color-orange: #fa3;\n --color-red-700: #c00;\n --color-red: #f33;\n --color-teal-600: #00897B;\n --color-white: #FFFFFF;\n\n /* Context-specific colors */\n --color-average-secondary: var(--color-orange-700);\n --color-average: var(--color-orange);\n --color-fail-secondary: var(--color-red-700);\n --color-fail: var(--color-red);\n --color-hover: var(--color-gray-50);\n --color-informative: var(--color-blue-900);\n --color-pass-secondary: var(--color-green-700);\n --color-pass: var(--color-green);\n --color-not-applicable: var(--color-gray-600);\n\n /* Component variables */\n --audit-description-padding-left: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right));\n --audit-explanation-line-height: 16px;\n --audit-group-margin-bottom: calc(var(--default-padding) * 6);\n --audit-group-padding-vertical: 8px;\n --audit-margin-horizontal: 5px;\n --audit-padding-vertical: 8px;\n --category-padding: calc(var(--default-padding) * 6) var(--edge-gap-padding) calc(var(--default-padding) * 4);\n --chevron-line-stroke: var(--color-gray-600);\n --chevron-size: 12px;\n --default-padding: 8px;\n --edge-gap-padding: calc(var(--default-padding) * 4);\n --env-item-background-color: var(--color-gray-100);\n --env-item-font-size: 28px;\n --env-item-line-height: 36px;\n --env-item-padding: 10px 0px;\n --env-name-min-width: 220px;\n --footer-padding-vertical: 16px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 48px;\n --gauge-circle-size-sm: 32px;\n --gauge-label-font-size-big: 18px;\n --gauge-label-font-size: var(--report-font-size-secondary);\n --gauge-label-line-height-big: 24px;\n --gauge-label-line-height: var(--report-line-height-secondary);\n --gauge-percentage-font-size-big: 38px;\n --gauge-percentage-font-size: var(--report-font-size-secondary);\n --gauge-wrapper-width: 120px;\n --header-line-height: 24px;\n --highlighter-background-color: var(--report-text-color);\n --icon-square-size: calc(var(--score-icon-size) * 0.88);\n --image-preview-size: 48px;\n --link-color: var(--color-blue-primary);\n --locale-selector-background-color: var(--color-white);\n --metric-toggle-lines-fill: #7F7F7F;\n --metric-value-font-size: calc(var(--report-font-size) * 1.8);\n --metrics-toggle-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-white);\n --plugin-badge-size-big: calc(var(--gauge-circle-size-big) / 2.7);\n --plugin-badge-size: calc(var(--gauge-circle-size) / 2.7);\n --plugin-icon-size: 65%;\n --pwa-icon-margin: 0 var(--default-padding);\n --pwa-icon-size: var(--topbar-logo-size);\n --report-background-color: #fff;\n --report-border-color-secondary: #ebebeb;\n --report-font-family-monospace: 'Roboto Mono', 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: Roboto, Helvetica, Arial, sans-serif;\n --report-font-size: 14px;\n --report-font-size-secondary: 12px;\n --report-icon-size: var(--score-icon-background-size);\n --report-line-height: 24px;\n --report-line-height-secondary: 20px;\n --report-monospace-font-size: calc(var(--report-font-size) * 0.85);\n --report-text-color-secondary: var(--color-gray-800);\n --report-text-color: var(--color-gray-900);\n --report-content-max-width: calc(60 * var(--report-font-size)); /* defaults to 840px */\n --report-content-min-width: 360px;\n --report-content-max-width-minus-edge-gap: calc(var(--report-content-max-width) - var(--edge-gap-padding) * 2);\n --score-container-padding: 8px;\n --score-icon-background-size: 24px;\n --score-icon-margin-left: 6px;\n --score-icon-margin-right: 14px;\n --score-icon-margin: 0 var(--score-icon-margin-right) 0 var(--score-icon-margin-left);\n --score-icon-size: 12px;\n --score-icon-size-big: 16px;\n --screenshot-overlay-background: rgba(0, 0, 0, 0.3);\n --section-padding-vertical: calc(var(--default-padding) * 6);\n --snippet-background-color: var(--color-gray-50);\n --snippet-color: #0938C2;\n --sparkline-height: 5px;\n --stackpack-padding-horizontal: 10px;\n --sticky-header-background-color: var(--report-background-color);\n --table-higlight-background-color: hsla(210, 17%, 77%, 0.1);\n --tools-icon-color: var(--color-gray-600);\n --topbar-background-color: var(--color-white);\n --topbar-height: 32px;\n --topbar-logo-size: 24px;\n --topbar-padding: 0 8px;\n --toplevel-warning-background-color: hsla(30, 100%, 75%, 10%);\n --toplevel-warning-message-text-color: var(--color-average-secondary);\n --toplevel-warning-padding: 18px;\n --toplevel-warning-text-color: var(--report-text-color);\n\n /* SVGs */\n --plugin-icon-url-dark: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\" viewBox=\"0 0 24 24\" fill=\"%23FFFFFF\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z\"/></svg>');\n --plugin-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\" viewBox=\"0 0 24 24\" fill=\"%23757575\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z\"/></svg>');\n\n --pass-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\"><title>check</title><path fill=\"%23178239\" d=\"M24 4C12.95 4 4 12.95 4 24c0 11.04 8.95 20 20 20 11.04 0 20-8.96 20-20 0-11.05-8.96-20-20-20zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z\"/></svg>');\n --average-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\"><title>info</title><path fill=\"%23E67700\" d=\"M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z\"/></svg>');\n --fail-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\"><title>warn</title><path fill=\"%23C7221F\" d=\"M2 42h44L24 4 2 42zm24-6h-4v-4h4v4zm0-8h-4v-8h4v8z\"/></svg>');\n\n --pwa-installable-gray-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"nonzero\"><circle fill=\"%23DAE0E3\" cx=\"12\" cy=\"12\" r=\"12\"/><path d=\"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z\" fill=\"%23FFF\"/></g></svg>');\n --pwa-optimized-gray-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"evenodd\"><rect fill=\"%23DAE0E3\" width=\"24\" height=\"24\" rx=\"12\"/><path fill=\"%23FFF\" d=\"M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z\"/><path d=\"M5 5h14v14H5z\"/></g></svg>');\n\n --pwa-installable-gray-url-dark: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"nonzero\"><circle fill=\"%23424242\" cx=\"12\" cy=\"12\" r=\"12\"/><path d=\"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z\" fill=\"%23FFF\"/></g></svg>');\n --pwa-optimized-gray-url-dark: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"evenodd\"><rect fill=\"%23424242\" width=\"24\" height=\"24\" rx=\"12\"/><path fill=\"%23FFF\" d=\"M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z\"/><path d=\"M5 5h14v14H5z\"/></g></svg>');\n\n --pwa-installable-color-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"nonzero\" fill=\"none\"><circle fill=\"%230CCE6B\" cx=\"12\" cy=\"12\" r=\"12\"/><path d=\"M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z\" fill=\"%23FFF\"/></g></svg>');\n --pwa-optimized-color-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"evenodd\"><rect fill=\"%230CCE6B\" width=\"24\" height=\"24\" rx=\"12\"/><path d=\"M5 5h14v14H5z\"/><path fill=\"%23FFF\" d=\"M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z\"/></g></svg>');\n\n --swap-locale-icon-url: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#000000\"><path d=\"M0 0h24v24H0V0z\" fill=\"none\"/><path d=\"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z\"/></svg>');\n}\n\n@media not print {\n .lh-dark {\n /* Pallete */\n --color-gray-200: var(--color-gray-800);\n --color-gray-300: #616161;\n --color-gray-400: var(--color-gray-600);\n --color-gray-700: var(--color-gray-400);\n --color-gray-50: #757575;\n --color-gray-600: var(--color-gray-500);\n --color-green-700: var(--color-green);\n --color-orange-700: var(--color-orange);\n --color-red-700: var(--color-red);\n --color-teal-600: var(--color-cyan-500);\n\n /* Context-specific colors */\n --color-hover: rgba(0, 0, 0, 0.2);\n --color-informative: var(--color-blue-200);\n\n /* Component variables */\n --env-item-background-color: #393535;\n --link-color: var(--color-blue-200);\n --locale-selector-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-gray-800);\n --report-background-color: var(--color-gray-900);\n --report-border-color-secondary: var(--color-gray-200);\n --report-text-color-secondary: var(--color-gray-400);\n --report-text-color: var(--color-gray-100);\n --snippet-color: var(--color-cyan-500);\n --topbar-background-color: var(--color-gray);\n --toplevel-warning-background-color: hsl(33deg 14% 18%);\n --toplevel-warning-message-text-color: var(--color-orange-700);\n --toplevel-warning-text-color: var(--color-gray-100);\n\n /* SVGs */\n --plugin-icon-url: var(--plugin-icon-url-dark);\n --pwa-installable-gray-url: var(--pwa-installable-gray-url-dark);\n --pwa-optimized-gray-url: var(--pwa-optimized-gray-url-dark);\n }\n}\n\n@media only screen and (max-width: 480px) {\n .lh-vars {\n --audit-group-margin-bottom: 20px;\n --edge-gap-padding: var(--default-padding);\n --env-name-min-width: 120px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 72px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 112px;\n --header-padding: 16px 0 16px 0;\n --image-preview-size: 24px;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-size: 14px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --topbar-height: 28px;\n --topbar-logo-size: 20px;\n }\n\n /* Not enough space to adequately show the relative savings bars. */\n .lh-sparkline {\n display: none;\n }\n}\n\n.lh-vars.lh-devtools {\n --audit-explanation-line-height: 14px;\n --audit-group-margin-bottom: 20px;\n --audit-group-padding-vertical: 12px;\n --audit-padding-vertical: 4px;\n --category-padding: 12px;\n --default-padding: 12px;\n --env-name-min-width: 120px;\n --footer-padding-vertical: 8px;\n --gauge-circle-size-big: 72px;\n --gauge-circle-size: 64px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 97px;\n --header-line-height: 20px;\n --header-padding: 16px 0 16px 0;\n --screenshot-overlay-background: transparent;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-family-monospace: 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n --report-font-size: 12px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --section-padding-vertical: 8px;\n}\n\n.lh-devtools.lh-root {\n height: 100%;\n}\n.lh-devtools.lh-root img {\n /* Override devtools default 'min-width: 0' so svg without size in a flexbox isn't collapsed. */\n min-width: auto;\n}\n.lh-devtools .lh-container {\n overflow-y: scroll;\n height: calc(100% - var(--topbar-height));\n}\n@media print {\n .lh-devtools .lh-container {\n overflow: unset;\n }\n}\n.lh-devtools .lh-sticky-header {\n /* This is normally the height of the topbar, but we want it to stick to the top of our scroll container .lh-container` */\n top: 0;\n}\n.lh-devtools .lh-element-screenshot__overlay {\n position: absolute;\n}\n\n@keyframes fadeIn {\n 0% { opacity: 0;}\n 100% { opacity: 0.6;}\n}\n\n.lh-root *, .lh-root *::before, .lh-root *::after {\n box-sizing: border-box;\n}\n\n.lh-root {\n font-family: var(--report-font-family);\n font-size: var(--report-font-size);\n margin: 0;\n line-height: var(--report-line-height);\n background: var(--report-background-color);\n color: var(--report-text-color);\n}\n\n.lh-root :focus-visible {\n outline: -webkit-focus-ring-color auto 3px;\n}\n.lh-root summary:focus {\n outline: none;\n box-shadow: 0 0 0 1px hsl(217, 89%, 61%);\n}\n\n.lh-root [hidden] {\n display: none !important;\n}\n\n.lh-root pre {\n margin: 0;\n}\n\n.lh-root details > summary {\n cursor: pointer;\n}\n\n.lh-hidden {\n display: none !important;\n}\n\n.lh-container {\n /*\n Text wrapping in the report is so much FUN!\n We have a `word-break: break-word;` globally here to prevent a few common scenarios, namely\n long non-breakable text (usually URLs) found in:\n 1. The footer\n 2. .lh-node (outerHTML)\n 3. .lh-code\n\n With that sorted, the next challenge is appropriate column sizing and text wrapping inside our\n .lh-details tables. Even more fun.\n * We don't want table headers (\"Potential Savings (ms)\") to wrap or their column values, but\n we'd be happy for the URL column to wrap if the URLs are particularly long.\n * We want the narrow columns to remain narrow, providing the most column width for URL\n * We don't want the table to extend past 100% width.\n * Long URLs in the URL column can wrap. Util.getURLDisplayName maxes them out at 64 characters,\n but they do not get any overflow:ellipsis treatment.\n */\n word-break: break-word;\n}\n\n.lh-audit-group a,\n.lh-category-header__description a,\n.lh-audit__description a,\n.lh-warnings a,\n.lh-footer a,\n.lh-table-column--link a {\n color: var(--link-color);\n}\n\n.lh-audit__description, .lh-audit__stackpack {\n --inner-audit-padding-right: var(--stackpack-padding-horizontal);\n padding-left: var(--audit-description-padding-left);\n padding-right: var(--inner-audit-padding-right);\n padding-top: 8px;\n padding-bottom: 8px;\n}\n\n.lh-details {\n margin-top: var(--default-padding);\n margin-bottom: var(--default-padding);\n margin-left: var(--audit-description-padding-left);\n /* whatever the .lh-details side margins are */\n width: 100%;\n}\n\n.lh-audit__stackpack {\n display: flex;\n align-items: center;\n}\n\n.lh-audit__stackpack__img {\n max-width: 30px;\n margin-right: var(--default-padding)\n}\n\n/* Report header */\n\n.lh-report-icon {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n cursor: pointer;\n}\n.lh-report-icon[disabled] {\n opacity: 0.3;\n pointer-events: none;\n}\n\n.lh-report-icon::before {\n content: \"\";\n margin: 4px;\n background-repeat: no-repeat;\n width: var(--report-icon-size);\n height: var(--report-icon-size);\n opacity: 0.7;\n display: inline-block;\n vertical-align: middle;\n}\n.lh-report-icon:hover::before {\n opacity: 1;\n}\n.lh-dark .lh-report-icon::before {\n filter: invert(1);\n}\n.lh-report-icon--print::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z\"/><path fill=\"none\" d=\"M0 0h24v24H0z\"/></svg>');\n}\n.lh-report-icon--copy::before {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\"/></svg>');\n}\n.lh-report-icon--open::before {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z\"/></svg>');\n}\n.lh-report-icon--download::before {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>');\n}\n.lh-report-icon--dark::before {\n background-image:url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 100 125\"><path d=\"M50 23.587c-16.27 0-22.799 12.574-22.799 21.417 0 12.917 10.117 22.451 12.436 32.471h20.726c2.32-10.02 12.436-19.554 12.436-32.471 0-8.843-6.528-21.417-22.799-21.417zM39.637 87.161c0 3.001 1.18 4.181 4.181 4.181h.426l.41 1.231C45.278 94.449 46.042 95 48.019 95h3.963c1.978 0 2.74-.551 3.365-2.427l.409-1.231h.427c3.002 0 4.18-1.18 4.18-4.181V80.91H39.637v6.251zM50 18.265c1.26 0 2.072-.814 2.072-2.073v-9.12C52.072 5.813 51.26 5 50 5c-1.259 0-2.072.813-2.072 2.073v9.12c0 1.259.813 2.072 2.072 2.072zM68.313 23.727c.994.774 2.135.634 2.91-.357l5.614-7.187c.776-.992.636-2.135-.356-2.909-.992-.776-2.135-.636-2.91.357l-5.613 7.186c-.778.993-.636 2.135.355 2.91zM91.157 36.373c-.306-1.222-1.291-1.815-2.513-1.51l-8.85 2.207c-1.222.305-1.814 1.29-1.51 2.512.305 1.223 1.291 1.814 2.513 1.51l8.849-2.206c1.223-.305 1.816-1.291 1.511-2.513zM86.757 60.48l-8.331-3.709c-1.15-.512-2.225-.099-2.736 1.052-.512 1.151-.1 2.224 1.051 2.737l8.33 3.707c1.15.514 2.225.101 2.736-1.05.513-1.149.1-2.223-1.05-2.737zM28.779 23.37c.775.992 1.917 1.131 2.909.357.992-.776 1.132-1.917.357-2.91l-5.615-7.186c-.775-.992-1.917-1.132-2.909-.357s-1.131 1.917-.356 2.909l5.614 7.187zM21.715 39.583c.305-1.223-.288-2.208-1.51-2.513l-8.849-2.207c-1.222-.303-2.208.289-2.513 1.511-.303 1.222.288 2.207 1.511 2.512l8.848 2.206c1.222.304 2.208-.287 2.513-1.509zM21.575 56.771l-8.331 3.711c-1.151.511-1.563 1.586-1.05 2.735.511 1.151 1.586 1.563 2.736 1.052l8.331-3.711c1.151-.511 1.563-1.586 1.05-2.735-.512-1.15-1.585-1.562-2.736-1.052z\"/></svg>');\n}\n.lh-report-icon--treemap::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"black\"><path d=\"M3 5v14h19V5H3zm2 2h15v4H5V7zm0 10v-4h4v4H5zm6 0v-4h9v4h-9z\"/></svg>');\n}\n.lh-report-icon--date::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 11h2v2H7v-2zm14-5v14a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h1V2h2v2h8V2h2v2h1a2 2 0 012 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z\"/></svg>');\n}\n.lh-report-icon--devices::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M4 6h18V4H4a2 2 0 00-2 2v11H0v3h14v-3H4V6zm19 2h-6a1 1 0 00-1 1v10c0 .6.5 1 1 1h6c.6 0 1-.5 1-1V9c0-.6-.5-1-1-1zm-1 9h-4v-7h4v7z\"/></svg>');\n}\n.lh-report-icon--world::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm7 6h-3c-.3-1.3-.8-2.5-1.4-3.6A8 8 0 0 1 18.9 8zm-7-4a14 14 0 0 1 2 4h-4a14 14 0 0 1 2-4zM4.3 14a8.2 8.2 0 0 1 0-4h3.3a16.5 16.5 0 0 0 0 4H4.3zm.8 2h3a14 14 0 0 0 1.3 3.6A8 8 0 0 1 5.1 16zm3-8H5a8 8 0 0 1 4.3-3.6L8 8zM12 20a14 14 0 0 1-2-4h4a14 14 0 0 1-2 4zm2.3-6H9.7a14.7 14.7 0 0 1 0-4h4.6a14.6 14.6 0 0 1 0 4zm.3 5.6c.6-1.2 1-2.4 1.4-3.6h3a8 8 0 0 1-4.4 3.6zm1.8-5.6a16.5 16.5 0 0 0 0-4h3.3a8.2 8.2 0 0 1 0 4h-3.3z\"/></svg>');\n}\n.lh-report-icon--stopwatch::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.1-6.6L20.5 6l-1.4-1.4L17.7 6A9 9 0 0 0 3 13a9 9 0 1 0 16-5.6zm-7 12.6a7 7 0 1 1 0-14 7 7 0 0 1 0 14z\"/></svg>');\n}\n.lh-report-icon--networkspeed::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M15.9 5c-.2 0-.3 0-.4.2v.2L10.1 17a2 2 0 0 0-.2 1 2 2 0 0 0 4 .4l2.4-12.9c0-.3-.2-.5-.5-.5zM1 9l2 2c2.9-2.9 6.8-4 10.5-3.6l1.2-2.7C10 3.8 4.7 5.3 1 9zm20 2 2-2a15.4 15.4 0 0 0-5.6-3.6L17 8.2c1.5.7 2.9 1.6 4.1 2.8zm-4 4 2-2a9.9 9.9 0 0 0-2.7-1.9l-.5 3 1.2.9zM5 13l2 2a7.1 7.1 0 0 1 4-2l1.3-2.9C9.7 10.1 7 11 5 13z\"/></svg>');\n}\n.lh-report-icon--samples-one::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><circle cx=\"7\" cy=\"14\" r=\"3\"/><path d=\"M7 18a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm4-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm5.6 17.6a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/></svg>');\n}\n.lh-report-icon--samples-many::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 18a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm4-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm5.6 17.6a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/><circle cx=\"7\" cy=\"14\" r=\"3\"/><circle cx=\"11\" cy=\"6\" r=\"3\"/></svg>');\n}\n.lh-report-icon--chrome::before {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-50 -50 562 562\"><path d=\"M256 25.6v25.6a204 204 0 0 1 144.8 60 204 204 0 0 1 60 144.8 204 204 0 0 1-60 144.8 204 204 0 0 1-144.8 60 204 204 0 0 1-144.8-60 204 204 0 0 1-60-144.8 204 204 0 0 1 60-144.8 204 204 0 0 1 144.8-60V0a256 256 0 1 0 0 512 256 256 0 0 0 0-512v25.6z\"/><path d=\"M256 179.2v25.6a51.3 51.3 0 0 1 0 102.4 51.3 51.3 0 0 1 0-102.4v-51.2a102.3 102.3 0 1 0-.1 204.7 102.3 102.3 0 0 0 .1-204.7v25.6z\"/><path d=\"M256 204.8h217.6a25.6 25.6 0 0 0 0-51.2H256a25.6 25.6 0 0 0 0 51.2m44.3 76.8L191.5 470.1a25.6 25.6 0 1 0 44.4 25.6l108.8-188.5a25.6 25.6 0 1 0-44.4-25.6m-88.6 0L102.9 93.2a25.7 25.7 0 0 0-35-9.4 25.7 25.7 0 0 0-9.4 35l108.8 188.5a25.7 25.7 0 0 0 35 9.4 25.9 25.9 0 0 0 9.4-35.1\"/></svg>');\n}\n\n\n\n.lh-buttons {\n display: flex;\n flex-wrap: wrap;\n margin: var(--default-padding) 0;\n}\n.lh-button {\n height: 32px;\n border: 1px solid var(--report-border-color-secondary);\n border-radius: 3px;\n color: var(--link-color);\n background-color: var(--report-background-color);\n margin: 5px;\n}\n\n.lh-button:first-of-type {\n margin-left: 0;\n}\n\n/* Node */\n.lh-node__snippet {\n font-family: var(--report-font-family-monospace);\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n line-height: 20px;\n}\n\n/* Score */\n\n.lh-audit__score-icon {\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n margin: var(--score-icon-margin);\n}\n\n.lh-audit--pass .lh-audit__display-text {\n color: var(--color-pass-secondary);\n}\n.lh-audit--pass .lh-audit__score-icon,\n.lh-scorescale-range--pass::before {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-audit__display-text {\n color: var(--color-average-secondary);\n}\n.lh-audit--average .lh-audit__score-icon,\n.lh-scorescale-range--average::before {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-audit--fail .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n.lh-audit--fail .lh-audit__score-icon,\n.lh-audit--error .lh-audit__score-icon,\n.lh-scorescale-range--fail::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-audit--manual .lh-audit__display-text,\n.lh-audit--notapplicable .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n.lh-audit--manual .lh-audit__score-icon,\n.lh-audit--notapplicable .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n background: none;\n}\n\n.lh-audit--informative .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n\n.lh-audit--informative .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n}\n\n.lh-audit__description,\n.lh-audit__stackpack {\n color: var(--report-text-color-secondary);\n}\n.lh-audit__adorn {\n border: 1px solid slategray;\n border-radius: 3px;\n margin: 0 3px;\n padding: 0 2px;\n line-height: 1.1;\n display: inline-block;\n font-size: 90%;\n}\n\n.lh-category-header__description {\n text-align: center;\n color: var(--color-gray-700);\n margin: 0px auto;\n max-width: 400px;\n}\n\n\n.lh-audit__display-text,\n.lh-load-opportunity__sparkline,\n.lh-chevron-container {\n margin: 0 var(--audit-margin-horizontal);\n}\n.lh-chevron-container {\n margin-right: 0;\n}\n\n.lh-audit__title-and-text {\n flex: 1;\n}\n\n.lh-audit__title-and-text code {\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n}\n\n/* Prepend display text with em dash separator. But not in Opportunities. */\n.lh-audit__display-text:not(:empty):before {\n content: '—';\n margin-right: var(--audit-margin-horizontal);\n}\n.lh-audit-group.lh-audit-group--load-opportunities .lh-audit__display-text:not(:empty):before {\n display: none;\n}\n\n/* Expandable Details (Audit Groups, Audits) */\n.lh-audit__header {\n display: flex;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-audit--load-opportunity .lh-audit__header {\n display: block;\n}\n\n\n.lh-metricfilter {\n display: grid;\n justify-content: end;\n align-items: center;\n grid-auto-flow: column;\n gap: 4px;\n color: var(--color-gray-700);\n}\n\n.lh-metricfilter__radio {\n position: absolute;\n left: -9999px;\n}\n.lh-metricfilter input[type='radio']:focus-visible + label {\n outline: -webkit-focus-ring-color auto 1px;\n}\n\n.lh-metricfilter__label {\n display: inline-flex;\n padding: 0 4px;\n height: 16px;\n text-decoration: underline;\n align-items: center;\n cursor: pointer;\n font-size: 90%;\n}\n\n.lh-metricfilter__label--active {\n background: var(--color-blue-primary);\n color: var(--color-white);\n border-radius: 3px;\n text-decoration: none;\n}\n/* Give the 'All' choice a more muted display */\n.lh-metricfilter__label--active[for=\"metric-All\"] {\n background-color: var(--color-blue-200) !important;\n color: black !important;\n}\n\n.lh-metricfilter__text {\n margin-right: 8px;\n}\n\n/* If audits are filtered, hide the itemcount for Passed Audits… */\n.lh-category--filtered .lh-audit-group .lh-audit-group__itemcount {\n display: none;\n}\n\n\n.lh-audit__header:hover {\n background-color: var(--color-hover);\n}\n\n/* We want to hide the browser's default arrow marker on summary elements. Admittedly, it's complicated. */\n.lh-root details > summary {\n /* Blink 89+ and Firefox will hide the arrow when display is changed from (new) default of `list-item` to block. https://chromestatus.com/feature/6730096436051968*/\n display: block;\n}\n/* Safari and Blink <=88 require using the -webkit-details-marker selector */\n.lh-root details > summary::-webkit-details-marker {\n display: none;\n}\n\n/* Perf Metric */\n\n.lh-metrics-container {\n display: grid;\n grid-auto-rows: 1fr;\n grid-template-columns: 1fr 1fr;\n grid-column-gap: var(--report-line-height);\n margin-bottom: var(--default-padding);\n}\n\n.lh-metric {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(-n+2) {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-metric__innerwrap {\n display: grid;\n /**\n * Icon -- Metric Name\n * -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 1fr;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-metric__details {\n order: -1;\n}\n\n.lh-metric__title {\n flex: 1;\n}\n\n.lh-calclink {\n padding-left: calc(1ex / 3);\n}\n\n.lh-metric__description {\n display: none;\n grid-column-start: 2;\n grid-column-end: 4;\n color: var(--report-text-color-secondary);\n}\n\n.lh-metric__value {\n font-size: var(--metric-value-font-size);\n margin: calc(var(--default-padding) / 2) 0;\n white-space: nowrap; /* No wrapping between metric value and the icon */\n grid-column-start: 2;\n}\n\n\n@media screen and (max-width: 535px) {\n .lh-metrics-container {\n display: block;\n }\n\n .lh-metric {\n border-bottom: none !important;\n }\n .lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(1) {\n border-bottom: 1px solid var(--report-border-color-secondary) !important;\n }\n\n /* Change the grid to 3 columns for narrow viewport. */\n .lh-metric__innerwrap {\n /**\n * Icon -- Metric Name -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 2fr 1fr;\n }\n .lh-metric__value {\n justify-self: end;\n grid-column-start: unset;\n }\n}\n\n/* No-JS toggle switch */\n/* Keep this selector sync'd w/ `magicSelector` in report-ui-features-test.js */\n .lh-metrics-toggle__input:checked ~ .lh-metrics-container .lh-metric__description {\n display: block;\n}\n\n/* TODO get rid of the SVGS and clean up these some more */\n.lh-metrics-toggle__input {\n opacity: 0;\n position: absolute;\n right: 0;\n top: 0px;\n}\n\n.lh-metrics-toggle__input + div > label > .lh-metrics-toggle__labeltext--hide,\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--show {\n display: none;\n}\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--hide {\n display: inline;\n}\n.lh-metrics-toggle__input:focus + div > label {\n outline: -webkit-focus-ring-color auto 3px;\n}\n\n.lh-metrics-toggle__label {\n cursor: pointer;\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n/* Pushes the metric description toggle button to the right. */\n.lh-audit-group--metrics .lh-audit-group__header {\n display: flex;\n justify-content: space-between;\n}\n\n.lh-metric__icon,\n.lh-scorescale-range::before {\n content: '';\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n display: inline-block;\n margin: var(--score-icon-margin);\n}\n\n.lh-metric--pass .lh-metric__value {\n color: var(--color-pass-secondary);\n}\n.lh-metric--pass .lh-metric__icon {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-metric--average .lh-metric__value {\n color: var(--color-average-secondary);\n}\n.lh-metric--average .lh-metric__icon {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-metric--fail .lh-metric__value {\n color: var(--color-fail-secondary);\n}\n.lh-metric--fail .lh-metric__icon,\n.lh-metric--error .lh-metric__icon {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-metric--error .lh-metric__value,\n.lh-metric--error .lh-metric__description {\n color: var(--color-fail-secondary);\n}\n\n/* Perf load opportunity */\n\n.lh-load-opportunity__cols {\n display: flex;\n align-items: flex-start;\n}\n\n.lh-load-opportunity__header .lh-load-opportunity__col {\n color: var(--color-gray-600);\n display: unset;\n line-height: calc(2.3 * var(--report-font-size));\n}\n\n.lh-load-opportunity__col {\n display: flex;\n}\n\n.lh-load-opportunity__col--one {\n flex: 5;\n align-items: center;\n margin-right: 2px;\n}\n.lh-load-opportunity__col--two {\n flex: 4;\n text-align: right;\n}\n\n.lh-audit--load-opportunity .lh-audit__display-text {\n text-align: right;\n flex: 0 0 calc(4 * var(--report-font-size));\n}\n\n\n/* Sparkline */\n\n.lh-load-opportunity__sparkline {\n flex: 1;\n margin-top: calc((var(--report-line-height) - var(--sparkline-height)) / 2);\n}\n\n.lh-sparkline {\n height: var(--sparkline-height);\n width: 100%;\n}\n\n.lh-sparkline__bar {\n height: 100%;\n float: right;\n}\n\n.lh-audit--pass .lh-sparkline__bar {\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-sparkline__bar {\n background: var(--color-average);\n}\n\n.lh-audit--fail .lh-sparkline__bar {\n background: var(--color-fail);\n}\n\n/* Filmstrip */\n\n.lh-filmstrip-container {\n /* smaller gap between metrics and filmstrip */\n margin: -8px auto 0 auto;\n}\n\n.lh-filmstrip {\n display: grid;\n justify-content: space-between;\n padding-bottom: var(--default-padding);\n width: 100%;\n grid-template-columns: repeat(auto-fit, 90px);\n}\n\n.lh-filmstrip__frame {\n text-align: right;\n position: relative;\n}\n\n.lh-filmstrip__thumbnail {\n border: 1px solid var(--report-border-color-secondary);\n max-height: 150px;\n max-width: 120px;\n}\n\n/* Audit */\n\n.lh-audit {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n/* Apply border-top to just the first audit. */\n.lh-audit {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n.lh-audit ~ .lh-audit {\n border-top: none;\n}\n\n\n.lh-audit--error .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n\n/* Audit Group */\n\n.lh-audit-group {\n margin-bottom: var(--audit-group-margin-bottom);\n position: relative;\n}\n.lh-audit-group--metrics {\n margin-bottom: calc(var(--audit-group-margin-bottom) / 2);\n}\n\n.lh-audit-group__header::before {\n /* By default, groups don't get an icon */\n content: none;\n width: var(--pwa-icon-size);\n height: var(--pwa-icon-size);\n margin: var(--pwa-icon-margin);\n display: inline-block;\n vertical-align: middle;\n}\n\n/* Style the \"over budget\" columns red. */\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(4),\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(5),\n.lh-audit-group--budgets #timing-budget tbody tr td:nth-child(3) {\n color: var(--color-red-700);\n}\n\n/* Align the \"over budget request count\" text to be close to the \"over budget bytes\" column. */\n.lh-audit-group--budgets .lh-table tbody tr td:nth-child(4){\n text-align: right;\n}\n\n.lh-audit-group--budgets .lh-details--budget {\n width: 100%;\n margin: 0 0 var(--default-padding);\n}\n\n.lh-audit-group--pwa-installable .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-installable-gray-url);\n}\n.lh-audit-group--pwa-optimized .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-optimized-gray-url);\n}\n.lh-audit-group--pwa-installable.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-installable-color-url);\n}\n.lh-audit-group--pwa-optimized.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-optimized-color-url);\n}\n\n.lh-audit-group--metrics .lh-audit-group__summary {\n margin-top: 0;\n margin-bottom: 0;\n}\n\n.lh-audit-group__summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.lh-audit-group__header .lh-chevron {\n margin-top: calc((var(--report-line-height) - 5px) / 2);\n}\n\n.lh-audit-group__header {\n letter-spacing: 0.8px;\n padding: var(--default-padding);\n padding-left: 0;\n}\n\n.lh-audit-group__header, .lh-audit-group__summary {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n.lh-audit-group__title {\n text-transform: uppercase;\n font-weight: 500;\n}\n\n.lh-audit-group__itemcount {\n color: var(--color-gray-600);\n}\n\n.lh-audit-group__footer {\n color: var(--color-gray-600);\n display: block;\n margin-top: var(--default-padding);\n}\n\n.lh-details,\n.lh-category-header__description,\n.lh-load-opportunity__header,\n.lh-audit-group__footer {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n}\n\n.lh-audit-explanation {\n margin: var(--audit-padding-vertical) 0 calc(var(--audit-padding-vertical) / 2) var(--audit-margin-horizontal);\n line-height: var(--audit-explanation-line-height);\n display: inline-block;\n}\n\n.lh-audit--fail .lh-audit-explanation {\n color: var(--color-fail-secondary);\n}\n\n/* Report */\n.lh-list > :not(:last-child) {\n margin-bottom: calc(var(--default-padding) * 2);\n}\n\n.lh-header-container {\n display: block;\n margin: 0 auto;\n position: relative;\n word-wrap: break-word;\n}\n\n.lh-header-container .lh-scores-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n\n\n.lh-report {\n min-width: var(--report-content-min-width);\n}\n\n.lh-exception {\n font-size: large;\n}\n\n.lh-code {\n white-space: normal;\n margin-top: 0;\n font-size: var(--report-monospace-font-size);\n}\n\n.lh-warnings {\n --item-margin: calc(var(--report-line-height) / 6);\n color: var(--color-average-secondary);\n margin: var(--audit-padding-vertical) 0;\n padding: var(--default-padding)\n var(--default-padding)\n var(--default-padding)\n calc(var(--audit-description-padding-left));\n background-color: var(--toplevel-warning-background-color);\n}\n.lh-warnings span {\n font-weight: bold;\n}\n\n.lh-warnings--toplevel {\n --item-margin: calc(var(--header-line-height) / 4);\n color: var(--toplevel-warning-text-color);\n margin-left: auto;\n margin-right: auto;\n max-width: var(--report-content-max-width-minus-edge-gap);\n padding: var(--toplevel-warning-padding);\n border-radius: 8px;\n}\n\n.lh-warnings__msg {\n color: var(--toplevel-warning-message-text-color);\n margin: 0;\n}\n\n.lh-warnings ul {\n margin: 0;\n}\n.lh-warnings li {\n margin: var(--item-margin) 0;\n}\n.lh-warnings li:last-of-type {\n margin-bottom: 0;\n}\n\n.lh-scores-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n}\n.lh-scores-header__solo {\n padding: 0;\n border: 0;\n}\n\n/* Gauge */\n\n.lh-gauge__wrapper--pass {\n color: var(--color-pass-secondary);\n fill: var(--color-pass);\n stroke: var(--color-pass);\n}\n\n.lh-gauge__wrapper--average {\n color: var(--color-average-secondary);\n fill: var(--color-average);\n stroke: var(--color-average);\n}\n\n.lh-gauge__wrapper--fail {\n color: var(--color-fail-secondary);\n fill: var(--color-fail);\n stroke: var(--color-fail);\n}\n\n.lh-gauge__wrapper--not-applicable {\n color: var(--color-not-applicable);\n fill: var(--color-not-applicable);\n stroke: var(--color-not-applicable);\n}\n\n.lh-fraction__wrapper .lh-fraction__content::before {\n content: '';\n height: var(--score-icon-size);\n width: var(--score-icon-size);\n margin: var(--score-icon-margin);\n display: inline-block;\n}\n.lh-fraction__wrapper--pass .lh-fraction__content {\n color: var(--color-pass-secondary);\n}\n.lh-fraction__wrapper--pass .lh-fraction__background {\n background-color: var(--color-pass);\n}\n.lh-fraction__wrapper--pass .lh-fraction__content::before {\n background-color: var(--color-pass);\n border-radius: 50%;\n}\n.lh-fraction__wrapper--average .lh-fraction__content {\n color: var(--color-average-secondary);\n}\n.lh-fraction__wrapper--average .lh-fraction__background,\n.lh-fraction__wrapper--average .lh-fraction__content::before {\n background-color: var(--color-average);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content {\n color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__background {\n background-color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n.lh-fraction__wrapper--null .lh-fraction__content {\n color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__background {\n background-color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__content::before {\n border-radius: 50%;\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-700);\n}\n\n.lh-fraction__background {\n position: absolute;\n height: 100%;\n width: 100%;\n border-radius: calc(var(--gauge-circle-size) / 2);\n opacity: 0.1;\n z-index: -1;\n}\n\n.lh-fraction__content-wrapper {\n height: var(--gauge-circle-size);\n display: flex;\n align-items: center;\n}\n\n.lh-fraction__content {\n display: flex;\n position: relative;\n align-items: center;\n justify-content: center;\n font-size: calc(0.3 * var(--gauge-circle-size));\n line-height: calc(0.4 * var(--gauge-circle-size));\n width: max-content;\n min-width: calc(1.5 * var(--gauge-circle-size));\n padding: calc(0.1 * var(--gauge-circle-size)) calc(0.2 * var(--gauge-circle-size));\n --score-icon-size: calc(0.21 * var(--gauge-circle-size));\n --score-icon-margin: 0 calc(0.15 * var(--gauge-circle-size)) 0 0;\n}\n\n.lh-gauge {\n stroke-linecap: round;\n width: var(--gauge-circle-size);\n height: var(--gauge-circle-size);\n}\n\n.lh-category .lh-gauge {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n.lh-gauge-base {\n opacity: 0.1;\n}\n\n.lh-gauge-arc {\n fill: none;\n transform-origin: 50% 50%;\n animation: load-gauge var(--transition-length) ease both;\n animation-delay: 250ms;\n}\n\n.lh-gauge__svg-wrapper {\n position: relative;\n height: var(--gauge-circle-size);\n}\n.lh-category .lh-gauge__svg-wrapper,\n.lh-category .lh-fraction__wrapper {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n/* The plugin badge overlay */\n.lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size);\n height: var(--plugin-badge-size);\n background-color: var(--plugin-badge-background-color);\n background-image: var(--plugin-icon-url);\n background-repeat: no-repeat;\n background-size: var(--plugin-icon-size);\n background-position: 58% 50%;\n content: \"\";\n position: absolute;\n right: -6px;\n bottom: 0px;\n display: block;\n z-index: 100;\n box-shadow: 0 0 4px rgba(0,0,0,.2);\n border-radius: 25%;\n}\n.lh-category .lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size-big);\n height: var(--plugin-badge-size-big);\n}\n\n@keyframes load-gauge {\n from { stroke-dasharray: 0 352; }\n}\n\n.lh-gauge__percentage {\n width: 100%;\n height: var(--gauge-circle-size);\n position: absolute;\n font-family: var(--report-font-family-monospace);\n font-size: calc(var(--gauge-circle-size) * 0.34 + 1.3px);\n line-height: 0;\n text-align: center;\n top: calc(var(--score-container-padding) + var(--gauge-circle-size) / 2);\n}\n\n.lh-category .lh-gauge__percentage {\n --gauge-circle-size: var(--gauge-circle-size-big);\n --gauge-percentage-font-size: var(--gauge-percentage-font-size-big);\n}\n\n.lh-gauge__wrapper,\n.lh-fraction__wrapper {\n position: relative;\n display: flex;\n align-items: center;\n flex-direction: column;\n text-decoration: none;\n padding: var(--score-container-padding);\n\n --transition-length: 1s;\n\n /* Contain the layout style paint & layers during animation*/\n contain: content;\n will-change: opacity; /* Only using for layer promotion */\n}\n\n.lh-gauge__label,\n.lh-fraction__label {\n font-size: var(--gauge-label-font-size);\n font-weight: 500;\n line-height: var(--gauge-label-line-height);\n margin-top: 10px;\n text-align: center;\n color: var(--report-text-color);\n word-break: keep-all;\n}\n\n/* TODO(#8185) use more BEM (.lh-gauge__label--big) instead of relying on descendant selector */\n.lh-category .lh-gauge__label,\n.lh-category .lh-fraction__label {\n --gauge-label-font-size: var(--gauge-label-font-size-big);\n --gauge-label-line-height: var(--gauge-label-line-height-big);\n margin-top: 14px;\n}\n\n.lh-scores-header .lh-gauge__wrapper,\n.lh-scores-header .lh-fraction__wrapper,\n.lh-scores-header .lh-gauge--pwa__wrapper,\n.lh-sticky-header .lh-gauge__wrapper,\n.lh-sticky-header .lh-fraction__wrapper,\n.lh-sticky-header .lh-gauge--pwa__wrapper {\n width: var(--gauge-wrapper-width);\n}\n\n.lh-scorescale {\n display: inline-flex;\n\n gap: calc(var(--default-padding) * 4);\n margin: 16px auto 0 auto;\n font-size: var(--report-font-size-secondary);\n color: var(--color-gray-700);\n\n}\n\n.lh-scorescale-range {\n display: flex;\n align-items: center;\n font-family: var(--report-font-family-monospace);\n white-space: nowrap;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale {\n border: 0;\n display: flex;\n justify-content: center;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale-range {\n font-family: unset;\n font-size: 12px;\n}\n\n.lh-scorescale-wrap {\n display: contents;\n}\n\n/* Hide category score gauages if it's a single category report */\n.lh-header--solo-category .lh-scores-wrapper {\n display: none;\n}\n\n\n.lh-categories {\n width: 100%;\n}\n\n.lh-category {\n padding: var(--category-padding);\n max-width: var(--report-content-max-width);\n margin: 0 auto;\n\n --sticky-header-height: calc(var(--gauge-circle-size-sm) + var(--score-container-padding) * 2);\n --topbar-plus-sticky-header: calc(var(--topbar-height) + var(--sticky-header-height));\n scroll-margin-top: var(--topbar-plus-sticky-header);\n\n /* Faster recalc style & layout of the report. https://web.dev/content-visibility/ */\n content-visibility: auto;\n contain-intrinsic-size: 1000px;\n}\n\n.lh-category-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n.lh-category-wrapper:last-of-type {\n border-bottom: 0;\n}\n\n.lh-category-header {\n margin-bottom: var(--section-padding-vertical);\n}\n\n.lh-category-header .lh-score__gauge {\n max-width: 400px;\n width: auto;\n margin: 0px auto;\n}\n\n.lh-category-header__finalscreenshot {\n display: grid;\n grid-template: none / 1fr 1px 1fr;\n justify-items: center;\n align-items: center;\n gap: var(--report-line-height);\n min-height: 288px;\n margin-bottom: var(--default-padding);\n}\n\n.lh-final-ss-image {\n /* constrain the size of the image to not be too large */\n max-height: calc(var(--gauge-circle-size-big) * 2.8);\n max-width: calc(var(--gauge-circle-size-big) * 3.5);\n border: 1px solid var(--color-gray-200);\n padding: 4px;\n border-radius: 3px;\n display: block;\n}\n\n.lh-category-headercol--separator {\n background: var(--color-gray-200);\n width: 1px;\n height: var(--gauge-circle-size-big);\n}\n\n@media screen and (max-width: 780px) {\n .lh-category-header__finalscreenshot {\n grid-template: 1fr 1fr / none\n }\n .lh-category-headercol--separator {\n display: none;\n }\n}\n\n\n/* 964 fits the min-width of the filmstrip */\n@media screen and (max-width: 964px) {\n .lh-report {\n margin-left: 0;\n width: 100%;\n }\n}\n\n@media print {\n body {\n -webkit-print-color-adjust: exact; /* print background colors */\n }\n .lh-container {\n display: block;\n }\n .lh-report {\n margin-left: 0;\n padding-top: 0;\n }\n .lh-categories {\n margin-top: 0;\n }\n}\n\n.lh-table {\n position: relative;\n border-collapse: separate;\n border-spacing: 0;\n /* Can't assign padding to table, so shorten the width instead. */\n width: calc(100% - var(--audit-description-padding-left) - var(--stackpack-padding-horizontal));\n border: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-table thead th {\n position: sticky;\n top: calc(var(--topbar-plus-sticky-header) + 1em);\n z-index: 1;\n background-color: var(--report-background-color);\n border-bottom: 1px solid var(--report-border-color-secondary);\n font-weight: normal;\n color: var(--color-gray-600);\n /* See text-wrapping comment on .lh-container. */\n word-break: normal;\n}\n\n.lh-row--even {\n background-color: var(--table-higlight-background-color);\n}\n.lh-row--hidden {\n display: none;\n}\n\n.lh-table th,\n.lh-table td {\n padding: var(--default-padding);\n}\n\n.lh-table tr {\n vertical-align: middle;\n}\n\n/* Looks unnecessary, but mostly for keeping the <th>s left-aligned */\n.lh-table-column--text,\n.lh-table-column--source-location,\n.lh-table-column--url,\n/* .lh-table-column--thumbnail, */\n/* .lh-table-column--empty,*/\n.lh-table-column--code,\n.lh-table-column--node {\n text-align: left;\n}\n\n.lh-table-column--code {\n min-width: 100px;\n}\n\n.lh-table-column--bytes,\n.lh-table-column--timespanMs,\n.lh-table-column--ms,\n.lh-table-column--numeric {\n text-align: right;\n word-break: normal;\n}\n\n\n\n.lh-table .lh-table-column--thumbnail {\n width: var(--image-preview-size);\n}\n\n.lh-table-column--url {\n min-width: 250px;\n}\n\n.lh-table-column--text {\n min-width: 80px;\n}\n\n/* Keep columns narrow if they follow the URL column */\n/* 12% was determined to be a decent narrow width, but wide enough for column headings */\n.lh-table-column--url + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--ms,\n.lh-table-column--url + .lh-table-column--ms + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--timespanMs {\n width: 12%;\n}\n\n.lh-text__url-host {\n display: inline;\n}\n\n.lh-text__url-host {\n margin-left: calc(var(--report-font-size) / 2);\n opacity: 0.6;\n font-size: 90%\n}\n\n.lh-thumbnail {\n object-fit: cover;\n width: var(--image-preview-size);\n height: var(--image-preview-size);\n display: block;\n}\n\n.lh-unknown pre {\n overflow: scroll;\n border: solid 1px var(--color-gray-200);\n}\n\n.lh-text__url > a {\n color: inherit;\n text-decoration: none;\n}\n\n.lh-text__url > a:hover {\n text-decoration: underline dotted #999;\n}\n\n.lh-sub-item-row {\n margin-left: 20px;\n margin-bottom: 0;\n color: var(--color-gray-700);\n}\n.lh-sub-item-row td {\n padding-top: 4px;\n padding-bottom: 4px;\n padding-left: 20px;\n}\n\n/* Chevron\n https://codepen.io/paulirish/pen/LmzEmK\n */\n.lh-chevron {\n --chevron-angle: 42deg;\n /* Edge doesn't support transform: rotate(calc(...)), so we define it here */\n --chevron-angle-right: -42deg;\n width: var(--chevron-size);\n height: var(--chevron-size);\n margin-top: calc((var(--report-line-height) - 12px) / 2);\n}\n\n.lh-chevron__lines {\n transition: transform 0.4s;\n transform: translateY(var(--report-line-height));\n}\n.lh-chevron__line {\n stroke: var(--chevron-line-stroke);\n stroke-width: var(--chevron-size);\n stroke-linecap: square;\n transform-origin: 50%;\n transform: rotate(var(--chevron-angle));\n transition: transform 300ms, stroke 300ms;\n}\n\n.lh-expandable-details .lh-chevron__line-right,\n.lh-expandable-details[open] .lh-chevron__line-left {\n transform: rotate(var(--chevron-angle-right));\n}\n\n.lh-expandable-details[open] .lh-chevron__line-right {\n transform: rotate(var(--chevron-angle));\n}\n\n\n.lh-expandable-details[open] .lh-chevron__lines {\n transform: translateY(calc(var(--chevron-size) * -1));\n}\n\n.lh-expandable-details[open] {\n animation: 300ms openDetails forwards;\n padding-bottom: var(--default-padding);\n}\n\n@keyframes openDetails {\n from {\n outline: 1px solid var(--report-background-color);\n }\n to {\n outline: 1px solid;\n box-shadow: 0 2px 4px rgba(0, 0, 0, .24);\n }\n}\n\n@media screen and (max-width: 780px) {\n /* no black outline if we're not confident the entire table can be displayed within bounds */\n .lh-expandable-details[open] {\n animation: none;\n }\n}\n\n.lh-expandable-details[open] summary, details.lh-clump > summary {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\ndetails.lh-clump[open] > summary {\n border-bottom-width: 0;\n}\n\n\n\ndetails .lh-clump-toggletext--hide,\ndetails[open] .lh-clump-toggletext--show { display: none; }\ndetails[open] .lh-clump-toggletext--hide { display: block;}\n\n\n/* Tooltip */\n.lh-tooltip-boundary {\n position: relative;\n}\n\n.lh-tooltip {\n position: absolute;\n display: none; /* Don't retain these layers when not needed */\n opacity: 0;\n background: #ffffff;\n white-space: pre-line; /* Render newlines in the text */\n min-width: 246px;\n max-width: 275px;\n padding: 15px;\n border-radius: 5px;\n text-align: initial;\n line-height: 1.4;\n}\n/* shrink tooltips to not be cutoff on left edge of narrow viewports\n 45vw is chosen to be ~= width of the left column of metrics\n*/\n@media screen and (max-width: 535px) {\n .lh-tooltip {\n min-width: 45vw;\n padding: 3vw;\n }\n}\n\n.lh-tooltip-boundary:hover .lh-tooltip {\n display: block;\n animation: fadeInTooltip 250ms;\n animation-fill-mode: forwards;\n animation-delay: 850ms;\n bottom: 100%;\n z-index: 1;\n will-change: opacity;\n right: 0;\n pointer-events: none;\n}\n\n.lh-tooltip::before {\n content: \"\";\n border: solid transparent;\n border-bottom-color: #fff;\n border-width: 10px;\n position: absolute;\n bottom: -20px;\n right: 6px;\n transform: rotate(180deg);\n pointer-events: none;\n}\n\n@keyframes fadeInTooltip {\n 0% { opacity: 0; }\n 75% { opacity: 1; }\n 100% { opacity: 1; filter: drop-shadow(1px 0px 1px #aaa) drop-shadow(0px 2px 4px hsla(206, 6%, 25%, 0.15)); pointer-events: auto; }\n}\n\n/* Element screenshot */\n.lh-element-screenshot {\n float: left;\n margin-right: 20px;\n}\n.lh-element-screenshot__content {\n overflow: hidden;\n min-width: 110px;\n display: flex;\n justify-content: center;\n background-color: var(--report-background-color);\n}\n.lh-element-screenshot__image {\n position: relative;\n /* Set by ElementScreenshotRenderer.installFullPageScreenshotCssVariable */\n background-image: var(--element-screenshot-url);\n outline: 2px solid #777;\n background-color: white;\n background-repeat: no-repeat;\n}\n.lh-element-screenshot__mask {\n position: absolute;\n background: #555;\n opacity: 0.8;\n}\n.lh-element-screenshot__element-marker {\n position: absolute;\n outline: 2px solid var(--color-lime-400);\n}\n.lh-element-screenshot__overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 2000; /* .lh-topbar is 1000 */\n background: var(--screenshot-overlay-background);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: zoom-out;\n}\n\n.lh-element-screenshot__overlay .lh-element-screenshot {\n margin-right: 0; /* clearing margin used in thumbnail case */\n outline: 1px solid var(--color-gray-700);\n}\n\n.lh-screenshot-overlay--enabled .lh-element-screenshot {\n cursor: zoom-out;\n}\n.lh-screenshot-overlay--enabled .lh-node .lh-element-screenshot {\n cursor: zoom-in;\n}\n\n\n.lh-meta__items {\n --meta-icon-size: calc(var(--report-icon-size) * 0.667);\n padding: var(--default-padding);\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n background-color: var(--env-item-background-color);\n border-radius: 3px;\n margin: 0 0 var(--default-padding) 0;\n font-size: 12px;\n column-gap: var(--default-padding);\n color: var(--color-gray-700);\n}\n\n.lh-meta__item {\n display: block;\n list-style-type: none;\n position: relative;\n padding: 0 0 0 calc(var(--meta-icon-size) + var(--default-padding) * 2);\n cursor: unset; /* disable pointer cursor from report-icon */\n}\n\n.lh-meta__item.lh-tooltip-boundary {\n text-decoration: dotted underline var(--color-gray-500);\n cursor: help;\n}\n\n.lh-meta__item.lh-report-icon::before {\n position: absolute;\n left: var(--default-padding);\n width: var(--meta-icon-size);\n height: var(--meta-icon-size);\n}\n\n.lh-meta__item.lh-report-icon:hover::before {\n opacity: 0.7;\n}\n\n.lh-meta__item .lh-tooltip {\n color: var(--color-gray-800);\n}\n\n.lh-meta__item .lh-tooltip::before {\n right: auto; /* Set the tooltip arrow to the leftside */\n left: 6px;\n}\n\n/* Change the grid for narrow viewport. */\n@media screen and (max-width: 640px) {\n .lh-meta__items {\n grid-template-columns: 1fr 1fr;\n }\n}\n@media screen and (max-width: 535px) {\n .lh-meta__items {\n display: block;\n }\n}\n\n\n/*# sourceURL=report-styles.css */\n");
|
|
1328
853
|
el0.append(el1);
|
|
1329
854
|
return el0;
|
|
1330
855
|
}
|
|
@@ -1339,209 +864,126 @@ function createTopbarComponent(dom) {
|
|
|
1339
864
|
el0.append(el1);
|
|
1340
865
|
const el2 = dom.createElement("div", "lh-topbar");
|
|
1341
866
|
const el3 = dom.createElementNS("http://www.w3.org/2000/svg", "svg", "lh-topbar__logo");
|
|
1342
|
-
el3.setAttribute('
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
el6.
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
el7.
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
el8.
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
el16.setAttribute('offset', '100%');
|
|
1403
|
-
el14.append(" ",el15," ",el16," ");
|
|
1404
|
-
el4.append(" ",el5," ",el8," ",el11," ",el14," ");
|
|
1405
|
-
const el17 = dom.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
867
|
+
el3.setAttribute('role', 'img');
|
|
868
|
+
el3.setAttribute('title', 'Lighthouse logo');
|
|
869
|
+
el3.setAttribute('fill', 'none');
|
|
870
|
+
el3.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
871
|
+
el3.setAttribute('viewBox', '0 0 48 48');
|
|
872
|
+
const el4 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
873
|
+
el4.setAttribute('d', 'm14 7 10-7 10 7v10h5v7h-5l5 24H9l5-24H9v-7h5V7Z');
|
|
874
|
+
el4.setAttribute('fill', '#F63');
|
|
875
|
+
const el5 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
876
|
+
el5.setAttribute('d', 'M31.561 24H14l-1.689 8.105L31.561 24ZM18.983 48H9l1.022-4.907L35.723 32.27l1.663 7.98L18.983 48Z');
|
|
877
|
+
el5.setAttribute('fill', '#FFA385');
|
|
878
|
+
const el6 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
879
|
+
el6.setAttribute('fill', '#FF3');
|
|
880
|
+
el6.setAttribute('d', 'M20.5 10h7v7h-7z');
|
|
881
|
+
el3.append(" ",el4," ",el5," ",el6," ");
|
|
882
|
+
const el7 = dom.createElement("a", "lh-topbar__url");
|
|
883
|
+
el7.setAttribute('href', '');
|
|
884
|
+
el7.setAttribute('target', '_blank');
|
|
885
|
+
el7.setAttribute('rel', 'noopener');
|
|
886
|
+
const el8 = dom.createElement("div", "lh-tools");
|
|
887
|
+
const el9 = dom.createElement("div", "lh-tools-locale lh-hidden");
|
|
888
|
+
const el10 = dom.createElement("button", "lh-button lh-tool-locale__button");
|
|
889
|
+
el10.setAttribute('id', 'lh-button__swap-locales');
|
|
890
|
+
el10.setAttribute('title', 'Show Language Picker');
|
|
891
|
+
el10.setAttribute('aria-label', 'Toggle language picker');
|
|
892
|
+
el10.setAttribute('aria-haspopup', 'menu');
|
|
893
|
+
el10.setAttribute('aria-expanded', 'false');
|
|
894
|
+
el10.setAttribute('aria-controls', 'lh-tools-locale__selector-wrapper');
|
|
895
|
+
const el11 = dom.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
896
|
+
el11.setAttribute('width', '20px');
|
|
897
|
+
el11.setAttribute('height', '20px');
|
|
898
|
+
el11.setAttribute('viewBox', '0 0 24 24');
|
|
899
|
+
el11.setAttribute('fill', 'currentColor');
|
|
900
|
+
const el12 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
901
|
+
el12.setAttribute('d', 'M0 0h24v24H0V0z');
|
|
902
|
+
el12.setAttribute('fill', 'none');
|
|
903
|
+
const el13 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
904
|
+
el13.setAttribute('d', 'M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z');
|
|
905
|
+
el11.append(el12,el13);
|
|
906
|
+
el10.append(" ",el11," ");
|
|
907
|
+
const el14 = dom.createElement("div", "lh-tools-locale__selector-wrapper");
|
|
908
|
+
el14.setAttribute('id', 'lh-tools-locale__selector-wrapper');
|
|
909
|
+
el14.setAttribute('role', 'menu');
|
|
910
|
+
el14.setAttribute('aria-labelledby', 'lh-button__swap-locales');
|
|
911
|
+
el14.setAttribute('aria-hidden', 'true');
|
|
912
|
+
el14.append(" "," ");
|
|
913
|
+
el9.append(" ",el10," ",el14," ");
|
|
914
|
+
const el15 = dom.createElement("button", "lh-tools__button");
|
|
915
|
+
el15.setAttribute('id', 'lh-tools-button');
|
|
916
|
+
el15.setAttribute('title', 'Tools menu');
|
|
917
|
+
el15.setAttribute('aria-label', 'Toggle report tools menu');
|
|
918
|
+
el15.setAttribute('aria-haspopup', 'menu');
|
|
919
|
+
el15.setAttribute('aria-expanded', 'false');
|
|
920
|
+
el15.setAttribute('aria-controls', 'lh-tools-dropdown');
|
|
921
|
+
const el16 = dom.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
922
|
+
el16.setAttribute('width', '100%');
|
|
923
|
+
el16.setAttribute('height', '100%');
|
|
924
|
+
el16.setAttribute('viewBox', '0 0 24 24');
|
|
925
|
+
const el17 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
926
|
+
el17.setAttribute('d', 'M0 0h24v24H0z');
|
|
1406
927
|
el17.setAttribute('fill', 'none');
|
|
1407
|
-
el17.setAttribute('fill-rule', 'evenodd');
|
|
1408
928
|
const el18 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1409
|
-
el18.setAttribute('d', 'M12
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
el19.
|
|
1413
|
-
el19.setAttribute('
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
el20.
|
|
1417
|
-
el20.setAttribute('
|
|
1418
|
-
el20.setAttribute('
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
el21.
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
el32.setAttribute('role', 'menu');
|
|
1468
|
-
el32.setAttribute('aria-labelledby', 'lh-button__swap-locales');
|
|
1469
|
-
el32.setAttribute('aria-hidden', 'true');
|
|
1470
|
-
el32.append(" "," ");
|
|
1471
|
-
el27.append(" ",el28," ",el32," ");
|
|
1472
|
-
const el33 = dom.createElement("button", "lh-tools__button");
|
|
1473
|
-
el33.setAttribute('id', 'lh-tools-button');
|
|
1474
|
-
el33.setAttribute('title', 'Tools menu');
|
|
1475
|
-
el33.setAttribute('aria-label', 'Toggle report tools menu');
|
|
1476
|
-
el33.setAttribute('aria-haspopup', 'menu');
|
|
1477
|
-
el33.setAttribute('aria-expanded', 'false');
|
|
1478
|
-
el33.setAttribute('aria-controls', 'lh-tools-dropdown');
|
|
1479
|
-
const el34 = dom.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1480
|
-
el34.setAttribute('width', '100%');
|
|
1481
|
-
el34.setAttribute('height', '100%');
|
|
1482
|
-
el34.setAttribute('viewBox', '0 0 24 24');
|
|
1483
|
-
const el35 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1484
|
-
el35.setAttribute('d', 'M0 0h24v24H0z');
|
|
1485
|
-
el35.setAttribute('fill', 'none');
|
|
1486
|
-
const el36 = dom.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1487
|
-
el36.setAttribute('d', 'M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z');
|
|
1488
|
-
el34.append(" ",el35," ",el36," ");
|
|
1489
|
-
el33.append(" ",el34," ");
|
|
1490
|
-
const el37 = dom.createElement("div", "lh-tools__dropdown");
|
|
1491
|
-
el37.setAttribute('id', 'lh-tools-dropdown');
|
|
1492
|
-
el37.setAttribute('role', 'menu');
|
|
1493
|
-
el37.setAttribute('aria-labelledby', 'lh-tools-button');
|
|
1494
|
-
const el38 = dom.createElement("a", "lh-report-icon lh-report-icon--print");
|
|
1495
|
-
el38.setAttribute('role', 'menuitem');
|
|
1496
|
-
el38.setAttribute('tabindex', '-1');
|
|
1497
|
-
el38.setAttribute('href', '#');
|
|
1498
|
-
el38.setAttribute('data-i18n', 'dropdownPrintSummary');
|
|
1499
|
-
el38.setAttribute('data-action', 'print-summary');
|
|
1500
|
-
const el39 = dom.createElement("a", "lh-report-icon lh-report-icon--print");
|
|
1501
|
-
el39.setAttribute('role', 'menuitem');
|
|
1502
|
-
el39.setAttribute('tabindex', '-1');
|
|
1503
|
-
el39.setAttribute('href', '#');
|
|
1504
|
-
el39.setAttribute('data-i18n', 'dropdownPrintExpanded');
|
|
1505
|
-
el39.setAttribute('data-action', 'print-expanded');
|
|
1506
|
-
const el40 = dom.createElement("a", "lh-report-icon lh-report-icon--copy");
|
|
1507
|
-
el40.setAttribute('role', 'menuitem');
|
|
1508
|
-
el40.setAttribute('tabindex', '-1');
|
|
1509
|
-
el40.setAttribute('href', '#');
|
|
1510
|
-
el40.setAttribute('data-i18n', 'dropdownCopyJSON');
|
|
1511
|
-
el40.setAttribute('data-action', 'copy');
|
|
1512
|
-
const el41 = dom.createElement("a", "lh-report-icon lh-report-icon--download lh-hidden");
|
|
1513
|
-
el41.setAttribute('role', 'menuitem');
|
|
1514
|
-
el41.setAttribute('tabindex', '-1');
|
|
1515
|
-
el41.setAttribute('href', '#');
|
|
1516
|
-
el41.setAttribute('data-i18n', 'dropdownSaveHTML');
|
|
1517
|
-
el41.setAttribute('data-action', 'save-html');
|
|
1518
|
-
const el42 = dom.createElement("a", "lh-report-icon lh-report-icon--download");
|
|
1519
|
-
el42.setAttribute('role', 'menuitem');
|
|
1520
|
-
el42.setAttribute('tabindex', '-1');
|
|
1521
|
-
el42.setAttribute('href', '#');
|
|
1522
|
-
el42.setAttribute('data-i18n', 'dropdownSaveJSON');
|
|
1523
|
-
el42.setAttribute('data-action', 'save-json');
|
|
1524
|
-
const el43 = dom.createElement("a", "lh-report-icon lh-report-icon--open");
|
|
1525
|
-
el43.setAttribute('role', 'menuitem');
|
|
1526
|
-
el43.setAttribute('tabindex', '-1');
|
|
1527
|
-
el43.setAttribute('href', '#');
|
|
1528
|
-
el43.setAttribute('data-i18n', 'dropdownViewer');
|
|
1529
|
-
el43.setAttribute('data-action', 'open-viewer');
|
|
1530
|
-
const el44 = dom.createElement("a", "lh-report-icon lh-report-icon--open");
|
|
1531
|
-
el44.setAttribute('role', 'menuitem');
|
|
1532
|
-
el44.setAttribute('tabindex', '-1');
|
|
1533
|
-
el44.setAttribute('href', '#');
|
|
1534
|
-
el44.setAttribute('data-i18n', 'dropdownSaveGist');
|
|
1535
|
-
el44.setAttribute('data-action', 'save-gist');
|
|
1536
|
-
const el45 = dom.createElement("a", "lh-report-icon lh-report-icon--dark");
|
|
1537
|
-
el45.setAttribute('role', 'menuitem');
|
|
1538
|
-
el45.setAttribute('tabindex', '-1');
|
|
1539
|
-
el45.setAttribute('href', '#');
|
|
1540
|
-
el45.setAttribute('data-i18n', 'dropdownDarkTheme');
|
|
1541
|
-
el45.setAttribute('data-action', 'toggle-dark');
|
|
1542
|
-
el37.append(" ",el38," ",el39," ",el40," "," ",el41," ",el42," ",el43," ",el44," ",el45," ");
|
|
1543
|
-
el26.append(" ",el27," ",el33," ",el37," ");
|
|
1544
|
-
el2.append(" "," ",el3," ",el25," ",el26," ");
|
|
929
|
+
el18.setAttribute('d', 'M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z');
|
|
930
|
+
el16.append(" ",el17," ",el18," ");
|
|
931
|
+
el15.append(" ",el16," ");
|
|
932
|
+
const el19 = dom.createElement("div", "lh-tools__dropdown");
|
|
933
|
+
el19.setAttribute('id', 'lh-tools-dropdown');
|
|
934
|
+
el19.setAttribute('role', 'menu');
|
|
935
|
+
el19.setAttribute('aria-labelledby', 'lh-tools-button');
|
|
936
|
+
const el20 = dom.createElement("a", "lh-report-icon lh-report-icon--print");
|
|
937
|
+
el20.setAttribute('role', 'menuitem');
|
|
938
|
+
el20.setAttribute('tabindex', '-1');
|
|
939
|
+
el20.setAttribute('href', '#');
|
|
940
|
+
el20.setAttribute('data-i18n', 'dropdownPrintSummary');
|
|
941
|
+
el20.setAttribute('data-action', 'print-summary');
|
|
942
|
+
const el21 = dom.createElement("a", "lh-report-icon lh-report-icon--print");
|
|
943
|
+
el21.setAttribute('role', 'menuitem');
|
|
944
|
+
el21.setAttribute('tabindex', '-1');
|
|
945
|
+
el21.setAttribute('href', '#');
|
|
946
|
+
el21.setAttribute('data-i18n', 'dropdownPrintExpanded');
|
|
947
|
+
el21.setAttribute('data-action', 'print-expanded');
|
|
948
|
+
const el22 = dom.createElement("a", "lh-report-icon lh-report-icon--copy");
|
|
949
|
+
el22.setAttribute('role', 'menuitem');
|
|
950
|
+
el22.setAttribute('tabindex', '-1');
|
|
951
|
+
el22.setAttribute('href', '#');
|
|
952
|
+
el22.setAttribute('data-i18n', 'dropdownCopyJSON');
|
|
953
|
+
el22.setAttribute('data-action', 'copy');
|
|
954
|
+
const el23 = dom.createElement("a", "lh-report-icon lh-report-icon--download lh-hidden");
|
|
955
|
+
el23.setAttribute('role', 'menuitem');
|
|
956
|
+
el23.setAttribute('tabindex', '-1');
|
|
957
|
+
el23.setAttribute('href', '#');
|
|
958
|
+
el23.setAttribute('data-i18n', 'dropdownSaveHTML');
|
|
959
|
+
el23.setAttribute('data-action', 'save-html');
|
|
960
|
+
const el24 = dom.createElement("a", "lh-report-icon lh-report-icon--download");
|
|
961
|
+
el24.setAttribute('role', 'menuitem');
|
|
962
|
+
el24.setAttribute('tabindex', '-1');
|
|
963
|
+
el24.setAttribute('href', '#');
|
|
964
|
+
el24.setAttribute('data-i18n', 'dropdownSaveJSON');
|
|
965
|
+
el24.setAttribute('data-action', 'save-json');
|
|
966
|
+
const el25 = dom.createElement("a", "lh-report-icon lh-report-icon--open");
|
|
967
|
+
el25.setAttribute('role', 'menuitem');
|
|
968
|
+
el25.setAttribute('tabindex', '-1');
|
|
969
|
+
el25.setAttribute('href', '#');
|
|
970
|
+
el25.setAttribute('data-i18n', 'dropdownViewer');
|
|
971
|
+
el25.setAttribute('data-action', 'open-viewer');
|
|
972
|
+
const el26 = dom.createElement("a", "lh-report-icon lh-report-icon--open");
|
|
973
|
+
el26.setAttribute('role', 'menuitem');
|
|
974
|
+
el26.setAttribute('tabindex', '-1');
|
|
975
|
+
el26.setAttribute('href', '#');
|
|
976
|
+
el26.setAttribute('data-i18n', 'dropdownSaveGist');
|
|
977
|
+
el26.setAttribute('data-action', 'save-gist');
|
|
978
|
+
const el27 = dom.createElement("a", "lh-report-icon lh-report-icon--dark");
|
|
979
|
+
el27.setAttribute('role', 'menuitem');
|
|
980
|
+
el27.setAttribute('tabindex', '-1');
|
|
981
|
+
el27.setAttribute('href', '#');
|
|
982
|
+
el27.setAttribute('data-i18n', 'dropdownDarkTheme');
|
|
983
|
+
el27.setAttribute('data-action', 'toggle-dark');
|
|
984
|
+
el19.append(" ",el20," ",el21," ",el22," "," ",el23," ",el24," ",el25," ",el26," ",el27," ");
|
|
985
|
+
el8.append(" ",el9," ",el15," ",el19," ");
|
|
986
|
+
el2.append(" "," ",el3," ",el7," ",el8," ");
|
|
1545
987
|
el0.append(el2);
|
|
1546
988
|
return el0;
|
|
1547
989
|
}
|
|
@@ -1779,125 +1221,655 @@ class DOM {
|
|
|
1779
1221
|
if (parsed && allowedProtocols.includes(parsed.protocol)) {
|
|
1780
1222
|
elem.href = parsed.href;
|
|
1781
1223
|
}
|
|
1782
|
-
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Only create blob URLs for JSON & HTML
|
|
1228
|
+
* @param {HTMLAnchorElement} elem
|
|
1229
|
+
* @param {Blob} blob
|
|
1230
|
+
*/
|
|
1231
|
+
safelySetBlobHref(elem, blob) {
|
|
1232
|
+
if (blob.type !== 'text/html' && blob.type !== 'application/json') {
|
|
1233
|
+
throw new Error('Unsupported blob type');
|
|
1234
|
+
}
|
|
1235
|
+
const href = URL.createObjectURL(blob);
|
|
1236
|
+
elem.href = href;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* @param {string} markdownText
|
|
1241
|
+
* @return {Element}
|
|
1242
|
+
*/
|
|
1243
|
+
convertMarkdownCodeSnippets(markdownText) {
|
|
1244
|
+
const element = this.createElement('span');
|
|
1245
|
+
|
|
1246
|
+
for (const segment of Util.splitMarkdownCodeSpans(markdownText)) {
|
|
1247
|
+
if (segment.isCode) {
|
|
1248
|
+
const pre = this.createElement('code');
|
|
1249
|
+
pre.textContent = segment.text;
|
|
1250
|
+
element.append(pre);
|
|
1251
|
+
} else {
|
|
1252
|
+
element.append(this._document.createTextNode(segment.text));
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
return element;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* The channel to use for UTM data when rendering links to the documentation.
|
|
1261
|
+
* @param {string} lighthouseChannel
|
|
1262
|
+
*/
|
|
1263
|
+
setLighthouseChannel(lighthouseChannel) {
|
|
1264
|
+
this._lighthouseChannel = lighthouseChannel;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* ONLY use if `dom.rootEl` isn't sufficient for your needs. `dom.rootEl` is preferred
|
|
1269
|
+
* for all scoping, because a document can have multiple reports within it.
|
|
1270
|
+
* @return {Document}
|
|
1271
|
+
*/
|
|
1272
|
+
document() {
|
|
1273
|
+
return this._document;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* TODO(paulirish): import and conditionally apply the DevTools frontend subclasses instead of this
|
|
1278
|
+
* @return {boolean}
|
|
1279
|
+
*/
|
|
1280
|
+
isDevTools() {
|
|
1281
|
+
return !!this._document.querySelector('.lh-devtools');
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Guaranteed context.querySelector. Always returns an element or throws if
|
|
1286
|
+
* nothing matches query.
|
|
1287
|
+
* @template {string} T
|
|
1288
|
+
* @param {T} query
|
|
1289
|
+
* @param {ParentNode} context
|
|
1290
|
+
* @return {ParseSelector<T>}
|
|
1291
|
+
*/
|
|
1292
|
+
find(query, context) {
|
|
1293
|
+
const result = context.querySelector(query);
|
|
1294
|
+
if (result === null) {
|
|
1295
|
+
throw new Error(`query ${query} not found`);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Because we control the report layout and templates, use the simpler
|
|
1299
|
+
// `typed-query-selector` types that don't require differentiating between
|
|
1300
|
+
// e.g. HTMLAnchorElement and SVGAElement. See https://github.com/GoogleChrome/lighthouse/issues/12011
|
|
1301
|
+
return /** @type {ParseSelector<T>} */ (result);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Helper for context.querySelectorAll. Returns an Array instead of a NodeList.
|
|
1306
|
+
* @template {string} T
|
|
1307
|
+
* @param {T} query
|
|
1308
|
+
* @param {ParentNode} context
|
|
1309
|
+
*/
|
|
1310
|
+
findAll(query, context) {
|
|
1311
|
+
const elements = Array.from(context.querySelectorAll(query));
|
|
1312
|
+
return elements;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Fires a custom DOM event on target.
|
|
1317
|
+
* @param {string} name Name of the event.
|
|
1318
|
+
* @param {Node=} target DOM node to fire the event on.
|
|
1319
|
+
* @param {*=} detail Custom data to include.
|
|
1320
|
+
*/
|
|
1321
|
+
fireEventOn(name, target = this._document, detail) {
|
|
1322
|
+
const event = new CustomEvent(name, detail ? {detail} : undefined);
|
|
1323
|
+
target.dispatchEvent(event);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Downloads a file (blob) using a[download].
|
|
1328
|
+
* @param {Blob|File} blob The file to save.
|
|
1329
|
+
* @param {string} filename
|
|
1330
|
+
*/
|
|
1331
|
+
saveFile(blob, filename) {
|
|
1332
|
+
const a = this.createElement('a');
|
|
1333
|
+
a.download = filename;
|
|
1334
|
+
this.safelySetBlobHref(a, blob);
|
|
1335
|
+
this._document.body.append(a); // Firefox requires anchor to be in the DOM.
|
|
1336
|
+
a.click();
|
|
1337
|
+
|
|
1338
|
+
// cleanup.
|
|
1339
|
+
this._document.body.removeChild(a);
|
|
1340
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 500);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
/**
|
|
1345
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
1346
|
+
* 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
|
|
1347
|
+
* 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.
|
|
1348
|
+
*/
|
|
1349
|
+
|
|
1350
|
+
/** @typedef {import('./i18n-formatter').I18nFormatter} I18nFormatter */
|
|
1351
|
+
|
|
1352
|
+
let svgSuffix = 0;
|
|
1353
|
+
|
|
1354
|
+
class Globals {
|
|
1355
|
+
/** @type {I18nFormatter} */
|
|
1356
|
+
// @ts-expect-error: Set in report renderer.
|
|
1357
|
+
static i18n = null;
|
|
1358
|
+
|
|
1359
|
+
/** @type {typeof UIStrings} */
|
|
1360
|
+
// @ts-expect-error: Set in report renderer.
|
|
1361
|
+
static strings = {};
|
|
1362
|
+
|
|
1363
|
+
/** @type {LH.ReportResult | null} */
|
|
1364
|
+
static reportJson = null;
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* @param {{providedStrings: Record<string, string>; i18n: I18nFormatter; reportJson: LH.ReportResult | null}} options
|
|
1368
|
+
*/
|
|
1369
|
+
static apply(options) {
|
|
1370
|
+
Globals.strings = {
|
|
1371
|
+
// Set missing renderer strings to default (english) values.
|
|
1372
|
+
...UIStrings,
|
|
1373
|
+
...options.providedStrings,
|
|
1374
|
+
};
|
|
1375
|
+
Globals.i18n = options.i18n;
|
|
1376
|
+
Globals.reportJson = options.reportJson;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
static getUniqueSuffix() {
|
|
1380
|
+
return svgSuffix++;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
static resetUniqueSuffix() {
|
|
1384
|
+
svgSuffix = 0;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
1390
|
+
* 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
|
|
1391
|
+
* 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.
|
|
1392
|
+
*/
|
|
1393
|
+
|
|
1394
|
+
const SCREENSHOT_PREFIX = 'data:image/jpeg;base64,';
|
|
1395
|
+
const RATINGS = Util.RATINGS;
|
|
1396
|
+
|
|
1397
|
+
class ReportUtils {
|
|
1398
|
+
/**
|
|
1399
|
+
* Returns a new LHR that's reshaped for slightly better ergonomics within the report rendereer.
|
|
1400
|
+
* Also, sets up the localized UI strings used within renderer and makes changes to old LHRs to be
|
|
1401
|
+
* compatible with current renderer.
|
|
1402
|
+
* The LHR passed in is not mutated.
|
|
1403
|
+
* TODO(team): we all agree the LHR shape change is technical debt we should fix
|
|
1404
|
+
* @param {LH.Result} result
|
|
1405
|
+
* @return {LH.ReportResult}
|
|
1406
|
+
*/
|
|
1407
|
+
static prepareReportResult(result) {
|
|
1408
|
+
// If any mutations happen to the report within the renderers, we want the original object untouched
|
|
1409
|
+
const clone = /** @type {LH.ReportResult} */ (JSON.parse(JSON.stringify(result)));
|
|
1410
|
+
|
|
1411
|
+
// If LHR is older (≤3.0.3), it has no locale setting. Set default.
|
|
1412
|
+
if (!clone.configSettings.locale) {
|
|
1413
|
+
clone.configSettings.locale = 'en';
|
|
1414
|
+
}
|
|
1415
|
+
if (!clone.configSettings.formFactor) {
|
|
1416
|
+
// @ts-expect-error fallback handling for emulatedFormFactor
|
|
1417
|
+
clone.configSettings.formFactor = clone.configSettings.emulatedFormFactor;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
clone.finalDisplayedUrl = Util.getFinalDisplayedUrl(clone);
|
|
1421
|
+
clone.mainDocumentUrl = Util.getMainDocumentUrl(clone);
|
|
1422
|
+
|
|
1423
|
+
for (const audit of Object.values(clone.audits)) {
|
|
1424
|
+
// Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
|
|
1425
|
+
// into 'notApplicable' (LHR ≥4.0).
|
|
1426
|
+
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
1427
|
+
// eslint-disable-next-line max-len
|
|
1428
|
+
if (audit.scoreDisplayMode === 'not_applicable' || audit.scoreDisplayMode === 'not-applicable') {
|
|
1429
|
+
audit.scoreDisplayMode = 'notApplicable';
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
if (audit.details) {
|
|
1433
|
+
// Turn `auditDetails.type` of undefined (LHR <4.2) and 'diagnostic' (LHR <5.0)
|
|
1434
|
+
// into 'debugdata' (LHR ≥5.0).
|
|
1435
|
+
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
1436
|
+
if (audit.details.type === undefined || audit.details.type === 'diagnostic') {
|
|
1437
|
+
// @ts-expect-error details is of type never.
|
|
1438
|
+
audit.details.type = 'debugdata';
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// Add the jpg data URL prefix to filmstrip screenshots without them (LHR <5.0).
|
|
1442
|
+
if (audit.details.type === 'filmstrip') {
|
|
1443
|
+
for (const screenshot of audit.details.items) {
|
|
1444
|
+
if (!screenshot.data.startsWith(SCREENSHOT_PREFIX)) {
|
|
1445
|
+
screenshot.data = SCREENSHOT_PREFIX + screenshot.data;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Circa 10.0, table items were refactored.
|
|
1451
|
+
if (audit.details.type === 'table') {
|
|
1452
|
+
for (const heading of audit.details.headings) {
|
|
1453
|
+
/** @type {{itemType: LH.Audit.Details.ItemValueType|undefined, text: string|undefined}} */
|
|
1454
|
+
// @ts-expect-error
|
|
1455
|
+
const {itemType, text} = heading;
|
|
1456
|
+
if (itemType !== undefined) {
|
|
1457
|
+
heading.valueType = itemType;
|
|
1458
|
+
// @ts-expect-error
|
|
1459
|
+
delete heading.itemType;
|
|
1460
|
+
}
|
|
1461
|
+
if (text !== undefined) {
|
|
1462
|
+
heading.label = text;
|
|
1463
|
+
// @ts-expect-error
|
|
1464
|
+
delete heading.text;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// @ts-expect-error
|
|
1468
|
+
const subItemsItemType = heading.subItemsHeading?.itemType;
|
|
1469
|
+
if (heading.subItemsHeading && subItemsItemType !== undefined) {
|
|
1470
|
+
heading.subItemsHeading.valueType = subItemsItemType;
|
|
1471
|
+
// @ts-expect-error
|
|
1472
|
+
delete heading.subItemsHeading.itemType;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// TODO: convert printf-style displayValue.
|
|
1478
|
+
// Added: #5099, v3
|
|
1479
|
+
// Removed: #6767, v4
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// For convenience, smoosh all AuditResults into their auditRef (which has just weight & group)
|
|
1484
|
+
if (typeof clone.categories !== 'object') throw new Error('No categories provided.');
|
|
1485
|
+
|
|
1486
|
+
/** @type {Map<string, Array<LH.ReportResult.AuditRef>>} */
|
|
1487
|
+
const relevantAuditToMetricsMap = new Map();
|
|
1488
|
+
|
|
1489
|
+
// This backcompat converts old LHRs (<9.0.0) to use the new "hidden" group.
|
|
1490
|
+
// Old LHRs used "no group" to identify audits that should be hidden in performance instead of the "hidden" group.
|
|
1491
|
+
// Newer LHRs use "no group" to identify opportunities and diagnostics whose groups are assigned by details type.
|
|
1492
|
+
const [majorVersion] = clone.lighthouseVersion.split('.').map(Number);
|
|
1493
|
+
const perfCategory = clone.categories['performance'];
|
|
1494
|
+
if (majorVersion < 9 && perfCategory) {
|
|
1495
|
+
if (!clone.categoryGroups) clone.categoryGroups = {};
|
|
1496
|
+
clone.categoryGroups['hidden'] = {title: ''};
|
|
1497
|
+
for (const auditRef of perfCategory.auditRefs) {
|
|
1498
|
+
if (!auditRef.group) {
|
|
1499
|
+
auditRef.group = 'hidden';
|
|
1500
|
+
} else if (['load-opportunities', 'diagnostics'].includes(auditRef.group)) {
|
|
1501
|
+
delete auditRef.group;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
for (const category of Object.values(clone.categories)) {
|
|
1507
|
+
// Make basic lookup table for relevantAudits
|
|
1508
|
+
category.auditRefs.forEach(metricRef => {
|
|
1509
|
+
if (!metricRef.relevantAudits) return;
|
|
1510
|
+
metricRef.relevantAudits.forEach(auditId => {
|
|
1511
|
+
const arr = relevantAuditToMetricsMap.get(auditId) || [];
|
|
1512
|
+
arr.push(metricRef);
|
|
1513
|
+
relevantAuditToMetricsMap.set(auditId, arr);
|
|
1514
|
+
});
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
category.auditRefs.forEach(auditRef => {
|
|
1518
|
+
const result = clone.audits[auditRef.id];
|
|
1519
|
+
auditRef.result = result;
|
|
1520
|
+
|
|
1521
|
+
// Attach any relevantMetric auditRefs
|
|
1522
|
+
if (relevantAuditToMetricsMap.has(auditRef.id)) {
|
|
1523
|
+
auditRef.relevantMetrics = relevantAuditToMetricsMap.get(auditRef.id);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// attach the stackpacks to the auditRef object
|
|
1527
|
+
if (clone.stackPacks) {
|
|
1528
|
+
clone.stackPacks.forEach(pack => {
|
|
1529
|
+
if (pack.descriptions[auditRef.id]) {
|
|
1530
|
+
auditRef.stackPacks = auditRef.stackPacks || [];
|
|
1531
|
+
auditRef.stackPacks.push({
|
|
1532
|
+
title: pack.title,
|
|
1533
|
+
iconDataURL: pack.iconDataURL,
|
|
1534
|
+
description: pack.descriptions[auditRef.id],
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// Add some minimal stuff so older reports still work.
|
|
1543
|
+
if (!clone.environment) {
|
|
1544
|
+
// @ts-expect-error
|
|
1545
|
+
clone.environment = {benchmarkIndex: 0};
|
|
1546
|
+
}
|
|
1547
|
+
if (!clone.configSettings.screenEmulation) {
|
|
1548
|
+
// @ts-expect-error
|
|
1549
|
+
clone.configSettings.screenEmulation = {};
|
|
1550
|
+
}
|
|
1551
|
+
if (!clone.i18n) {
|
|
1552
|
+
// @ts-expect-error
|
|
1553
|
+
clone.i18n = {};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// In 10.0, full-page-screenshot became a top-level property on the LHR.
|
|
1557
|
+
if (clone.audits['full-page-screenshot']) {
|
|
1558
|
+
const details = /** @type {LH.Result.FullPageScreenshot=} */ (
|
|
1559
|
+
clone.audits['full-page-screenshot'].details);
|
|
1560
|
+
if (details) {
|
|
1561
|
+
clone.fullPageScreenshot = {
|
|
1562
|
+
screenshot: details.screenshot,
|
|
1563
|
+
nodes: details.nodes,
|
|
1564
|
+
};
|
|
1565
|
+
} else {
|
|
1566
|
+
clone.fullPageScreenshot = null;
|
|
1567
|
+
}
|
|
1568
|
+
delete clone.audits['full-page-screenshot'];
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
return clone;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* @param {LH.Result['configSettings']} settings
|
|
1576
|
+
* @return {!{deviceEmulation: string, screenEmulation?: string, networkThrottling: string, cpuThrottling: string, summary: string}}
|
|
1577
|
+
*/
|
|
1578
|
+
static getEmulationDescriptions(settings) {
|
|
1579
|
+
let cpuThrottling;
|
|
1580
|
+
let networkThrottling;
|
|
1581
|
+
let summary;
|
|
1582
|
+
|
|
1583
|
+
const throttling = settings.throttling;
|
|
1584
|
+
const i18n = Globals.i18n;
|
|
1585
|
+
const strings = Globals.strings;
|
|
1586
|
+
|
|
1587
|
+
switch (settings.throttlingMethod) {
|
|
1588
|
+
case 'provided':
|
|
1589
|
+
summary = networkThrottling = cpuThrottling = strings.throttlingProvided;
|
|
1590
|
+
break;
|
|
1591
|
+
case 'devtools': {
|
|
1592
|
+
const {cpuSlowdownMultiplier, requestLatencyMs} = throttling;
|
|
1593
|
+
// eslint-disable-next-line max-len
|
|
1594
|
+
cpuThrottling = `${i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`;
|
|
1595
|
+
networkThrottling = `${i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` +
|
|
1596
|
+
`${i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` +
|
|
1597
|
+
`${i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`;
|
|
1598
|
+
|
|
1599
|
+
const isSlow4G = () => {
|
|
1600
|
+
return requestLatencyMs === 150 * 3.75 &&
|
|
1601
|
+
throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 &&
|
|
1602
|
+
throttling.uploadThroughputKbps === 750 * 0.9;
|
|
1603
|
+
};
|
|
1604
|
+
summary = isSlow4G() ? strings.runtimeSlow4g : strings.runtimeCustom;
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
case 'simulate': {
|
|
1608
|
+
const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling;
|
|
1609
|
+
// eslint-disable-next-line max-len
|
|
1610
|
+
cpuThrottling = `${i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
|
|
1611
|
+
networkThrottling = `${i18n.formatMilliseconds(rttMs)} TCP RTT, ` +
|
|
1612
|
+
`${i18n.formatKbps(throughputKbps)} throughput (Simulated)`;
|
|
1613
|
+
|
|
1614
|
+
const isSlow4G = () => {
|
|
1615
|
+
return rttMs === 150 && throughputKbps === 1.6 * 1024;
|
|
1616
|
+
};
|
|
1617
|
+
summary = isSlow4G() ?
|
|
1618
|
+
strings.runtimeSlow4g : strings.runtimeCustom;
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
default:
|
|
1622
|
+
summary = cpuThrottling = networkThrottling = strings.runtimeUnknown;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation,
|
|
1626
|
+
// because we handle the emulation outside of Lighthouse. Since the screen truly is emulated
|
|
1627
|
+
// as a mobile device, ignore `.disabled` in devtools and just check the form factor
|
|
1628
|
+
const isScreenEmulationDisabled = settings.channel === 'devtools' ?
|
|
1629
|
+
false :
|
|
1630
|
+
settings.screenEmulation.disabled;
|
|
1631
|
+
const isScreenEmulationMobile = settings.channel === 'devtools' ?
|
|
1632
|
+
settings.formFactor === 'mobile' :
|
|
1633
|
+
settings.screenEmulation.mobile;
|
|
1634
|
+
|
|
1635
|
+
let deviceEmulation = strings.runtimeMobileEmulation;
|
|
1636
|
+
if (isScreenEmulationDisabled) {
|
|
1637
|
+
deviceEmulation = strings.runtimeNoEmulation;
|
|
1638
|
+
} else if (!isScreenEmulationMobile) {
|
|
1639
|
+
deviceEmulation = strings.runtimeDesktopEmulation;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
const screenEmulation = isScreenEmulationDisabled ?
|
|
1643
|
+
undefined :
|
|
1644
|
+
// eslint-disable-next-line max-len
|
|
1645
|
+
`${settings.screenEmulation.width}x${settings.screenEmulation.height}, DPR ${settings.screenEmulation.deviceScaleFactor}`;
|
|
1646
|
+
|
|
1647
|
+
return {
|
|
1648
|
+
deviceEmulation,
|
|
1649
|
+
screenEmulation,
|
|
1650
|
+
cpuThrottling,
|
|
1651
|
+
networkThrottling,
|
|
1652
|
+
summary,
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/**
|
|
1657
|
+
* Used to determine if the "passed" for the purposes of showing up in the "failed" or "passed"
|
|
1658
|
+
* sections of the report.
|
|
1659
|
+
*
|
|
1660
|
+
* @param {{score: (number|null), scoreDisplayMode: string}} audit
|
|
1661
|
+
* @return {boolean}
|
|
1662
|
+
*/
|
|
1663
|
+
static showAsPassed(audit) {
|
|
1664
|
+
switch (audit.scoreDisplayMode) {
|
|
1665
|
+
case 'manual':
|
|
1666
|
+
case 'notApplicable':
|
|
1667
|
+
return true;
|
|
1668
|
+
case 'error':
|
|
1669
|
+
case 'informative':
|
|
1670
|
+
return false;
|
|
1671
|
+
case 'numeric':
|
|
1672
|
+
case 'binary':
|
|
1673
|
+
default:
|
|
1674
|
+
return Number(audit.score) >= RATINGS.PASS.minScore;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
/**
|
|
1679
|
+
* Convert a score to a rating label.
|
|
1680
|
+
* TODO: Return `'error'` for `score === null && !scoreDisplayMode`.
|
|
1681
|
+
*
|
|
1682
|
+
* @param {number|null} score
|
|
1683
|
+
* @param {string=} scoreDisplayMode
|
|
1684
|
+
* @return {string}
|
|
1685
|
+
*/
|
|
1686
|
+
static calculateRating(score, scoreDisplayMode) {
|
|
1687
|
+
// Handle edge cases first, manual and not applicable receive 'pass', errored audits receive 'error'
|
|
1688
|
+
if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'notApplicable') {
|
|
1689
|
+
return RATINGS.PASS.label;
|
|
1690
|
+
} else if (scoreDisplayMode === 'error') {
|
|
1691
|
+
return RATINGS.ERROR.label;
|
|
1692
|
+
} else if (score === null) {
|
|
1693
|
+
return RATINGS.FAIL.label;
|
|
1694
|
+
}
|
|
1783
1695
|
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
if (blob.type !== 'text/html' && blob.type !== 'application/json') {
|
|
1791
|
-
throw new Error('Unsupported blob type');
|
|
1696
|
+
// At this point, we're rating a standard binary/numeric audit
|
|
1697
|
+
let rating = RATINGS.FAIL.label;
|
|
1698
|
+
if (score >= RATINGS.PASS.minScore) {
|
|
1699
|
+
rating = RATINGS.PASS.label;
|
|
1700
|
+
} else if (score >= RATINGS.AVERAGE.minScore) {
|
|
1701
|
+
rating = RATINGS.AVERAGE.label;
|
|
1792
1702
|
}
|
|
1793
|
-
|
|
1794
|
-
elem.href = href;
|
|
1703
|
+
return rating;
|
|
1795
1704
|
}
|
|
1796
1705
|
|
|
1797
1706
|
/**
|
|
1798
|
-
* @param {
|
|
1799
|
-
* @return {Element}
|
|
1707
|
+
* @param {LH.ReportResult.Category} category
|
|
1800
1708
|
*/
|
|
1801
|
-
|
|
1802
|
-
|
|
1709
|
+
static calculateCategoryFraction(category) {
|
|
1710
|
+
let numPassableAudits = 0;
|
|
1711
|
+
let numPassed = 0;
|
|
1712
|
+
let numInformative = 0;
|
|
1713
|
+
let totalWeight = 0;
|
|
1714
|
+
for (const auditRef of category.auditRefs) {
|
|
1715
|
+
const auditPassed = ReportUtils.showAsPassed(auditRef.result);
|
|
1803
1716
|
|
|
1804
|
-
|
|
1805
|
-
if (
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
} else {
|
|
1810
|
-
|
|
1717
|
+
// Don't count the audit if it's manual, N/A, or isn't displayed.
|
|
1718
|
+
if (auditRef.group === 'hidden' ||
|
|
1719
|
+
auditRef.result.scoreDisplayMode === 'manual' ||
|
|
1720
|
+
auditRef.result.scoreDisplayMode === 'notApplicable') {
|
|
1721
|
+
continue;
|
|
1722
|
+
} else if (auditRef.result.scoreDisplayMode === 'informative') {
|
|
1723
|
+
if (!auditPassed) {
|
|
1724
|
+
++numInformative;
|
|
1725
|
+
}
|
|
1726
|
+
continue;
|
|
1811
1727
|
}
|
|
1812
|
-
}
|
|
1813
1728
|
|
|
1814
|
-
|
|
1729
|
+
++numPassableAudits;
|
|
1730
|
+
totalWeight += auditRef.weight;
|
|
1731
|
+
if (auditPassed) numPassed++;
|
|
1732
|
+
}
|
|
1733
|
+
return {numPassed, numPassableAudits, numInformative, totalWeight};
|
|
1815
1734
|
}
|
|
1816
1735
|
|
|
1817
1736
|
/**
|
|
1818
|
-
*
|
|
1819
|
-
* @param {string} lighthouseChannel
|
|
1737
|
+
* @param {string} categoryId
|
|
1820
1738
|
*/
|
|
1821
|
-
|
|
1822
|
-
|
|
1739
|
+
static isPluginCategory(categoryId) {
|
|
1740
|
+
return categoryId.startsWith('lighthouse-plugin-');
|
|
1823
1741
|
}
|
|
1824
1742
|
|
|
1825
1743
|
/**
|
|
1826
|
-
*
|
|
1827
|
-
* for all scoping, because a document can have multiple reports within it.
|
|
1828
|
-
* @return {Document}
|
|
1744
|
+
* @param {LH.Result.GatherMode} gatherMode
|
|
1829
1745
|
*/
|
|
1830
|
-
|
|
1831
|
-
return
|
|
1746
|
+
static shouldDisplayAsFraction(gatherMode) {
|
|
1747
|
+
return gatherMode === 'timespan' || gatherMode === 'snapshot';
|
|
1832
1748
|
}
|
|
1749
|
+
}
|
|
1833
1750
|
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1751
|
+
/**
|
|
1752
|
+
* Report-renderer-specific strings.
|
|
1753
|
+
*/
|
|
1754
|
+
const UIStrings = {
|
|
1755
|
+
/** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */
|
|
1756
|
+
varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.',
|
|
1757
|
+
/** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */
|
|
1758
|
+
calculatorLink: 'See calculator.',
|
|
1759
|
+
/** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */
|
|
1760
|
+
showRelevantAudits: 'Show audits relevant to:',
|
|
1761
|
+
/** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */
|
|
1762
|
+
opportunityResourceColumnLabel: 'Opportunity',
|
|
1763
|
+
/** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */
|
|
1764
|
+
opportunitySavingsColumnLabel: 'Estimated Savings',
|
|
1841
1765
|
|
|
1842
|
-
/**
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1766
|
+
/** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */
|
|
1767
|
+
errorMissingAuditInfo: 'Report error: no audit information',
|
|
1768
|
+
/** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */
|
|
1769
|
+
errorLabel: 'Error!',
|
|
1770
|
+
/** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */
|
|
1771
|
+
warningHeader: 'Warnings: ',
|
|
1772
|
+
/** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */
|
|
1773
|
+
warningAuditsGroupTitle: 'Passed audits but with warnings',
|
|
1774
|
+
/** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
|
|
1775
|
+
passedAuditsGroupTitle: 'Passed audits',
|
|
1776
|
+
/** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
|
|
1777
|
+
notApplicableAuditsGroupTitle: 'Not applicable',
|
|
1778
|
+
/** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
|
|
1779
|
+
manualAuditsGroupTitle: 'Additional items to manually check',
|
|
1855
1780
|
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
// e.g. HTMLAnchorElement and SVGAElement. See https://github.com/GoogleChrome/lighthouse/issues/12011
|
|
1859
|
-
return /** @type {ParseSelector<T>} */ (result);
|
|
1860
|
-
}
|
|
1781
|
+
/** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */
|
|
1782
|
+
toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:',
|
|
1861
1783
|
|
|
1862
|
-
/**
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
* @param {ParentNode} context
|
|
1867
|
-
*/
|
|
1868
|
-
findAll(query, context) {
|
|
1869
|
-
const elements = Array.from(context.querySelectorAll(query));
|
|
1870
|
-
return elements;
|
|
1871
|
-
}
|
|
1784
|
+
/** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */
|
|
1785
|
+
crcInitialNavigation: 'Initial Navigation',
|
|
1786
|
+
/** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */
|
|
1787
|
+
crcLongestDurationLabel: 'Maximum critical path latency:',
|
|
1872
1788
|
|
|
1873
|
-
/**
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
* @param {*=} detail Custom data to include.
|
|
1878
|
-
*/
|
|
1879
|
-
fireEventOn(name, target = this._document, detail) {
|
|
1880
|
-
const event = new CustomEvent(name, detail ? {detail} : undefined);
|
|
1881
|
-
target.dispatchEvent(event);
|
|
1882
|
-
}
|
|
1789
|
+
/** Label for button that shows all lines of the snippet when clicked */
|
|
1790
|
+
snippetExpandButtonLabel: 'Expand snippet',
|
|
1791
|
+
/** Label for button that only shows a few lines of the snippet when clicked */
|
|
1792
|
+
snippetCollapseButtonLabel: 'Collapse snippet',
|
|
1883
1793
|
|
|
1884
|
-
/**
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
*/
|
|
1889
|
-
saveFile(blob, filename) {
|
|
1890
|
-
const a = this.createElement('a');
|
|
1891
|
-
a.download = filename;
|
|
1892
|
-
this.safelySetBlobHref(a, blob);
|
|
1893
|
-
this._document.body.append(a); // Firefox requires anchor to be in the DOM.
|
|
1894
|
-
a.click();
|
|
1794
|
+
/** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */
|
|
1795
|
+
lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.',
|
|
1796
|
+
/** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */
|
|
1797
|
+
labDataTitle: 'Lab Data',
|
|
1895
1798
|
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1799
|
+
/** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */
|
|
1800
|
+
thirdPartyResourcesLabel: 'Show 3rd-party resources',
|
|
1801
|
+
/** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */
|
|
1802
|
+
viewTreemapLabel: 'View Treemap',
|
|
1803
|
+
/** This label is for a button that will show the user a trace of the page. */
|
|
1804
|
+
viewTraceLabel: 'View Trace',
|
|
1805
|
+
/** This label is for a button that will show the user a trace of the page. */
|
|
1806
|
+
viewOriginalTraceLabel: 'View Original Trace',
|
|
1807
|
+
|
|
1808
|
+
/** Option in a dropdown menu that opens a small, summary report in a print dialog. */
|
|
1809
|
+
dropdownPrintSummary: 'Print Summary',
|
|
1810
|
+
/** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */
|
|
1811
|
+
dropdownPrintExpanded: 'Print Expanded',
|
|
1812
|
+
/** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */
|
|
1813
|
+
dropdownCopyJSON: 'Copy JSON',
|
|
1814
|
+
/** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */
|
|
1815
|
+
dropdownSaveHTML: 'Save as HTML',
|
|
1816
|
+
/** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */
|
|
1817
|
+
dropdownSaveJSON: 'Save as JSON',
|
|
1818
|
+
/** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */
|
|
1819
|
+
dropdownViewer: 'Open in Viewer',
|
|
1820
|
+
/** Option in a dropdown menu that saves the current report as a new GitHub Gist. */
|
|
1821
|
+
dropdownSaveGist: 'Save as Gist',
|
|
1822
|
+
/** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */
|
|
1823
|
+
dropdownDarkTheme: 'Toggle Dark Theme',
|
|
1824
|
+
|
|
1825
|
+
/** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */
|
|
1826
|
+
runtimeSettingsDevice: 'Device',
|
|
1827
|
+
/** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */
|
|
1828
|
+
runtimeSettingsNetworkThrottling: 'Network throttling',
|
|
1829
|
+
/** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/
|
|
1830
|
+
runtimeSettingsCPUThrottling: 'CPU throttling',
|
|
1831
|
+
/** 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. */
|
|
1832
|
+
runtimeSettingsUANetwork: 'User agent (network)',
|
|
1833
|
+
/** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */
|
|
1834
|
+
runtimeSettingsBenchmark: 'Unthrottled CPU/Memory Power',
|
|
1835
|
+
/** 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 */
|
|
1836
|
+
runtimeSettingsAxeVersion: 'Axe version',
|
|
1837
|
+
/** 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' */
|
|
1838
|
+
runtimeSettingsScreenEmulation: 'Screen emulation',
|
|
1839
|
+
|
|
1840
|
+
/** Label for button to create an issue against the Lighthouse GitHub project. */
|
|
1841
|
+
footerIssue: 'File an issue',
|
|
1842
|
+
|
|
1843
|
+
/** Descriptive explanation for emulation setting when no device emulation is set. */
|
|
1844
|
+
runtimeNoEmulation: 'No emulation',
|
|
1845
|
+
/** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */
|
|
1846
|
+
runtimeMobileEmulation: 'Emulated Moto G4',
|
|
1847
|
+
/** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */
|
|
1848
|
+
runtimeDesktopEmulation: 'Emulated Desktop',
|
|
1849
|
+
/** Descriptive explanation for a runtime setting that is set to an unknown value. */
|
|
1850
|
+
runtimeUnknown: 'Unknown',
|
|
1851
|
+
/** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */
|
|
1852
|
+
runtimeSingleLoad: 'Single page load',
|
|
1853
|
+
/** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */
|
|
1854
|
+
runtimeAnalysisWindow: 'Initial page load',
|
|
1855
|
+
/** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */
|
|
1856
|
+
runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len
|
|
1857
|
+
|
|
1858
|
+
/** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */
|
|
1859
|
+
throttlingProvided: 'Provided by environment',
|
|
1860
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
|
|
1861
|
+
show: 'Show',
|
|
1862
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
|
|
1863
|
+
hide: 'Hide',
|
|
1864
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
|
|
1865
|
+
expandView: 'Expand view',
|
|
1866
|
+
/** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
|
|
1867
|
+
collapseView: 'Collapse view',
|
|
1868
|
+
/** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */
|
|
1869
|
+
runtimeSlow4g: 'Slow 4G throttling',
|
|
1870
|
+
/** Label indicating that Lighthouse throttled the page using custom throttling settings. */
|
|
1871
|
+
runtimeCustom: 'Custom throttling',
|
|
1872
|
+
};
|
|
1901
1873
|
|
|
1902
1874
|
/**
|
|
1903
1875
|
* @license
|
|
@@ -1933,10 +1905,10 @@ class CategoryRenderer {
|
|
|
1933
1905
|
*/
|
|
1934
1906
|
get _clumpTitles() {
|
|
1935
1907
|
return {
|
|
1936
|
-
warning:
|
|
1937
|
-
manual:
|
|
1938
|
-
passed:
|
|
1939
|
-
notApplicable:
|
|
1908
|
+
warning: Globals.strings.warningAuditsGroupTitle,
|
|
1909
|
+
manual: Globals.strings.manualAuditsGroupTitle,
|
|
1910
|
+
passed: Globals.strings.passedAuditsGroupTitle,
|
|
1911
|
+
notApplicable: Globals.strings.notApplicableAuditsGroupTitle,
|
|
1940
1912
|
};
|
|
1941
1913
|
}
|
|
1942
1914
|
|
|
@@ -1956,7 +1928,7 @@ class CategoryRenderer {
|
|
|
1956
1928
|
* @return {!Element}
|
|
1957
1929
|
*/
|
|
1958
1930
|
populateAuditValues(audit, component) {
|
|
1959
|
-
const strings =
|
|
1931
|
+
const strings = Globals.strings;
|
|
1960
1932
|
const auditEl = this.dom.find('.lh-audit', component);
|
|
1961
1933
|
auditEl.id = audit.result.id;
|
|
1962
1934
|
const scoreDisplayMode = audit.result.scoreDisplayMode;
|
|
@@ -2079,7 +2051,7 @@ class CategoryRenderer {
|
|
|
2079
2051
|
* @return {!Element}
|
|
2080
2052
|
*/
|
|
2081
2053
|
_setRatingClass(element, score, scoreDisplayMode) {
|
|
2082
|
-
const rating =
|
|
2054
|
+
const rating = ReportUtils.calculateRating(score, scoreDisplayMode);
|
|
2083
2055
|
element.classList.add(`lh-audit--${scoreDisplayMode.toLowerCase()}`);
|
|
2084
2056
|
if (scoreDisplayMode !== 'informative') {
|
|
2085
2057
|
element.classList.add(`lh-audit--${rating}`);
|
|
@@ -2228,8 +2200,8 @@ class CategoryRenderer {
|
|
|
2228
2200
|
el.append(descriptionEl);
|
|
2229
2201
|
}
|
|
2230
2202
|
|
|
2231
|
-
this.dom.find('.lh-clump-toggletext--show', el).textContent =
|
|
2232
|
-
this.dom.find('.lh-clump-toggletext--hide', el).textContent =
|
|
2203
|
+
this.dom.find('.lh-clump-toggletext--show', el).textContent = Globals.strings.show;
|
|
2204
|
+
this.dom.find('.lh-clump-toggletext--hide', el).textContent = Globals.strings.hide;
|
|
2233
2205
|
|
|
2234
2206
|
clumpElement.classList.add(`lh-clump--${clumpId.toLowerCase()}`);
|
|
2235
2207
|
return el;
|
|
@@ -2243,7 +2215,7 @@ class CategoryRenderer {
|
|
|
2243
2215
|
*/
|
|
2244
2216
|
renderCategoryScore(category, groupDefinitions, options) {
|
|
2245
2217
|
let categoryScore;
|
|
2246
|
-
if (options &&
|
|
2218
|
+
if (options && ReportUtils.shouldDisplayAsFraction(options.gatherMode)) {
|
|
2247
2219
|
categoryScore = this.renderCategoryFraction(category);
|
|
2248
2220
|
} else {
|
|
2249
2221
|
categoryScore = this.renderScoreGauge(category, groupDefinitions);
|
|
@@ -2271,7 +2243,7 @@ class CategoryRenderer {
|
|
|
2271
2243
|
const tmpl = this.dom.createComponent('gauge');
|
|
2272
2244
|
const wrapper = this.dom.find('a.lh-gauge__wrapper', tmpl);
|
|
2273
2245
|
|
|
2274
|
-
if (
|
|
2246
|
+
if (ReportUtils.isPluginCategory(category.id)) {
|
|
2275
2247
|
wrapper.classList.add('lh-gauge__wrapper--plugin');
|
|
2276
2248
|
}
|
|
2277
2249
|
|
|
@@ -2287,16 +2259,16 @@ class CategoryRenderer {
|
|
|
2287
2259
|
percentageEl.textContent = scoreOutOf100.toString();
|
|
2288
2260
|
if (category.score === null) {
|
|
2289
2261
|
percentageEl.textContent = '?';
|
|
2290
|
-
percentageEl.title =
|
|
2262
|
+
percentageEl.title = Globals.strings.errorLabel;
|
|
2291
2263
|
}
|
|
2292
2264
|
|
|
2293
2265
|
// Render a numerical score if the category has applicable audits, or no audits whatsoever.
|
|
2294
2266
|
if (category.auditRefs.length === 0 || this.hasApplicableAudits(category)) {
|
|
2295
|
-
wrapper.classList.add(`lh-gauge__wrapper--${
|
|
2267
|
+
wrapper.classList.add(`lh-gauge__wrapper--${ReportUtils.calculateRating(category.score)}`);
|
|
2296
2268
|
} else {
|
|
2297
2269
|
wrapper.classList.add(`lh-gauge__wrapper--not-applicable`);
|
|
2298
2270
|
percentageEl.textContent = '-';
|
|
2299
|
-
percentageEl.title =
|
|
2271
|
+
percentageEl.title = Globals.strings.notApplicableAuditsGroupTitle;
|
|
2300
2272
|
}
|
|
2301
2273
|
|
|
2302
2274
|
this.dom.find('.lh-gauge__label', tmpl).textContent = category.title;
|
|
@@ -2311,7 +2283,8 @@ class CategoryRenderer {
|
|
|
2311
2283
|
const tmpl = this.dom.createComponent('fraction');
|
|
2312
2284
|
const wrapper = this.dom.find('a.lh-fraction__wrapper', tmpl);
|
|
2313
2285
|
|
|
2314
|
-
const {numPassed, numPassableAudits, totalWeight} =
|
|
2286
|
+
const {numPassed, numPassableAudits, totalWeight} =
|
|
2287
|
+
ReportUtils.calculateCategoryFraction(category);
|
|
2315
2288
|
|
|
2316
2289
|
const fraction = numPassed / numPassableAudits;
|
|
2317
2290
|
const content = this.dom.find('.lh-fraction__content', tmpl);
|
|
@@ -2319,7 +2292,7 @@ class CategoryRenderer {
|
|
|
2319
2292
|
text.textContent = `${numPassed}/${numPassableAudits}`;
|
|
2320
2293
|
content.append(text);
|
|
2321
2294
|
|
|
2322
|
-
let rating =
|
|
2295
|
+
let rating = ReportUtils.calculateRating(fraction);
|
|
2323
2296
|
|
|
2324
2297
|
// If none of the available audits can affect the score, a rating isn't useful.
|
|
2325
2298
|
// The flow report should display the fraction with neutral icon and coloring in this case.
|
|
@@ -2384,7 +2357,7 @@ class CategoryRenderer {
|
|
|
2384
2357
|
return scoreDisplayMode;
|
|
2385
2358
|
}
|
|
2386
2359
|
|
|
2387
|
-
if (
|
|
2360
|
+
if (ReportUtils.showAsPassed(auditRef.result)) {
|
|
2388
2361
|
if (this._auditHasWarning(auditRef)) {
|
|
2389
2362
|
return 'warning';
|
|
2390
2363
|
} else {
|
|
@@ -2598,9 +2571,10 @@ class CriticalRequestChainRenderer {
|
|
|
2598
2571
|
if (!segment.hasChildren) {
|
|
2599
2572
|
const {startTime, endTime, transferSize} = segment.node.request;
|
|
2600
2573
|
const span = dom.createElement('span', 'lh-crc-node__chain-duration');
|
|
2601
|
-
span.textContent =
|
|
2574
|
+
span.textContent =
|
|
2575
|
+
' - ' + Globals.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', ';
|
|
2602
2576
|
const span2 = dom.createElement('span', 'lh-crc-node__chain-duration');
|
|
2603
|
-
span2.textContent =
|
|
2577
|
+
span2.textContent = Globals.i18n.formatBytesToKiB(transferSize, 0.01);
|
|
2604
2578
|
|
|
2605
2579
|
treevalEl.append(span, span2);
|
|
2606
2580
|
}
|
|
@@ -2639,11 +2613,11 @@ class CriticalRequestChainRenderer {
|
|
|
2639
2613
|
const containerEl = dom.find('.lh-crc', tmpl);
|
|
2640
2614
|
|
|
2641
2615
|
// Fill in top summary.
|
|
2642
|
-
dom.find('.lh-crc-initial-nav', tmpl).textContent =
|
|
2616
|
+
dom.find('.lh-crc-initial-nav', tmpl).textContent = Globals.strings.crcInitialNavigation;
|
|
2643
2617
|
dom.find('.lh-crc__longest_duration_label', tmpl).textContent =
|
|
2644
|
-
|
|
2618
|
+
Globals.strings.crcLongestDurationLabel;
|
|
2645
2619
|
dom.find('.lh-crc__longest_duration', tmpl).textContent =
|
|
2646
|
-
|
|
2620
|
+
Globals.i18n.formatMilliseconds(details.longestChain.duration);
|
|
2647
2621
|
|
|
2648
2622
|
// Construct visual tree.
|
|
2649
2623
|
const root = CRCRenderer.initTree(details.chains);
|
|
@@ -2665,6 +2639,18 @@ const CRCRenderer = CriticalRequestChainRenderer;
|
|
|
2665
2639
|
* 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.
|
|
2666
2640
|
*/
|
|
2667
2641
|
|
|
2642
|
+
/** @typedef {import('./dom.js').DOM} DOM */
|
|
2643
|
+
/** @typedef {LH.Audit.Details.Rect} Rect */
|
|
2644
|
+
/** @typedef {{width: number, height: number}} Size */
|
|
2645
|
+
|
|
2646
|
+
/**
|
|
2647
|
+
* @typedef InstallOverlayFeatureParams
|
|
2648
|
+
* @property {DOM} dom
|
|
2649
|
+
* @property {Element} rootEl
|
|
2650
|
+
* @property {Element} overlayContainerEl
|
|
2651
|
+
* @property {LH.Result.FullPageScreenshot} fullPageScreenshot
|
|
2652
|
+
*/
|
|
2653
|
+
|
|
2668
2654
|
/**
|
|
2669
2655
|
* @param {LH.Result.FullPageScreenshot['screenshot']} screenshot
|
|
2670
2656
|
* @param {LH.Audit.Details.Rect} rect
|
|
@@ -2743,7 +2729,7 @@ class ElementScreenshotRenderer {
|
|
|
2743
2729
|
*/
|
|
2744
2730
|
static renderClipPathInScreenshot(dom, maskEl, positionClip, elementRect, elementPreviewSize) {
|
|
2745
2731
|
const clipPathEl = dom.find('clipPath', maskEl);
|
|
2746
|
-
const clipId = `clip-${
|
|
2732
|
+
const clipId = `clip-${Globals.getUniqueSuffix()}`;
|
|
2747
2733
|
clipPathEl.id = clipId;
|
|
2748
2734
|
maskEl.style.clipPath = `url(#${clipId})`;
|
|
2749
2735
|
|
|
@@ -2992,9 +2978,9 @@ class DetailsRenderer {
|
|
|
2992
2978
|
*/
|
|
2993
2979
|
_renderBytes(details) {
|
|
2994
2980
|
// TODO: handle displayUnit once we have something other than 'KiB'
|
|
2995
|
-
const value =
|
|
2981
|
+
const value = Globals.i18n.formatBytesToKiB(details.value, details.granularity || 0.1);
|
|
2996
2982
|
const textEl = this._renderText(value);
|
|
2997
|
-
textEl.title =
|
|
2983
|
+
textEl.title = Globals.i18n.formatBytes(details.value);
|
|
2998
2984
|
return textEl;
|
|
2999
2985
|
}
|
|
3000
2986
|
|
|
@@ -3005,9 +2991,9 @@ class DetailsRenderer {
|
|
|
3005
2991
|
_renderMilliseconds(details) {
|
|
3006
2992
|
let value;
|
|
3007
2993
|
if (details.displayUnit === 'duration') {
|
|
3008
|
-
value =
|
|
2994
|
+
value = Globals.i18n.formatDuration(details.value);
|
|
3009
2995
|
} else {
|
|
3010
|
-
value =
|
|
2996
|
+
value = Globals.i18n.formatMilliseconds(details.value, details.granularity || 10);
|
|
3011
2997
|
}
|
|
3012
2998
|
|
|
3013
2999
|
return this._renderText(value);
|
|
@@ -3086,7 +3072,7 @@ class DetailsRenderer {
|
|
|
3086
3072
|
* @return {Element}
|
|
3087
3073
|
*/
|
|
3088
3074
|
_renderNumeric(details) {
|
|
3089
|
-
const value =
|
|
3075
|
+
const value = Globals.i18n.formatNumber(details.value, details.granularity || 0.1);
|
|
3090
3076
|
const element = this._dom.createElement('div', 'lh-numeric');
|
|
3091
3077
|
element.textContent = value;
|
|
3092
3078
|
return element;
|
|
@@ -3465,27 +3451,18 @@ const NBSP2 = '\xa0';
|
|
|
3465
3451
|
const KiB = 1024;
|
|
3466
3452
|
const MiB = KiB * KiB;
|
|
3467
3453
|
|
|
3468
|
-
|
|
3469
|
-
* @template T
|
|
3470
|
-
*/
|
|
3471
|
-
class I18n {
|
|
3454
|
+
class I18nFormatter {
|
|
3472
3455
|
/**
|
|
3473
3456
|
* @param {LH.Locale} locale
|
|
3474
|
-
* @param {T} strings
|
|
3475
3457
|
*/
|
|
3476
|
-
constructor(locale
|
|
3458
|
+
constructor(locale) {
|
|
3477
3459
|
// When testing, use a locale with more exciting numeric formatting.
|
|
3478
3460
|
if (locale === 'en-XA') locale = 'de';
|
|
3479
3461
|
|
|
3480
3462
|
this._locale = locale;
|
|
3481
|
-
this._strings = strings;
|
|
3482
3463
|
this._cachedNumberFormatters = new Map();
|
|
3483
3464
|
}
|
|
3484
3465
|
|
|
3485
|
-
get strings() {
|
|
3486
|
-
return this._strings;
|
|
3487
|
-
}
|
|
3488
|
-
|
|
3489
3466
|
/**
|
|
3490
3467
|
* @param {number} number
|
|
3491
3468
|
* @param {number|undefined} granularity
|
|
@@ -3753,7 +3730,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3753
3730
|
const tmpl = this.dom.createComponent('metric');
|
|
3754
3731
|
const element = this.dom.find('.lh-metric', tmpl);
|
|
3755
3732
|
element.id = audit.result.id;
|
|
3756
|
-
const rating =
|
|
3733
|
+
const rating = ReportUtils.calculateRating(audit.result.score, audit.result.scoreDisplayMode);
|
|
3757
3734
|
element.classList.add(`lh-metric--${rating}`);
|
|
3758
3735
|
|
|
3759
3736
|
const titleEl = this.dom.find('.lh-metric__title', tmpl);
|
|
@@ -3801,7 +3778,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3801
3778
|
this.dom.find('span.lh-audit__display-text, div.lh-audit__display-text', element);
|
|
3802
3779
|
const sparklineWidthPct = `${details.overallSavingsMs / scale * 100}%`;
|
|
3803
3780
|
this.dom.find('div.lh-sparkline__bar', element).style.width = sparklineWidthPct;
|
|
3804
|
-
displayEl.textContent =
|
|
3781
|
+
displayEl.textContent = Globals.i18n.formatSeconds(details.overallSavingsMs, 0.01);
|
|
3805
3782
|
|
|
3806
3783
|
// Set [title] tooltips
|
|
3807
3784
|
if (audit.result.displayValue) {
|
|
@@ -3868,9 +3845,9 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3868
3845
|
});
|
|
3869
3846
|
const paramPairs = [...metricPairs];
|
|
3870
3847
|
|
|
3871
|
-
if (
|
|
3872
|
-
paramPairs.push(['device',
|
|
3873
|
-
paramPairs.push(['version',
|
|
3848
|
+
if (Globals.reportJson) {
|
|
3849
|
+
paramPairs.push(['device', Globals.reportJson.configSettings.formFactor]);
|
|
3850
|
+
paramPairs.push(['version', Globals.reportJson.lighthouseVersion]);
|
|
3874
3851
|
}
|
|
3875
3852
|
|
|
3876
3853
|
const params = new URLSearchParams(paramPairs);
|
|
@@ -3902,7 +3879,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3902
3879
|
* @override
|
|
3903
3880
|
*/
|
|
3904
3881
|
render(category, groups, options) {
|
|
3905
|
-
const strings =
|
|
3882
|
+
const strings = Globals.strings;
|
|
3906
3883
|
const element = this.dom.createElement('div', 'lh-category');
|
|
3907
3884
|
element.id = category.id;
|
|
3908
3885
|
element.append(this.renderCategoryHeader(category, groups, options));
|
|
@@ -3914,7 +3891,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3914
3891
|
|
|
3915
3892
|
// Metric descriptions toggle.
|
|
3916
3893
|
const checkboxEl = this.dom.createElement('input', 'lh-metrics-toggle__input');
|
|
3917
|
-
const checkboxId = `lh-metrics-toggle${
|
|
3894
|
+
const checkboxId = `lh-metrics-toggle${Globals.getUniqueSuffix()}`;
|
|
3918
3895
|
checkboxEl.setAttribute('aria-label', 'Toggle the display of metric descriptions');
|
|
3919
3896
|
checkboxEl.type = 'checkbox';
|
|
3920
3897
|
checkboxEl.id = checkboxId;
|
|
@@ -3924,8 +3901,8 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3924
3901
|
labelEl.htmlFor = checkboxId;
|
|
3925
3902
|
const showEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--show');
|
|
3926
3903
|
const hideEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--hide');
|
|
3927
|
-
showEl.textContent =
|
|
3928
|
-
hideEl.textContent =
|
|
3904
|
+
showEl.textContent = Globals.strings.expandView;
|
|
3905
|
+
hideEl.textContent = Globals.strings.collapseView;
|
|
3929
3906
|
|
|
3930
3907
|
const metricsBoxesEl = this.dom.createElement('div', 'lh-metrics-container');
|
|
3931
3908
|
metricsGroupEl.insertBefore(metricsBoxesEl, metricsFooterEl);
|
|
@@ -3964,7 +3941,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3964
3941
|
// Opportunities
|
|
3965
3942
|
const opportunityAudits = category.auditRefs
|
|
3966
3943
|
.filter(audit => this._classifyPerformanceAudit(audit) === 'load-opportunity')
|
|
3967
|
-
.filter(audit => !
|
|
3944
|
+
.filter(audit => !ReportUtils.showAsPassed(audit.result))
|
|
3968
3945
|
.sort((auditA, auditB) => this._getWastedMs(auditB) - this._getWastedMs(auditA));
|
|
3969
3946
|
|
|
3970
3947
|
const filterableMetrics = metricAudits.filter(a => !!a.relevantAudits);
|
|
@@ -3998,7 +3975,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
3998
3975
|
// Diagnostics
|
|
3999
3976
|
const diagnosticAudits = category.auditRefs
|
|
4000
3977
|
.filter(audit => this._classifyPerformanceAudit(audit) === 'diagnostic')
|
|
4001
|
-
.filter(audit => !
|
|
3978
|
+
.filter(audit => !ReportUtils.showAsPassed(audit.result))
|
|
4002
3979
|
.sort((a, b) => {
|
|
4003
3980
|
const scoreA = a.result.scoreDisplayMode === 'informative' ? 100 : Number(a.result.score);
|
|
4004
3981
|
const scoreB = b.result.scoreDisplayMode === 'informative' ? 100 : Number(b.result.score);
|
|
@@ -4014,7 +3991,8 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
4014
3991
|
|
|
4015
3992
|
// Passed audits
|
|
4016
3993
|
const passedAudits = category.auditRefs
|
|
4017
|
-
.filter(audit =>
|
|
3994
|
+
.filter(audit =>
|
|
3995
|
+
this._classifyPerformanceAudit(audit) && ReportUtils.showAsPassed(audit.result));
|
|
4018
3996
|
|
|
4019
3997
|
if (!passedAudits.length) return element;
|
|
4020
3998
|
|
|
@@ -4057,7 +4035,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
4057
4035
|
renderMetricAuditFilter(filterableMetrics, categoryEl) {
|
|
4058
4036
|
const metricFilterEl = this.dom.createElement('div', 'lh-metricfilter');
|
|
4059
4037
|
const textEl = this.dom.createChildOf(metricFilterEl, 'span', 'lh-metricfilter__text');
|
|
4060
|
-
textEl.textContent =
|
|
4038
|
+
textEl.textContent = Globals.strings.showRelevantAudits;
|
|
4061
4039
|
|
|
4062
4040
|
const filterChoices = /** @type {LH.ReportResult.AuditRef[]} */ ([
|
|
4063
4041
|
({acronym: 'All'}),
|
|
@@ -4066,7 +4044,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
4066
4044
|
|
|
4067
4045
|
// Form labels need to reference unique IDs, but multiple reports rendered in the same DOM (eg PSI)
|
|
4068
4046
|
// would mean ID conflict. To address this, we 'scope' these radio inputs with a unique suffix.
|
|
4069
|
-
const uniqSuffix =
|
|
4047
|
+
const uniqSuffix = Globals.getUniqueSuffix();
|
|
4070
4048
|
for (const metric of filterChoices) {
|
|
4071
4049
|
const elemId = `metric-${metric.acronym}-${uniqSuffix}`;
|
|
4072
4050
|
const radioEl = this.dom.createChildOf(metricFilterEl, 'input', 'lh-metricfilter__radio');
|
|
@@ -4217,7 +4195,7 @@ class PwaCategoryRenderer extends CategoryRenderer {
|
|
|
4217
4195
|
|
|
4218
4196
|
// Remove any that have a failing audit.
|
|
4219
4197
|
for (const auditRef of auditRefs) {
|
|
4220
|
-
if (!
|
|
4198
|
+
if (!ReportUtils.showAsPassed(auditRef.result) && auditRef.group) {
|
|
4221
4199
|
uniqueGroupIds.delete(auditRef.group);
|
|
4222
4200
|
}
|
|
4223
4201
|
}
|
|
@@ -4238,7 +4216,7 @@ class PwaCategoryRenderer extends CategoryRenderer {
|
|
|
4238
4216
|
for (const groupId of groupIds) {
|
|
4239
4217
|
const groupAuditRefs = auditRefs.filter(ref => ref.group === groupId);
|
|
4240
4218
|
const auditCount = groupAuditRefs.length;
|
|
4241
|
-
const passedCount = groupAuditRefs.filter(ref =>
|
|
4219
|
+
const passedCount = groupAuditRefs.filter(ref => ReportUtils.showAsPassed(ref.result)).length;
|
|
4242
4220
|
|
|
4243
4221
|
const title = groupDefinitions[groupId].title;
|
|
4244
4222
|
tips.push(`${title}: ${passedCount}/${auditCount}`);
|
|
@@ -4278,7 +4256,7 @@ class PwaCategoryRenderer extends CategoryRenderer {
|
|
|
4278
4256
|
const defsEl = svgRoot.querySelector('defs');
|
|
4279
4257
|
if (!defsEl) return;
|
|
4280
4258
|
|
|
4281
|
-
const idSuffix =
|
|
4259
|
+
const idSuffix = Globals.getUniqueSuffix();
|
|
4282
4260
|
const elementsToUpdate = defsEl.querySelectorAll('[id]');
|
|
4283
4261
|
for (const el of elementsToUpdate) {
|
|
4284
4262
|
const oldId = el.id;
|
|
@@ -4320,7 +4298,6 @@ class PwaCategoryRenderer extends CategoryRenderer {
|
|
|
4320
4298
|
* (this is handled by terser)
|
|
4321
4299
|
*/
|
|
4322
4300
|
|
|
4323
|
-
|
|
4324
4301
|
class ReportRenderer {
|
|
4325
4302
|
/**
|
|
4326
4303
|
* @param {DOM} dom
|
|
@@ -4359,7 +4336,7 @@ class ReportRenderer {
|
|
|
4359
4336
|
|
|
4360
4337
|
this._dom.setLighthouseChannel(lhr.configSettings.channel || 'unknown');
|
|
4361
4338
|
|
|
4362
|
-
const report =
|
|
4339
|
+
const report = ReportUtils.prepareReportResult(lhr);
|
|
4363
4340
|
|
|
4364
4341
|
this._dom.rootEl.textContent = ''; // Remove previous report.
|
|
4365
4342
|
this._dom.rootEl.append(this._renderReport(report));
|
|
@@ -4400,7 +4377,7 @@ class ReportRenderer {
|
|
|
4400
4377
|
|
|
4401
4378
|
this._renderMetaBlock(report, footer);
|
|
4402
4379
|
|
|
4403
|
-
this._dom.find('.lh-footer__version_issue', footer).textContent =
|
|
4380
|
+
this._dom.find('.lh-footer__version_issue', footer).textContent = Globals.strings.footerIssue;
|
|
4404
4381
|
this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion;
|
|
4405
4382
|
return footer;
|
|
4406
4383
|
}
|
|
@@ -4410,7 +4387,7 @@ class ReportRenderer {
|
|
|
4410
4387
|
* @param {DocumentFragment} footer
|
|
4411
4388
|
*/
|
|
4412
4389
|
_renderMetaBlock(report, footer) {
|
|
4413
|
-
const envValues =
|
|
4390
|
+
const envValues = ReportUtils.getEmulationDescriptions(report.configSettings || {});
|
|
4414
4391
|
const match = report.userAgent.match(/(\w*Chrome\/[\d.]+)/); // \w* to include 'HeadlessChrome'
|
|
4415
4392
|
const chromeVer = Array.isArray(match)
|
|
4416
4393
|
? match[1].replace('/', ' ').replace('Chrome', 'Chromium')
|
|
@@ -4420,35 +4397,35 @@ class ReportRenderer {
|
|
|
4420
4397
|
const axeVersion = report.environment.credits?.['axe-core'];
|
|
4421
4398
|
|
|
4422
4399
|
const devicesTooltipTextLines = [
|
|
4423
|
-
`${
|
|
4424
|
-
`${
|
|
4400
|
+
`${Globals.strings.runtimeSettingsBenchmark}: ${benchmarkIndex}`,
|
|
4401
|
+
`${Globals.strings.runtimeSettingsCPUThrottling}: ${envValues.cpuThrottling}`,
|
|
4425
4402
|
];
|
|
4426
4403
|
if (envValues.screenEmulation) {
|
|
4427
4404
|
devicesTooltipTextLines.push(
|
|
4428
|
-
`${
|
|
4405
|
+
`${Globals.strings.runtimeSettingsScreenEmulation}: ${envValues.screenEmulation}`);
|
|
4429
4406
|
}
|
|
4430
4407
|
if (axeVersion) {
|
|
4431
|
-
devicesTooltipTextLines.push(`${
|
|
4408
|
+
devicesTooltipTextLines.push(`${Globals.strings.runtimeSettingsAxeVersion}: ${axeVersion}`);
|
|
4432
4409
|
}
|
|
4433
4410
|
|
|
4434
4411
|
// [CSS icon class, textContent, tooltipText]
|
|
4435
4412
|
const metaItems = [
|
|
4436
4413
|
['date',
|
|
4437
|
-
`Captured at ${
|
|
4414
|
+
`Captured at ${Globals.i18n.formatDateTime(report.fetchTime)}`],
|
|
4438
4415
|
['devices',
|
|
4439
4416
|
`${envValues.deviceEmulation} with Lighthouse ${report.lighthouseVersion}`,
|
|
4440
4417
|
devicesTooltipTextLines.join('\n')],
|
|
4441
4418
|
['samples-one',
|
|
4442
|
-
|
|
4443
|
-
|
|
4419
|
+
Globals.strings.runtimeSingleLoad,
|
|
4420
|
+
Globals.strings.runtimeSingleLoadTooltip],
|
|
4444
4421
|
['stopwatch',
|
|
4445
|
-
|
|
4422
|
+
Globals.strings.runtimeAnalysisWindow],
|
|
4446
4423
|
['networkspeed',
|
|
4447
4424
|
`${envValues.summary}`,
|
|
4448
|
-
`${
|
|
4425
|
+
`${Globals.strings.runtimeSettingsNetworkThrottling}: ${envValues.networkThrottling}`],
|
|
4449
4426
|
['chrome',
|
|
4450
4427
|
`Using ${chromeVer}` + (channel ? ` with ${channel}` : ''),
|
|
4451
|
-
`${
|
|
4428
|
+
`${Globals.strings.runtimeSettingsUANetwork}: "${report.environment.networkUserAgent}"`],
|
|
4452
4429
|
];
|
|
4453
4430
|
|
|
4454
4431
|
const metaItemsEl = this._dom.find('.lh-meta__items', footer);
|
|
@@ -4476,7 +4453,7 @@ class ReportRenderer {
|
|
|
4476
4453
|
|
|
4477
4454
|
const container = this._dom.createComponent('warningsToplevel');
|
|
4478
4455
|
const message = this._dom.find('.lh-warnings__msg', container);
|
|
4479
|
-
message.textContent =
|
|
4456
|
+
message.textContent = Globals.strings.toplevelWarningsMessage;
|
|
4480
4457
|
|
|
4481
4458
|
const warnings = [];
|
|
4482
4459
|
for (const warningString of report.runWarnings) {
|
|
@@ -4530,7 +4507,7 @@ class ReportRenderer {
|
|
|
4530
4507
|
}
|
|
4531
4508
|
|
|
4532
4509
|
|
|
4533
|
-
if (
|
|
4510
|
+
if (ReportUtils.isPluginCategory(category.id)) {
|
|
4534
4511
|
pluginGauges.push(categoryGauge);
|
|
4535
4512
|
} else if (renderer.renderCategoryScore === categoryRenderer.renderCategoryScore) {
|
|
4536
4513
|
// The renderer for default categories is just the default CategoryRenderer.
|
|
@@ -4552,13 +4529,11 @@ class ReportRenderer {
|
|
|
4552
4529
|
* @return {!DocumentFragment}
|
|
4553
4530
|
*/
|
|
4554
4531
|
_renderReport(report) {
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4532
|
+
Globals.apply({
|
|
4533
|
+
providedStrings: report.i18n.rendererFormattedStrings,
|
|
4534
|
+
i18n: new I18nFormatter(report.configSettings.locale),
|
|
4535
|
+
reportJson: report,
|
|
4559
4536
|
});
|
|
4560
|
-
Util.i18n = i18n;
|
|
4561
|
-
Util.reportJson = report;
|
|
4562
4537
|
|
|
4563
4538
|
const detailsRenderer = new DetailsRenderer(this._dom, {
|
|
4564
4539
|
fullPageScreenshot: report.fullPageScreenshot ?? undefined,
|
|
@@ -5539,7 +5514,7 @@ class ReportUIFeatures {
|
|
|
5539
5514
|
this.json.audits['script-treemap-data'] && this.json.audits['script-treemap-data'].details;
|
|
5540
5515
|
if (showTreemapApp) {
|
|
5541
5516
|
this.addButton({
|
|
5542
|
-
text:
|
|
5517
|
+
text: Globals.strings.viewTreemapLabel,
|
|
5543
5518
|
icon: 'treemap',
|
|
5544
5519
|
onClick: () => openTreemap(this.json),
|
|
5545
5520
|
});
|
|
@@ -5548,8 +5523,8 @@ class ReportUIFeatures {
|
|
|
5548
5523
|
if (this._opts.onViewTrace) {
|
|
5549
5524
|
this.addButton({
|
|
5550
5525
|
text: lhr.configSettings.throttlingMethod === 'simulate' ?
|
|
5551
|
-
|
|
5552
|
-
|
|
5526
|
+
Globals.strings.viewOriginalTraceLabel :
|
|
5527
|
+
Globals.strings.viewTraceLabel,
|
|
5553
5528
|
onClick: () => this._opts.onViewTrace?.(),
|
|
5554
5529
|
});
|
|
5555
5530
|
}
|
|
@@ -5560,11 +5535,11 @@ class ReportUIFeatures {
|
|
|
5560
5535
|
|
|
5561
5536
|
// Fill in all i18n data.
|
|
5562
5537
|
for (const node of this._dom.findAll('[data-i18n]', this._dom.rootEl)) {
|
|
5563
|
-
// These strings are guaranteed to (at least) have a default English string in
|
|
5538
|
+
// These strings are guaranteed to (at least) have a default English string in UIStrings,
|
|
5564
5539
|
// so this cannot be undefined as long as `report-ui-features.data-i18n` test passes.
|
|
5565
5540
|
const i18nKey = node.getAttribute('data-i18n');
|
|
5566
|
-
const i18nAttr = /** @type {keyof typeof
|
|
5567
|
-
node.textContent =
|
|
5541
|
+
const i18nAttr = /** @type {keyof typeof Globals.strings} */ (i18nKey);
|
|
5542
|
+
node.textContent = Globals.strings[i18nAttr];
|
|
5568
5543
|
}
|
|
5569
5544
|
}
|
|
5570
5545
|
|
|
@@ -5706,7 +5681,7 @@ class ReportUIFeatures {
|
|
|
5706
5681
|
this._dom.find('.lh-3p-filter-count', filterTemplate).textContent =
|
|
5707
5682
|
`${thirdPartyRows.length}`;
|
|
5708
5683
|
this._dom.find('.lh-3p-ui-string', filterTemplate).textContent =
|
|
5709
|
-
|
|
5684
|
+
Globals.strings.thirdPartyResourcesLabel;
|
|
5710
5685
|
|
|
5711
5686
|
const allThirdParty = thirdPartyRows.length === rowEls.length;
|
|
5712
5687
|
const allFirstParty = !thirdPartyRows.length;
|