lighthouse 9.0.0-dev.20211119 → 9.0.0-dev.20211123

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 (84) hide show
  1. package/dist/report/bundle.esm.js +1 -1
  2. package/dist/report/flow.js +8 -8
  3. package/dist/report/standalone.js +1 -1
  4. package/flow-report/assets/standalone-flow-template.html +0 -1
  5. package/lighthouse-cli/cli-flags.js +4 -4
  6. package/lighthouse-core/audits/audit.js +39 -7
  7. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +1 -26
  8. package/lighthouse-core/audits/deprecations.js +10 -14
  9. package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +5 -4
  10. package/lighthouse-core/audits/dobetterweb/js-libraries.js +1 -1
  11. package/lighthouse-core/audits/dobetterweb/no-document-write.js +6 -4
  12. package/lighthouse-core/audits/dobetterweb/notification-on-start.js +6 -4
  13. package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +6 -4
  14. package/lighthouse-core/audits/errors-in-console.js +7 -4
  15. package/lighthouse-core/audits/no-unload-listeners.js +9 -10
  16. package/lighthouse-core/audits/seo/canonical.js +0 -14
  17. package/lighthouse-core/audits/seo/font-size.js +4 -2
  18. package/lighthouse-core/audits/seo/is-crawlable.js +2 -2
  19. package/lighthouse-core/audits/third-party-summary.js +2 -2
  20. package/lighthouse-core/audits/valid-source-maps.js +1 -1
  21. package/lighthouse-core/audits/violation-audit.js +10 -3
  22. package/lighthouse-core/computed/js-bundles.js +1 -3
  23. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +3 -3
  24. package/lighthouse-core/gather/driver/execution-context.js +1 -1
  25. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +7 -7
  26. package/lighthouse-core/gather/gatherers/stacks.js +0 -3
  27. package/lighthouse-core/runner.js +7 -7
  28. package/package.json +3 -5
  29. package/report/generator/flow-report-assets.js +2 -1
  30. package/report/generator/report-assets.js +2 -5
  31. package/report/generator/report-generator.js +0 -1
  32. package/report/renderer/report-ui-features.js +1 -1
  33. package/report/renderer/swap-locale-feature.js +0 -5
  34. package/report/test/clients/bundle-test.js +2 -2
  35. package/report/test/renderer/report-ui-features-test.js +0 -1
  36. package/shared/localization/locales/ar-XB.json +0 -3
  37. package/shared/localization/locales/ar.json +0 -3
  38. package/shared/localization/locales/bg.json +0 -3
  39. package/shared/localization/locales/ca.json +0 -3
  40. package/shared/localization/locales/cs.json +0 -3
  41. package/shared/localization/locales/da.json +0 -3
  42. package/shared/localization/locales/de.json +0 -3
  43. package/shared/localization/locales/el.json +0 -3
  44. package/shared/localization/locales/en-GB.json +0 -3
  45. package/shared/localization/locales/en-US.json +0 -3
  46. package/shared/localization/locales/en-XA.json +0 -3
  47. package/shared/localization/locales/en-XL.json +0 -3
  48. package/shared/localization/locales/es-419.json +0 -3
  49. package/shared/localization/locales/es.json +0 -3
  50. package/shared/localization/locales/fi.json +0 -3
  51. package/shared/localization/locales/fil.json +0 -3
  52. package/shared/localization/locales/fr.json +0 -3
  53. package/shared/localization/locales/he.json +0 -3
  54. package/shared/localization/locales/hi.json +0 -3
  55. package/shared/localization/locales/hr.json +0 -3
  56. package/shared/localization/locales/hu.json +0 -3
  57. package/shared/localization/locales/id.json +0 -3
  58. package/shared/localization/locales/it.json +0 -3
  59. package/shared/localization/locales/ja.json +0 -3
  60. package/shared/localization/locales/ko.json +0 -3
  61. package/shared/localization/locales/lt.json +0 -3
  62. package/shared/localization/locales/lv.json +0 -3
  63. package/shared/localization/locales/nl.json +0 -3
  64. package/shared/localization/locales/no.json +0 -3
  65. package/shared/localization/locales/pl.json +0 -3
  66. package/shared/localization/locales/pt-PT.json +0 -3
  67. package/shared/localization/locales/pt.json +0 -3
  68. package/shared/localization/locales/ro.json +0 -3
  69. package/shared/localization/locales/ru.json +0 -3
  70. package/shared/localization/locales/sk.json +0 -3
  71. package/shared/localization/locales/sl.json +0 -3
  72. package/shared/localization/locales/sr-Latn.json +0 -3
  73. package/shared/localization/locales/sr.json +0 -3
  74. package/shared/localization/locales/sv.json +0 -3
  75. package/shared/localization/locales/ta.json +0 -3
  76. package/shared/localization/locales/te.json +0 -3
  77. package/shared/localization/locales/th.json +0 -3
  78. package/shared/localization/locales/tr.json +0 -3
  79. package/shared/localization/locales/uk.json +0 -3
  80. package/shared/localization/locales/vi.json +0 -3
  81. package/shared/localization/locales/zh-HK.json +0 -3
  82. package/shared/localization/locales/zh-TW.json +0 -3
  83. package/shared/localization/locales/zh.json +0 -3
  84. package/types/externs.d.ts +5 -24
