lighthouse 11.1.0-dev.20231002 → 11.1.0-dev.20231004
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +8 -0
- package/core/audits/audit.js +21 -11
- package/core/audits/bf-cache.js +1 -0
- package/core/audits/bootup-time.js +4 -1
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
- package/core/audits/byte-efficiency/duplicated-javascript.js +1 -1
- package/core/audits/byte-efficiency/efficient-animated-content.js +1 -1
- package/core/audits/byte-efficiency/legacy-javascript.js +1 -1
- package/core/audits/byte-efficiency/modern-image-formats.js +1 -1
- package/core/audits/byte-efficiency/offscreen-images.js +1 -1
- package/core/audits/byte-efficiency/render-blocking-resources.js +2 -3
- package/core/audits/byte-efficiency/total-byte-weight.js +3 -1
- package/core/audits/byte-efficiency/unminified-css.js +1 -1
- package/core/audits/byte-efficiency/unminified-javascript.js +1 -1
- package/core/audits/byte-efficiency/unused-css-rules.js +1 -1
- package/core/audits/byte-efficiency/unused-javascript.js +1 -1
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +3 -8
- package/core/audits/byte-efficiency/uses-optimized-images.js +1 -1
- package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +1 -0
- package/core/audits/byte-efficiency/uses-responsive-images.js +1 -1
- package/core/audits/byte-efficiency/uses-text-compression.js +1 -1
- package/core/audits/csp-xss.js +1 -1
- package/core/audits/dobetterweb/dom-size.js +3 -1
- package/core/audits/dobetterweb/no-document-write.js +1 -0
- package/core/audits/dobetterweb/uses-http2.js +2 -3
- package/core/audits/dobetterweb/uses-passive-event-listeners.js +1 -0
- package/core/audits/font-display.js +1 -0
- package/core/audits/installable-manifest.js +2 -2
- package/core/audits/largest-contentful-paint-element.js +3 -2
- package/core/audits/layout-shift-elements.js +8 -4
- package/core/audits/lcp-lazy-loaded.js +1 -0
- package/core/audits/long-tasks.js +8 -1
- package/core/audits/mainthread-work-breakdown.js +3 -1
- package/core/audits/metrics/cumulative-layout-shift.js +4 -1
- package/core/audits/metrics/first-contentful-paint.js +1 -0
- package/core/audits/metrics/largest-contentful-paint.js +1 -0
- package/core/audits/metrics/speed-index.js +1 -0
- package/core/audits/metrics/total-blocking-time.js +1 -0
- package/core/audits/prioritize-lcp-image.js +2 -3
- package/core/audits/redirects.js +2 -3
- package/core/audits/server-response-time.js +2 -0
- package/core/audits/third-party-facades.js +1 -0
- package/core/audits/third-party-summary.js +5 -1
- package/core/audits/unsized-images.js +1 -1
- package/core/audits/uses-rel-preconnect.js +2 -3
- package/core/audits/uses-rel-preload.js +2 -3
- package/core/audits/viewport.js +1 -0
- package/core/audits/work-during-interaction.js +6 -2
- package/core/gather/session.js +2 -0
- package/core/lib/csp-evaluator.js +13 -13
- package/dist/report/bundle.esm.js +12 -89
- package/dist/report/flow.js +17 -94
- package/dist/report/standalone.js +13 -90
- package/package.json +4 -4
- package/report/assets/styles.css +1 -78
- package/report/assets/templates.html +0 -35
- package/report/renderer/components.d.ts +2 -2
- package/report/renderer/components.js +2 -51
- package/report/renderer/performance-category-renderer.d.ts +16 -18
- package/report/renderer/performance-category-renderer.js +90 -102
- package/shared/localization/locales/en-US.json +14 -14
- package/shared/localization/locales/en-XL.json +14 -14
- package/shared/util.d.ts +22 -0
- package/shared/util.js +31 -0
- package/types/audit.d.ts +5 -1
- package/types/internal/rxjs.d.ts +3 -0
- package/types/lhr/audit-result.d.ts +16 -0
- /package/{core/lib → shared}/statistics.d.ts +0 -0
- /package/{core/lib → shared}/statistics.js +0 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import {CategoryRenderer} from './category-renderer.js';
|
|
10
10
|
import {ReportUtils} from './report-utils.js';
|
|
11
11
|
import {Globals} from './report-globals.js';
|
|
12
|
+
import {Util} from '../../shared/util.js';
|
|
12
13
|
import {createGauge, updateGauge} from './explodey-gauge.js';
|
|
13
14
|
|
|
14
15
|
export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
@@ -44,61 +45,6 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
44
45
|
return element;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/**
|
|
48
|
-
* @param {LH.ReportResult.AuditRef} audit
|
|
49
|
-
* @param {number} scale
|
|
50
|
-
* @return {!Element}
|
|
51
|
-
*/
|
|
52
|
-
_renderOpportunity(audit, scale) {
|
|
53
|
-
const oppTmpl = this.dom.createComponent('opportunity');
|
|
54
|
-
const element = this.populateAuditValues(audit, oppTmpl);
|
|
55
|
-
element.id = audit.result.id;
|
|
56
|
-
|
|
57
|
-
if (!audit.result.details || audit.result.scoreDisplayMode === 'error') {
|
|
58
|
-
return element;
|
|
59
|
-
}
|
|
60
|
-
const details = audit.result.details;
|
|
61
|
-
if (details.overallSavingsMs === undefined) {
|
|
62
|
-
return element;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Overwrite the displayValue with opportunity's wastedMs
|
|
66
|
-
// TODO: normalize this to one tagName.
|
|
67
|
-
const displayEl =
|
|
68
|
-
this.dom.find('span.lh-audit__display-text, div.lh-audit__display-text', element);
|
|
69
|
-
const sparklineWidthPct = `${details.overallSavingsMs / scale * 100}%`;
|
|
70
|
-
this.dom.find('div.lh-sparkline__bar', element).style.width = sparklineWidthPct;
|
|
71
|
-
displayEl.textContent = Globals.i18n.formatSeconds(details.overallSavingsMs, 0.01);
|
|
72
|
-
|
|
73
|
-
// Set [title] tooltips
|
|
74
|
-
if (audit.result.displayValue) {
|
|
75
|
-
const displayValue = audit.result.displayValue;
|
|
76
|
-
this.dom.find('div.lh-load-opportunity__sparkline', element).title = displayValue;
|
|
77
|
-
displayEl.title = displayValue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return element;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Get an audit's wastedMs to sort the opportunity by, and scale the sparkline width
|
|
85
|
-
* Opportunities with an error won't have a details object, so MIN_VALUE is returned to keep any
|
|
86
|
-
* erroring opportunities last in sort order.
|
|
87
|
-
* @param {LH.ReportResult.AuditRef} audit
|
|
88
|
-
* @return {number}
|
|
89
|
-
*/
|
|
90
|
-
_getWastedMs(audit) {
|
|
91
|
-
if (audit.result.details) {
|
|
92
|
-
const details = audit.result.details;
|
|
93
|
-
if (typeof details.overallSavingsMs !== 'number') {
|
|
94
|
-
throw new Error('non-opportunity details passed to _getWastedMs');
|
|
95
|
-
}
|
|
96
|
-
return details.overallSavingsMs;
|
|
97
|
-
} else {
|
|
98
|
-
return Number.MIN_VALUE;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
48
|
/**
|
|
103
49
|
* Get a link to the interactive scoring calculator with the metric values.
|
|
104
50
|
* @param {LH.ReportResult.AuditRef[]} auditRefs
|
|
@@ -147,18 +93,55 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
147
93
|
}
|
|
148
94
|
|
|
149
95
|
/**
|
|
150
|
-
*
|
|
151
|
-
* The audit details type will determine which of the two groups an audit is in.
|
|
96
|
+
* Returns true if the audit is a general performance insight (i.e. not a metric or hidden audit).
|
|
152
97
|
*
|
|
153
98
|
* @param {LH.ReportResult.AuditRef} audit
|
|
154
|
-
* @return {
|
|
99
|
+
* @return {boolean}
|
|
155
100
|
*/
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
101
|
+
_isPerformanceInsight(audit) {
|
|
102
|
+
return !audit.group;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns overallImpact and linearImpact for an audit.
|
|
107
|
+
* The overallImpact is determined by the audit saving's effect on the overall performance score.
|
|
108
|
+
* We use linearImpact to compare audits where their overallImpact is rounded down to 0.
|
|
109
|
+
*
|
|
110
|
+
* @param {LH.ReportResult.AuditRef} audit
|
|
111
|
+
* @param {LH.ReportResult.AuditRef[]} metricAudits
|
|
112
|
+
* @return {{overallImpact: number, overallLinearImpact: number}}
|
|
113
|
+
*/
|
|
114
|
+
overallImpact(audit, metricAudits) {
|
|
115
|
+
if (!audit.result.metricSavings) {
|
|
116
|
+
return {overallImpact: 0, overallLinearImpact: 0};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let overallImpact = 0;
|
|
120
|
+
let overallLinearImpact = 0;
|
|
121
|
+
for (const [k, savings] of Object.entries(audit.result.metricSavings)) {
|
|
122
|
+
// Get metric savings for individual audit.
|
|
123
|
+
if (savings === undefined) continue;
|
|
124
|
+
|
|
125
|
+
// Get the metric data.
|
|
126
|
+
const mAudit = metricAudits.find(audit => audit.acronym === k);
|
|
127
|
+
if (!mAudit) continue;
|
|
128
|
+
if (mAudit.result.score === null) continue;
|
|
129
|
+
|
|
130
|
+
const mValue = mAudit.result.numericValue;
|
|
131
|
+
if (!mValue) continue;
|
|
132
|
+
|
|
133
|
+
const linearImpact = savings / mValue * mAudit.weight;
|
|
134
|
+
overallLinearImpact += linearImpact;
|
|
135
|
+
|
|
136
|
+
const scoringOptions = mAudit.result.scoringOptions;
|
|
137
|
+
if (!scoringOptions) continue;
|
|
138
|
+
|
|
139
|
+
const newMetricScore = Util.computeLogNormalScore(scoringOptions, mValue - savings);
|
|
140
|
+
|
|
141
|
+
const weightedMetricImpact = (newMetricScore - mAudit.result.score) * mAudit.weight;
|
|
142
|
+
overallImpact += weightedMetricImpact;
|
|
160
143
|
}
|
|
161
|
-
return
|
|
144
|
+
return {overallImpact, overallLinearImpact};
|
|
162
145
|
}
|
|
163
146
|
|
|
164
147
|
/**
|
|
@@ -228,53 +211,58 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
228
211
|
filmstripEl && timelineEl.append(filmstripEl);
|
|
229
212
|
}
|
|
230
213
|
|
|
231
|
-
// Opportunities
|
|
232
|
-
const opportunityAudits = category.auditRefs
|
|
233
|
-
.filter(audit => this._classifyPerformanceAudit(audit) === 'load-opportunity')
|
|
234
|
-
.filter(audit => !ReportUtils.showAsPassed(audit.result))
|
|
235
|
-
.sort((auditA, auditB) => this._getWastedMs(auditB) - this._getWastedMs(auditA));
|
|
236
|
-
|
|
237
214
|
const filterableMetrics = metricAudits.filter(a => !!a.relevantAudits);
|
|
238
215
|
// TODO: only add if there are opportunities & diagnostics rendered.
|
|
239
216
|
if (filterableMetrics.length) {
|
|
240
217
|
this.renderMetricAuditFilter(filterableMetrics, element);
|
|
241
218
|
}
|
|
242
219
|
|
|
243
|
-
if (opportunityAudits.length) {
|
|
244
|
-
// Scale the sparklines relative to savings, minimum 2s to not overstate small savings
|
|
245
|
-
const minimumScale = 2000;
|
|
246
|
-
const wastedMsValues = opportunityAudits.map(audit => this._getWastedMs(audit));
|
|
247
|
-
const maxWaste = Math.max(...wastedMsValues);
|
|
248
|
-
const scale = Math.max(Math.ceil(maxWaste / 1000) * 1000, minimumScale);
|
|
249
|
-
const [groupEl, footerEl] = this.renderAuditGroup(groups['load-opportunities']);
|
|
250
|
-
const tmpl = this.dom.createComponent('opportunityHeader');
|
|
251
|
-
|
|
252
|
-
this.dom.find('.lh-load-opportunity__col--one', tmpl).textContent =
|
|
253
|
-
strings.opportunityResourceColumnLabel;
|
|
254
|
-
this.dom.find('.lh-load-opportunity__col--two', tmpl).textContent =
|
|
255
|
-
strings.opportunitySavingsColumnLabel;
|
|
256
|
-
|
|
257
|
-
const headerEl = this.dom.find('.lh-load-opportunity__header', tmpl);
|
|
258
|
-
groupEl.insertBefore(headerEl, footerEl);
|
|
259
|
-
opportunityAudits.forEach(item =>
|
|
260
|
-
groupEl.insertBefore(this._renderOpportunity(item, scale), footerEl));
|
|
261
|
-
groupEl.classList.add('lh-audit-group--load-opportunities');
|
|
262
|
-
element.append(groupEl);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
220
|
// Diagnostics
|
|
266
221
|
const diagnosticAudits = category.auditRefs
|
|
267
|
-
.filter(audit => this.
|
|
268
|
-
.filter(audit => !ReportUtils.showAsPassed(audit.result))
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
222
|
+
.filter(audit => this._isPerformanceInsight(audit))
|
|
223
|
+
.filter(audit => !ReportUtils.showAsPassed(audit.result));
|
|
224
|
+
|
|
225
|
+
/** @type {Array<{auditRef:LH.ReportResult.AuditRef, overallImpact: number, overallLinearImpact: number, guidanceLevel: number}>} */
|
|
226
|
+
const auditImpacts = [];
|
|
227
|
+
diagnosticAudits.forEach(audit => {
|
|
228
|
+
const {
|
|
229
|
+
overallImpact: overallImpact,
|
|
230
|
+
overallLinearImpact: overallLinearImpact,
|
|
231
|
+
} = this.overallImpact(audit, metricAudits);
|
|
232
|
+
const guidanceLevel = audit.result.guidanceLevel ?? 1;
|
|
233
|
+
auditImpacts.push({auditRef: audit, overallImpact, overallLinearImpact, guidanceLevel});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
auditImpacts.sort((a, b) => {
|
|
237
|
+
// Performance diagnostics should only have score display modes of "informative" and "metricSavings"
|
|
238
|
+
// If the score display mode is "metricSavings", the `score` will be a coarse approximation of the overall impact.
|
|
239
|
+
// Therefore, it makes sense to sort audits by score first to ensure visual clarity with the score icons.
|
|
240
|
+
const scoreA = a.auditRef.result.scoreDisplayMode === 'informative'
|
|
241
|
+
? 100
|
|
242
|
+
: Number(a.auditRef.result.score);
|
|
243
|
+
const scoreB = b.auditRef.result.scoreDisplayMode === 'informative'
|
|
244
|
+
? 100
|
|
245
|
+
: Number(b.auditRef.result.score);
|
|
246
|
+
if (scoreA !== scoreB) return scoreA - scoreB;
|
|
247
|
+
|
|
248
|
+
// Overall impact is the estimated improvement to the performance score
|
|
249
|
+
if (a.overallImpact !== b.overallImpact) return b.overallImpact - a.overallImpact;
|
|
250
|
+
|
|
251
|
+
// Fall back to the linear impact if the normal impact is rounded down to 0
|
|
252
|
+
if (
|
|
253
|
+
a.overallImpact === 0 && b.overallImpact === 0 &&
|
|
254
|
+
a.overallLinearImpact !== b.overallLinearImpact
|
|
255
|
+
) {
|
|
256
|
+
return b.overallLinearImpact - a.overallLinearImpact;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Audits that have no estimated savings should be prioritized by the guidance level
|
|
260
|
+
return b.guidanceLevel - a.guidanceLevel;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (auditImpacts.length) {
|
|
276
264
|
const [groupEl, footerEl] = this.renderAuditGroup(groups['diagnostics']);
|
|
277
|
-
|
|
265
|
+
auditImpacts.forEach(item => groupEl.insertBefore(this.renderAudit(item.auditRef), footerEl));
|
|
278
266
|
groupEl.classList.add('lh-audit-group--diagnostics');
|
|
279
267
|
element.append(groupEl);
|
|
280
268
|
}
|
|
@@ -282,7 +270,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
282
270
|
// Passed audits
|
|
283
271
|
const passedAudits = category.auditRefs
|
|
284
272
|
.filter(audit =>
|
|
285
|
-
this.
|
|
273
|
+
this._isPerformanceInsight(audit) && ReportUtils.showAsPassed(audit.result));
|
|
286
274
|
|
|
287
275
|
if (!passedAudits.length) return element;
|
|
288
276
|
|
|
@@ -774,7 +774,7 @@
|
|
|
774
774
|
"message": "Syntax"
|
|
775
775
|
},
|
|
776
776
|
"core/audits/csp-xss.js | metaTagMessage": {
|
|
777
|
-
"message": "The page contains a CSP defined in a
|
|
777
|
+
"message": "The page contains a CSP defined in a `<meta>` tag. Consider moving the CSP to an HTTP header or defining another strict CSP in an HTTP header."
|
|
778
778
|
},
|
|
779
779
|
"core/audits/csp-xss.js | noCsp": {
|
|
780
780
|
"message": "No CSP found in enforcement mode"
|
|
@@ -1026,7 +1026,7 @@
|
|
|
1026
1026
|
"message": "Page is loaded in an incognito window"
|
|
1027
1027
|
},
|
|
1028
1028
|
"core/audits/installable-manifest.js | manifest-display-not-supported": {
|
|
1029
|
-
"message": "Manifest
|
|
1029
|
+
"message": "Manifest `display` property must be one of `standalone`, `fullscreen`, or `minimal-ui`"
|
|
1030
1030
|
},
|
|
1031
1031
|
"core/audits/installable-manifest.js | manifest-display-override-not-supported": {
|
|
1032
1032
|
"message": "Manifest contains 'display_override' field, and the first supported display mode must be one of 'standalone', 'fullscreen', or 'minimal-ui'"
|
|
@@ -1038,7 +1038,7 @@
|
|
|
1038
1038
|
"message": "Manifest URL changed while the manifest was being fetched."
|
|
1039
1039
|
},
|
|
1040
1040
|
"core/audits/installable-manifest.js | manifest-missing-name-or-short-name": {
|
|
1041
|
-
"message": "Manifest does not contain a
|
|
1041
|
+
"message": "Manifest does not contain a `name` or `short_name` field"
|
|
1042
1042
|
},
|
|
1043
1043
|
"core/audits/installable-manifest.js | manifest-missing-suitable-icon": {
|
|
1044
1044
|
"message": "Manifest does not contain a suitable icon - PNG, SVG or WebP format of at least {value0} px is required, the sizes attribute must be set, and the purpose attribute, if set, must include \"any\"."
|
|
@@ -2211,25 +2211,25 @@
|
|
|
2211
2211
|
"message": "Pages that use WebXR are not currently eligible for back/forward cache."
|
|
2212
2212
|
},
|
|
2213
2213
|
"core/lib/csp-evaluator.js | allowlistFallback": {
|
|
2214
|
-
"message": "Consider adding https: and http: URL schemes (ignored by browsers supporting 'strict-dynamic') to be backward compatible with older browsers."
|
|
2214
|
+
"message": "Consider adding https: and http: URL schemes (ignored by browsers supporting `'strict-dynamic'`) to be backward compatible with older browsers."
|
|
2215
2215
|
},
|
|
2216
2216
|
"core/lib/csp-evaluator.js | deprecatedDisownOpener": {
|
|
2217
|
-
"message": "disown-opener is deprecated since CSP3. Please, use the Cross-Origin-Opener-Policy header instead."
|
|
2217
|
+
"message": "`disown-opener` is deprecated since CSP3. Please, use the Cross-Origin-Opener-Policy header instead."
|
|
2218
2218
|
},
|
|
2219
2219
|
"core/lib/csp-evaluator.js | deprecatedReferrer": {
|
|
2220
|
-
"message": "referrer is deprecated since CSP2. Please, use the Referrer-Policy header instead."
|
|
2220
|
+
"message": "`referrer` is deprecated since CSP2. Please, use the Referrer-Policy header instead."
|
|
2221
2221
|
},
|
|
2222
2222
|
"core/lib/csp-evaluator.js | deprecatedReflectedXSS": {
|
|
2223
|
-
"message": "reflected-xss is deprecated since CSP2. Please, use the X-XSS-Protection header instead."
|
|
2223
|
+
"message": "`reflected-xss` is deprecated since CSP2. Please, use the X-XSS-Protection header instead."
|
|
2224
2224
|
},
|
|
2225
2225
|
"core/lib/csp-evaluator.js | missingBaseUri": {
|
|
2226
|
-
"message": "Missing base-uri allows injected
|
|
2226
|
+
"message": "Missing `base-uri` allows injected `<base>` tags to set the base URL for all relative URLs (e.g. scripts) to an attacker controlled domain. Consider setting `base-uri` to `'none'` or `'self'`."
|
|
2227
2227
|
},
|
|
2228
2228
|
"core/lib/csp-evaluator.js | missingObjectSrc": {
|
|
2229
|
-
"message": "Missing object-src allows the injection of plugins that execute unsafe scripts. Consider setting object-src to 'none' if you can."
|
|
2229
|
+
"message": "Missing `object-src` allows the injection of plugins that execute unsafe scripts. Consider setting `object-src` to `'none'` if you can."
|
|
2230
2230
|
},
|
|
2231
2231
|
"core/lib/csp-evaluator.js | missingScriptSrc": {
|
|
2232
|
-
"message": "script-src directive is missing. This can allow the execution of unsafe scripts."
|
|
2232
|
+
"message": "`script-src` directive is missing. This can allow the execution of unsafe scripts."
|
|
2233
2233
|
},
|
|
2234
2234
|
"core/lib/csp-evaluator.js | missingSemicolon": {
|
|
2235
2235
|
"message": "Did you forget the semicolon? {keyword} seems to be a directive, not a keyword."
|
|
@@ -2250,10 +2250,10 @@
|
|
|
2250
2250
|
"message": "No CSP configures a reporting destination. This makes it difficult to maintain the CSP over time and monitor for any breakages."
|
|
2251
2251
|
},
|
|
2252
2252
|
"core/lib/csp-evaluator.js | reportToOnly": {
|
|
2253
|
-
"message": "The reporting destination is only configured via the report-to directive. This directive is only supported in Chromium-based browsers so it is recommended to also use a report-uri directive."
|
|
2253
|
+
"message": "The reporting destination is only configured via the report-to directive. This directive is only supported in Chromium-based browsers so it is recommended to also use a `report-uri` directive."
|
|
2254
2254
|
},
|
|
2255
2255
|
"core/lib/csp-evaluator.js | strictDynamic": {
|
|
2256
|
-
"message": "Host allowlists can frequently be bypassed. Consider using CSP nonces or hashes instead, along with 'strict-dynamic' if necessary."
|
|
2256
|
+
"message": "Host allowlists can frequently be bypassed. Consider using CSP nonces or hashes instead, along with `'strict-dynamic'` if necessary."
|
|
2257
2257
|
},
|
|
2258
2258
|
"core/lib/csp-evaluator.js | unknownDirective": {
|
|
2259
2259
|
"message": "Unknown CSP directive."
|
|
@@ -2262,10 +2262,10 @@
|
|
|
2262
2262
|
"message": "{keyword} seems to be an invalid keyword."
|
|
2263
2263
|
},
|
|
2264
2264
|
"core/lib/csp-evaluator.js | unsafeInline": {
|
|
2265
|
-
"message": "'unsafe-inline' allows the execution of unsafe in-page scripts and event handlers. Consider using CSP nonces or hashes to allow scripts individually."
|
|
2265
|
+
"message": "`'unsafe-inline'` allows the execution of unsafe in-page scripts and event handlers. Consider using CSP nonces or hashes to allow scripts individually."
|
|
2266
2266
|
},
|
|
2267
2267
|
"core/lib/csp-evaluator.js | unsafeInlineFallback": {
|
|
2268
|
-
"message": "Consider adding 'unsafe-inline' (ignored by browsers supporting nonces/hashes) to be backward compatible with older browsers."
|
|
2268
|
+
"message": "Consider adding `'unsafe-inline'` (ignored by browsers supporting nonces/hashes) to be backward compatible with older browsers."
|
|
2269
2269
|
},
|
|
2270
2270
|
"core/lib/deprecation-description.js | feature": {
|
|
2271
2271
|
"message": "Check the feature status page for more details."
|
|
@@ -774,7 +774,7 @@
|
|
|
774
774
|
"message": "Ŝýn̂t́âx́"
|
|
775
775
|
},
|
|
776
776
|
"core/audits/csp-xss.js | metaTagMessage": {
|
|
777
|
-
"message": "T̂h́ê ṕâǵê ćôńt̂áîńŝ á ĈŚP̂ d́êf́îńêd́ îń â
|
|
777
|
+
"message": "T̂h́ê ṕâǵê ćôńt̂áîńŝ á ĈŚP̂ d́êf́îńêd́ îń â `<meta>` t́âǵ. Ĉón̂śîd́êŕ m̂óv̂ín̂ǵ t̂h́ê ĆŜṔ t̂ó âń ĤT́T̂Ṕ ĥéâd́êŕ ôŕ d̂éf̂ín̂ín̂ǵ âńôt́ĥér̂ śt̂ŕîćt̂ ĆŜṔ îń âń ĤT́T̂Ṕ ĥéâd́êŕ."
|
|
778
778
|
},
|
|
779
779
|
"core/audits/csp-xss.js | noCsp": {
|
|
780
780
|
"message": "N̂ó ĈŚP̂ f́ôún̂d́ îń êńf̂ór̂ćêḿêńt̂ ḿôd́ê"
|
|
@@ -1026,7 +1026,7 @@
|
|
|
1026
1026
|
"message": "P̂áĝé îś l̂óâd́êd́ îń âń îńĉóĝńît́ô ẃîńd̂óŵ"
|
|
1027
1027
|
},
|
|
1028
1028
|
"core/audits/installable-manifest.js | manifest-display-not-supported": {
|
|
1029
|
-
"message": "M̂án̂íf̂éŝt́
|
|
1029
|
+
"message": "M̂án̂íf̂éŝt́ `display` p̂ŕôṕêŕt̂ý m̂úŝt́ b̂é ôńê óf̂ `standalone`, `fullscreen`, ór̂ `minimal-ui`"
|
|
1030
1030
|
},
|
|
1031
1031
|
"core/audits/installable-manifest.js | manifest-display-override-not-supported": {
|
|
1032
1032
|
"message": "M̂án̂íf̂éŝt́ ĉón̂t́âín̂ś 'd̂íŝṕl̂áŷ_óv̂ér̂ŕîd́ê' f́îél̂d́, âńd̂ t́ĥé f̂ír̂śt̂ śûṕp̂ór̂t́êd́ d̂íŝṕl̂áŷ ḿôd́ê ḿûśt̂ b́ê ón̂é ôf́ 'ŝt́âńd̂ál̂ón̂é', 'f̂úl̂ĺŝćr̂éêń', ôŕ 'm̂ín̂ím̂ál̂-úî'"
|
|
@@ -1038,7 +1038,7 @@
|
|
|
1038
1038
|
"message": "M̂án̂íf̂éŝt́ ÛŔL̂ ćĥán̂ǵêd́ ŵh́îĺê t́ĥé m̂án̂íf̂éŝt́ ŵáŝ b́êín̂ǵ f̂ét̂ćĥéd̂."
|
|
1039
1039
|
},
|
|
1040
1040
|
"core/audits/installable-manifest.js | manifest-missing-name-or-short-name": {
|
|
1041
|
-
"message": "M̂án̂íf̂éŝt́ d̂óêś n̂ót̂ ćôńt̂áîń â
|
|
1041
|
+
"message": "M̂án̂íf̂éŝt́ d̂óêś n̂ót̂ ćôńt̂áîń â `name` ór̂ `short_name` f́îél̂d́"
|
|
1042
1042
|
},
|
|
1043
1043
|
"core/audits/installable-manifest.js | manifest-missing-suitable-icon": {
|
|
1044
1044
|
"message": "M̂án̂íf̂éŝt́ d̂óêś n̂ót̂ ćôńt̂áîń â śûít̂áb̂ĺê íĉón̂ - ṔN̂Ǵ, ŜV́Ĝ ór̂ Ẃêb́P̂ f́ôŕm̂át̂ óf̂ át̂ ĺêáŝt́ {value0} p̂x́ îś r̂éq̂úîŕêd́, t̂h́ê śîźêś ât́t̂ŕîb́ût́ê ḿûśt̂ b́ê śêt́, âńd̂ t́ĥé p̂úr̂ṕôśê át̂t́r̂íb̂út̂é, îf́ ŝét̂, ḿûśt̂ ín̂ćl̂úd̂é \"âńŷ\"."
|
|
@@ -2211,25 +2211,25 @@
|
|
|
2211
2211
|
"message": "P̂áĝéŝ t́ĥát̂ úŝé Ŵéb̂X́R̂ ár̂é n̂ót̂ ćûŕr̂én̂t́l̂ý êĺîǵîb́l̂é f̂ór̂ b́âćk̂/f́ôŕŵár̂d́ ĉáĉh́ê."
|
|
2212
2212
|
},
|
|
2213
2213
|
"core/lib/csp-evaluator.js | allowlistFallback": {
|
|
2214
|
-
"message": "Ĉón̂śîd́êŕ âd́d̂ín̂ǵ ĥt́t̂ṕŝ: án̂d́ ĥt́t̂ṕ: ÛŔL̂ śĉh́êḿêś (îǵn̂ór̂éd̂ b́ŷ b́r̂óŵśêŕŝ śûṕp̂ór̂t́îńĝ '
|
|
2214
|
+
"message": "Ĉón̂śîd́êŕ âd́d̂ín̂ǵ ĥt́t̂ṕŝ: án̂d́ ĥt́t̂ṕ: ÛŔL̂ śĉh́êḿêś (îǵn̂ór̂éd̂ b́ŷ b́r̂óŵśêŕŝ śûṕp̂ór̂t́îńĝ `'strict-dynamic'`) t́ô b́ê b́âćk̂ẃâŕd̂ ćôḿp̂át̂íb̂ĺê ẃît́ĥ ól̂d́êŕ b̂ŕôẃŝér̂ś."
|
|
2215
2215
|
},
|
|
2216
2216
|
"core/lib/csp-evaluator.js | deprecatedDisownOpener": {
|
|
2217
|
-
"message": "
|
|
2217
|
+
"message": "`disown-opener` îś d̂ép̂ŕêćât́êd́ ŝín̂ćê ĆŜṔ3. P̂ĺêáŝé, ûśê t́ĥé Ĉŕôśŝ-Ór̂íĝín̂-Óp̂én̂ér̂-Ṕôĺîćŷ h́êád̂ér̂ ín̂śt̂éâd́."
|
|
2218
2218
|
},
|
|
2219
2219
|
"core/lib/csp-evaluator.js | deprecatedReferrer": {
|
|
2220
|
-
"message": "
|
|
2220
|
+
"message": "`referrer` îś d̂ép̂ŕêćât́êd́ ŝín̂ćê ĆŜṔ2. P̂ĺêáŝé, ûśê t́ĥé R̂éf̂ér̂ŕêŕ-P̂ól̂íĉý ĥéâd́êŕ îńŝt́êád̂."
|
|
2221
2221
|
},
|
|
2222
2222
|
"core/lib/csp-evaluator.js | deprecatedReflectedXSS": {
|
|
2223
|
-
"message": "
|
|
2223
|
+
"message": "`reflected-xss` îś d̂ép̂ŕêćât́êd́ ŝín̂ćê ĆŜṔ2. P̂ĺêáŝé, ûśê t́ĥé X̂-X́ŜŚ-P̂ŕôt́êćt̂íôń ĥéâd́êŕ îńŝt́êád̂."
|
|
2224
2224
|
},
|
|
2225
2225
|
"core/lib/csp-evaluator.js | missingBaseUri": {
|
|
2226
|
-
"message": "M̂íŝśîńĝ
|
|
2226
|
+
"message": "M̂íŝśîńĝ `base-uri` ál̂ĺôẃŝ ín̂j́êćt̂éd̂ `<base>` t́âǵŝ t́ô śêt́ t̂h́ê b́âśê ÚR̂Ĺ f̂ór̂ ál̂ĺ r̂él̂át̂ív̂é ÛŔL̂ś (ê.ǵ. ŝćr̂íp̂t́ŝ) t́ô án̂ át̂t́âćk̂ér̂ ćôńt̂ŕôĺl̂éd̂ d́ôḿâín̂. Ćôńŝíd̂ér̂ śêt́t̂ín̂ǵ `base-uri` t̂ó `'none'` ôŕ `'self'`."
|
|
2227
2227
|
},
|
|
2228
2228
|
"core/lib/csp-evaluator.js | missingObjectSrc": {
|
|
2229
|
-
"message": "M̂íŝśîńĝ
|
|
2229
|
+
"message": "M̂íŝśîńĝ `object-src` ál̂ĺôẃŝ t́ĥé îńĵéĉt́îón̂ óf̂ ṕl̂úĝín̂ś t̂h́ât́ êx́êćût́ê ún̂śâf́ê śĉŕîṕt̂ś. Ĉón̂śîd́êŕ ŝét̂t́îńĝ `object-src` t́ô `'none'` íf̂ ýôú ĉán̂."
|
|
2230
2230
|
},
|
|
2231
2231
|
"core/lib/csp-evaluator.js | missingScriptSrc": {
|
|
2232
|
-
"message": "
|
|
2232
|
+
"message": "`script-src` d̂ír̂éĉt́îv́ê íŝ ḿîśŝín̂ǵ. T̂h́îś ĉán̂ ál̂ĺôẃ t̂h́ê éx̂éĉút̂íôń ôf́ ûńŝáf̂é ŝćr̂íp̂t́ŝ."
|
|
2233
2233
|
},
|
|
2234
2234
|
"core/lib/csp-evaluator.js | missingSemicolon": {
|
|
2235
2235
|
"message": "D̂íd̂ ýôú f̂ór̂ǵêt́ t̂h́ê śêḿîćôĺôń? {keyword} ŝéêḿŝ t́ô b́ê á d̂ír̂éĉt́îv́ê, ńôt́ â ḱêýŵór̂d́."
|
|
@@ -2250,10 +2250,10 @@
|
|
|
2250
2250
|
"message": "N̂ó ĈŚP̂ ćôńf̂íĝúr̂éŝ á r̂ép̂ór̂t́îńĝ d́êśt̂ín̂át̂íôń. T̂h́îś m̂ák̂éŝ ít̂ d́îf́f̂íĉúl̂t́ t̂ó m̂áîńt̂áîń t̂h́ê ĆŜṔ ôv́êŕ t̂ím̂é âńd̂ ḿôńît́ôŕ f̂ór̂ án̂ý b̂ŕêák̂áĝéŝ."
|
|
2251
2251
|
},
|
|
2252
2252
|
"core/lib/csp-evaluator.js | reportToOnly": {
|
|
2253
|
-
"message": "T̂h́ê ŕêṕôŕt̂ín̂ǵ d̂éŝt́îńât́îón̂ íŝ ón̂ĺŷ ćôńf̂íĝúr̂éd̂ v́îá t̂h́ê ŕêṕôŕt̂-t́ô d́îŕêćt̂ív̂é. T̂h́îś d̂ír̂éĉt́îv́ê íŝ ón̂ĺŷ śûṕp̂ór̂t́êd́ îń Ĉh́r̂óm̂íûḿ-b̂áŝéd̂ b́r̂óŵśêŕŝ śô ít̂ íŝ ŕêćôḿm̂én̂d́êd́ t̂ó âĺŝó ûśê á r̂é
|
|
2253
|
+
"message": "T̂h́ê ŕêṕôŕt̂ín̂ǵ d̂éŝt́îńât́îón̂ íŝ ón̂ĺŷ ćôńf̂íĝúr̂éd̂ v́îá t̂h́ê ŕêṕôŕt̂-t́ô d́îŕêćt̂ív̂é. T̂h́îś d̂ír̂éĉt́îv́ê íŝ ón̂ĺŷ śûṕp̂ór̂t́êd́ îń Ĉh́r̂óm̂íûḿ-b̂áŝéd̂ b́r̂óŵśêŕŝ śô ít̂ íŝ ŕêćôḿm̂én̂d́êd́ t̂ó âĺŝó ûśê á `report-uri` d̂ír̂éĉt́îv́ê."
|
|
2254
2254
|
},
|
|
2255
2255
|
"core/lib/csp-evaluator.js | strictDynamic": {
|
|
2256
|
-
"message": "Ĥóŝt́ âĺl̂óŵĺîśt̂ś ĉán̂ f́r̂éq̂úêńt̂ĺŷ b́ê b́ŷṕâśŝéd̂. Ćôńŝíd̂ér̂ úŝín̂ǵ ĈŚP̂ ńôńĉéŝ ór̂ h́âśĥéŝ ín̂śt̂éâd́, âĺôńĝ ẃît́ĥ '
|
|
2256
|
+
"message": "Ĥóŝt́ âĺl̂óŵĺîśt̂ś ĉán̂ f́r̂éq̂úêńt̂ĺŷ b́ê b́ŷṕâśŝéd̂. Ćôńŝíd̂ér̂ úŝín̂ǵ ĈŚP̂ ńôńĉéŝ ór̂ h́âśĥéŝ ín̂śt̂éâd́, âĺôńĝ ẃît́ĥ `'strict-dynamic'` íf̂ ńêćêśŝár̂ý."
|
|
2257
2257
|
},
|
|
2258
2258
|
"core/lib/csp-evaluator.js | unknownDirective": {
|
|
2259
2259
|
"message": "Ûńk̂ńôẃn̂ ĆŜṔ d̂ír̂éĉt́îv́ê."
|
|
@@ -2262,10 +2262,10 @@
|
|
|
2262
2262
|
"message": "{keyword} ŝéêḿŝ t́ô b́ê án̂ ín̂v́âĺîd́ k̂éŷẃôŕd̂."
|
|
2263
2263
|
},
|
|
2264
2264
|
"core/lib/csp-evaluator.js | unsafeInline": {
|
|
2265
|
-
"message": "'
|
|
2265
|
+
"message": "`'unsafe-inline'` âĺl̂óŵś t̂h́ê éx̂éĉút̂íôń ôf́ ûńŝáf̂é îń-p̂áĝé ŝćr̂íp̂t́ŝ án̂d́ êv́êńt̂ h́âńd̂ĺêŕŝ. Ćôńŝíd̂ér̂ úŝín̂ǵ ĈŚP̂ ńôńĉéŝ ór̂ h́âśĥéŝ t́ô ál̂ĺôẃ ŝćr̂íp̂t́ŝ ín̂d́îv́îd́ûál̂ĺŷ."
|
|
2266
2266
|
},
|
|
2267
2267
|
"core/lib/csp-evaluator.js | unsafeInlineFallback": {
|
|
2268
|
-
"message": "Ĉón̂śîd́êŕ âd́d̂ín̂ǵ '
|
|
2268
|
+
"message": "Ĉón̂śîd́êŕ âd́d̂ín̂ǵ `'unsafe-inline'` (îǵn̂ór̂éd̂ b́ŷ b́r̂óŵśêŕŝ śûṕp̂ór̂t́îńĝ ńôńĉéŝ/h́âśĥéŝ) t́ô b́ê b́âćk̂ẃâŕd̂ ćôḿp̂át̂íb̂ĺê ẃît́ĥ ól̂d́êŕ b̂ŕôẃŝér̂ś."
|
|
2269
2269
|
},
|
|
2270
2270
|
"core/lib/deprecation-description.js | feature": {
|
|
2271
2271
|
"message": "Ĉh́êćk̂ t́ĥé f̂éât́ûŕê śt̂át̂úŝ ṕâǵê f́ôŕ m̂ór̂é d̂ét̂áîĺŝ."
|
package/shared/util.d.ts
CHANGED
|
@@ -130,5 +130,27 @@ export class Util {
|
|
|
130
130
|
lineNumber: number;
|
|
131
131
|
truncated?: boolean | undefined;
|
|
132
132
|
}[];
|
|
133
|
+
/**
|
|
134
|
+
* Computes a score between 0 and 1 based on the measured `value`. Score is determined by
|
|
135
|
+
* considering a log-normal distribution governed by two control points (the 10th
|
|
136
|
+
* percentile value and the median value) and represents the percentage of sites that are
|
|
137
|
+
* greater than `value`.
|
|
138
|
+
*
|
|
139
|
+
* Score characteristics:
|
|
140
|
+
* - within [0, 1]
|
|
141
|
+
* - rounded to two digits
|
|
142
|
+
* - value must meet or beat a controlPoint value to meet or exceed its percentile score:
|
|
143
|
+
* - value > median will give a score < 0.5; value ≤ median will give a score ≥ 0.5.
|
|
144
|
+
* - value > p10 will give a score < 0.9; value ≤ p10 will give a score ≥ 0.9.
|
|
145
|
+
* - values < p10 will get a slight boost so a score of 1 is achievable by a
|
|
146
|
+
* `value` other than those close to 0. Scores of > ~0.99524 end up rounded to 1.
|
|
147
|
+
* @param {{median: number, p10: number}} controlPoints
|
|
148
|
+
* @param {number} value
|
|
149
|
+
* @return {number}
|
|
150
|
+
*/
|
|
151
|
+
static computeLogNormalScore(controlPoints: {
|
|
152
|
+
median: number;
|
|
153
|
+
p10: number;
|
|
154
|
+
}, value: number): number;
|
|
133
155
|
}
|
|
134
156
|
//# sourceMappingURL=util.d.ts.map
|
package/shared/util.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import * as statistics from './statistics.js';
|
|
8
|
+
|
|
7
9
|
/** @typedef {import('../types/lhr/audit-details').default.SnippetValue} SnippetValue */
|
|
8
10
|
|
|
9
11
|
const ELLIPSIS = '\u2026';
|
|
@@ -362,6 +364,35 @@ class Util {
|
|
|
362
364
|
|
|
363
365
|
return lines.filter(line => lineNumbersToKeep.has(line.lineNumber));
|
|
364
366
|
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Computes a score between 0 and 1 based on the measured `value`. Score is determined by
|
|
370
|
+
* considering a log-normal distribution governed by two control points (the 10th
|
|
371
|
+
* percentile value and the median value) and represents the percentage of sites that are
|
|
372
|
+
* greater than `value`.
|
|
373
|
+
*
|
|
374
|
+
* Score characteristics:
|
|
375
|
+
* - within [0, 1]
|
|
376
|
+
* - rounded to two digits
|
|
377
|
+
* - value must meet or beat a controlPoint value to meet or exceed its percentile score:
|
|
378
|
+
* - value > median will give a score < 0.5; value ≤ median will give a score ≥ 0.5.
|
|
379
|
+
* - value > p10 will give a score < 0.9; value ≤ p10 will give a score ≥ 0.9.
|
|
380
|
+
* - values < p10 will get a slight boost so a score of 1 is achievable by a
|
|
381
|
+
* `value` other than those close to 0. Scores of > ~0.99524 end up rounded to 1.
|
|
382
|
+
* @param {{median: number, p10: number}} controlPoints
|
|
383
|
+
* @param {number} value
|
|
384
|
+
* @return {number}
|
|
385
|
+
*/
|
|
386
|
+
static computeLogNormalScore(controlPoints, value) {
|
|
387
|
+
let percentile = statistics.getLogNormalScore(controlPoints, value);
|
|
388
|
+
// Add a boost to scores of 90+, linearly ramping from 0 at 0.9 to half a
|
|
389
|
+
// point (0.005) at 1. Expands scores in (0.9, 1] to (0.9, 1.005], so more top
|
|
390
|
+
// scores will be a perfect 1 after the two-digit `Math.floor()` rounding below.
|
|
391
|
+
if (percentile > 0.9) { // getLogNormalScore ensures `percentile` can't exceed 1.
|
|
392
|
+
percentile += 0.05 * (percentile - 0.9);
|
|
393
|
+
}
|
|
394
|
+
return Math.floor(percentile * 100) / 100;
|
|
395
|
+
}
|
|
365
396
|
}
|
|
366
397
|
|
|
367
398
|
export {
|
package/types/audit.d.ts
CHANGED
|
@@ -86,8 +86,12 @@ declare module Audit {
|
|
|
86
86
|
details?: AuditDetails;
|
|
87
87
|
/** If an audit encounters unusual execution circumstances, strings can be put in this optional array to add top-level warnings to the LHR. */
|
|
88
88
|
runWarnings?: Array<IcuMessage>;
|
|
89
|
-
/**
|
|
89
|
+
/** Estimates of how much this audit affects various performance metrics. Values will be in the unit of the respective metrics. */
|
|
90
90
|
metricSavings?: MetricSavings;
|
|
91
|
+
/** Score details including p10 and median for calculating an audit's log-normal score. */
|
|
92
|
+
scoringOptions?: ScoreOptions;
|
|
93
|
+
/** A string identifying how the score should be interpreted for display. Overrides audit meta `scoreDisplayMode` if defined. */
|
|
94
|
+
scoreDisplayMode?: AuditResult.ScoreDisplayMode;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/** The Audit.Product type for audits that do not return a `numericValue`. */
|
package/types/internal/rxjs.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ interface ScoreDisplayModes {
|
|
|
12
12
|
NUMERIC: 'numeric';
|
|
13
13
|
/** Pass/fail audit (0 and 1 are only possible scores). */
|
|
14
14
|
BINARY: 'binary';
|
|
15
|
+
/**
|
|
16
|
+
* Audit result score is determined by the metric savings and product score:
|
|
17
|
+
* 1 - audit passed based on product score
|
|
18
|
+
* 0.5 - audit failed and had no metric savings
|
|
19
|
+
* 0 - audit failed and had metric savings
|
|
20
|
+
*/
|
|
21
|
+
METRIC_SAVINGS: 'metricSavings';
|
|
15
22
|
/** The audit exists only to tell you to review something yourself. Score is null and should be ignored. */
|
|
16
23
|
MANUAL: 'manual';
|
|
17
24
|
/** The audit is an FYI only, and can't be interpreted as pass/fail. Score is null and should be ignored. */
|
|
@@ -66,4 +73,13 @@ export interface Result {
|
|
|
66
73
|
numericUnit?: string;
|
|
67
74
|
/** Extra information about the page provided by some types of audits, in one of several possible forms that can be rendered in the HTML report. */
|
|
68
75
|
details?: FormattedIcu<AuditDetails>;
|
|
76
|
+
/** Estimates of how much this audit affects various performance metrics. Values will be in the unit of the respective metrics. */
|
|
77
|
+
metricSavings?: MetricSavings
|
|
78
|
+
/** Score details including p10 and median for calculating an audit's log-normal score. */
|
|
79
|
+
scoringOptions?: {
|
|
80
|
+
p10: number;
|
|
81
|
+
median: number;
|
|
82
|
+
};
|
|
83
|
+
/** A number indicating how much guidance Lighthouse provides to solve the problem in this audit on a 1-3 scale. Higher means more guidance. */
|
|
84
|
+
guidanceLevel?: number;
|
|
69
85
|
}
|
|
File without changes
|
|
File without changes
|