lighthouse 11.4.0 → 11.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +14 -4
  2. package/cli/test/smokehouse/core-tests.js +2 -0
  3. package/core/audits/audit.d.ts +5 -0
  4. package/core/audits/audit.js +46 -2
  5. package/core/audits/bf-cache.js +1 -1
  6. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  7. package/core/audits/byte-efficiency/legacy-javascript.js +13 -2
  8. package/core/audits/byte-efficiency/render-blocking-resources.d.ts +5 -4
  9. package/core/audits/byte-efficiency/render-blocking-resources.js +15 -9
  10. package/core/audits/byte-efficiency/unused-css-rules.js +1 -1
  11. package/core/audits/byte-efficiency/unused-javascript.js +1 -1
  12. package/core/audits/layout-shift-elements.js +1 -1
  13. package/core/audits/layout-shifts.d.ts +33 -0
  14. package/core/audits/layout-shifts.js +158 -0
  15. package/core/audits/prioritize-lcp-image.js +1 -1
  16. package/core/audits/unsized-images.js +1 -1
  17. package/core/audits/viewport.js +10 -0
  18. package/core/computed/metrics/cumulative-layout-shift.d.ts +20 -1
  19. package/core/computed/metrics/cumulative-layout-shift.js +74 -4
  20. package/core/computed/trace-engine-result.d.ts +40 -0
  21. package/core/computed/trace-engine-result.js +69 -0
  22. package/core/computed/unused-css.js +4 -4
  23. package/core/computed/viewport-meta.d.ts +4 -0
  24. package/core/computed/viewport-meta.js +6 -1
  25. package/core/config/default-config.js +4 -1
  26. package/core/config/metrics-to-audits.js +1 -0
  27. package/core/gather/driver/target-manager.js +10 -1
  28. package/core/gather/driver/wait-for-condition.js +1 -1
  29. package/core/gather/gatherers/dobetterweb/response-compression.js +1 -12
  30. package/core/gather/gatherers/root-causes.d.ts +20 -0
  31. package/core/gather/gatherers/root-causes.js +133 -0
  32. package/core/gather/gatherers/trace-elements.d.ts +38 -7
  33. package/core/gather/gatherers/trace-elements.js +113 -34
  34. package/core/gather/gatherers/trace.js +6 -3
  35. package/core/gather/navigation-runner.js +1 -1
  36. package/core/gather/session.js +16 -3
  37. package/core/lib/i18n/i18n.js +2 -0
  38. package/core/lib/lighthouse-compatibility.js +4 -0
  39. package/core/lib/network-request.js +10 -2
  40. package/core/lib/polyfill-dom-rect.d.ts +2 -0
  41. package/core/lib/polyfill-dom-rect.js +111 -0
  42. package/core/lib/trace-engine.d.ts +7 -0
  43. package/core/lib/trace-engine.js +19 -0
  44. package/core/scripts/download-chrome.sh +17 -0
  45. package/dist/report/bundle.esm.js +14 -10
  46. package/dist/report/flow.js +15 -11
  47. package/dist/report/standalone.js +13 -9
  48. package/flow-report/src/i18n/i18n.d.ts +2 -0
  49. package/package.json +5 -4
  50. package/report/assets/styles.css +9 -5
  51. package/report/renderer/category-renderer.d.ts +5 -12
  52. package/report/renderer/category-renderer.js +18 -18
  53. package/report/renderer/components.js +1 -1
  54. package/report/renderer/performance-category-renderer.d.ts +2 -1
  55. package/report/renderer/performance-category-renderer.js +90 -69
  56. package/report/renderer/pwa-category-renderer.js +11 -2
  57. package/report/renderer/report-utils.d.ts +1 -0
  58. package/report/renderer/report-utils.js +3 -0
  59. package/shared/localization/locales/en-US.json +27 -0
  60. package/shared/localization/locales/en-XL.json +27 -0
  61. package/types/artifacts.d.ts +10 -1
  62. package/types/audit.d.ts +9 -1
  63. package/types/config.d.ts +1 -0
  64. package/types/lhr/audit-result.d.ts +1 -7
  65. package/types/trace-engine.d.ts +1516 -0
