lighthouse 9.2.0 → 9.3.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 (141) hide show
  1. package/changelog.md +67 -0
  2. package/dist/report/bundle.esm.js +23 -11
  3. package/dist/report/flow.js +11 -11
  4. package/dist/report/standalone.js +8 -8
  5. package/flow-report/src/common.tsx +1 -1
  6. package/flow-report/src/sidebar/flow.tsx +1 -1
  7. package/flow-report/src/util.ts +1 -2
  8. package/lighthouse-cli/bin.js +0 -4
  9. package/lighthouse-cli/cli-flags.js +22 -8
  10. package/lighthouse-cli/run.js +5 -1
  11. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -2
  12. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  13. package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +103 -0
  14. package/lighthouse-cli/test/smokehouse/readme.md +10 -8
  15. package/lighthouse-cli/test/smokehouse/report-assert.js +1 -1
  16. package/lighthouse-cli/test/smokehouse/smokehouse.js +4 -5
  17. package/lighthouse-core/audits/accessibility/axe-audit.js +4 -4
  18. package/lighthouse-core/audits/accessibility/object-alt.js +3 -3
  19. package/lighthouse-core/audits/audit.js +19 -4
  20. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  21. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +1 -1
  22. package/lighthouse-core/audits/deprecations.js +2 -2
  23. package/lighthouse-core/audits/dobetterweb/inspector-issues.js +11 -11
  24. package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -1
  25. package/lighthouse-core/audits/font-display.js +1 -1
  26. package/lighthouse-core/audits/installable-manifest.js +1 -1
  27. package/lighthouse-core/audits/is-on-https.js +1 -1
  28. package/lighthouse-core/audits/maskable-icon.js +1 -1
  29. package/lighthouse-core/audits/multi-check-audit.js +1 -1
  30. package/lighthouse-core/audits/network-requests.js +4 -4
  31. package/lighthouse-core/audits/preload-fonts.js +2 -3
  32. package/lighthouse-core/audits/preload-lcp-image.js +2 -12
  33. package/lighthouse-core/audits/seo/is-crawlable.js +2 -1
  34. package/lighthouse-core/audits/themed-omnibox.js +1 -1
  35. package/lighthouse-core/audits/uses-rel-preload.js +2 -3
  36. package/lighthouse-core/audits/valid-source-maps.js +2 -2
  37. package/lighthouse-core/computed/image-records.js +1 -1
  38. package/lighthouse-core/computed/js-bundles.js +1 -1
  39. package/lighthouse-core/computed/metrics/lantern-metric.js +1 -1
  40. package/lighthouse-core/computed/metrics/timing-summary.js +35 -35
  41. package/lighthouse-core/computed/page-dependency-graph.js +2 -2
  42. package/lighthouse-core/computed/resource-summary.js +1 -1
  43. package/lighthouse-core/config/config-helpers.js +2 -2
  44. package/lighthouse-core/config/config.js +1 -1
  45. package/lighthouse-core/fraggle-rock/config/filters.js +23 -1
  46. package/lighthouse-core/fraggle-rock/config/validation.js +2 -2
  47. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +5 -4
  48. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +0 -1
  49. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +0 -1
  50. package/lighthouse-core/fraggle-rock/user-flow.js +4 -4
  51. package/lighthouse-core/gather/connections/connection.js +1 -1
  52. package/lighthouse-core/gather/driver/execution-context.js +1 -1
  53. package/lighthouse-core/gather/driver/network-monitor.js +2 -2
  54. package/lighthouse-core/gather/driver/target-manager.js +1 -1
  55. package/lighthouse-core/gather/driver.js +1 -1
  56. package/lighthouse-core/gather/gatherers/console-messages.js +2 -2
  57. package/lighthouse-core/gather/gatherers/inspector-issues.js +34 -46
  58. package/lighthouse-core/gather/gatherers/js-usage.js +1 -1
  59. package/lighthouse-core/gather/gatherers/seo/font-size.js +1 -1
  60. package/lighthouse-core/gather/gatherers/trace-elements.js +8 -43
  61. package/lighthouse-core/index.js +4 -3
  62. package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
  63. package/lighthouse-core/lib/asset-saver.js +6 -4
  64. package/lighthouse-core/lib/dependency-graph/simulator/connection-pool.js +1 -1
  65. package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +1 -1
  66. package/lighthouse-core/lib/emulation.js +1 -1
  67. package/lighthouse-core/lib/icons.js +1 -2
  68. package/lighthouse-core/lib/median-run.js +1 -2
  69. package/lighthouse-core/lib/network-recorder.js +2 -2
  70. package/lighthouse-core/lib/network-request.js +1 -1
  71. package/lighthouse-core/lib/stack-packs.js +5 -0
  72. package/lighthouse-core/lib/tracehouse/cpu-profile-model.js +4 -6
  73. package/lighthouse-core/lib/tracehouse/main-thread-tasks.js +1 -1
  74. package/lighthouse-core/lib/tracehouse/trace-processor.js +14 -17
  75. package/lighthouse-core/lib/url-shim.js +15 -0
  76. package/lighthouse-core/runner.js +62 -49
  77. package/lighthouse-core/scripts/download-chrome.sh +15 -2
  78. package/package.json +21 -25
  79. package/report/assets/templates.html +2 -1
  80. package/report/clients/standalone.js +5 -1
  81. package/report/renderer/category-renderer.js +1 -1
  82. package/report/renderer/components.js +2 -2
  83. package/report/renderer/performance-category-renderer.js +4 -4
  84. package/report/renderer/report-renderer.js +1 -1
  85. package/report/renderer/report-ui-features.js +15 -3
  86. package/report/test/generator/report-generator-test.js +1 -1
  87. package/report/test/renderer/report-ui-features-test.js +22 -4
  88. package/report/test-assets/faux-psi.js +2 -3
  89. package/report/types/report-renderer.d.ts +2 -0
  90. package/shared/localization/locales/ar-XB.json +40 -4
  91. package/shared/localization/locales/ar.json +59 -23
  92. package/shared/localization/locales/bg.json +40 -4
  93. package/shared/localization/locales/ca.json +45 -9
  94. package/shared/localization/locales/cs.json +39 -3
  95. package/shared/localization/locales/da.json +43 -7
  96. package/shared/localization/locales/de.json +42 -6
  97. package/shared/localization/locales/el.json +59 -23
  98. package/shared/localization/locales/en-GB.json +43 -7
  99. package/shared/localization/locales/en-US.json +48 -6
  100. package/shared/localization/locales/en-XA.json +40 -4
  101. package/shared/localization/locales/en-XL.json +48 -6
  102. package/shared/localization/locales/es-419.json +39 -3
  103. package/shared/localization/locales/es.json +46 -10
  104. package/shared/localization/locales/fi.json +57 -21
  105. package/shared/localization/locales/fil.json +40 -4
  106. package/shared/localization/locales/fr.json +45 -9
  107. package/shared/localization/locales/he.json +41 -5
  108. package/shared/localization/locales/hi.json +59 -23
  109. package/shared/localization/locales/hr.json +57 -21
  110. package/shared/localization/locales/hu.json +39 -3
  111. package/shared/localization/locales/id.json +40 -4
  112. package/shared/localization/locales/it.json +40 -4
  113. package/shared/localization/locales/ja.json +39 -3
  114. package/shared/localization/locales/ko.json +40 -4
  115. package/shared/localization/locales/lt.json +40 -4
  116. package/shared/localization/locales/lv.json +56 -20
  117. package/shared/localization/locales/nl.json +40 -4
  118. package/shared/localization/locales/no.json +56 -20
  119. package/shared/localization/locales/pl.json +56 -20
  120. package/shared/localization/locales/pt-PT.json +57 -21
  121. package/shared/localization/locales/pt.json +41 -5
  122. package/shared/localization/locales/ro.json +39 -3
  123. package/shared/localization/locales/ru.json +40 -4
  124. package/shared/localization/locales/sk.json +39 -3
  125. package/shared/localization/locales/sl.json +39 -3
  126. package/shared/localization/locales/sr-Latn.json +40 -4
  127. package/shared/localization/locales/sr.json +40 -4
  128. package/shared/localization/locales/sv.json +39 -3
  129. package/shared/localization/locales/ta.json +47 -11
  130. package/shared/localization/locales/te.json +61 -25
  131. package/shared/localization/locales/th.json +39 -3
  132. package/shared/localization/locales/tr.json +40 -4
  133. package/shared/localization/locales/uk.json +40 -4
  134. package/shared/localization/locales/vi.json +63 -27
  135. package/shared/localization/locales/zh-HK.json +40 -4
  136. package/shared/localization/locales/zh-TW.json +45 -9
  137. package/shared/localization/locales/zh.json +41 -5
  138. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
  139. package/third-party/snyk/snapshot.json +4 -1
  140. package/types/artifacts.d.ts +16 -6
  141. package/types/robots-parser/index.d.ts +0 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "scripts": {
15
15
  "prepack": "yarn build-report --standalone --flow --esm",
16
16
  "build-all": "npm-run-posix-or-windows build-all:task",
17
- "build-all:task": "yarn build-report && yarn build-cdt-lib && yarn build-devtools && (yarn build-extension & yarn build-lr & yarn build-viewer & yarn build-treemap & yarn build-smokehouse-bundle & wait) && yarn build-pack",
17
+ "build-all:task": "yarn build-report && yarn build-cdt-lib && yarn build-devtools && concurrently 'yarn build-extension' 'yarn build-lr' 'yarn build-viewer' 'yarn build-treemap' 'yarn build-smokehouse-bundle' && yarn build-pack",
18
18
  "build-all:task:windows": "yarn build-report && yarn build-cdt-lib && yarn build-extension && yarn build-devtools && yarn build-lr && yarn build-viewer && yarn build-treemap && yarn build-smokehouse-bundle",
19
19
  "build-cdt-lib": "node ./build/build-cdt-lib.js",
20
20
  "build-extension": "yarn build-extension-chrome && yarn build-extension-firefox",
@@ -95,9 +95,6 @@
95
95
  },