@@ -203,7 +203,7 @@ class O{constructor(e){this._dom=e,this._toggleEl,this._menuEl,this.onDocumentKe
203
203
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
204
204
  * See the License for the specific language governing permissions and
205
205
  * limitations under the License.
206
- */class j{constructor(e,t={}){this.json,this._dom=e,this._opts=t,this._topbar=t.omitTopbar?null:new I(this,e),this.onMediaQueryChange=this.onMediaQueryChange.bind(this)}initFeatures(e){this.json=e,this._topbar&&(this._topbar.enable(e),this._topbar.resetUIState()),this._setupMediaQueryListeners(),this._setupThirdPartyFilter(),this._setupElementScreenshotOverlay(this._dom.rootEl);let t=!1;!(this._dom.isDevTools()||this._opts.disableAutoDarkModeAndFireworks)&&window.matchMedia("(prefers-color-scheme: dark)").matches&&(t=!0);["performance","accessibility","best-practices","seo"].every((t=>{const n=e.categories[t];return n&&1===n.score}))&&(t=!0,this._enableFireworks()),t&&T(this._dom,!0);if(e.categories.performance&&e.categories.performance.auditRefs.some((t=>Boolean("metrics"===t.group&&e.audits[t.id].errorMessage)))){this._dom.find("input.lh-metrics-toggle__input",this._dom.rootEl).checked=!0}this.json.audits["script-treemap-data"]&&this.json.audits["script-treemap-data"].details&&this.addButton({text:l.i18n.strings.viewTreemapLabel,icon:"treemap",onClick:()=>function(e){if(!e.audits["script-treemap-data"].details)throw new Error("no script treemap data found");U({lhr:{requestedUrl:e.requestedUrl,finalUrl:e.finalUrl,audits:{"script-treemap-data":e.audits["script-treemap-data"]},configSettings:{locale:e.configSettings.locale}}},P()+"/treemap/","treemap-"+$(e))}(this.json)});for(const e of this._dom.findAll("[data-i18n]",this._dom.rootEl)){const t=e.getAttribute("data-i18n");e.textContent=l.i18n.strings[t]}}addButton(e){const t=this._dom.rootEl.querySelector(".lh-audit-group--metrics");if(!t)return;let n=t.querySelector(".lh-buttons");n||(n=this._dom.createChildOf(t,"div","lh-buttons"));const r=["lh-button"];e.icon&&(r.push("lh-report-icon"),r.push(`lh-report-icon--${e.icon}`));const o=this._dom.createChildOf(n,"button",r.join(" "));return o.textContent=e.text,o.addEventListener("click",e.onClick),o}getReportHtml(){return this._topbar&&this._topbar.resetUIState(),`<!doctype html><body>${this._dom.rootEl.outerHTML}`}saveAsGist(){throw new Error("Cannot save as gist from base report")}_enableFireworks(){const e=this._dom.find(".lh-scores-container",this._dom.rootEl);e.classList.add("lh-score100"),e.addEventListener("click",(t=>{e.classList.toggle("lh-fireworks-paused")}))}_setupMediaQueryListeners(){const e=self.matchMedia("(max-width: 500px)");e.addListener(this.onMediaQueryChange),this.onMediaQueryChange(e)}_resetUIState(){this._topbar&&this._topbar.resetUIState()}onMediaQueryChange(e){this._dom.rootEl.classList.toggle("lh-narrow",e.matches)}_setupThirdPartyFilter(){const e=["uses-rel-preconnect","third-party-facades"],t=["legacy-javascript"];Array.from(this._dom.rootEl.querySelectorAll("table.lh-table")).filter((e=>e.querySelector("td.lh-table-column--url, td.lh-table-column--source-location"))).filter((t=>{const n=t.closest(".lh-audit");if(!n)throw new Error(".lh-table not within audit");return!e.includes(n.id)})).forEach((e=>{const n=function(e){return Array.from(e.tBodies[0].rows)}(e),r=this._getThirdPartyRows(n,this.json.finalUrl),o=this._dom.createComponent("3pFilter"),i=this._dom.find("input",o);i.addEventListener("change",(e=>{const t=e.target instanceof HTMLInputElement&&!e.target.checked;let o=!0,i=n[0];for(;i;){const e=t&&r.includes(i);do{i.classList.toggle("lh-row--hidden",e),i.classList.toggle("lh-row--even",!e&&o),i.classList.toggle("lh-row--odd",!e&&!o),i=i.nextElementSibling}while(i&&i.classList.contains("lh-sub-item-row"));e||(o=!o)}})),this._dom.find(".lh-3p-filter-count",o).textContent=`${r.length}`,this._dom.find(".lh-3p-ui-string",o).textContent=l.i18n.strings.thirdPartyResourcesLabel;const a=r.length===n.length,s=!r.length;if((a||s)&&(this._dom.find("div.lh-3p-filter",o).hidden=!0),!e.parentNode)return;e.parentNode.insertBefore(o,e);const c=e.closest(".lh-audit");if(!c)throw new Error(".lh-table not within audit");t.includes(c.id)&&!a&&i.click()}))}_setupElementScreenshotOverlay(e){const t=this.json.audits["full-page-screenshot"]&&this.json.audits["full-page-screenshot"].details&&"full-page-screenshot"===this.json.audits["full-page-screenshot"].details.type&&this.json.audits["full-page-screenshot"].details;t&&A.installOverlayFeature({dom:this._dom,rootEl:e,overlayContainerEl:e,fullPageScreenshot:t})}_getThirdPartyRows(e,t){const n=[],r=l.getRootDomain(t);for(const t of e){if(t.classList.contains("lh-sub-item-row"))continue;const e=t.querySelector("div.lh-text__url");if(!e)continue;const o=e.dataset.url;if(!o)continue;l.getRootDomain(o)!==r&&n.push(t)}return n}_saveFile(e){const t=G.getLhrFilenamePrefix(this.json);this._dom.saveFile(e,t)}}
206
+ */class j{constructor(e,t={}){this.json,this._dom=e,this._opts=t,this._topbar=t.omitTopbar?null:new I(this,e),this.onMediaQueryChange=this.onMediaQueryChange.bind(this)}initFeatures(e){this.json=e,this._topbar&&(this._topbar.enable(e),this._topbar.resetUIState()),this._setupMediaQueryListeners(),this._setupThirdPartyFilter(),this._setupElementScreenshotOverlay(this._dom.rootEl);let t=!1;!(this._dom.isDevTools()||this._opts.disableAutoDarkModeAndFireworks)&&window.matchMedia("(prefers-color-scheme: dark)").matches&&(t=!0);["performance","accessibility","best-practices","seo"].every((t=>{const n=e.categories[t];return n&&1===n.score}))&&(t=!0,this._enableFireworks()),t&&T(this._dom,!0);if(e.categories.performance&&e.categories.performance.auditRefs.some((t=>Boolean("metrics"===t.group&&e.audits[t.id].errorMessage)))){this._dom.find("input.lh-metrics-toggle__input",this._dom.rootEl).checked=!0}this.json.audits["script-treemap-data"]&&this.json.audits["script-treemap-data"].details&&this.addButton({text:l.i18n.strings.viewTreemapLabel,icon:"treemap",onClick:()=>function(e){if(!e.audits["script-treemap-data"].details)throw new Error("no script treemap data found");U({lhr:{requestedUrl:e.requestedUrl,finalUrl:e.finalUrl,audits:{"script-treemap-data":e.audits["script-treemap-data"]},configSettings:{locale:e.configSettings.locale}}},P()+"/treemap/","treemap-"+$(e))}(this.json)});for(const e of this._dom.findAll("[data-i18n]",this._dom.rootEl)){const t=e.getAttribute("data-i18n");e.textContent=l.i18n.strings[t]}}addButton(e){const t=this._dom.rootEl.querySelector(".lh-audit-group--metrics");if(!t)return;let n=t.querySelector(".lh-buttons");n||(n=this._dom.createChildOf(t,"div","lh-buttons"));const r=["lh-button"];e.icon&&(r.push("lh-report-icon"),r.push(`lh-report-icon--${e.icon}`));const o=this._dom.createChildOf(n,"button",r.join(" "));return o.textContent=e.text,o.addEventListener("click",e.onClick),o}getReportHtml(){return this._topbar&&this._topbar.resetUIState(),`<!doctype html><body style="margin: 0">${this._dom.rootEl.outerHTML}`}saveAsGist(){throw new Error("Cannot save as gist from base report")}_enableFireworks(){const e=this._dom.find(".lh-scores-container",this._dom.rootEl);e.classList.add("lh-score100"),e.addEventListener("click",(t=>{e.classList.toggle("lh-fireworks-paused")}))}_setupMediaQueryListeners(){const e=self.matchMedia("(max-width: 500px)");e.addListener(this.onMediaQueryChange),this.onMediaQueryChange(e)}_resetUIState(){this._topbar&&this._topbar.resetUIState()}onMediaQueryChange(e){this._dom.rootEl.classList.toggle("lh-narrow",e.matches)}_setupThirdPartyFilter(){const e=["uses-rel-preconnect","third-party-facades"],t=["legacy-javascript"];Array.from(this._dom.rootEl.querySelectorAll("table.lh-table")).filter((e=>e.querySelector("td.lh-table-column--url, td.lh-table-column--source-location"))).filter((t=>{const n=t.closest(".lh-audit");if(!n)throw new Error(".lh-table not within audit");return!e.includes(n.id)})).forEach((e=>{const n=function(e){return Array.from(e.tBodies[0].rows)}(e),r=this._getThirdPartyRows(n,this.json.finalUrl),o=this._dom.createComponent("3pFilter"),i=this._dom.find("input",o);i.addEventListener("change",(e=>{const t=e.target instanceof HTMLInputElement&&!e.target.checked;let o=!0,i=n[0];for(;i;){const e=t&&r.includes(i);do{i.classList.toggle("lh-row--hidden",e),i.classList.toggle("lh-row--even",!e&&o),i.classList.toggle("lh-row--odd",!e&&!o),i=i.nextElementSibling}while(i&&i.classList.contains("lh-sub-item-row"));e||(o=!o)}})),this._dom.find(".lh-3p-filter-count",o).textContent=`${r.length}`,this._dom.find(".lh-3p-ui-string",o).textContent=l.i18n.strings.thirdPartyResourcesLabel;const a=r.length===n.length,s=!r.length;if((a||s)&&(this._dom.find("div.lh-3p-filter",o).hidden=!0),!e.parentNode)return;e.parentNode.insertBefore(o,e);const c=e.closest(".lh-audit");if(!c)throw new Error(".lh-table not within audit");t.includes(c.id)&&!a&&i.click()}))}_setupElementScreenshotOverlay(e){const t=this.json.audits["full-page-screenshot"]&&this.json.audits["full-page-screenshot"].details&&"full-page-screenshot"===this.json.audits["full-page-screenshot"].details.type&&this.json.audits["full-page-screenshot"].details;t&&A.installOverlayFeature({dom:this._dom,rootEl:e,overlayContainerEl:e,fullPageScreenshot:t})}_getThirdPartyRows(e,t){const n=[],r=l.getRootDomain(t);for(const t of e){if(t.classList.contains("lh-sub-item-row"))continue;const e=t.querySelector("div.lh-text__url");if(!e)continue;const o=e.dataset.url;if(!o)continue;l.getRootDomain(o)!==r&&n.push(t)}return n}_saveFile(e){const t=G.getLhrFilenamePrefix(this.json);this._dom.saveFile(e,t)}}
207
207
  /**
208
208
  * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
209
209
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
@@ -21,7 +21,6 @@ limitations under the License.
21
21
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
22
22
  <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEhklEQVR4AWJxL/BhIAesev1U5tcflpncgNrKIsqNIwzC9feMpDUzs70kOczMzMzJJcxwCTMzncPMnOwtzBwzMzPb0vRfeZPp0VhPS5I39V5fdiXV1/VD+9QC7OVn9BsyH1XIoEI1PfmJvLFowVV564+34DFUHudbmfDh4kVXh//7XwE+WjS/YfXZe3yr4j2rqj1AIhSB7hZ8ZtPZu/zw8cK523U4wE1/rvPfWrz4zs0m9ZdC9yUJAlASdBAgocRegfF/f3/h/PuaFsxMdwjAR0vm1+06eMMfIrhLqTWqdH4EumU2SPfMhigJAlRQbZrgrRsl9U+Y2DYDFCz3ILC9kiAiqSrMwbWT0nceEnR+9Kggc2zjOJCASDENkg0a5HfZZgDP81CM3CrQs2Z1+o7DJ6ePr8sK0AOCHv5Jjdt3evyYSaZ351VIStIxPRAUtrBYbxC6w+BZ0ivVSBKkIhJhemSyZpfB00EiPO2VjzYkxhcqXQqCWCShGplvi3y0QxqbuBurMjyJeWnkHZuAEgIQGsUBqwrfjZ+IlBgKyRJzVVYF8O6qFWdh86YzQzMrZigYmxAyfvHgLZQ/LC1CbeniW2Hkqr/PH16SgvGuf2/uzNMBwJA/njxizGPtSyAf7EziJCMGRDRdhoAC4PL1A/SrKQMAAQkEfpJAcRQdrBJ7gNwjSpJsdwK+CANBkqa1LgQB4IicV9nYUct7gaxuDJUErQIiEAiMxLVOFlKzIktPpT0ggpdpC/8YAHnxbgkUY4tAAFSR7AAXNyAAWHJrA/kHGjzg5nleuwFO7Nd/IoDw4Pm58+4jNLmYG0wRA5bErc2Mr3Y+dXTDW1VvwqbJkzMCHQ4S1GTCBOIgUHJrGdEwqzR+jAp/o2qAZelUDoQnruEEdDclJI6576AlNVfc+22XN/+Y1vnJD0Yind6UpEEvn/Hqq15EYjCW7jZCJEpnNvDgkyelDjs106kuux2AAXCSobULOWP8mLhYlpoDMK4qAFXJGk+grtH8YXVz5KJblqaG1+VUdTc0I290bmUQAriGITRbdQnom0aoFj8kx1+wMD2ifncAXUQE4SkDqN1hE0jEophs1SUwZAOhUAiMCLwRtamtTZtbbmZErSAUHbSysaoEmnrsakiMiUAURi283gN6wans9oX8rOCrj7/JP35DFD+iQ7Au/K2KE1jzx6ujjUnXFH9KjEq6ZlhsTBICrNLJf47Pv/pkHzvup1w4dmUbEei0+bcXRqJuh5kVARQ8byyYxOwNGr7A87xh1tp8sGT+uMInrwi++Xj7TQz2d27NvwEkrOflAFQGIDA5khASBCGdO2/Z/MnLPwYfv5TFhjW7QhVKAB6afwe2LpFlFsCnlQEosgQgDsdOG1/LKeNqJS4JCSPJ/i+TakwEARor7gER1Iva5JmPOJK0RUqmoPnnlzFCtmIAhAAQEIQRgDaiYPIauNXcnDlRIrWNFY3hm7PG9YRqr7IV7HrCgAC17befjEvRq2nGhAHtBqDpOuI/I1diUUAMYIxEdyejBJqLnNoszGZtfiX/CztGv2mq+sdaAAAAAElFTkSuQmCC">
23
23
  <title>Lighthouse Flow Report</title>
24
- <style>/*%%LIGHTHOUSE_CSS%%*/</style>
25
24
  <style>/*%%LIGHTHOUSE_FLOW_CSS%%*/</style>
26
25
  <style>body {margin: 0}</style>
27
26
  </head>
@@ -225,7 +225,7 @@ function getFlags(manualArgv, options = {}) {
225
225
  .options({
226
226
  'output': {
227
227
  type: 'array',
228
- default: /** @type {['html']} */ (['html']),
228
+ default: /** @type {const} */ (['html']),
229
229
  coerce: coerceOutput,
230
230
  describe: 'Reporter for the results, supports multiple values. choices: "json", "html", "csv"',
231
231
  },
@@ -309,9 +309,9 @@ function getFlags(manualArgv, options = {}) {
309
309
  })
310
310
 
311
311
  // Choices added outside of `options()` and cast so tsc picks them up.
312
- .choices('form-factor', /** @type {['mobile', 'desktop']} */ (['mobile', 'desktop']))
313
- .choices('throttling-method', /** @type {['devtools', 'provided', 'simulate']} */ (['devtools', 'provided', 'simulate']))
314
- .choices('preset', /** @type {['perf', 'experimental', 'desktop']} */ (['perf', 'experimental', 'desktop']))
312
+ .choices('form-factor', /** @type {const} */ (['mobile', 'desktop']))
313
+ .choices('throttling-method', /** @type {const} */ (['devtools', 'provided', 'simulate']))
314
+ .choices('preset', /** @type {const} */ (['perf', 'experimental', 'desktop']))
315
315
 
316
316
  .check(argv => {
317
317
  // Lighthouse doesn't need a URL if...
@@ -233,21 +233,53 @@ class Audit {
233
233
  }
234
234
 
235
235
  /**
236
- * @param {LH.Artifacts.ConsoleMessage} entry
237
- * @return {LH.Audit.Details.SourceLocationValue | undefined}
236
+ * @param {LH.Artifacts.Bundle} bundle
237
+ * @param {number} generatedLine
238
+ * @param {number} generatedColumn
239
+ * @return {LH.Audit.Details.SourceLocationValue['original']}
238
240
  */
239
- static makeSourceLocationFromConsoleMessage(entry) {
240
- if (!entry.url) return;
241
+ static _findOriginalLocation(bundle, generatedLine, generatedColumn) {
242
+ const entry = bundle?.map.findEntry(generatedLine, generatedColumn);
243
+ if (!entry) return;
241
244
 
245
+ return {
246
+ file: entry.sourceURL || '',
247
+ line: entry.sourceLineNumber || 0,
248
+ column: entry.sourceColumnNumber || 0,
249
+ };
250
+ }
251
+
252
+ /**
253
+ * @param {string} url
254
+ * @param {number} line 0-indexed
255
+ * @param {number} column 0-indexed
256
+ * @param {LH.Artifacts.Bundle=} bundle
257
+ * @return {LH.Audit.Details.SourceLocationValue}
258
+ */
259
+ static makeSourceLocation(url, line, column, bundle) {
242
260
  return {
243
261
  type: 'source-location',
244
- url: entry.url,
262
+ url,
245
263
  urlProvider: 'network',
246
- line: entry.lineNumber || 0,
247
- column: entry.columnNumber || 0,
264
+ line,
265
+ column,
266
+ original: bundle && this._findOriginalLocation(bundle, line, column),
248
267
  };
249
268
  }
250
269
 
270
+ /**
271
+ * @param {LH.Artifacts.ConsoleMessage} entry
272
+ * @param {LH.Artifacts.Bundle=} bundle
273
+ * @return {LH.Audit.Details.SourceLocationValue | undefined}
274
+ */
275
+ static makeSourceLocationFromConsoleMessage(entry, bundle) {
276
+ if (!entry.url) return;
277
+
278
+ const line = entry.lineNumber || 0;
279
+ const column = entry.columnNumber || 0;
280
+ return this.makeSourceLocation(entry.url, line, column, bundle);
281
+ }
282
+
251
283
  /**
252
284
  * @param {number|null} score
253
285
  * @param {LH.Audit.ScoreDisplayMode} scoreDisplayMode
@@ -397,23 +397,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
397
397
  return transferRatio;
398
398
  }
399
399
 
400
- /**
401
- * @param {LH.Artifacts.Bundle} bundle
402
- * @param {number} generatedLine
403
- * @param {number} generatedColumn
404
- * @return {LH.Audit.Details.SourceLocationValue['original']}
405
- */
406
- static _findOriginalLocation(bundle, generatedLine, generatedColumn) {
407
- const entry = bundle && bundle.map.findEntry(generatedLine, generatedColumn);
408
- if (!entry) return;
409
-
410
- return {
411
- file: entry.sourceURL || '',
412
- line: entry.sourceLineNumber || 0,
413
- column: entry.sourceColumnNumber || 0,
414
- };
415
- }
416
-
417
400
  /**
418
401
  * @param {LH.Artifacts} artifacts
419
402
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -456,18 +439,10 @@ class LegacyJavascript extends ByteEfficiencyAudit {
456
439
  const bundle = bundles.find(bundle => bundle.script.src === url);
457
440
  for (const match of matches) {
458
441
  const {name, line, column} = match;
459
-
460
442
  /** @type {SubItem} */
461
443
  const subItem = {
462
444
  signal: name,
463
- location: {
464
- type: 'source-location',
465
- url,
466
- line,
467
- column,
468
- original: bundle && this._findOriginalLocation(bundle, line, column),
469
- urlProvider: 'network',
470
- },
445
+ location: ByteEfficiencyAudit.makeSourceLocation(url, line, column, bundle),
471
446
  };
472
447
  item.subItems.items.push(subItem);
473
448
  }
@@ -14,6 +14,7 @@
14
14
  // TODO: when M97 is sufficiently old, drop support for console messages
15
15
 
16
16
  const Audit = require('./audit.js');
17
+ const JsBundles = require('../computed/js-bundles.js');
17
18
  const i18n = require('../lib/i18n/i18n.js');
18
19
 
19
20
  const UIStrings = {
@@ -47,34 +48,29 @@ class Deprecations extends Audit {
47
48
  title: str_(UIStrings.title),
48
49
  failureTitle: str_(UIStrings.failureTitle),
49
50
  description: str_(UIStrings.description),
50
- requiredArtifacts: ['ConsoleMessages', 'InspectorIssues'],
51
+ requiredArtifacts: ['ConsoleMessages', 'InspectorIssues', 'SourceMaps', 'ScriptElements'],
51
52
  };
52
53
  }
53
54
 
54
55
  /**
55
56
  * @param {LH.Artifacts} artifacts
56
- * @return {LH.Audit.Product}
57
+ * @param {LH.Audit.Context} context
58
+ * @return {Promise<LH.Audit.Product>}
57
59
  */
58
- static audit(artifacts) {
60
+ static async audit(artifacts, context) {
59
61
  const entries = artifacts.ConsoleMessages;
62
+ const bundles = await JsBundles.request(artifacts, context);
60
63
 
61
64
  let deprecations;
62
-
63
65
  if (artifacts.InspectorIssues.deprecations.length) {
64
66
  deprecations = artifacts.InspectorIssues.deprecations
65
67
  .map(deprecation => {
68
+ const {url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
69
+ const bundle = bundles.find(bundle => bundle.script.src === url);
66
70
  return {
67
71
  value: deprecation.message || '',
68
- /** @type {LH.Audit.Details.SourceLocationValue} */
69
- source: {
70
- type: 'source-location',
71
- url: deprecation.sourceCodeLocation.url,
72
- urlProvider: 'network',
73
- line: deprecation.sourceCodeLocation.lineNumber,
74
- // Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed,
75
- // but we use 0-indexed.
76
- column: deprecation.sourceCodeLocation.columnNumber - 1,
77
- },
72
+ // Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed, but we use 0-indexed.
73
+ source: Audit.makeSourceLocation(url, lineNumber, columnNumber - 1, bundle),
78
74
  };
79
75
  });
80
76
  } else {
@@ -38,17 +38,18 @@ class GeolocationOnStart extends ViolationAudit {
38
38
  failureTitle: str_(UIStrings.failureTitle),
39
39
  description: str_(UIStrings.description),
40
40
  supportedModes: ['navigation'],
41
- requiredArtifacts: ['ConsoleMessages'],
41
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
42
42
  };
43
43
  }
44
44
 
45
45
  /**
46
46
  * @param {LH.Artifacts} artifacts
47
- * @return {LH.Audit.Product}
47
+ * @param {LH.Audit.Context} context
48
+ * @return {Promise<LH.Audit.Product>}
48
49
  */
49
- static audit(artifacts) {
50
+ static async audit(artifacts, context) {
50
51
  // 'Only request geolocation information in response to a user gesture.'
51
- const results = ViolationAudit.getViolationResults(artifacts, /geolocation/);
52
+ const results = await ViolationAudit.getViolationResults(artifacts, context, /geolocation/);
52
53
 
53
54
  /** @type {LH.Audit.Details.Table['headings']} */
54
55
  const headings = [
@@ -61,7 +61,7 @@ class JsLibrariesAudit extends Audit {
61
61
  const details = Audit.makeTableDetails(headings, libDetails, {});
62
62
 
63
63
  const debugData = {
64
- type: /** @type {'debugdata'} */ ('debugdata'),
64
+ type: /** @type {const} */ ('debugdata'),
65
65
  stacks: artifacts.Stacks.map(stack => {
66
66
  return {
67
67
  id: stack.id,
@@ -54,16 +54,18 @@ class NoDocWriteAudit extends ViolationAudit {
54
54
  title: str_(UIStrings.title),
55
55
  failureTitle: str_(UIStrings.failureTitle),
56
56
  description: str_(UIStrings.description),
57
- requiredArtifacts: ['ConsoleMessages'],
57
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
58
58
  };
59
59
  }
60
60
 
61
61
  /**
62
62
  * @param {LH.Artifacts} artifacts
63
- * @return {LH.Audit.Product}
63
+ * @param {LH.Audit.Context} context
64
+ * @return {Promise<LH.Audit.Product>}
64
65
  */
65
- static audit(artifacts) {
66
- const results = ViolationAudit.getViolationResults(artifacts, /document\.write/);
66
+ static async audit(artifacts, context) {
67
+ const results =
68
+ await ViolationAudit.getViolationResults(artifacts, context, /document\.write/);
67
69
 
68
70
  /** @type {LH.Audit.Details.Table['headings']} */
69
71
  const headings = [
@@ -38,16 +38,18 @@ class NotificationOnStart extends ViolationAudit {
38
38
  failureTitle: str_(UIStrings.failureTitle),
39
39
  description: str_(UIStrings.description),
40
40
  supportedModes: ['navigation'],
41
- requiredArtifacts: ['ConsoleMessages'],
41
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
42
42
  };
43
43
  }
44
44
 
45
45
  /**
46
46
  * @param {LH.Artifacts} artifacts
47
- * @return {LH.Audit.Product}
47
+ * @param {LH.Audit.Context} context
48
+ * @return {Promise<LH.Audit.Product>}
48
49
  */
49
- static audit(artifacts) {
50
- const results = ViolationAudit.getViolationResults(artifacts, /notification permission/);
50
+ static async audit(artifacts, context) {
51
+ const results =
52
+ await ViolationAudit.getViolationResults(artifacts, context, /notification permission/);
51
53
 
52
54
  /** @type {LH.Audit.Details.Table['headings']} */
53
55
  const headings = [
@@ -37,16 +37,18 @@ class PassiveEventsAudit extends ViolationAudit {
37
37
  title: str_(UIStrings.title),
38
38
  failureTitle: str_(UIStrings.failureTitle),
39
39
  description: str_(UIStrings.description),
40
- requiredArtifacts: ['ConsoleMessages'],
40
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
41
41
  };
42
42
  }
43
43
 
44
44
  /**
45
45
  * @param {LH.Artifacts} artifacts
46
- * @return {LH.Audit.Product}
46
+ * @param {LH.Audit.Context} context
47
+ * @return {Promise<LH.Audit.Product>}
47
48
  */
48
- static audit(artifacts) {
49
- const results = ViolationAudit.getViolationResults(artifacts, /passive event listener/);
49
+ static async audit(artifacts, context) {
50
+ const results =
51
+ await ViolationAudit.getViolationResults(artifacts, context, /passive event listener/);
50
52
 
51
53
  /** @type {LH.Audit.Details.Table['headings']} */
52
54
  const headings = [
@@ -12,6 +12,7 @@
12
12
 
13
13
  const log = require('lighthouse-logger');
14
14
  const Audit = require('./audit.js');
15
+ const JsBundles = require('../computed/js-bundles.js');
15
16
  const i18n = require('../lib/i18n/i18n.js');
16
17
 
17
18
  const UIStrings = {
@@ -39,7 +40,7 @@ class ErrorLogs extends Audit {
39
40
  title: str_(UIStrings.title),
40
41
  failureTitle: str_(UIStrings.failureTitle),
41
42
  description: str_(UIStrings.description),
42
- requiredArtifacts: ['ConsoleMessages'],
43
+ requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
43
44
  };
44
45
  }
45
46
 
@@ -75,20 +76,22 @@ class ErrorLogs extends Audit {
75
76
  /**
76
77
  * @param {LH.Artifacts} artifacts
77
78
  * @param {LH.Audit.Context} context
78
- * @return {LH.Audit.Product}
79
+ * @return {Promise<LH.Audit.Product>}
79
80
  */
80
- static audit(artifacts, context) {
81
+ static async audit(artifacts, context) {
81
82
  /** @type {AuditOptions} */
82
83
  const auditOptions = context.options;
84
+ const bundles = await JsBundles.request(artifacts, context);
83
85
 
84
86
  /** @type {Array<{source: string, description: string|undefined, sourceLocation: LH.Audit.Details.SourceLocationValue|undefined}>} */
85
87
  const consoleRows = artifacts.ConsoleMessages
86
88
  .filter(item => item.level === 'error')
87
89
  .map(item => {
90
+ const bundle = bundles.find(bundle => bundle.script.src === item.url);
88
91
  return {
89
92
  source: item.source,
90
93
  description: item.text,
91
- sourceLocation: Audit.makeSourceLocationFromConsoleMessage(item),
94
+ sourceLocation: Audit.makeSourceLocationFromConsoleMessage(item, bundle),
92
95
  };
93
96
  });
94
97
 
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const Audit = require('./audit.js');
9
+ const JsBundles = require('../computed/js-bundles.js');
9
10
  const i18n = require('./../lib/i18n/i18n.js');
10
11
 
11
12
  const UIStrings = {
@@ -29,15 +30,16 @@ class NoUnloadListeners extends Audit {
29
30
  title: str_(UIStrings.title),
30
31
  failureTitle: str_(UIStrings.failureTitle),
31
32
  description: str_(UIStrings.description),
32
- requiredArtifacts: ['GlobalListeners', 'JsUsage'],
33
+ requiredArtifacts: ['GlobalListeners', 'JsUsage', 'SourceMaps', 'ScriptElements'],
33
34
  };
34
35
  }
35
36
 
36
37
  /**
37
38
  * @param {LH.Artifacts} artifacts
38
- * @return {LH.Audit.Product}
39
+ * @param {LH.Audit.Context} context
40
+ * @return {Promise<LH.Audit.Product>}
39
41
  */
40
- static audit(artifacts) {
42
+ static async audit(artifacts, context) {
41
43
  const unloadListeners = artifacts.GlobalListeners.filter(l => l.type === 'unload');
42
44
  if (!unloadListeners.length) {
43
45
  return {
@@ -45,6 +47,8 @@ class NoUnloadListeners extends Audit {
45
47
  };
46
48
  }
47
49
 
50
+ const bundles = await JsBundles.request(artifacts, context);
51
+
48
52
  /** @type {LH.Audit.Details.Table['headings']} */
49
53
  const headings = [
50
54
  {key: 'source', itemType: 'source-location', text: str_(i18n.UIStrings.columnSource)},
@@ -74,14 +78,9 @@ class NoUnloadListeners extends Audit {
74
78
  };
75
79
  }
76
80
 
81
+ const bundle = bundles.find(bundle => bundle.script.src === url);
77
82
  return {
78
- source: {
79
- type: 'source-location',
80
- url,
81
- urlProvider: 'network',
82
- line: listener.lineNumber,
83
- column: listener.columnNumber,
84
- },
83
+ source: Audit.makeSourceLocation(url, listener.lineNumber, listener.columnNumber, bundle),
85
84
  };
86
85
  });
87
86
 
@@ -38,11 +38,6 @@ const UIStrings = {
38
38
  * @example {https://example.com/} url
39
39
  */
40
40
  explanationPointsElsewhere: 'Points to another `hreflang` location ({url})',
41
- /**
42
- * @description Explanatory message stating that there was a failure in an audit caused by a URL pointing to a different domain.
43
- * @example {https://example.com/} url
44
- * */
45
- explanationDifferentDomain: 'Points to a different domain ({url})',
46
41
  /** Explanatory message stating that the page's canonical URL was pointing to the domain's root URL, which is a common mistake. "points" refers to the action of the 'rel=canonical' referencing another link. "root" refers to the starting/home page of the website. "domain" refers to the registered domain name of the website. */
47
42
  explanationRoot: 'Points to the domain\'s root URL (the homepage), ' +
48
43
  'instead of an equivalent page of content',
@@ -174,15 +169,6 @@ class Canonical extends Audit {
174
169
  };
175
170
  }
176
171
 
177
- // bing and yahoo don't allow canonical URLs pointing to different domains, it's also
178
- // a common mistake to publish a page with canonical pointing to e.g. a test domain or localhost
179
- if (!URL.rootDomainsMatch(canonicalURL, baseURL)) {
180
- return {
181
- score: 0,
182
- explanation: str_(UIStrings.explanationDifferentDomain, {url: canonicalURL.href}),
183
- };
184
- }
185
-
186
172
  // another common mistake is to have canonical pointing from all pages of the website to its root
187
173
  if (
188
174
  canonicalURL.origin === baseURL.origin &&
@@ -196,9 +196,11 @@ function findStyleRuleSource(baseURL, styleDeclaration, parentNode) {
196
196
  }
197
197
  }
198
198
 
199
- const url = stylesheet.sourceURL;
199
+ const source = Audit.makeSourceLocation(stylesheet.sourceURL, line, column);
200
+ source.urlProvider = urlProvider;
201
+
200
202
  return {
201
- source: {type: 'source-location', url, urlProvider, line, column},
203
+ source,
202
204
  selector,
203
205
  };
204
206
  }
@@ -126,9 +126,9 @@ class IsCrawlable extends Audit {
126
126
  const line = robotsTxt.getMatchingLineNumber(mainResource.url) || 1;
127
127
  blockingDirectives.push({
128
128
  source: {
129
- type: /** @type {'source-location'} */ ('source-location'),
129
+ type: /** @type {const} */ ('source-location'),
130
130
  url: robotsFileUrl.href,
131
- urlProvider: /** @type {'network'} */ ('network'),
131
+ urlProvider: /** @type {const} */ ('network'),
132
132
  line: line - 1,
133
133
  column: 0,
134
134
  },
@@ -216,12 +216,12 @@ class ThirdPartySummary extends Audit {
216
216
  return {
217
217
  ...stats,
218
218
  entity: {
219
- type: /** @type {'link'} */ ('link'),
219
+ type: /** @type {const} */ ('link'),
220
220
  text: entity.name,
221
221
  url: entity.homepage || '',
222
222
  },
223
223
  subItems: {
224
- type: /** @type {'subitems'} */ ('subitems'),
224
+ type: /** @type {const} */ ('subitems'),
225
225
  items: ThirdPartySummary.makeSubItems(entity, summaries, stats),
226
226
  },
227
227
  };
@@ -110,7 +110,7 @@ class ValidSourceMaps extends Audit {
110
110
  scriptUrl: scriptElement.src,
111
111
  sourceMapUrl: sourceMap && sourceMap.sourceMapUrl,
112
112
  subItems: {
113
- type: /** @type {'subitems'} */ ('subitems'),
113
+ type: /** @type {const} */ ('subitems'),
114
114
  items: errors,
115
115
  },
116
116
  });
@@ -6,14 +6,18 @@
6
6
  'use strict';
7
7
 
8
8
  const Audit = require('./audit.js');
9
+ const JsBundles = require('../computed/js-bundles.js');
9
10
 
10
11
  class ViolationAudit extends Audit {
11
12
  /**
12
13
  * @param {LH.Artifacts} artifacts
14
+ * @param {LH.Audit.Context} context
13
15
  * @param {RegExp} pattern
14
- * @return {Array<{source: LH.Audit.Details.SourceLocationValue}>}
16
+ * @return {Promise<Array<{source: LH.Audit.Details.SourceLocationValue}>>}
15
17
  */
16
- static getViolationResults(artifacts, pattern) {
18
+ static async getViolationResults(artifacts, context, pattern) {
19
+ const bundles = await JsBundles.request(artifacts, context);
20
+
17
21
  /**
18
22
  * @template T
19
23
  * @param {T} value
@@ -26,7 +30,10 @@ class ViolationAudit extends Audit {
26
30
  const seen = new Set();
27
31
  return artifacts.ConsoleMessages
28
32
  .filter(entry => entry.url && entry.source === 'violation' && pattern.test(entry.text))
29
- .map(Audit.makeSourceLocationFromConsoleMessage)
33
+ .map(entry => {
34
+ const bundle = bundles.find(bundle => bundle.script.src === entry.url);
35
+ return Audit.makeSourceLocationFromConsoleMessage(entry, bundle);
36
+ })
30
37
  .filter(filterUndefined)
31
38
  .filter(source => {
32
39
  // Filter out duplicate entries since they are not differentiable to the user
@@ -98,9 +98,7 @@ class JSBundles {
98
98
 
99
99
  const compiledUrl = SourceMap.scriptUrl || 'compiled.js';
100
100
  const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map';
101
- // Hack: CDT expects undefined properties to be explicit.
102
- const rawMapForCdt = /** @type {any} */ (rawMap);
103
- const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMapForCdt);
101
+ const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMap);
104
102
 
105
103
  const content = scriptElement && scriptElement.content ? scriptElement.content : '';
106
104
  const sizes = computeGeneratedFileSizes(map, content);
@@ -142,8 +142,8 @@ async function _collectDebugData(navigationContext, phaseState) {
142
142
  /**
143
143
  * @param {NavigationContext} navigationContext
144
144
  * @param {PhaseState} phaseState
145
- * @param {UnPromise<ReturnType<typeof _setupNavigation>>} setupResult
146
- * @param {UnPromise<ReturnType<typeof _navigate>>} navigateResult
145
+ * @param {Awaited<ReturnType<typeof _setupNavigation>>} setupResult
146
+ * @param {Awaited<ReturnType<typeof _navigate>>} navigateResult
147
147
  * @return {Promise<{finalUrl: string, artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
148
148
  */
149
149
  async function _computeNavigationResult(
@@ -195,7 +195,7 @@ async function _computeNavigationResult(
195
195
  async function _navigation(navigationContext) {
196
196
  const artifactState = getEmptyArtifactState();
197
197
  const phaseState = {
198
- gatherMode: /** @type {'navigation'} */ ('navigation'),
198
+ gatherMode: /** @type {const} */ ('navigation'),
199
199
  driver: navigationContext.driver,
200
200
  computedCache: navigationContext.computedCache,
201
201
  artifactDefinitions: navigationContext.navigation.artifacts,
@@ -166,7 +166,7 @@ class ExecutionContext {
166
166
  * @param {{args: T, useIsolation?: boolean, deps?: Array<Function|string>}} options `args` should
167
167
  * match the args of `mainFn`, and can be any serializable value. `deps` are functions that must be
168
168
  * defined for `mainFn` to work.
169
- * @return {FlattenedPromise<R>}
169
+ * @return {Promise<Awaited<R>>}
170
170
  */
171
171
  evaluate(mainFn, options) {
172
172
  const argsSerialized = ExecutionContext.serializeArguments(options.args);
@@ -16,19 +16,19 @@ const pageFunctions = require('../../lib/page-functions.js');
16
16
  const FULL_PAGE_SCREENSHOT_QUALITY = 30;
17
17
 
18
18
  /**
19
- * @param {string} str
19
+ * @template {string} S
20
+ * @param {S} str
20
21
  */
21
- function snakeCaseToCamelCase(str) {
22
- return str.replace(/(-\w)/g, m => m[1].toUpperCase());
22
+ function kebabCaseToCamelCase(str) {
23
+ return /** @type {KebabToCamelCase<S>} */ (str.replace(/(-\w)/g, m => m[1].toUpperCase()));
23
24
  }
24
25
 
25
26
  /* c8 ignore start */
26
27
 
27
28
  // eslint-disable-next-line no-inner-declarations
28
29
  function getObservedDeviceMetrics() {
29
- // Convert the Web API's snake case (landscape-primary) to camel case (landscapePrimary).
30
- const screenOrientationType = /** @type {LH.Crdp.Emulation.ScreenOrientationType} */ (
31
- snakeCaseToCamelCase(window.screen.orientation.type));
30
+ // Convert the Web API's kebab case (landscape-primary) to camel case (landscapePrimary).
31
+ const screenOrientationType = kebabCaseToCamelCase(window.screen.orientation.type);
32
32
  return {
33
33
  width: document.documentElement.clientWidth,
34
34
  height: document.documentElement.clientHeight,
@@ -168,7 +168,7 @@ class FullPageScreenshot extends FRGatherer {
168
168
  observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
169
169
  args: [],
170
170
  useIsolation: true,
171
- deps: [snakeCaseToCamelCase],
171
+ deps: [kebabCaseToCamelCase],
172
172
  });
173
173
  }
174
174
 
@@ -82,9 +82,6 @@ class Stacks extends FRGatherer {
82
82
  constructor() {
83
83
  super();
84
84
 
85
- // Because this file uses `fs.readFile` it gets parsed by a different branch of the browserify internals
86
- // that cannot handle the latest ECMAScript features.
87
- // See https://github.com/GoogleChrome/lighthouse/issues/12134
88
85
  /** @type {LH.Gatherer.GathererMeta} */
89
86
  this.meta = {
90
87
  supportedModes: ['snapshot', 'navigation'],