@@ -211,73 +211,103 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
211
211
  filmstripEl && timelineEl.append(filmstripEl);
212
212
  }
213
213
 
214
- const filterableMetrics = metricAudits.filter(a => !!a.relevantAudits);
215
- // TODO: only add if there are opportunities & diagnostics rendered.
216
- if (filterableMetrics.length) {
217
- this.renderMetricAuditFilter(filterableMetrics, element);
218
- }
214
+ const allInsights = category.auditRefs
215
+ .filter(audit => this._isPerformanceInsight(audit))
216
+ .map(auditRef => {
217
+ const {overallImpact, overallLinearImpact} = this.overallImpact(auditRef, metricAudits);
218
+ const guidanceLevel = auditRef.result.guidanceLevel || 1;
219
+ const auditEl = this.renderAudit(auditRef);
220
+
221
+ return {auditRef, auditEl, overallImpact, overallLinearImpact, guidanceLevel};
222
+ });
219
223
 
220
224
  // Diagnostics
221
- const diagnosticAudits = category.auditRefs
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
- });
225
+ const diagnosticAudits = allInsights
226
+ .filter(audit => !ReportUtils.showAsPassed(audit.auditRef.result));
227
+
228
+ const passedAudits = allInsights
229
+ .filter(audit => ReportUtils.showAsPassed(audit.auditRef.result));
235
230
 
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) {
250
- return b.overallImpact * b.guidanceLevel - a.overallImpact * a.guidanceLevel;
231
+ const [groupEl, footerEl] = this.renderAuditGroup(groups['diagnostics']);
232
+ groupEl.classList.add('lh-audit-group--diagnostics');
233
+
234
+ /**
235
+ * @param {string} acronym
236
+ */
237
+ function refreshFilteredAudits(acronym) {
238
+ for (const audit of allInsights) {
239
+ if (acronym === 'All') {
240
+ audit.auditEl.hidden = false;
241
+ } else {
242
+ const shouldHide = audit.auditRef.result.metricSavings?.[acronym] === undefined;
243
+ audit.auditEl.hidden = shouldHide;
244
+ }
251
245
  }
252
246
 
253
- // Fall back to the linear impact if the normal impact is rounded down to 0
254
- if (
255
- a.overallImpact === 0 && b.overallImpact === 0 &&
256
- a.overallLinearImpact !== b.overallLinearImpact
257
- ) {
258
- return b.overallLinearImpact * b.guidanceLevel - a.overallLinearImpact * a.guidanceLevel;
247
+ diagnosticAudits.sort((a, b) => {
248
+ // Performance diagnostics should only have score display modes of "informative" and "metricSavings"
249
+ // If the score display mode is "metricSavings", the `score` will be a coarse approximation of the overall impact.
250
+ // Therefore, it makes sense to sort audits by score first to ensure visual clarity with the score icons.
251
+ const scoreA = a.auditRef.result.score || 0;
252
+ const scoreB = b.auditRef.result.score || 0;
253
+ if (scoreA !== scoreB) return scoreA - scoreB;
254
+
255
+ // If there is a metric filter applied, we should sort by the impact to that specific metric.
256
+ if (acronym !== 'All') {
257
+ const aSavings = a.auditRef.result.metricSavings?.[acronym] ?? -1;
258
+ const bSavings = b.auditRef.result.metricSavings?.[acronym] ?? -1;
259
+ if (aSavings !== bSavings) return bSavings - aSavings;
260
+ }
261
+
262
+ // Overall impact is the estimated improvement to the performance score
263
+ if (a.overallImpact !== b.overallImpact) {
264
+ return b.overallImpact * b.guidanceLevel - a.overallImpact * a.guidanceLevel;
265
+ }
266
+
267
+ // Fall back to the linear impact if the normal impact is rounded down to 0
268
+ if (
269
+ a.overallImpact === 0 && b.overallImpact === 0 &&
270
+ a.overallLinearImpact !== b.overallLinearImpact
271
+ ) {
272
+ return b.overallLinearImpact * b.guidanceLevel - a.overallLinearImpact * a.guidanceLevel;
273
+ }
274
+
275
+ // Audits that have no estimated savings should be prioritized by the guidance level
276
+ return b.guidanceLevel - a.guidanceLevel;
277
+ });
278
+
279
+ for (const audit of diagnosticAudits) {
280
+ groupEl.insertBefore(audit.auditEl, footerEl);
259
281
  }
282
+ }
260
283
 
261
- // Audits that have no estimated savings should be prioritized by the guidance level
262
- return b.guidanceLevel - a.guidanceLevel;
263
- });
284
+ /** @type {Set<string>} */
285
+ const filterableMetricAcronyms = new Set();
286
+ for (const audit of diagnosticAudits) {
287
+ const metricSavings = audit.auditRef.result.metricSavings || {};
288
+ for (const [key, value] of Object.entries(metricSavings)) {
289
+ if (typeof value === 'number') filterableMetricAcronyms.add(key);
290
+ }
291
+ }
264
292
 