96
96
  "devDependencies": {
97
97
  "@build-tracker/cli": "^1.0.0-beta.15",
98
- "@firebase/app-types": "0.3.1",
99
- "@firebase/auth-types": "0.3.2",
100
- "@firebase/util": "0.2.1",
101
98
  "@rollup/plugin-alias": "^3.1.2",
102
99
  "@rollup/plugin-commonjs": "^20.0.0",
103
100
  "@rollup/plugin-dynamic-import-vars": "^1.1.1",
@@ -115,8 +112,8 @@
115
112
  "@types/estree": "^0.0.50",
116
113
  "@types/gh-pages": "^2.0.0",
117
114
  "@types/google.analytics": "0.0.39",
118
- "@types/jest": "^24.0.9",
119
- "@types/jpeg-js": "^0.3.0",
115
+ "@types/jest": "^27.0.3",
116
+ "@types/jpeg-js": "^0.3.7",
120
117
  "@types/jsdom": "^16.2.13",
121
118
  "@types/lodash.clonedeep": "^4.5.6",
122
119
  "@types/lodash.get": "^4.4.6",
@@ -128,10 +125,9 @@
128
125
  "@types/resize-observer-browser": "^0.1.1",
129
126
  "@types/semver": "^5.5.0",
130
127
  "@types/tabulator-tables": "^4.9.1",
131
- "@types/update-notifier": "^4.1.0",
132
128
  "@types/ws": "^7.0.0",
133
- "@types/yargs": "^16.0.0",
134
- "@types/yargs-parser": "^15.0.0",
129
+ "@types/yargs": "^17.0.8",
130
+ "@types/yargs-parser": "^20.2.1",
135
131
  "@typescript-eslint/eslint-plugin": "^5.6.0",
136
132
  "@typescript-eslint/parser": "^5.6.0",
137
133
  "acorn": "^8.5.0",
@@ -140,11 +136,12 @@
140
136
  "c8": "^7.4.0",
141
137
  "chalk": "^2.4.1",
142
138
  "chrome-devtools-frontend": "1.0.922924",
143
- "conventional-changelog-cli": "^1.3.4",
144
- "cpy": "^7.0.1",
139
+ "concurrently": "^6.4.0",
140
+ "conventional-changelog-cli": "^2.1.1",
141
+ "cpy": "^8.1.2",
145
142
  "cross-env": "^7.0.2",
146
143
  "csv-validator": "^0.0.3",
147
- "devtools-protocol": "0.0.939404",
144
+ "devtools-protocol": "0.0.964215",
148
145
  "es-main": "^1.0.2",
149
146
  "eslint": "^8.4.1",
150
147
  "eslint-config-google": "^0.14.0",
@@ -173,7 +170,7 @@
173
170
  "resolve": "^1.20.0",
174
171
  "rollup": "^2.52.7",
175
172
  "rollup-plugin-node-resolve": "^5.2.0",
176
- "rollup-plugin-polyfill-node": "^0.7.0",
173
+ "rollup-plugin-polyfill-node": "^0.8.0",
177
174
  "rollup-plugin-replace": "^2.2.0",
178
175
  "rollup-plugin-shim": "^1.0.0",
179
176
  "rollup-plugin-terser": "^7.0.2",
@@ -194,31 +191,30 @@
194
191
  "enquirer": "^2.3.6",
195
192
  "http-link-header": "^0.8.0",
196
193
  "intl-messageformat": "^4.4.0",
197
- "jpeg-js": "^0.4.1",
194
+ "jpeg-js": "^0.4.3",
198
195
  "js-library-detector": "^6.4.0",
199
196
  "lighthouse-logger": "^1.3.0",
200
- "lighthouse-stack-packs": "^1.6.0",
197
+ "lighthouse-stack-packs": "^1.7.0",
201
198
  "lodash.clonedeep": "^4.5.0",
202
199
  "lodash.get": "^4.4.2",
203
200
  "lodash.isequal": "^4.5.0",
204
201
  "lodash.set": "^4.3.2",
205
- "lookup-closest-locale": "6.0.4",
202
+ "lookup-closest-locale": "6.2.0",
206
203
  "metaviewport-parser": "0.2.0",
207
- "open": "^6.4.0",
204
+ "open": "^8.4.0",
208
205
  "parse-cache-control": "1.0.1",
209
- "ps-list": "^7.2.0",
206
+ "ps-list": "^8.0.0",
210
207
  "raven": "^2.2.1",
211
- "robots-parser": "^2.0.1",
208
+ "robots-parser": "^3.0.0",
212
209
  "semver": "^5.3.0",
213
210
  "speedline-core": "^1.4.3",
214
- "third-party-web": "^0.12.4",
215
- "update-notifier": "^4.1.0",
211
+ "third-party-web": "^0.12.7",
216
212
  "ws": "^7.0.0",
217
- "yargs": "^16.1.1",
218
- "yargs-parser": "^20.2.4"
213
+ "yargs": "^17.3.1",
214
+ "yargs-parser": "^21.0.0"
219
215
  },