265
- if (auditImpacts.length) {
266
- const [groupEl, footerEl] = this.renderAuditGroup(groups['diagnostics']);
267
- auditImpacts.forEach(item => groupEl.insertBefore(this.renderAudit(item.auditRef), footerEl));
268
- groupEl.classList.add('lh-audit-group--diagnostics');
269
- element.append(groupEl);
293
+ const filterableMetrics =
294
+ metricAudits.filter(a => a.acronym && filterableMetricAcronyms.has(a.acronym));
295
+
296
+ // TODO: only add if there are opportunities & diagnostics rendered.
297
+ if (filterableMetrics.length) {
298
+ this.renderMetricAuditFilter(filterableMetrics, element, refreshFilteredAudits);
270
299
  }
271
300
 
272
- // Passed audits
273
- const passedAudits = category.auditRefs
274
- .filter(audit =>
275
- this._isPerformanceInsight(audit) && ReportUtils.showAsPassed(audit.result));
301
+ refreshFilteredAudits('All');
302
+
303
+ if (diagnosticAudits.length) {
304
+ element.append(groupEl);
305
+ }
276
306
 
277
307
  if (!passedAudits.length) return element;
278
308
 
279
309
  const clumpOpts = {
280
- auditRefs: passedAudits,
310
+ auditRefsOrEls: passedAudits.map(audit => audit.auditEl),
281
311
  groupDefinitions: groups,
282
312
  };
283
313
  const passedElem = this.renderClump('passed', clumpOpts);
@@ -318,16 +348,17 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
318
348
  * Render the control to filter the audits by metric. The filtering is done at runtime by CSS only
319
349
  * @param {LH.ReportResult.AuditRef[]} filterableMetrics
320
350
  * @param {HTMLDivElement} categoryEl
351
+ * @param {(acronym: string) => void} onFilterChange
321
352
  */
322
- renderMetricAuditFilter(filterableMetrics, categoryEl) {
353
+ renderMetricAuditFilter(filterableMetrics, categoryEl, onFilterChange) {
323
354
  const metricFilterEl = this.dom.createElement('div', 'lh-metricfilter');
324
355
  const textEl = this.dom.createChildOf(metricFilterEl, 'span', 'lh-metricfilter__text');
325
356
  textEl.textContent = Globals.strings.showRelevantAudits;
326
357
 
327
- const filterChoices = /** @type {LH.ReportResult.AuditRef[]} */ ([
328
- ({acronym: 'All'}),
358
+ const filterChoices = [
359
+ /** @type {const} */ ({acronym: 'All', id: 'All'}),
329
360
  ...filterableMetrics,
330
- ]);
361
+ ];
331
362
 
332
363
  // Form labels need to reference unique IDs, but multiple reports rendered in the same DOM (eg PSI)
333
364
  // would mean ID conflict. To address this, we 'scope' these radio inputs with a unique suffix.
@@ -341,7 +372,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
341
372
 
342
373
  const labelEl = this.dom.createChildOf(metricFilterEl, 'label', 'lh-metricfilter__label');
343
374
  labelEl.htmlFor = elemId;
344
- labelEl.title = metric.result?.title;
375
+ labelEl.title = 'result' in metric ? metric.result.title : '';
345
376
  labelEl.textContent = metric.acronym || metric.id;
346
377
 
347
378
  if (metric.acronym === 'All') {
@@ -357,17 +388,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
357
388
  }
358
389
  categoryEl.classList.toggle('lh-category--filtered', metric.acronym !== 'All');
359
390
 
360
- for (const perfAuditEl of categoryEl.querySelectorAll('div.lh-audit')) {
361
- if (metric.acronym === 'All') {
362
- perfAuditEl.hidden = false;
363
- continue;
364
- }
365
-
366
- perfAuditEl.hidden = true;
367
- if (metric.relevantAudits && metric.relevantAudits.includes(perfAuditEl.id)) {
368
- perfAuditEl.hidden = false;
369
- }
370
- }
391
+ onFilterChange(metric.acronym || 'All');
371
392
 
372
393
  // Hide groups/clumps if all child audits are also hidden.
373
394
  const groupEls = categoryEl.querySelectorAll('div.lh-audit-group, details.lh-audit-group');
@@ -17,6 +17,12 @@ export class PwaCategoryRenderer extends CategoryRenderer {
17
17
  render(category, groupDefinitions = {}) {
18
18
  const categoryElem = this.dom.createElement('div', 'lh-category');
19
19
  categoryElem.id = category.id;
20
+ // Deprecation warning banner.
21
+ const pwaMessageContainer = this.dom.createComponent('warningsToplevel');
22
+ const pwaMessageEl = this.dom.find('.lh-warnings__msg', pwaMessageContainer);
23
+ pwaMessageEl.append(this.dom.convertMarkdownLinkSnippets(Globals.strings.pwaRemovalMessage));
24
+
25
+ categoryElem.append(pwaMessageContainer);
20
26
  categoryElem.append(this.renderCategoryHeader(category, groupDefinitions));
21
27
 
22
28
  const auditRefs = category.auditRefs;
@@ -29,8 +35,11 @@ export class PwaCategoryRenderer extends CategoryRenderer {
29
35
 
30
36
  // Manual audits are still in a manual clump.
31
37
  const manualAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode === 'manual');
32
- const manualElem = this.renderClump('manual',
33
- {auditRefs: manualAuditRefs, description: category.manualDescription, openByDefault: true});
38
+ const manualElem = this.renderClump('manual', {
39
+ auditRefsOrEls: manualAuditRefs,
40
+ description: category.manualDescription,
41
+ openByDefault: true,
42
+ });
34
43
  categoryElem.append(manualElem);
35
44
 
36
45
  return categoryElem;
@@ -136,5 +136,6 @@ export namespace UIStrings {
136
136
  const firstPartyChipLabel: string;
137
137
  const openInANewTabTooltip: string;
138
138
  const unattributable: string;
139
+ const pwaRemovalMessage: string;
139
140
  }
140
141
  //# sourceMappingURL=report-utils.d.ts.map
@@ -476,6 +476,9 @@ const UIStrings = {
476
476
  openInANewTabTooltip: 'Open in a new tab',
477
477
  /** Generic category name for all resources that could not be attributed to a 1st or 3rd party entity. */
478
478
  unattributable: 'Unattributable',
479
+
480
+ /** Message communicating the removal of the PWA category. */
481
+ pwaRemovalMessage: 'Alongside [Chrome’s updated Installability Criteria](https://developer.chrome.com/blog/update-install-criteria), Lighthouse will be deprecating the PWA category in a future release. Please refer to the [updated PWA documentation](https://developer.chrome.com/docs/devtools/progressive-web-apps/) for future PWA testing.',
479
482
  };
480
483
 
481
484
  export {
@@ -1166,6 +1166,30 @@
1166
1166
  "core/audits/layout-shift-elements.js | title": {
1167
1167
  "message": "Avoid large layout shifts"
1168
1168
  },
1169
+ "core/audits/layout-shifts.js | columnScore": {
1170
+ "message": "Layout shift score"
1171
+ },
1172
+ "core/audits/layout-shifts.js | description": {
1173
+ "message": "These are the largest layout shifts observed on the page. Each table item represents a single layout shift, and shows the element that shifted the most. Below each item are possible root causes that led to the layout shift. Some of these layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)"
1174
+ },
1175
+ "core/audits/layout-shifts.js | displayValueShiftsFound": {
1176
+ "message": "{shiftCount, plural, =1 {1 layout shift found} other {# layout shifts found}}"
1177
+ },
1178
+ "core/audits/layout-shifts.js | rootCauseFontChanges": {
1179
+ "message": "Web font loaded"
1180
+ },
1181
+ "core/audits/layout-shifts.js | rootCauseInjectedIframe": {
1182
+ "message": "Injected iframe"
1183
+ },
1184
+ "core/audits/layout-shifts.js | rootCauseRenderBlockingRequest": {
1185
+ "message": "A late network request adjusted the page layout"
1186
+ },
1187
+ "core/audits/layout-shifts.js | rootCauseUnsizedMedia": {
1188
+ "message": "Media element lacking an explicit size"
1189
+ },
1190
+ "core/audits/layout-shifts.js | title": {
1191
+ "message": "Avoid large layout shifts"
1192
+ },
1169
1193
  "core/audits/lcp-lazy-loaded.js | description": {
1170
1194
  "message": "Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. [Learn more about optimal lazy loading](https://web.dev/articles/lcp-lazy-loading)."
1171
1195
  },
@@ -3374,6 +3398,9 @@
3374
3398
  "report/renderer/report-utils.js | passedAuditsGroupTitle": {
3375
3399
  "message": "Passed audits"
3376
3400
  },
3401
+ "report/renderer/report-utils.js | pwaRemovalMessage": {
3402
+ "message": "Alongside [Chrome’s updated Installability Criteria](https://developer.chrome.com/blog/update-install-criteria), Lighthouse will be deprecating the PWA category in a future release. Please refer to the [updated PWA documentation](https://developer.chrome.com/docs/devtools/progressive-web-apps/) for future PWA testing."
3403
+ },
3377
3404
  "report/renderer/report-utils.js | runtimeAnalysisWindow": {
3378
3405
  "message": "Initial page load"
3379
3406
  },
@@ -1166,6 +1166,30 @@
1166
1166
  "core/audits/layout-shift-elements.js | title": {
1167
1167
  "message": "Âv́ôíd̂ ĺâŕĝé l̂áŷóût́ ŝh́îf́t̂ś"
1168
1168
  },
1169
+ "core/audits/layout-shifts.js | columnScore": {
1170
+ "message": "L̂áŷóût́ ŝh́îf́t̂ śĉór̂é"
1171
+ },
1172
+ "core/audits/layout-shifts.js | description": {
1173
+ "message": "T̂h́êśê ár̂é t̂h́ê ĺâŕĝéŝt́ l̂áŷóût́ ŝh́îf́t̂ś ôb́ŝér̂v́êd́ ôń t̂h́ê ṕâǵê. Éâćĥ t́âb́l̂é ît́êḿ r̂ép̂ŕêśêńt̂ś â śîńĝĺê ĺâýôút̂ śĥíf̂t́, âńd̂ śĥóŵś t̂h́ê él̂ém̂én̂t́ t̂h́ât́ ŝh́îf́t̂éd̂ t́ĥé m̂óŝt́. B̂él̂óŵ éâćĥ ít̂ém̂ ár̂é p̂óŝśîb́l̂é r̂óôt́ ĉáûśêś t̂h́ât́ l̂éd̂ t́ô t́ĥé l̂áŷóût́ ŝh́îf́t̂. Śôḿê óf̂ t́ĥéŝé l̂áŷóût́ ŝh́îf́t̂ś m̂áŷ ńôt́ b̂é îńĉĺûd́êd́ îń t̂h́ê ĆL̂Ś m̂ét̂ŕîć v̂ál̂úê d́ûé t̂ó [ŵín̂d́ôẃîńĝ](https://web.dev/articles/cls#what_is_cls). [Ĺêár̂ń ĥóŵ t́ô ím̂ṕr̂óv̂é ĈĹŜ](https://web.dev/articles/optimize-cls)"
1174
+ },
1175
+ "core/audits/layout-shifts.js | displayValueShiftsFound": {
1176
+ "message": "{shiftCount, plural, =1 {1 l̂áŷóût́ ŝh́îf́t̂ f́ôún̂d́} other {# l̂áŷóût́ ŝh́îf́t̂ś f̂óûńd̂}}"
1177
+ },
1178
+ "core/audits/layout-shifts.js | rootCauseFontChanges": {
1179
+ "message": "Ŵéb̂ f́ôńt̂ ĺôád̂éd̂"
1180
+ },
1181
+ "core/audits/layout-shifts.js | rootCauseInjectedIframe": {
1182
+ "message": "Îńĵéĉt́êd́ îf́r̂ám̂é"
1183
+ },
1184
+ "core/audits/layout-shifts.js | rootCauseRenderBlockingRequest": {
1185
+ "message": "Â ĺât́ê ńêt́ŵór̂ḱ r̂éq̂úêśt̂ ád̂j́ûśt̂éd̂ t́ĥé p̂áĝé l̂áŷóût́"
1186
+ },
1187
+ "core/audits/layout-shifts.js | rootCauseUnsizedMedia": {
1188
+ "message": "M̂éd̂íâ él̂ém̂én̂t́ l̂áĉḱîńĝ án̂ éx̂ṕl̂íĉít̂ śîźê"
1189
+ },
1190
+ "core/audits/layout-shifts.js | title": {
1191
+ "message": "Âv́ôíd̂ ĺâŕĝé l̂áŷóût́ ŝh́îf́t̂ś"
1192
+ },
1169
1193
  "core/audits/lcp-lazy-loaded.js | description": {
1170
1194
  "message": "Âb́ôv́ê-t́ĥé-f̂ól̂d́ îḿâǵêś t̂h́ât́ âŕê ĺâźîĺŷ ĺôád̂éd̂ ŕêńd̂ér̂ ĺât́êŕ îń t̂h́ê ṕâǵê ĺîf́êćŷćl̂é, ŵh́îćĥ ćâń d̂él̂áŷ t́ĥé l̂ár̂ǵêśt̂ ćôńt̂én̂t́f̂úl̂ ṕâín̂t́. [L̂éâŕn̂ ḿôŕê áb̂óût́ ôṕt̂ím̂ál̂ ĺâźŷ ĺôád̂ín̂ǵ](https://web.dev/articles/lcp-lazy-loading)."
1171
1195
  },
@@ -3374,6 +3398,9 @@
3374
3398
  "report/renderer/report-utils.js | passedAuditsGroupTitle": {
3375
3399
  "message": "P̂áŝśêd́ âúd̂ít̂ś"
3376
3400
  },
3401
+ "report/renderer/report-utils.js | pwaRemovalMessage": {
3402
+ "message": "Âĺôńĝśîd́ê [Ćĥŕôḿê’ś ûṕd̂át̂éd̂ Ín̂śt̂ál̂ĺâb́îĺît́ŷ Ćr̂ít̂ér̂íâ](https://developer.chrome.com/blog/update-install-criteria), Ĺîǵĥt́ĥóûśê ẃîĺl̂ b́ê d́êṕr̂éĉát̂ín̂ǵ t̂h́ê ṔŴÁ ĉát̂éĝór̂ý îń â f́ût́ûŕê ŕêĺêáŝé. P̂ĺêáŝé r̂éf̂ér̂ t́ô t́ĥé [ûṕd̂át̂éd̂ ṔŴÁ d̂óĉúm̂én̂t́ât́îón̂](https://developer.chrome.com/docs/devtools/progressive-web-apps/) f́ôŕ f̂út̂úr̂é P̂ẂÂ t́êśt̂ín̂ǵ."
3403
+ },
3377
3404
  "report/renderer/report-utils.js | runtimeAnalysisWindow": {
3378
3405
  "message": "Îńît́îál̂ ṕâǵê ĺôád̂"
3379
3406
  },
@@ -23,6 +23,7 @@ import LHResult from './lhr/lhr.js'
23
23
  import Protocol from './protocol.js';
24
24
  import Util from './utility-types.js';
25
25
  import Audit from './audit.js';
26
+ import {TraceProcessor, LayoutShiftRootCauses} from './trace-engine.js';
26
27
 
27
28
  export type Artifacts = BaseArtifacts & GathererArtifacts;
28
29
 
@@ -142,6 +143,8 @@ export interface GathererArtifacts extends PublicGathererArtifacts {
142
143
  ResponseCompression: {requestId: string, url: string, mimeType: string, transferSize: number, resourceSize: number, gzipSize?: number}[];
143
144
  /** Information on fetching and the content of the /robots.txt file. */
144
145
  RobotsTxt: {status: number|null, content: string|null, errorMessage?: string};
146
+ /** The result of calling the shared trace engine root cause analysis. */
147
+ RootCauses: Artifacts.TraceEngineRootCauses;
145
148
  /** Information on all scripts in the page. */
146
149
  Scripts: Artifacts.Script[];
147
150
  /** Version information for all ServiceWorkers active after the first page load. */
@@ -559,13 +562,19 @@ declare module Artifacts {
559
562
  }
560
563
 
561
564
  interface TraceElement {
562
- traceEventType: 'largest-contentful-paint'|'layout-shift'|'animation'|'responsiveness';
565
+ traceEventType: 'largest-contentful-paint'|'layout-shift'|'layout-shift-element'|'animation'|'responsiveness';
563
566
  node: NodeDetails;
564
567
  nodeId: number;
565
568
  animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[];
566
569
  type?: string;
567
570
  }
568
571
 
572
+ type TraceEngineResult = TraceProcessor['data'];
573
+
574
+ interface TraceEngineRootCauses {
575
+ layoutShifts: Record<number, LayoutShiftRootCauses>;
576
+ }
577
+
569
578
  interface ViewportDimensions {
570
579
  innerWidth: number;
571
580
  innerHeight: number;
package/types/audit.d.ts CHANGED
@@ -20,6 +20,14 @@ declare module Audit {
20
20
  export type ScoreDisplayModes = AuditResult.ScoreDisplayModes;
21
21
  export type MetricSavings = AuditResult.MetricSavings;
22
22
 
23
+ export type ProductMetricSavings = {
24
+ FCP?: number;
25
+ LCP?: number;
26
+ TBT?: number;
27
+ CLS?: number;
28
+ INP?: number;
29
+ };
30
+
23
31
  type Context = Util.Immutable<{
24
32
  /** audit options */
25
33
  options: Record<string, any>;
@@ -87,7 +95,7 @@ declare module Audit {
87
95
  /** If an audit encounters unusual execution circumstances, strings can be put in this optional array to add top-level warnings to the LHR. */
88
96
  runWarnings?: Array<IcuMessage>;
89
97
  /** Estimates of how much this audit affects various performance metrics. Values will be in the unit of the respective metrics. */
90
- metricSavings?: MetricSavings;
98
+ metricSavings?: ProductMetricSavings;
91
99
  /** Score details including p10 and median for calculating an audit's log-normal score. */
92
100
  scoringOptions?: ScoreOptions;
93
101
  /** A string identifying how the score should be interpreted for display. Overrides audit meta `scoreDisplayMode` if defined. */
package/types/config.d.ts CHANGED
@@ -9,6 +9,7 @@ import {Audit} from '../core/audits/audit.js';
9
9
  import {SharedFlagsSettings, ConfigSettings} from './lhr/settings.js';
10
10
  import Gatherer from './gatherer.js';
11
11
  import {IcuMessage} from './lhr/i18n.js';
12
+ import Result from './lhr/lhr.js';
12
13
 
13
14
  interface ClassOf<T> {
14
15
  new (): T;
@@ -31,13 +31,7 @@ interface ScoreDisplayModes {
31
31
 
32
32
  type ScoreDisplayMode = ScoreDisplayModes[keyof ScoreDisplayModes];
33
33
 
34
- interface MetricSavings {
35
- LCP?: number;
36
- FCP?: number;
37
- CLS?: number;
38
- TBT?: number;
39
- INP?: number;
40
- }
34
+ export type MetricSavings = Partial<Record<string, number>>;
41
35
 
42
36
  /** Audit result returned in Lighthouse report. All audits offer a description and score of 0-1. */
43
37
  export interface Result {