220
216
  "resolutions": {
221
- "puppeteer/**/devtools-protocol": "0.0.939404"
217
+ "puppeteer/**/devtools-protocol": "0.0.964215"
222
218
  },
223
219
  "repository": "GoogleChrome/lighthouse",
224
220
  "keywords": [
@@ -417,7 +417,8 @@ limitations under the License.
417
417
  <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--print" data-i18n="dropdownPrintSummary" data-action="print-summary"></a>
418
418
  <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--print" data-i18n="dropdownPrintExpanded" data-action="print-expanded"></a>
419
419
  <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--copy" data-i18n="dropdownCopyJSON" data-action="copy"></a>
420
- <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--download" data-i18n="dropdownSaveHTML" data-action="save-html"></a>
420
+ <!-- Only enabled if Options.getStandaloneReportHTML is set. -->
421
+ <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--download lh-hidden" data-i18n="dropdownSaveHTML" data-action="save-html"></a>
421
422
  <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--download" data-i18n="dropdownSaveJSON" data-action="save-json"></a>
422
423
  <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--open" data-i18n="dropdownViewer" data-action="open-viewer"></a>
423
424
  <a role="menuitem" tabindex="-1" href="#" class="lh-report-icon lh-report-icon--open" data-i18n="dropdownSaveGist" data-action="save-gist"></a>
@@ -21,7 +21,11 @@ function __initLighthouseReport__() {
21
21
  // @ts-expect-error
22
22
  const lhr = window.__LIGHTHOUSE_JSON__;
23
23
 
24
- const reportRootEl = renderReport(lhr);
24
+ const reportRootEl = renderReport(lhr, {
25
+ getStandaloneReportHTML() {
26
+ return document.documentElement.outerHTML;
27
+ },
28
+ });
25
29
  document.body.append(reportRootEl);
26
30
 
27
31
  document.addEventListener('lh-analytics', /** @param {Event} e */ e => {
@@ -480,7 +480,7 @@ export class CategoryRenderer {
480
480
  * @return {boolean}
481
481
  */
482
482
  _auditHasWarning(audit) {
483
- return Boolean(audit.result.warnings && audit.result.warnings.length);
483
+ return Boolean(audit.result.warnings?.length);
484
484
  }
485
485
 
486
486
  /**
@@ -706,7 +706,7 @@ function createTopbarComponent(dom) {
706
706
  el40.setAttribute('href', '#');
707
707
  el40.setAttribute('data-i18n', 'dropdownCopyJSON');
708
708
  el40.setAttribute('data-action', 'copy');
709
- const el41 = dom.createElement('a', 'lh-report-icon lh-report-icon--download');
709
+ const el41 = dom.createElement('a', 'lh-report-icon lh-report-icon--download lh-hidden');
710
710
  el41.setAttribute('role', 'menuitem');
711
711
  el41.setAttribute('tabindex', '-1');
712
712
  el41.setAttribute('href', '#');
@@ -736,7 +736,7 @@ function createTopbarComponent(dom) {
736
736
  el45.setAttribute('href', '#');
737
737
  el45.setAttribute('data-i18n', 'dropdownDarkTheme');
738
738
  el45.setAttribute('data-action', 'toggle-dark');
739
- el37.append(' ', el38, ' ', el39, ' ', el40, ' ', el41, ' ', el42, ' ', el43, ' ', el44, ' ', el45, ' ');
739
+ el37.append(' ', el38, ' ', el39, ' ', el40, ' ', ' ', el41, ' ', el42, ' ', el43, ' ', el44, ' ', el45, ' ');
740
740
  el26.append(' ', el27, ' ', el33, ' ', el37, ' ');
741
741
  el2.append(' ', ' ', el3, ' ', el25, ' ', el26, ' ');
742
742
  el0.append(el2);
@@ -225,8 +225,8 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
225
225
  // Filmstrip
226
226
  const timelineEl = this.dom.createChildOf(element, 'div', 'lh-filmstrip-container');
227
227
  const thumbnailAudit = category.auditRefs.find(audit => audit.id === 'screenshot-thumbnails');
228
- const thumbnailResult = thumbnailAudit && thumbnailAudit.result;
229
- if (thumbnailResult && thumbnailResult.details) {
228
+ const thumbnailResult = thumbnailAudit?.result;
229
+ if (thumbnailResult?.details) {
230
230
  timelineEl.id = thumbnailResult.id;
231
231
  const filmstripEl = this.detailsRenderer.render(thumbnailResult.details);
232
232
  filmstripEl && timelineEl.appendChild(filmstripEl);
@@ -301,7 +301,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
301
301
  const budgetTableEls = [];
302
302
  ['performance-budget', 'timing-budget'].forEach((id) => {
303
303
  const audit = category.auditRefs.find(audit => audit.id === id);
304
- if (audit && audit.result.details) {
304
+ if (audit?.result.details) {
305
305
  const table = this.detailsRenderer.render(audit.result.details);
306
306
  if (table) {
307
307
  table.id = id;
@@ -347,7 +347,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
347
347
 
348
348
  const labelEl = this.dom.createChildOf(metricFilterEl, 'label', 'lh-metricfilter__label');
349
349
  labelEl.htmlFor = elemId;
350
- labelEl.title = metric.result && metric.result.title;
350
+ labelEl.title = metric.result?.title;
351
351
  labelEl.textContent = metric.acronym || metric.id;
352
352
 
353
353
  if (metric.acronym === 'All') {
@@ -260,7 +260,7 @@ export class ReportRenderer {
260
260
  Util.reportJson = report;
261
261
 
262
262
  const fullPageScreenshot =
263
- report.audits['full-page-screenshot'] && report.audits['full-page-screenshot'].details &&
263
+ report.audits['full-page-screenshot']?.details &&
264
264
  report.audits['full-page-screenshot'].details.type === 'full-page-screenshot' ?
265
265
  report.audits['full-page-screenshot'].details : undefined;
266
266
  const detailsRenderer = new DetailsRenderer(this._dom, {
@@ -112,6 +112,10 @@ export class ReportUIFeatures {
112
112
  });
113
113
  }
114
114
 
115
+ if (this._opts.getStandaloneReportHTML) {
116
+ this._dom.find('a[data-action="save-html"]', this._dom.rootEl).classList.remove('lh-hidden');
117
+ }
118
+
115
119
  // Fill in all i18n data.
116
120
  for (const node of this._dom.findAll('[data-i18n]', this._dom.rootEl)) {
117
121
  // These strings are guaranteed to (at least) have a default English string in Util.UIStrings,
@@ -146,15 +150,23 @@ export class ReportUIFeatures {
146
150
  return buttonEl;
147
151
  }
148
152
 
153
+ resetUIState() {
154
+ if (this._topbar) {
155
+ this._topbar.resetUIState();
156
+ }
157
+ }
158
+
149
159
  /**
150
160
  * Returns the html that recreates this report.
151
161
  * @return {string}
152
162
  */
153
163
  getReportHtml() {
154
- if (this._topbar) {
155
- this._topbar.resetUIState();
164
+ if (!this._opts.getStandaloneReportHTML) {
165
+ throw new Error('`getStandaloneReportHTML` is not set');
156
166
  }
157
- return `<!doctype html><body style="margin: 0">${this._dom.rootEl.outerHTML}`;
167
+
168
+ this.resetUIState();
169
+ return this._opts.getStandaloneReportHTML();
158
170
  }
159
171
 
160
172
  /**
@@ -97,7 +97,7 @@ describe('ReportGenerator', () => {
97
97
  expect(lines.slice(0, 3).join('\n')).toMatchInlineSnapshot(`
98
98
  "requestedUrl,finalUrl,category,name,title,type,score
99
99
  \\"http://localhost:10200/dobetterweb/dbw_tester.html\\",\\"http://localhost:10200/dobetterweb/dbw_tester.html\\",\\"Performance\\",\\"performance-score\\",\\"Overall Performance Category Score\\",\\"numeric\\",\\"0.26\\"
100
- \\"http://localhost:10200/dobetterweb/dbw_tester.html\\",\\"http://localhost:10200/dobetterweb/dbw_tester.html\\",\\"Performance\\",\\"first-contentful-paint\\",\\"First Contentful Paint\\",\\"numeric\\",\\"0.02\\"
100
+ \\"http://localhost:10200/dobetterweb/dbw_tester.html\\",\\"http://localhost:10200/dobetterweb/dbw_tester.html\\",\\"Performance\\",\\"first-contentful-paint\\",\\"First Contentful Paint\\",\\"numeric\\",\\"0.01\\"
101
101
  "
102
102
  `);
103
103
 
@@ -27,15 +27,16 @@ describe('ReportUIFeatures', () => {
27
27
 
28
28
  /**
29
29
  * @param {LH.JSON} lhr
30
+ * @param {LH.Renderer.Options=} opts
30
31
  * @return {HTMLElement}
31
32
  */
32
- function render(lhr) {
33
+ function render(lhr, opts) {
33
34
  const detailsRenderer = new DetailsRenderer(dom);
34
35
  const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
35
36
  const renderer = new ReportRenderer(dom, categoryRenderer);
36
- const reportUIFeatures = new ReportUIFeatures(dom);
37
+ const reportUIFeatures = new ReportUIFeatures(dom, opts);
37
38
  const container = dom.find('body', dom.document());
38
- renderer.renderReport(lhr, container);
39
+ renderer.renderReport(lhr, container, opts);
39
40
  reportUIFeatures.initFeatures(lhr);
40
41
  return container;
41
42
  }
@@ -60,6 +61,7 @@ describe('ReportUIFeatures', () => {
60
61
 
61
62
  global.HTMLElement = document.window.HTMLElement;
62
63
  global.HTMLInputElement = document.window.HTMLInputElement;
64
+ global.CustomEvent = document.window.CustomEvent;
63
65
 
64
66
  global.window = document.window;
65
67
  global.window.requestAnimationFrame = fn => fn();
@@ -83,6 +85,7 @@ describe('ReportUIFeatures', () => {
83
85
  global.window = undefined;
84
86
  global.HTMLElement = undefined;
85
87
  global.HTMLInputElement = undefined;
88
+ global.CustomEvent = undefined;
86
89
  });
87
90
 
88
91
  describe('initFeatures', () => {
@@ -275,6 +278,22 @@ describe('ReportUIFeatures', () => {
275
278
  expect(filterControl.hidden).toEqual(true);
276
279
  });
277
280
  });
281
+
282
+ it('save-html option enabled if callback present', () => {
283
+ let container = render(sampleResults);
284
+ const getSaveEl = () => dom.find('a[data-action="save-html"]', container);
285
+ expect(getSaveEl().classList.contains('lh-hidden')).toBeTruthy();
286
+
287
+ const getHtmlMock = jest.fn();
288
+ container = render(sampleResults, {
289
+ getStandaloneReportHTML: getHtmlMock,
290
+ });
291
+ expect(getSaveEl().classList.contains('lh-hidden')).toBeFalsy();
292
+
293
+ expect(getHtmlMock).not.toBeCalled();
294
+ getSaveEl().click();
295
+ expect(getHtmlMock).toBeCalled();
296
+ });
278
297
  });
279
298
 
280
299
  describe('fireworks', () => {
@@ -351,7 +370,6 @@ describe('ReportUIFeatures', () => {
351
370
  assert.ok(!dropDown._toggleEl.classList.contains('lh-active'));
352
371
  });
353
372
 
354
-
355
373
  it('Escape key removes active class', () => {
356
374
  dropDown._toggleEl.click();
357
375
  assert.ok(dropDown._toggleEl.classList.contains('lh-active'));
@@ -92,13 +92,12 @@ async function distinguishLHR(lhr, tabId) {
92
92
  lhr.categories.performance.score = 0.81;
93
93
  }
94
94
 
95
- const finalSSDetails = lhr.audits['final-screenshot'] && lhr.audits['final-screenshot'].details;
95
+ const finalSSDetails = lhr.audits['final-screenshot']?.details;
96
96
  if (finalSSDetails && finalSSDetails.type === 'screenshot') {
97
97
  finalSSDetails.data = await decorateScreenshot(finalSSDetails.data, tabId);
98
98
  }
99
99
 
100
- const fpSSDetails = lhr.audits['full-page-screenshot'] &&
101
- lhr.audits['full-page-screenshot'].details;
100
+ const fpSSDetails = lhr.audits['full-page-screenshot']?.details;
102
101
  if (fpSSDetails && fpSSDetails.type === 'full-page-screenshot') {
103
102
  fpSSDetails.screenshot.data = await decorateScreenshot(fpSSDetails.screenshot.data, tabId);
104
103
  }
@@ -24,6 +24,8 @@ declare module Renderer {
24
24
  * Flow report uses this to convert `#seo` to `#index=0&anchor=seo`.
25
25
  */
26
26
  onPageAnchorRendered?: (link: HTMLAnchorElement) => void;
27
+ /** If defined, `Save as HTML` option is shown in dropdown menu. */
28
+ getStandaloneReportHTML?: () => string;
27
29
  }
28
30
  }
29
31
 
@@ -477,13 +477,13 @@
477
477
  "message": "`[user-scalable=\"no\"]` ‏‮is‬‏ ‏‮not‬‏ ‏‮used‬‏ ‏‮in‬‏ ‏‮the‬‏ `<meta name=\"viewport\">` ‏‮element‬‏ ‏‮and‬‏ ‏‮the‬‏ `[maximum-scale]` ‏‮attribute‬‏ ‏‮is‬‏ ‏‮not‬‏ ‏‮less‬‏ ‏‮than‬‏ 5."
478
478
  },
479
479
  "lighthouse-core/audits/accessibility/object-alt.js | description": {
480
- "message": "‏‮Screen‬‏ ‏‮readers‬‏ ‏‮cannot‬‏ ‏‮translate‬‏ ‏‮non‬‏-‏‮text‬‏ ‏‮content‬‏. ‏‮Adding‬‏ ‏‮alt‬‏ ‏‮text‬‏ ‏‮to‬‏ `<object>` ‏‮elements‬‏ ‏‮helps‬‏ ‏‮screen‬‏ ‏‮readers‬‏ ‏‮convey‬‏ ‏‮meaning‬‏ ‏‮to‬‏ ‏‮users‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://web.dev/object-alt/)."
480
+ "message": "‏‮Screen‬‏ ‏‮readers‬‏ ‏‮cannot‬‏ ‏‮translate‬‏ ‏‮non‬‏-‏‮text‬‏ ‏‮content‬‏. ‏‮Adding‬‏ ‏‮alternate‬‏ ‏‮text‬‏ ‏‮to‬‏ `<object>` ‏‮elements‬‏ ‏‮helps‬‏ ‏‮screen‬‏ ‏‮readers‬‏ ‏‮convey‬‏ ‏‮meaning‬‏ ‏‮to‬‏ ‏‮users‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://web.dev/object-alt/)."
481
481
  },
482
482
  "lighthouse-core/audits/accessibility/object-alt.js | failureTitle": {
483
- "message": "`<object>` ‏‮elements‬‏ ‏‮do‬‏ ‏‮not‬‏ ‏‮have‬‏ `[alt]` ‏‮text‬‏"
483
+ "message": "`<object>` ‏‮elements‬‏ ‏‮do‬‏ ‏‮not‬‏ ‏‮have‬‏ ‏‮alternate‬‏ ‏‮text‬‏"
484
484
  },
485
485
  "lighthouse-core/audits/accessibility/object-alt.js | title": {
486
- "message": "`<object>` ‏‮elements‬‏ ‏‮have‬‏ `[alt]` ‏‮text‬‏"
486
+ "message": "`<object>` ‏‮elements‬‏ ‏‮have‬‏ ‏‮alternate‬‏ ‏‮text‬‏"
487
487
  },
488
488
  "lighthouse-core/audits/accessibility/tabindex.js | description": {
489
489
  "message": "‏‮A‬‏ ‏‮value‬‏ ‏‮greater‬‏ ‏‮than‬‏ 0 ‏‮implies‬‏ ‏‮an‬‏ ‏‮explicit‬‏ ‏‮navigation‬‏ ‏‮ordering‬‏. ‏‮Although‬‏ ‏‮technically‬‏ ‏‮valid‬‏, ‏‮this‬‏ ‏‮often‬‏ ‏‮creates‬‏ ‏‮frustrating‬‏ ‏‮experiences‬‏ ‏‮for‬‏ ‏‮users‬‏ ‏‮who‬‏ ‏‮rely‬‏ ‏‮on‬‏ ‏‮assistive‬‏ ‏‮technologies‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://web.dev/tabindex/)."
@@ -1052,6 +1052,9 @@
1052
1052
  "lighthouse-core/audits/installable-manifest.js | not-offline-capable": {
1053
1053
  "message": "‏‮Page‬‏ ‏‮does‬‏ ‏‮not‬‏ ‏‮work‬‏ ‏‮offline‬‏"
1054
1054
  },
1055
+ "lighthouse-core/audits/installable-manifest.js | pipeline-restarted": {
1056
+ "message": "‏‮PWA‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮uninstalled‬‏ ‏‮and‬‏ ‏‮installability‬‏ ‏‮checks‬‏ ‏‮resetting‬‏."
1057
+ },
1055
1058
  "lighthouse-core/audits/installable-manifest.js | platform-not-supported-on-android": {
1056
1059
  "message": "‏‮The‬‏ ‏‮specified‬‏ ‏‮application‬‏ ‏‮platform‬‏ ‏‮is‬‏ ‏‮not‬‏ ‏‮supported‬‏ ‏‮on‬‏ ‏‮Android‬‏"
1057
1060
  },
@@ -1061,6 +1064,9 @@
1061
1064
  "lighthouse-core/audits/installable-manifest.js | prefer-related-applications-only-beta-stable": {
1062
1065
  "message": "‏‮prefer‬‏_‏‮related‬‏_‏‮applications‬‏ ‏‮is‬‏ ‏‮only‬‏ ‏‮supported‬‏ ‏‮on‬‏ ‏‮Chrome‬‏ ‏‮Beta‬‏ ‏‮and‬‏ ‏‮Stable‬‏ ‏‮channels‬‏ ‏‮on‬‏ ‏‮Android‬‏."
1063
1066
  },
1067
+ "lighthouse-core/audits/installable-manifest.js | protocol-timeout": {
1068
+ "message": "‏‮Lighthouse‬‏ ‏‮could‬‏ ‏‮not‬‏ ‏‮determine‬‏ ‏‮if‬‏ ‏‮there‬‏ ‏‮was‬‏ ‏‮a‬‏ ‏‮service‬‏ ‏‮worker‬‏. ‏‮Please‬‏ ‏‮try‬‏ ‏‮with‬‏ ‏‮a‬‏ ‏‮newer‬‏ ‏‮version‬‏ ‏‮of‬‏ ‏‮Chrome‬‏."
1069
+ },
1064
1070
  "lighthouse-core/audits/installable-manifest.js | start-url-not-valid": {
1065
1071
  "message": "‏‮Manifest‬‏ ‏‮start‬‏ ‏‮URL‬‏ ‏‮is‬‏ ‏‮not‬‏ ‏‮valid‬‏"
1066
1072
  },
@@ -1212,7 +1218,7 @@
1212
1218
  "message": "‏‮Server‬‏ ‏‮Backend‬‏ ‏‮Latencies‬‏"
1213
1219
  },
1214
1220
  "lighthouse-core/audits/no-unload-listeners.js | description": {
1215
- "message": "‏‮The‬‏ `unload` ‏‮event‬‏ ‏‮does‬‏ ‏‮not‬‏ ‏‮fire‬‏ ‏‮reliably‬‏ ‏‮and‬‏ ‏‮listening‬‏ ‏‮for‬‏ ‏‮it‬‏ ‏‮can‬‏ ‏‮prevent‬‏ ‏‮browser‬‏ ‏‮optimizations‬‏ ‏‮like‬‏ ‏‮the‬‏ ‏‮Back‬‏-‏‮Forward‬‏ ‏‮Cache‬‏. ‏‮Consider‬‏ ‏‮using‬‏ ‏‮the‬‏ `pagehide` ‏‮or‬‏ `visibilitychange` ‏‮events‬‏ ‏‮instead‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://developers.google.com/web/updates/2018/07/page-lifecycle-api#the-unload-event)"
1221
+ "message": "‏‮The‬‏ `unload` ‏‮event‬‏ ‏‮does‬‏ ‏‮not‬‏ ‏‮fire‬‏ ‏‮reliably‬‏ ‏‮and‬‏ ‏‮listening‬‏ ‏‮for‬‏ ‏‮it‬‏ ‏‮can‬‏ ‏‮prevent‬‏ ‏‮browser‬‏ ‏‮optimizations‬‏ ‏‮like‬‏ ‏‮the‬‏ ‏‮Back‬‏-‏‮Forward‬‏ ‏‮Cache‬‏. ‏‮Use‬‏ `pagehide` ‏‮or‬‏ `visibilitychange` ‏‮events‬‏ ‏‮instead‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://web.dev/bfcache/#never-use-the-unload-event)"
1216
1222
  },
1217
1223
  "lighthouse-core/audits/no-unload-listeners.js | failureTitle": {
1218
1224
  "message": "‏‮Registers‬‏ ‏‮an‬‏ `unload` ‏‮listener‬‏"
@@ -2219,6 +2225,36 @@
2219
2225
  "node_modules/lighthouse-stack-packs/packs/magento.js | uses-rel-preload": {
2220
2226
  "message": "`<link rel=preload>` ‏‮tags‬‏ ‏‮can‬‏ ‏‮be‬‏ ‏‮added‬‏ ‏‮by‬‏ [‏‮modifying‬‏ ‏‮a‬‏ ‏‮themes‬‏'‏‮s‬‏ ‏‮layout‬‏](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-manage.html)."
2221
2227
  },
2228
+ "node_modules/lighthouse-stack-packs/packs/next.js | modern-image-formats": {
2229
+ "message": "‏‮Use‬‏ ‏‮the‬‏ `Next.js` ‏‮Image‬‏ ‏‮Optimization‬‏ ‏‮API‬‏ ‏‮to‬‏ ‏‮serve‬‏ ‏‮modern‬‏ ‏‮formats‬‏ ‏‮like‬‏ `WebP` ‏‮and‬‏ `AVIF`. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/api-reference/next/image#acceptable-formats)."
2230
+ },
2231
+ "node_modules/lighthouse-stack-packs/packs/next.js | offscreen-images": {
2232
+ "message": "‏‮Use‬‏ ‏‮the‬‏ `next/image` ‏‮component‬‏, ‏‮which‬‏ ‏‮defaults‬‏ ‏‮to‬‏ `loading=\"lazy\"`. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/api-reference/next/image#loading)."
2233
+ },
2234
+ "node_modules/lighthouse-stack-packs/packs/next.js | render-blocking-resources": {
2235
+ "message": "‏‮Use‬‏ ‏‮the‬‏ `next/script` ‏‮component‬‏ ‏‮to‬‏ ‏‮defer‬‏ ‏‮loading‬‏ ‏‮of‬‏ ‏‮non‬‏-‏‮critical‬‏ ‏‮third‬‏-‏‮party‬‏ ‏‮scripts‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/basic-features/script)."
2236
+ },
2237
+ "node_modules/lighthouse-stack-packs/packs/next.js | unused-css-rules": {
2238
+ "message": "‏‮Consider‬‏ ‏‮setting‬‏ ‏‮up‬‏ `PurgeCSS` ‏‮in‬‏ `Next.js` ‏‮configuration‬‏ ‏‮to‬‏ ‏‮remove‬‏ ‏‮unused‬‏ ‏‮rules‬‏ ‏‮from‬‏ ‏‮stylesheets‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://purgecss.com/guides/next.html)."
2239
+ },
2240
+ "node_modules/lighthouse-stack-packs/packs/next.js | unused-javascript": {
2241
+ "message": "‏‮Use‬‏ `Webpack Bundle Analyzer` ‏‮to‬‏ ‏‮detect‬‏ ‏‮unused‬‏ ‏‮JavaScript‬‏ ‏‮code‬‏. [‏‮Learn‬‏ ‏‮mode‬‏](https://github.com/vercel/next.js/tree/canary/packages/next-bundle-analyzer)"
2242
+ },
2243
+ "node_modules/lighthouse-stack-packs/packs/next.js | user-timings": {
2244
+ "message": "‏‮Consider‬‏ ‏‮using‬‏ `Next.js Analytics` ‏‮to‬‏ ‏‮measure‬‏ ‏‮your‬‏ ‏‮app‬‏'‏‮s‬‏ ‏‮real‬‏-‏‮world‬‏ ‏‮performance‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/advanced-features/measuring-performance)."
2245
+ },
2246
+ "node_modules/lighthouse-stack-packs/packs/next.js | uses-long-cache-ttl": {
2247
+ "message": "‏‮Configure‬‏ ‏‮caching‬‏ ‏‮for‬‏ ‏‮immutable‬‏ ‏‮assets‬‏ ‏‮and‬‏ `Server-side Rendered` (‏‮SSR‬‏) ‏‮pages‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/going-to-production#caching)."
2248
+ },
2249
+ "node_modules/lighthouse-stack-packs/packs/next.js | uses-optimized-images": {
2250
+ "message": "‏‮Use‬‏ ‏‮the‬‏ `next/image` ‏‮component‬‏ ‏‮instead‬‏ ‏‮of‬‏ `<img>` ‏‮to‬‏ ‏‮optimize‬‏ ‏‮images‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/basic-features/image-optimization)."
2251
+ },
2252
+ "node_modules/lighthouse-stack-packs/packs/next.js | uses-responsive-images": {
2253
+ "message": "‏‮Use‬‏ ‏‮the‬‏ `next/image` ‏‮component‬‏ ‏‮to‬‏ ‏‮set‬‏ ‏‮the‬‏ ‏‮appropriate‬‏ `sizes`. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/api-reference/next/image#sizes)."
2254
+ },
2255
+ "node_modules/lighthouse-stack-packs/packs/next.js | uses-text-compression": {
2256
+ "message": "‏‮Enable‬‏ ‏‮compression‬‏ ‏‮on‬‏ ‏‮your‬‏ ‏‮Next‬‏.‏‮js‬‏ ‏‮server‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://nextjs.org/docs/api-reference/next.config.js/compression)."
2257
+ },
2222
2258
  "node_modules/lighthouse-stack-packs/packs/octobercms.js | efficient-animated-content": {
2223
2259
  "message": "[‏‮Replace‬‏ ‏‮animated‬‏ ‏‮GIFs‬‏ ‏‮with‬‏ ‏‮video‬‏](https://web.dev/replace-gifs-with-videos/) ‏‮for‬‏ ‏‮faster‬‏ ‏‮web‬‏ ‏‮page‬‏ ‏‮loads‬‏ ‏‮and‬‏ ‏‮consider‬‏ ‏‮using‬‏ ‏‮modern‬‏ ‏‮file‬‏ ‏‮formats‬‏ ‏‮such‬‏ ‏‮as‬‏ [‏‮WebM‬‏](https://web.dev/replace-gifs-with-videos/#create-webm-videos) ‏‮or‬‏ [‏‮AV‬‏1](https://developers.google.com/web/updates/2018/09/chrome-70-media-updates#av1-decoder) ‏‮to‬‏ ‏‮improve‬‏ ‏‮compression‬‏ ‏‮efficiency‬‏ ‏‮by‬‏ ‏‮greater‬‏ ‏‮than‬‏ 30% ‏‮over‬‏ ‏‮the‬‏ ‏‮current‬‏ ‏‮state‬‏-‏‮of‬‏-‏‮the‬‏-‏‮art‬‏ ‏‮video‬‏ ‏‮codec‬‏, ‏‮VP‬‏9."
2224
2260
  },