lighthouse 9.5.0-dev.20221019 → 9.5.0-dev.20221020

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 (71) hide show
  1. package/core/audits/accessibility/axe-audit.js +1 -1
  2. package/core/audits/autocomplete.js +3 -3
  3. package/core/audits/bootup-time.js +5 -5
  4. package/core/audits/byte-efficiency/duplicated-javascript.js +1 -1
  5. package/core/audits/byte-efficiency/legacy-javascript.js +1 -1
  6. package/core/audits/byte-efficiency/total-byte-weight.js +2 -2
  7. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +3 -3
  8. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +4 -4
  9. package/core/audits/csp-xss.js +3 -3
  10. package/core/audits/deprecations.js +2 -2
  11. package/core/audits/dobetterweb/dom-size.js +3 -3
  12. package/core/audits/dobetterweb/geolocation-on-start.js +1 -1
  13. package/core/audits/dobetterweb/inspector-issues.js +1 -1
  14. package/core/audits/dobetterweb/js-libraries.js +2 -2
  15. package/core/audits/dobetterweb/no-document-write.js +1 -1
  16. package/core/audits/dobetterweb/notification-on-start.js +1 -1
  17. package/core/audits/dobetterweb/password-inputs-can-be-pasted-into.js +1 -1
  18. package/core/audits/dobetterweb/uses-http2.js +2 -2
  19. package/core/audits/dobetterweb/uses-passive-event-listeners.js +1 -1
  20. package/core/audits/errors-in-console.js +4 -2
  21. package/core/audits/font-display.js +2 -2
  22. package/core/audits/image-aspect-ratio.js +4 -4
  23. package/core/audits/image-size-responsive.js +5 -5
  24. package/core/audits/installable-manifest.js +1 -1
  25. package/core/audits/is-on-https.js +2 -2
  26. package/core/audits/largest-contentful-paint-element.js +1 -1
  27. package/core/audits/layout-shift-elements.js +3 -3
  28. package/core/audits/lcp-lazy-loaded.js +1 -1
  29. package/core/audits/long-tasks.js +3 -3
  30. package/core/audits/main-thread-tasks.js +2 -2
  31. package/core/audits/mainthread-work-breakdown.js +4 -2
  32. package/core/audits/network-requests.js +11 -11
  33. package/core/audits/network-rtt.js +2 -2
  34. package/core/audits/network-server-latency.js +3 -3
  35. package/core/audits/no-unload-listeners.js +1 -1
  36. package/core/audits/non-composited-animations.js +2 -2
  37. package/core/audits/performance-budget.js +5 -5
  38. package/core/audits/preload-fonts.js +1 -1
  39. package/core/audits/resource-summary.js +3 -3
  40. package/core/audits/seo/crawlable-anchors.js +2 -2
  41. package/core/audits/seo/font-size.js +4 -4
  42. package/core/audits/seo/hreflang.js +3 -3
  43. package/core/audits/seo/is-crawlable.js +1 -1
  44. package/core/audits/seo/link-text.js +2 -2
  45. package/core/audits/seo/plugins.js +1 -1
  46. package/core/audits/seo/robots-txt.js +3 -3
  47. package/core/audits/seo/tap-targets.js +3 -3
  48. package/core/audits/themed-omnibox.js +6 -14
  49. package/core/audits/third-party-facades.js +3 -3
  50. package/core/audits/third-party-summary.js +3 -3
  51. package/core/audits/timing-budget.js +3 -3
  52. package/core/audits/unsized-images.js +2 -2
  53. package/core/audits/user-timings.js +6 -6
  54. package/core/audits/valid-source-maps.js +3 -3
  55. package/core/audits/work-during-interaction.js +6 -6
  56. package/core/lib/bfcache-strings.js +655 -0
  57. package/core/lib/deprecations-strings.js +3 -2
  58. package/core/lib/manifest-parser.js +0 -16
  59. package/core/user-flow.js +64 -8
  60. package/core/util.cjs +27 -0
  61. package/dist/report/bundle.esm.js +38 -73
  62. package/dist/report/flow.js +2 -2
  63. package/dist/report/standalone.js +2 -2
  64. package/package.json +2 -3
  65. package/report/renderer/details-renderer.js +12 -73
  66. package/report/renderer/util.js +27 -0
  67. package/report/test/renderer/details-renderer-test.js +38 -38
  68. package/report/test/renderer/util-test.js +25 -0
  69. package/shared/localization/locales/en-US.json +352 -1
  70. package/shared/localization/locales/en-XL.json +352 -1
  71. package/types/lhr/audit-details.d.ts +4 -34
@@ -14,7 +14,7 @@
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */const e="…",t="data:image/jpeg;base64,",n={label:"pass",minScore:.9},r={label:"average",minScore:.5},o={label:"fail"},i={label:"error"},a=["com","co","gov","edu","ac","org","go","gob","or","net","in","ne","nic","gouv","web","spb","blog","jus","kiev","mil","wi","qc","ca","bel","on"];class l{static i18n=null;static get PASS_THRESHOLD(){return.9}static get MS_DISPLAY_VALUE(){return"%10d ms"}static getFinalDisplayedUrl(e){if(e.finalDisplayedUrl)return e.finalDisplayedUrl;if(e.finalUrl)return e.finalUrl;throw new Error("Could not determine final displayed URL")}static getMainDocumentUrl(e){return e.mainDocumentUrl||e.finalUrl}static prepareReportResult(e){const n=JSON.parse(JSON.stringify(e));n.configSettings.locale||(n.configSettings.locale="en"),n.configSettings.formFactor||(n.configSettings.formFactor=n.configSettings.emulatedFormFactor),n.finalDisplayedUrl=this.getFinalDisplayedUrl(n),n.mainDocumentUrl=this.getMainDocumentUrl(n);for(const e of Object.values(n.audits))if("not_applicable"!==e.scoreDisplayMode&&"not-applicable"!==e.scoreDisplayMode||(e.scoreDisplayMode="notApplicable"),e.details&&(void 0!==e.details.type&&"diagnostic"!==e.details.type||(e.details.type="debugdata"),"filmstrip"===e.details.type))for(const n of e.details.items)n.data.startsWith(t)||(n.data=t+n.data);if("object"!=typeof n.categories)throw new Error("No categories provided.");const r=new Map,[o]=n.lighthouseVersion.split(".").map(Number),i=n.categories.performance;if(o<9&&i){n.categoryGroups||(n.categoryGroups={}),n.categoryGroups.hidden={title:""};for(const e of i.auditRefs)e.group?["load-opportunities","diagnostics"].includes(e.group)&&delete e.group:e.group="hidden"}for(const e of Object.values(n.categories))e.auditRefs.forEach((e=>{e.relevantAudits&&e.relevantAudits.forEach((t=>{const n=r.get(t)||[];n.push(e),r.set(t,n)}))})),e.auditRefs.forEach((e=>{const t=n.audits[e.id];e.result=t,r.has(e.id)&&(e.relevantMetrics=r.get(e.id)),n.stackPacks&&n.stackPacks.forEach((t=>{t.descriptions[e.id]&&(e.stackPacks=e.stackPacks||[],e.stackPacks.push({title:t.title,iconDataURL:t.iconDataURL,description:t.descriptions[e.id]}))}))}));return n}static showAsPassed(e){switch(e.scoreDisplayMode){case"manual":case"notApplicable":return!0;case"error":case"informative":return!1;case"numeric":case"binary":default:return Number(e.score)>=n.minScore}}static calculateRating(e,t){if("manual"===t||"notApplicable"===t)return n.label;if("error"===t)return i.label;if(null===e)return o.label;let a=o.label;return e>=n.minScore?a=n.label:e>=r.minScore&&(a=r.label),a}static splitMarkdownCodeSpans(e){const t=[],n=e.split(/`(.*?)`/g);for(let e=0;e<n.length;e++){const r=n[e];if(!r)continue;const o=e%2!=0;t.push({isCode:o,text:r})}return t}static splitMarkdownLink(e){const t=[],n=e.split(/\[([^\]]+?)\]\((https?:\/\/.*?)\)/g);for(;n.length;){const[e,r,o]=n.splice(0,3);e&&t.push({isLink:!1,text:e}),r&&o&&t.push({isLink:!0,text:r,linkHref:o})}return t}static getURLDisplayName(t,n){const r=void 0!==(n=n||{numPathParts:void 0,preserveQuery:void 0,preserveHost:void 0}).numPathParts?n.numPathParts:2,o=void 0===n.preserveQuery||n.preserveQuery,i=n.preserveHost||!1;let a;if("about:"===t.protocol||"data:"===t.protocol)a=t.href;else{a=t.pathname;const n=a.split("/").filter((e=>e.length));r&&n.length>r&&(a=e+n.slice(-1*r).join("/")),i&&(a=`${t.host}/${a.replace(/^\//,"")}`),o&&(a=`${a}${t.search}`)}if("data:"!==t.protocol&&(a=a.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g,"$1…"),a=a.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,"$1…"),a=a.replace(/(\d{3})\d{6,}/g,"$1…"),a=a.replace(/\u2026+/g,e),a.length>64&&a.includes("?")&&(a=a.replace(/\?([^=]*)(=)?.*/,"?$1$2…"),a.length>64&&(a=a.replace(/\?.*/,"?…")))),a.length>64){const t=a.lastIndexOf(".");a=t>=0?a.slice(0,63-(a.length-t))+`…${a.slice(t)}`:a.slice(0,63)+e}return a}static parseURL(e){const t=new URL(e);return{file:l.getURLDisplayName(t),hostname:t.hostname,origin:t.origin}}static createOrReturnURL(e){return e instanceof URL?e:new URL(e)}static getTld(e){const t=e.split(".").slice(-2);return a.includes(t[0])?`.${t.join(".")}`:`.${t[t.length-1]}`}static getRootDomain(e){const t=l.createOrReturnURL(e).hostname,n=l.getTld(t).split(".");return t.split(".").slice(-n.length).join(".")}static getEmulationDescriptions(e){let t,n,r;const o=e.throttling;switch(e.throttlingMethod){case"provided":r=n=t=l.i18n.strings.throttlingProvided;break;case"devtools":{const{cpuSlowdownMultiplier:e,requestLatencyMs:i}=o;t=`${l.i18n.formatNumber(e)}x slowdown (DevTools)`,n=`${l.i18n.formatMilliseconds(i)} HTTP RTT, ${l.i18n.formatKbps(o.downloadThroughputKbps)} down, ${l.i18n.formatKbps(o.uploadThroughputKbps)} up (DevTools)`;r=(()=>562.5===i&&o.downloadThroughputKbps===1638.4*.9&&675===o.uploadThroughputKbps)()?l.i18n.strings.runtimeSlow4g:l.i18n.strings.runtimeCustom;break}case"simulate":{const{cpuSlowdownMultiplier:e,rttMs:i,throughputKbps:a}=o;t=`${l.i18n.formatNumber(e)}x slowdown (Simulated)`,n=`${l.i18n.formatMilliseconds(i)} TCP RTT, ${l.i18n.formatKbps(a)} throughput (Simulated)`;r=(()=>150===i&&1638.4===a)()?l.i18n.strings.runtimeSlow4g:l.i18n.strings.runtimeCustom;break}default:r=t=n=l.i18n.strings.runtimeUnknown}return{deviceEmulation:{mobile:l.i18n.strings.runtimeMobileEmulation,desktop:l.i18n.strings.runtimeDesktopEmulation}[e.formFactor]||l.i18n.strings.runtimeNoEmulation,cpuThrottling:t,networkThrottling:n,summary:r}}static filterRelevantLines(e,t,n){if(0===t.length)return e.slice(0,2*n+1);const r=new Set;return(t=t.sort(((e,t)=>(e.lineNumber||0)-(t.lineNumber||0)))).forEach((({lineNumber:e})=>{let t=e-n,o=e+n;for(;t<1;)t++,o++;r.has(t-3-1)&&(t-=3);for(let e=t;e<=o;e++){const t=e;r.add(t)}})),e.filter((e=>r.has(e.lineNumber)))}static isPluginCategory(e){return e.startsWith("lighthouse-plugin-")}static shouldDisplayAsFraction(e){return"timespan"===e||"snapshot"===e}static calculateCategoryFraction(e){let t=0,n=0,r=0,o=0;for(const i of e.auditRefs){const e=l.showAsPassed(i.result);"hidden"!==i.group&&"manual"!==i.result.scoreDisplayMode&&"notApplicable"!==i.result.scoreDisplayMode&&("informative"!==i.result.scoreDisplayMode?(++t,o+=i.weight,e&&n++):e||++r)}return{numPassed:n,numPassableAudits:t,numInformative:r,totalWeight:o}}}l.reportJson=null;let s=0;l.getUniqueSuffix=()=>s++,l.resetUniqueSuffix=()=>{s=0};l.UIStrings={varianceDisclaimer:"Values are estimated and may vary. The [performance score is calculated](https://web.dev/performance-scoring/) directly from these metrics.",calculatorLink:"See calculator.",showRelevantAudits:"Show audits relevant to:",opportunityResourceColumnLabel:"Opportunity",opportunitySavingsColumnLabel:"Estimated Savings",errorMissingAuditInfo:"Report error: no audit information",errorLabel:"Error!",warningHeader:"Warnings: ",warningAuditsGroupTitle:"Passed audits but with warnings",passedAuditsGroupTitle:"Passed audits",notApplicableAuditsGroupTitle:"Not applicable",manualAuditsGroupTitle:"Additional items to manually check",toplevelWarningsMessage:"There were issues affecting this run of Lighthouse:",crcInitialNavigation:"Initial Navigation",crcLongestDurationLabel:"Maximum critical path latency:",snippetExpandButtonLabel:"Expand snippet",snippetCollapseButtonLabel:"Collapse snippet",lsPerformanceCategoryDescription:"[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.",labDataTitle:"Lab Data",thirdPartyResourcesLabel:"Show 3rd-party resources",viewTreemapLabel:"View Treemap",viewTraceLabel:"View Trace",viewOriginalTraceLabel:"View Original Trace",dropdownPrintSummary:"Print Summary",dropdownPrintExpanded:"Print Expanded",dropdownCopyJSON:"Copy JSON",dropdownSaveHTML:"Save as HTML",dropdownSaveJSON:"Save as JSON",dropdownViewer:"Open in Viewer",dropdownSaveGist:"Save as Gist",dropdownDarkTheme:"Toggle Dark Theme",runtimeSettingsDevice:"Device",runtimeSettingsNetworkThrottling:"Network throttling",runtimeSettingsCPUThrottling:"CPU throttling",runtimeSettingsUANetwork:"User agent (network)",runtimeSettingsBenchmark:"CPU/Memory Power",runtimeSettingsAxeVersion:"Axe version",footerIssue:"File an issue",runtimeNoEmulation:"No emulation",runtimeMobileEmulation:"Emulated Moto G4",runtimeDesktopEmulation:"Emulated Desktop",runtimeUnknown:"Unknown",runtimeSingleLoad:"Single page load",runtimeAnalysisWindow:"Initial page load",runtimeSingleLoadTooltip:"This data is taken from a single page load, as opposed to field data summarizing many sessions.",throttlingProvided:"Provided by environment",show:"Show",hide:"Hide",expandView:"Expand view",collapseView:"Collapse view",runtimeSlow4g:"Slow 4G throttling",runtimeCustom:"Custom throttling"};
17
+ */const e="…",t="data:image/jpeg;base64,",n={label:"pass",minScore:.9},r={label:"average",minScore:.5},o={label:"fail"},i={label:"error"},a=["com","co","gov","edu","ac","org","go","gob","or","net","in","ne","nic","gouv","web","spb","blog","jus","kiev","mil","wi","qc","ca","bel","on"];class l{static i18n=null;static get PASS_THRESHOLD(){return.9}static get MS_DISPLAY_VALUE(){return"%10d ms"}static getFinalDisplayedUrl(e){if(e.finalDisplayedUrl)return e.finalDisplayedUrl;if(e.finalUrl)return e.finalUrl;throw new Error("Could not determine final displayed URL")}static getMainDocumentUrl(e){return e.mainDocumentUrl||e.finalUrl}static prepareReportResult(e){const n=JSON.parse(JSON.stringify(e));n.configSettings.locale||(n.configSettings.locale="en"),n.configSettings.formFactor||(n.configSettings.formFactor=n.configSettings.emulatedFormFactor),n.finalDisplayedUrl=this.getFinalDisplayedUrl(n),n.mainDocumentUrl=this.getMainDocumentUrl(n);for(const e of Object.values(n.audits))if("not_applicable"!==e.scoreDisplayMode&&"not-applicable"!==e.scoreDisplayMode||(e.scoreDisplayMode="notApplicable"),e.details){if(void 0!==e.details.type&&"diagnostic"!==e.details.type||(e.details.type="debugdata"),"filmstrip"===e.details.type)for(const n of e.details.items)n.data.startsWith(t)||(n.data=t+n.data);if("table"===e.details.type)for(const t of e.details.headings){const{itemType:e,text:n}=t;void 0!==e&&(t.valueType=e,delete t.itemType),void 0!==n&&(t.label=n,delete t.text);const r=t.subItemsHeading?.itemType;t.subItemsHeading&&void 0!==r&&(t.subItemsHeading.valueType=r,delete t.subItemsHeading.itemType)}}if("object"!=typeof n.categories)throw new Error("No categories provided.");const r=new Map,[o]=n.lighthouseVersion.split(".").map(Number),i=n.categories.performance;if(o<9&&i){n.categoryGroups||(n.categoryGroups={}),n.categoryGroups.hidden={title:""};for(const e of i.auditRefs)e.group?["load-opportunities","diagnostics"].includes(e.group)&&delete e.group:e.group="hidden"}for(const e of Object.values(n.categories))e.auditRefs.forEach((e=>{e.relevantAudits&&e.relevantAudits.forEach((t=>{const n=r.get(t)||[];n.push(e),r.set(t,n)}))})),e.auditRefs.forEach((e=>{const t=n.audits[e.id];e.result=t,r.has(e.id)&&(e.relevantMetrics=r.get(e.id)),n.stackPacks&&n.stackPacks.forEach((t=>{t.descriptions[e.id]&&(e.stackPacks=e.stackPacks||[],e.stackPacks.push({title:t.title,iconDataURL:t.iconDataURL,description:t.descriptions[e.id]}))}))}));return n}static showAsPassed(e){switch(e.scoreDisplayMode){case"manual":case"notApplicable":return!0;case"error":case"informative":return!1;case"numeric":case"binary":default:return Number(e.score)>=n.minScore}}static calculateRating(e,t){if("manual"===t||"notApplicable"===t)return n.label;if("error"===t)return i.label;if(null===e)return o.label;let a=o.label;return e>=n.minScore?a=n.label:e>=r.minScore&&(a=r.label),a}static splitMarkdownCodeSpans(e){const t=[],n=e.split(/`(.*?)`/g);for(let e=0;e<n.length;e++){const r=n[e];if(!r)continue;const o=e%2!=0;t.push({isCode:o,text:r})}return t}static splitMarkdownLink(e){const t=[],n=e.split(/\[([^\]]+?)\]\((https?:\/\/.*?)\)/g);for(;n.length;){const[e,r,o]=n.splice(0,3);e&&t.push({isLink:!1,text:e}),r&&o&&t.push({isLink:!0,text:r,linkHref:o})}return t}static getURLDisplayName(t,n){const r=void 0!==(n=n||{numPathParts:void 0,preserveQuery:void 0,preserveHost:void 0}).numPathParts?n.numPathParts:2,o=void 0===n.preserveQuery||n.preserveQuery,i=n.preserveHost||!1;let a;if("about:"===t.protocol||"data:"===t.protocol)a=t.href;else{a=t.pathname;const n=a.split("/").filter((e=>e.length));r&&n.length>r&&(a=e+n.slice(-1*r).join("/")),i&&(a=`${t.host}/${a.replace(/^\//,"")}`),o&&(a=`${a}${t.search}`)}if("data:"!==t.protocol&&(a=a.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g,"$1…"),a=a.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,"$1…"),a=a.replace(/(\d{3})\d{6,}/g,"$1…"),a=a.replace(/\u2026+/g,e),a.length>64&&a.includes("?")&&(a=a.replace(/\?([^=]*)(=)?.*/,"?$1$2…"),a.length>64&&(a=a.replace(/\?.*/,"?…")))),a.length>64){const t=a.lastIndexOf(".");a=t>=0?a.slice(0,63-(a.length-t))+`…${a.slice(t)}`:a.slice(0,63)+e}return a}static parseURL(e){const t=new URL(e);return{file:l.getURLDisplayName(t),hostname:t.hostname,origin:t.origin}}static createOrReturnURL(e){return e instanceof URL?e:new URL(e)}static getTld(e){const t=e.split(".").slice(-2);return a.includes(t[0])?`.${t.join(".")}`:`.${t[t.length-1]}`}static getRootDomain(e){const t=l.createOrReturnURL(e).hostname,n=l.getTld(t).split(".");return t.split(".").slice(-n.length).join(".")}static getEmulationDescriptions(e){let t,n,r;const o=e.throttling;switch(e.throttlingMethod){case"provided":r=n=t=l.i18n.strings.throttlingProvided;break;case"devtools":{const{cpuSlowdownMultiplier:e,requestLatencyMs:i}=o;t=`${l.i18n.formatNumber(e)}x slowdown (DevTools)`,n=`${l.i18n.formatMilliseconds(i)} HTTP RTT, ${l.i18n.formatKbps(o.downloadThroughputKbps)} down, ${l.i18n.formatKbps(o.uploadThroughputKbps)} up (DevTools)`;r=(()=>562.5===i&&o.downloadThroughputKbps===1638.4*.9&&675===o.uploadThroughputKbps)()?l.i18n.strings.runtimeSlow4g:l.i18n.strings.runtimeCustom;break}case"simulate":{const{cpuSlowdownMultiplier:e,rttMs:i,throughputKbps:a}=o;t=`${l.i18n.formatNumber(e)}x slowdown (Simulated)`,n=`${l.i18n.formatMilliseconds(i)} TCP RTT, ${l.i18n.formatKbps(a)} throughput (Simulated)`;r=(()=>150===i&&1638.4===a)()?l.i18n.strings.runtimeSlow4g:l.i18n.strings.runtimeCustom;break}default:r=t=n=l.i18n.strings.runtimeUnknown}return{deviceEmulation:{mobile:l.i18n.strings.runtimeMobileEmulation,desktop:l.i18n.strings.runtimeDesktopEmulation}[e.formFactor]||l.i18n.strings.runtimeNoEmulation,cpuThrottling:t,networkThrottling:n,summary:r}}static filterRelevantLines(e,t,n){if(0===t.length)return e.slice(0,2*n+1);const r=new Set;return(t=t.sort(((e,t)=>(e.lineNumber||0)-(t.lineNumber||0)))).forEach((({lineNumber:e})=>{let t=e-n,o=e+n;for(;t<1;)t++,o++;r.has(t-3-1)&&(t-=3);for(let e=t;e<=o;e++){const t=e;r.add(t)}})),e.filter((e=>r.has(e.lineNumber)))}static isPluginCategory(e){return e.startsWith("lighthouse-plugin-")}static shouldDisplayAsFraction(e){return"timespan"===e||"snapshot"===e}static calculateCategoryFraction(e){let t=0,n=0,r=0,o=0;for(const i of e.auditRefs){const e=l.showAsPassed(i.result);"hidden"!==i.group&&"manual"!==i.result.scoreDisplayMode&&"notApplicable"!==i.result.scoreDisplayMode&&("informative"!==i.result.scoreDisplayMode?(++t,o+=i.weight,e&&n++):e||++r)}return{numPassed:n,numPassableAudits:t,numInformative:r,totalWeight:o}}}l.reportJson=null;let s=0;l.getUniqueSuffix=()=>s++,l.resetUniqueSuffix=()=>{s=0};l.UIStrings={varianceDisclaimer:"Values are estimated and may vary. The [performance score is calculated](https://web.dev/performance-scoring/) directly from these metrics.",calculatorLink:"See calculator.",showRelevantAudits:"Show audits relevant to:",opportunityResourceColumnLabel:"Opportunity",opportunitySavingsColumnLabel:"Estimated Savings",errorMissingAuditInfo:"Report error: no audit information",errorLabel:"Error!",warningHeader:"Warnings: ",warningAuditsGroupTitle:"Passed audits but with warnings",passedAuditsGroupTitle:"Passed audits",notApplicableAuditsGroupTitle:"Not applicable",manualAuditsGroupTitle:"Additional items to manually check",toplevelWarningsMessage:"There were issues affecting this run of Lighthouse:",crcInitialNavigation:"Initial Navigation",crcLongestDurationLabel:"Maximum critical path latency:",snippetExpandButtonLabel:"Expand snippet",snippetCollapseButtonLabel:"Collapse snippet",lsPerformanceCategoryDescription:"[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.",labDataTitle:"Lab Data",thirdPartyResourcesLabel:"Show 3rd-party resources",viewTreemapLabel:"View Treemap",viewTraceLabel:"View Trace",viewOriginalTraceLabel:"View Original Trace",dropdownPrintSummary:"Print Summary",dropdownPrintExpanded:"Print Expanded",dropdownCopyJSON:"Copy JSON",dropdownSaveHTML:"Save as HTML",dropdownSaveJSON:"Save as JSON",dropdownViewer:"Open in Viewer",dropdownSaveGist:"Save as Gist",dropdownDarkTheme:"Toggle Dark Theme",runtimeSettingsDevice:"Device",runtimeSettingsNetworkThrottling:"Network throttling",runtimeSettingsCPUThrottling:"CPU throttling",runtimeSettingsUANetwork:"User agent (network)",runtimeSettingsBenchmark:"CPU/Memory Power",runtimeSettingsAxeVersion:"Axe version",footerIssue:"File an issue",runtimeNoEmulation:"No emulation",runtimeMobileEmulation:"Emulated Moto G4",runtimeDesktopEmulation:"Emulated Desktop",runtimeUnknown:"Unknown",runtimeSingleLoad:"Single page load",runtimeAnalysisWindow:"Initial page load",runtimeSingleLoadTooltip:"This data is taken from a single page load, as opposed to field data summarizing many sessions.",throttlingProvided:"Provided by environment",show:"Show",hide:"Hide",expandView:"Expand view",collapseView:"Collapse view",runtimeSlow4g:"Slow 4G throttling",runtimeCustom:"Custom throttling"};
18
18
  /**
19
19
  * @license
20
20
  * Copyright 2017 The Lighthouse Authors. All Rights Reserved.
@@ -84,7 +84,7 @@ class c{constructor(e,t){this._document=e,this._lighthouseChannel="unknown",this
84
84
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
85
85
  * See the License for the specific language governing permissions and
86
86
  * limitations under the License.
87
- */const m=["http://","https://","data:"];class f{constructor(e,t={}){this._dom=e,this._fullPageScreenshot=t.fullPageScreenshot}render(e){switch(e.type){case"filmstrip":return this._renderFilmstrip(e);case"list":return this._renderList(e);case"table":return this._renderTable(e);case"criticalrequestchain":return h.render(this._dom,e,this);case"opportunity":return this._renderTable(e);case"screenshot":case"debugdata":case"full-page-screenshot":case"treemap-data":return null;default:return this._renderUnknown(e.type,e)}}_renderBytes(e){const t=l.i18n.formatBytesToKiB(e.value,e.granularity||.1),n=this._renderText(t);return n.title=l.i18n.formatBytes(e.value),n}_renderMilliseconds(e){let t;return t="duration"===e.displayUnit?l.i18n.formatDuration(e.value):l.i18n.formatMilliseconds(e.value,e.granularity||10),this._renderText(t)}renderTextURL(e){const t=e;let n,r,o;try{const e=l.parseURL(t);n="/"===e.file?e.origin:e.file,r="/"===e.file||""===e.hostname?"":`(${e.hostname})`,o=t}catch(e){n=t}const i=this._dom.createElement("div","lh-text__url");if(i.append(this._renderLink({text:n,url:t})),r){const e=this._renderText(r);e.classList.add("lh-text__url-host"),i.append(e)}return o&&(i.title=t,i.dataset.url=t),i}_renderLink(e){const t=this._dom.createElement("a");if(this._dom.safelySetHref(t,e.url),!t.href){const t=this._renderText(e.text);return t.classList.add("lh-link"),t}return t.rel="noopener",t.target="_blank",t.textContent=e.text,t.classList.add("lh-link"),t}_renderText(e){const t=this._dom.createElement("div","lh-text");return t.textContent=e,t}_renderNumeric(e){const t=l.i18n.formatNumber(e.value,e.granularity||.1),n=this._dom.createElement("div","lh-numeric");return n.textContent=t,n}_renderThumbnail(e){const t=this._dom.createElement("img","lh-thumbnail"),n=e;return t.src=n,t.title=n,t.alt="",t}_renderUnknown(e,t){console.error(`Unknown details type: ${e}`,t);const n=this._dom.createElement("details","lh-unknown");return this._dom.createChildOf(n,"summary").textContent=`We don't know how to render audit details of type \`${e}\`. The Lighthouse version that collected this data is likely newer than the Lighthouse version of the report renderer. Expand for the raw JSON.`,this._dom.createChildOf(n,"pre").textContent=JSON.stringify(t,null,2),n}_renderTableValue(e,t){if(null==e)return null;if("object"==typeof e)switch(e.type){case"code":return this._renderCode(e.value);case"link":return this._renderLink(e);case"node":return this.renderNode(e);case"numeric":return this._renderNumeric(e);case"source-location":return this.renderSourceLocation(e);case"url":return this.renderTextURL(e.value);default:return this._renderUnknown(e.type,e)}switch(t.valueType){case"bytes":{const n=Number(e);return this._renderBytes({value:n,granularity:t.granularity})}case"code":{const t=String(e);return this._renderCode(t)}case"ms":{const n={value:Number(e),granularity:t.granularity,displayUnit:t.displayUnit};return this._renderMilliseconds(n)}case"numeric":{const n=Number(e);return this._renderNumeric({value:n,granularity:t.granularity})}case"text":{const t=String(e);return this._renderText(t)}case"thumbnail":{const t=String(e);return this._renderThumbnail(t)}case"timespanMs":{const t=Number(e);return this._renderMilliseconds({value:t})}case"url":{const t=String(e);return m.some((e=>t.startsWith(e)))?this.renderTextURL(t):this._renderCode(t)}default:return this._renderUnknown(t.valueType,e)}}_getCanonicalizedHeadingsFromTable(e){return"opportunity"===e.type?e.headings:e.headings.map((e=>this._getCanonicalizedHeading(e)))}_getCanonicalizedHeading(e){let t;return e.subItemsHeading&&(t=this._getCanonicalizedsubItemsHeading(e.subItemsHeading,e)),{key:e.key,valueType:e.itemType,subItemsHeading:t,label:e.text,displayUnit:e.displayUnit,granularity:e.granularity}}_getCanonicalizedsubItemsHeading(e,t){return e.key||console.warn("key should not be null"),{key:e.key||"",valueType:e.itemType||t.itemType,granularity:e.granularity||t.granularity,displayUnit:e.displayUnit||t.displayUnit}}_getDerivedsubItemsHeading(e){return e.subItemsHeading?{key:e.subItemsHeading.key||"",valueType:e.subItemsHeading.valueType||e.valueType,granularity:e.subItemsHeading.granularity||e.granularity,displayUnit:e.subItemsHeading.displayUnit||e.displayUnit,label:""}:null}_renderTableRow(e,t){const n=this._dom.createElement("tr");for(const r of t){if(!r||!r.key){this._dom.createChildOf(n,"td","lh-table-column--empty");continue}const t=e[r.key];let o;if(null!=t&&(o=this._renderTableValue(t,r)),o){const e=`lh-table-column--${r.valueType}`;this._dom.createChildOf(n,"td",e).append(o)}else this._dom.createChildOf(n,"td","lh-table-column--empty")}return n}_renderTableRowsFromItem(e,t){const n=this._dom.createFragment();if(n.append(this._renderTableRow(e,t)),!e.subItems)return n;const r=t.map(this._getDerivedsubItemsHeading);if(!r.some(Boolean))return n;for(const t of e.subItems.items){const e=this._renderTableRow(t,r);e.classList.add("lh-sub-item-row"),n.append(e)}return n}_renderTable(e){if(!e.items.length)return this._dom.createElement("span");const t=this._dom.createElement("table","lh-table"),n=this._dom.createChildOf(t,"thead"),r=this._dom.createChildOf(n,"tr"),o=this._getCanonicalizedHeadingsFromTable(e);for(const e of o){const t=`lh-table-column--${e.valueType||"text"}`,n=this._dom.createElement("div","lh-text");n.textContent=e.label,this._dom.createChildOf(r,"th",t).append(n)}const i=this._dom.createChildOf(t,"tbody");let a=!0;for(const t of e.items){const e=this._renderTableRowsFromItem(t,o);for(const t of this._dom.findAll("tr",e))t.classList.add(a?"lh-row--even":"lh-row--odd");a=!a,i.append(e)}return t}_renderList(e){const t=this._dom.createElement("div","lh-list");return e.items.forEach((e=>{const n=this.render(e);n&&t.append(n)})),t}renderNode(e){const t=this._dom.createElement("span","lh-node");if(e.nodeLabel){const n=this._dom.createElement("div");n.textContent=e.nodeLabel,t.append(n)}if(e.snippet){const n=this._dom.createElement("div");n.classList.add("lh-node__snippet"),n.textContent=e.snippet,t.append(n)}if(e.selector&&(t.title=e.selector),e.path&&t.setAttribute("data-path",e.path),e.selector&&t.setAttribute("data-selector",e.selector),e.snippet&&t.setAttribute("data-snippet",e.snippet),!this._fullPageScreenshot)return t;const n=e.lhId&&this._fullPageScreenshot.nodes[e.lhId];if(!n||0===n.width||0===n.height)return t;const r=g.render(this._dom,this._fullPageScreenshot.screenshot,n,{width:147,height:100});return r&&t.prepend(r),t}renderSourceLocation(e){if(!e.url)return null;const t=`${e.url}:${e.line+1}:${e.column}`;let n,r;if(e.original){n=`${e.original.file||"<unmapped>"}:${e.original.line+1}:${e.original.column}`}if("network"===e.urlProvider&&n)r=this._renderLink({url:e.url,text:n}),r.title=`maps to generated location ${t}`;else if("network"!==e.urlProvider||n)if("comment"===e.urlProvider&&n)r=this._renderText(`${n} (from source map)`),r.title=`${t} (from sourceURL)`;else{if("comment"!==e.urlProvider||n)return null;r=this._renderText(`${t} (from sourceURL)`)}else r=this.renderTextURL(e.url),this._dom.find(".lh-link",r).textContent+=`:${e.line+1}:${e.column}`;return r.classList.add("lh-source-location"),r.setAttribute("data-source-url",e.url),r.setAttribute("data-source-line",String(e.line)),r.setAttribute("data-source-column",String(e.column)),r}_renderFilmstrip(e){const t=this._dom.createElement("div","lh-filmstrip");for(const n of e.items){const e=this._dom.createChildOf(t,"div","lh-filmstrip__frame"),r=this._dom.createChildOf(e,"img","lh-filmstrip__thumbnail");r.src=n.data,r.alt="Screenshot"}return t}_renderCode(e){const t=this._dom.createElement("pre","lh-code");return t.textContent=e,t}}
87
+ */const m=["http://","https://","data:"];class f{constructor(e,t={}){this._dom=e,this._fullPageScreenshot=t.fullPageScreenshot}render(e){switch(e.type){case"filmstrip":return this._renderFilmstrip(e);case"list":return this._renderList(e);case"table":case"opportunity":return this._renderTable(e);case"criticalrequestchain":return h.render(this._dom,e,this);case"screenshot":case"debugdata":case"full-page-screenshot":case"treemap-data":return null;default:return this._renderUnknown(e.type,e)}}_renderBytes(e){const t=l.i18n.formatBytesToKiB(e.value,e.granularity||.1),n=this._renderText(t);return n.title=l.i18n.formatBytes(e.value),n}_renderMilliseconds(e){let t;return t="duration"===e.displayUnit?l.i18n.formatDuration(e.value):l.i18n.formatMilliseconds(e.value,e.granularity||10),this._renderText(t)}renderTextURL(e){const t=e;let n,r,o;try{const e=l.parseURL(t);n="/"===e.file?e.origin:e.file,r="/"===e.file||""===e.hostname?"":`(${e.hostname})`,o=t}catch(e){n=t}const i=this._dom.createElement("div","lh-text__url");if(i.append(this._renderLink({text:n,url:t})),r){const e=this._renderText(r);e.classList.add("lh-text__url-host"),i.append(e)}return o&&(i.title=t,i.dataset.url=t),i}_renderLink(e){const t=this._dom.createElement("a");if(this._dom.safelySetHref(t,e.url),!t.href){const t=this._renderText(e.text);return t.classList.add("lh-link"),t}return t.rel="noopener",t.target="_blank",t.textContent=e.text,t.classList.add("lh-link"),t}_renderText(e){const t=this._dom.createElement("div","lh-text");return t.textContent=e,t}_renderNumeric(e){const t=l.i18n.formatNumber(e.value,e.granularity||.1),n=this._dom.createElement("div","lh-numeric");return n.textContent=t,n}_renderThumbnail(e){const t=this._dom.createElement("img","lh-thumbnail"),n=e;return t.src=n,t.title=n,t.alt="",t}_renderUnknown(e,t){console.error(`Unknown details type: ${e}`,t);const n=this._dom.createElement("details","lh-unknown");return this._dom.createChildOf(n,"summary").textContent=`We don't know how to render audit details of type \`${e}\`. The Lighthouse version that collected this data is likely newer than the Lighthouse version of the report renderer. Expand for the raw JSON.`,this._dom.createChildOf(n,"pre").textContent=JSON.stringify(t,null,2),n}_renderTableValue(e,t){if(null==e)return null;if("object"==typeof e)switch(e.type){case"code":return this._renderCode(e.value);case"link":return this._renderLink(e);case"node":return this.renderNode(e);case"numeric":return this._renderNumeric(e);case"source-location":return this.renderSourceLocation(e);case"url":return this.renderTextURL(e.value);default:return this._renderUnknown(e.type,e)}switch(t.valueType){case"bytes":{const n=Number(e);return this._renderBytes({value:n,granularity:t.granularity})}case"code":{const t=String(e);return this._renderCode(t)}case"ms":{const n={value:Number(e),granularity:t.granularity,displayUnit:t.displayUnit};return this._renderMilliseconds(n)}case"numeric":{const n=Number(e);return this._renderNumeric({value:n,granularity:t.granularity})}case"text":{const t=String(e);return this._renderText(t)}case"thumbnail":{const t=String(e);return this._renderThumbnail(t)}case"timespanMs":{const t=Number(e);return this._renderMilliseconds({value:t})}case"url":{const t=String(e);return m.some((e=>t.startsWith(e)))?this.renderTextURL(t):this._renderCode(t)}default:return this._renderUnknown(t.valueType,e)}}_getDerivedSubItemsHeading(e){return e.subItemsHeading?{key:e.subItemsHeading.key||"",valueType:e.subItemsHeading.valueType||e.valueType,granularity:e.subItemsHeading.granularity||e.granularity,displayUnit:e.subItemsHeading.displayUnit||e.displayUnit,label:""}:null}_renderTableRow(e,t){const n=this._dom.createElement("tr");for(const r of t){if(!r||!r.key){this._dom.createChildOf(n,"td","lh-table-column--empty");continue}const t=e[r.key];let o;if(null!=t&&(o=this._renderTableValue(t,r)),o){const e=`lh-table-column--${r.valueType}`;this._dom.createChildOf(n,"td",e).append(o)}else this._dom.createChildOf(n,"td","lh-table-column--empty")}return n}_renderTableRowsFromItem(e,t){const n=this._dom.createFragment();if(n.append(this._renderTableRow(e,t)),!e.subItems)return n;const r=t.map(this._getDerivedSubItemsHeading);if(!r.some(Boolean))return n;for(const t of e.subItems.items){const e=this._renderTableRow(t,r);e.classList.add("lh-sub-item-row"),n.append(e)}return n}_renderTable(e){if(!e.items.length)return this._dom.createElement("span");const t=this._dom.createElement("table","lh-table"),n=this._dom.createChildOf(t,"thead"),r=this._dom.createChildOf(n,"tr");for(const t of e.headings){const e=`lh-table-column--${t.valueType||"text"}`,n=this._dom.createElement("div","lh-text");n.textContent=t.label,this._dom.createChildOf(r,"th",e).append(n)}const o=this._dom.createChildOf(t,"tbody");let i=!0;for(const t of e.items){const n=this._renderTableRowsFromItem(t,e.headings);for(const e of this._dom.findAll("tr",n))e.classList.add(i?"lh-row--even":"lh-row--odd");i=!i,o.append(n)}return t}_renderList(e){const t=this._dom.createElement("div","lh-list");return e.items.forEach((e=>{const n=this.render(e);n&&t.append(n)})),t}renderNode(e){const t=this._dom.createElement("span","lh-node");if(e.nodeLabel){const n=this._dom.createElement("div");n.textContent=e.nodeLabel,t.append(n)}if(e.snippet){const n=this._dom.createElement("div");n.classList.add("lh-node__snippet"),n.textContent=e.snippet,t.append(n)}if(e.selector&&(t.title=e.selector),e.path&&t.setAttribute("data-path",e.path),e.selector&&t.setAttribute("data-selector",e.selector),e.snippet&&t.setAttribute("data-snippet",e.snippet),!this._fullPageScreenshot)return t;const n=e.lhId&&this._fullPageScreenshot.nodes[e.lhId];if(!n||0===n.width||0===n.height)return t;const r=g.render(this._dom,this._fullPageScreenshot.screenshot,n,{width:147,height:100});return r&&t.prepend(r),t}renderSourceLocation(e){if(!e.url)return null;const t=`${e.url}:${e.line+1}:${e.column}`;let n,r;if(e.original){n=`${e.original.file||"<unmapped>"}:${e.original.line+1}:${e.original.column}`}if("network"===e.urlProvider&&n)r=this._renderLink({url:e.url,text:n}),r.title=`maps to generated location ${t}`;else if("network"!==e.urlProvider||n)if("comment"===e.urlProvider&&n)r=this._renderText(`${n} (from source map)`),r.title=`${t} (from sourceURL)`;else{if("comment"!==e.urlProvider||n)return null;r=this._renderText(`${t} (from sourceURL)`)}else r=this.renderTextURL(e.url),this._dom.find(".lh-link",r).textContent+=`:${e.line+1}:${e.column}`;return r.classList.add("lh-source-location"),r.setAttribute("data-source-url",e.url),r.setAttribute("data-source-line",String(e.line)),r.setAttribute("data-source-column",String(e.column)),r}_renderFilmstrip(e){const t=this._dom.createElement("div","lh-filmstrip");for(const n of e.items){const e=this._dom.createChildOf(t,"div","lh-filmstrip__frame"),r=this._dom.createChildOf(e,"img","lh-filmstrip__thumbnail");r.src=n.data,r.alt="Screenshot"}return t}_renderCode(e){const t=this._dom.createElement("pre","lh-code");return t.textContent=e,t}}
88
88
  /**
89
89
  * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved.
90
90
  * 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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "9.5.0-dev.20221019",
4
+ "version": "9.5.0-dev.20221020",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -134,7 +134,7 @@
134
134
  "archiver": "^3.0.0",
135
135
  "c8": "^7.11.3",
136
136
  "chalk": "^2.4.1",
137
- "chrome-devtools-frontend": "1.0.1034999",
137
+ "chrome-devtools-frontend": "1.0.1040857",
138
138
  "concurrently": "^6.4.0",
139
139
  "conventional-changelog-cli": "^2.1.1",
140
140
  "cpy": "^8.1.2",
@@ -188,7 +188,6 @@
188
188
  "chrome-launcher": "^0.15.1",
189
189
  "configstore": "^5.0.1",
190
190
  "csp_evaluator": "1.1.1",
191
- "cssstyle": "1.2.1",
192
191
  "enquirer": "^2.3.6",
193
192
  "http-link-header": "^0.8.0",
194
193
  "intl-messageformat": "^4.4.0",
@@ -23,6 +23,7 @@
23
23
  /** @typedef {LH.FormattedIcu<LH.Audit.Details.Table>} Table */
24
24
  /** @typedef {LH.FormattedIcu<LH.Audit.Details.TableItem>} TableItem */
25
25
  /** @typedef {LH.FormattedIcu<LH.Audit.Details.ItemValue>} TableItemValue */
26
+ /** @typedef {LH.FormattedIcu<LH.Audit.Details.TableColumnHeading>} TableColumnHeading */
26
27
 
27
28
  import {Util} from './util.js';
28
29
  import {CriticalRequestChainRenderer} from './crc-details-renderer.js';
@@ -51,11 +52,10 @@ export class DetailsRenderer {
51
52
  case 'list':
52
53
  return this._renderList(details);
53
54
  case 'table':
55
+ case 'opportunity':
54
56
  return this._renderTable(details);
55
57
  case 'criticalrequestchain':
56
58
  return CriticalRequestChainRenderer.render(this._dom, details, this);
57
- case 'opportunity':
58
- return this._renderTable(details);
59
59
 
60
60
  // Internal-only details, not for rendering.
61
61
  case 'screenshot':
@@ -213,7 +213,7 @@ export class DetailsRenderer {
213
213
  * based on the heading's valueType, unless the value itself has a `type`
214
214
  * property to override it.
215
215
  * @param {TableItemValue} value
216
- * @param {LH.Audit.Details.OpportunityColumnHeading} heading
216
+ * @param {LH.Audit.Details.TableColumnHeading} heading
217
217
  * @return {Element|null}
218
218
  */
219
219
  _renderTableValue(value, heading) {
@@ -298,73 +298,14 @@ export class DetailsRenderer {
298
298
  }
299
299
  }
300
300
 
301
- /**
302
- * Get the headings of a table-like details object, converted into the
303
- * OpportunityColumnHeading type until we have all details use the same
304
- * heading format.
305
- * @param {Table|OpportunityTable} tableLike
306
- * @return {OpportunityTable['headings']}
307
- */
308
- _getCanonicalizedHeadingsFromTable(tableLike) {
309
- if (tableLike.type === 'opportunity') {
310
- return tableLike.headings;
311
- }
312
-
313
- return tableLike.headings.map(heading => this._getCanonicalizedHeading(heading));
314
- }
315
-
316
- /**
317
- * Get the headings of a table-like details object, converted into the
318
- * OpportunityColumnHeading type until we have all details use the same
319
- * heading format.
320
- * @param {Table['headings'][number]} heading
321
- * @return {OpportunityTable['headings'][number]}
322
- */
323
- _getCanonicalizedHeading(heading) {
324
- let subItemsHeading;
325
- if (heading.subItemsHeading) {
326
- subItemsHeading = this._getCanonicalizedsubItemsHeading(heading.subItemsHeading, heading);
327
- }
328
-
329
- return {
330
- key: heading.key,
331
- valueType: heading.itemType,
332
- subItemsHeading,
333
- label: heading.text,
334
- displayUnit: heading.displayUnit,
335
- granularity: heading.granularity,
336
- };
337
- }
338
-
339
- /**
340
- * @param {Exclude<LH.Audit.Details.TableColumnHeading['subItemsHeading'], undefined>} subItemsHeading
341
- * @param {LH.Audit.Details.TableColumnHeading} parentHeading
342
- * @return {LH.Audit.Details.OpportunityColumnHeading['subItemsHeading']}
343
- */
344
- _getCanonicalizedsubItemsHeading(subItemsHeading, parentHeading) {
345
- // Low-friction way to prevent committing a falsy key (which is never allowed for
346
- // a subItemsHeading) from passing in CI.
347
- if (!subItemsHeading.key) {
348
- // eslint-disable-next-line no-console
349
- console.warn('key should not be null');
350
- }
351
-
352
- return {
353
- key: subItemsHeading.key || '',
354
- valueType: subItemsHeading.itemType || parentHeading.itemType,
355
- granularity: subItemsHeading.granularity || parentHeading.granularity,
356
- displayUnit: subItemsHeading.displayUnit || parentHeading.displayUnit,
357
- };
358
- }
359
-
360
301
  /**
361
302
  * Returns a new heading where the values are defined first by `heading.subItemsHeading`,
362
303
  * and secondly by `heading`. If there is no subItemsHeading, returns null, which will
363
304
  * be rendered as an empty column.
364
- * @param {LH.Audit.Details.OpportunityColumnHeading} heading
365
- * @return {LH.Audit.Details.OpportunityColumnHeading | null}
305
+ * @param {LH.Audit.Details.TableColumnHeading} heading
306
+ * @return {LH.Audit.Details.TableColumnHeading | null}
366
307
  */
367
- _getDerivedsubItemsHeading(heading) {
308
+ _getDerivedSubItemsHeading(heading) {
368
309
  if (!heading.subItemsHeading) return null;
369
310
  return {
370
311
  key: heading.subItemsHeading.key || '',
@@ -377,7 +318,7 @@ export class DetailsRenderer {
377
318
 
378
319
  /**
379
320
  * @param {TableItem} item
380
- * @param {(LH.Audit.Details.OpportunityColumnHeading | null)[]} headings
321
+ * @param {(LH.Audit.Details.TableColumnHeading | null)[]} headings
381
322
  */
382
323
  _renderTableRow(item, headings) {
383
324
  const rowElem = this._dom.createElement('tr');
@@ -414,7 +355,7 @@ export class DetailsRenderer {
414
355
  * Renders one or more rows from a details table item. A single table item can
415
356
  * expand into multiple rows, if there is a subItemsHeading.
416
357
  * @param {TableItem} item
417
- * @param {LH.Audit.Details.OpportunityColumnHeading[]} headings
358
+ * @param {LH.Audit.Details.TableColumnHeading[]} headings
418
359
  */
419
360
  _renderTableRowsFromItem(item, headings) {
420
361
  const fragment = this._dom.createFragment();
@@ -422,7 +363,7 @@ export class DetailsRenderer {
422
363
 
423
364
  if (!item.subItems) return fragment;
424
365
 
425
- const subItemsHeadings = headings.map(this._getDerivedsubItemsHeading);
366
+ const subItemsHeadings = headings.map(this._getDerivedSubItemsHeading);
426
367
  if (!subItemsHeadings.some(Boolean)) return fragment;
427
368
 
428
369
  for (const subItem of item.subItems.items) {
@@ -435,7 +376,7 @@ export class DetailsRenderer {
435
376
  }
436
377
 
437
378
  /**
438
- * @param {OpportunityTable|Table} details
379
+ * @param {{headings: TableColumnHeading[], items: TableItem[]}} details
439
380
  * @return {Element}
440
381
  */
441
382
  _renderTable(details) {
@@ -445,9 +386,7 @@ export class DetailsRenderer {
445
386
  const theadElem = this._dom.createChildOf(tableElem, 'thead');
446
387
  const theadTrElem = this._dom.createChildOf(theadElem, 'tr');
447
388
 
448
- const headings = this._getCanonicalizedHeadingsFromTable(details);
449
-
450
- for (const heading of headings) {
389
+ for (const heading of details.headings) {
451
390
  const valueType = heading.valueType || 'text';
452
391
  const classes = `lh-table-column--${valueType}`;
453
392
  const labelEl = this._dom.createElement('div', 'lh-text');
@@ -458,7 +397,7 @@ export class DetailsRenderer {
458
397
  const tbodyElem = this._dom.createChildOf(tableElem, 'tbody');
459
398
  let even = true;
460
399
  for (const item of details.items) {
461
- const rowsFragment = this._renderTableRowsFromItem(item, headings);
400
+ const rowsFragment = this._renderTableRowsFromItem(item, details.headings);
462
401
  for (const rowEl of this._dom.findAll('tr', rowsFragment)) {
463
402
  // For zebra styling.
464
403
  rowEl.classList.add(even ? 'lh-row--even' : 'lh-row--odd');
@@ -123,6 +123,33 @@ class Util {
123
123
  }
124
124
  }
125
125
  }
126
+
127
+ // Circa 10.0, table items were refactored.
128
+ if (audit.details.type === 'table') {
129
+ for (const heading of audit.details.headings) {
130
+ /** @type {{itemType: LH.Audit.Details.ItemValueType|undefined, text: string|undefined}} */
131
+ // @ts-expect-error
132
+ const {itemType, text} = heading;
133
+ if (itemType !== undefined) {
134
+ heading.valueType = itemType;
135
+ // @ts-expect-error
136
+ delete heading.itemType;
137
+ }
138
+ if (text !== undefined) {
139
+ heading.label = text;
140
+ // @ts-expect-error
141
+ delete heading.text;
142
+ }
143
+
144
+ // @ts-expect-error
145
+ const subItemsItemType = heading.subItemsHeading?.itemType;
146
+ if (heading.subItemsHeading && subItemsItemType !== undefined) {
147
+ heading.subItemsHeading.valueType = subItemsItemType;
148
+ // @ts-expect-error
149
+ delete heading.subItemsHeading.itemType;
150
+ }
151
+ }
152
+ }
126
153
  }
127
154
  }
128
155
 
@@ -57,9 +57,9 @@ describe('DetailsRenderer', () => {
57
57
  const el = renderer.render({
58
58
  type: 'table',
59
59
  headings: [
60
- {text: 'First', key: 'a', itemType: 'text'},
61
- {text: 'Second', key: 'b', itemType: 'text'},
62
- {text: 'Preview', key: 'c', itemType: 'thumbnail'},
60
+ {text: 'First', key: 'a', valueType: 'text'},
61
+ {text: 'Second', key: 'b', valueType: 'text'},
62
+ {text: 'Preview', key: 'c', valueType: 'thumbnail'},
63
63
  ],
64
64
  items: [
65
65
  {
@@ -88,11 +88,11 @@ describe('DetailsRenderer', () => {
88
88
  const el = renderer.render({
89
89
  type: 'table',
90
90
  headings: [
91
- {text: '', key: 'bytes', itemType: 'bytes'},
92
- {text: '', key: 'numeric', itemType: 'numeric'},
93
- {text: '', key: 'ms', itemType: 'ms'},
91
+ {text: '', key: 'bytes', valueType: 'bytes'},
92
+ {text: '', key: 'numeric', valueType: 'numeric'},
93
+ {text: '', key: 'ms', valueType: 'ms'},
94
94
  // Verify that 0 is ignored.
95
- {text: '', key: 'ms', itemType: 'ms', granularity: 0},
95
+ {text: '', key: 'ms', valueType: 'ms', granularity: 0},
96
96
  ],
97
97
  items: [
98
98
  {
@@ -114,9 +114,9 @@ describe('DetailsRenderer', () => {
114
114
  const el = renderer.render({
115
115
  type: 'table',
116
116
  headings: [
117
- {text: '', key: 'bytes', itemType: 'bytes', granularity: 0.01},
118
- {text: '', key: 'numeric', itemType: 'numeric', granularity: 100},
119
- {text: '', key: 'ms', itemType: 'ms', granularity: 1},
117
+ {text: '', key: 'bytes', valueType: 'bytes', granularity: 0.01},
118
+ {text: '', key: 'numeric', valueType: 'numeric', granularity: 100},
119
+ {text: '', key: 'ms', valueType: 'ms', granularity: 1},
120
120
  ],
121
121
  items: [
122
122
  {
@@ -190,7 +190,7 @@ describe('DetailsRenderer', () => {
190
190
  it('renders lists', () => {
191
191
  const table = {
192
192
  type: 'table',
193
- headings: [{text: '', key: 'numeric', itemType: 'numeric'}],
193
+ headings: [{text: '', key: 'numeric', valueType: 'numeric'}],
194
194
  items: [{numeric: 1234.567}],
195
195
  };
196
196
 
@@ -264,7 +264,7 @@ describe('DetailsRenderer', () => {
264
264
  it('renders text values', () => {
265
265
  const details = {
266
266
  type: 'table',
267
- headings: [{key: 'content', itemType: 'text', text: 'Heading'}],
267
+ headings: [{key: 'content', valueType: 'text', label: 'Heading'}],
268
268
  items: [{content: 'My text content'}],
269
269
  };
270
270
 
@@ -277,7 +277,7 @@ describe('DetailsRenderer', () => {
277
277
  it('renders not much if items are empty', () => {
278
278
  const details = {
279
279
  type: 'table',
280
- headings: [{key: 'content', itemType: 'text', text: 'Heading'}],
280
+ headings: [{key: 'content', valueType: 'text', label: 'Heading'}],
281
281
  items: [],
282
282
  };
283
283
 
@@ -288,7 +288,7 @@ describe('DetailsRenderer', () => {
288
288
  it('renders an empty cell if item is missing a property', () => {
289
289
  const details = {
290
290
  type: 'table',
291
- headings: [{key: 'content', itemType: 'text', text: 'Heading'}],
291
+ headings: [{key: 'content', valueType: 'text', label: 'Heading'}],
292
292
  items: [
293
293
  {},
294
294
  {content: undefined},
@@ -316,7 +316,7 @@ describe('DetailsRenderer', () => {
316
316
  it('renders code values from a string', () => {
317
317
  const details = {
318
318
  type: 'table',
319
- headings: [{key: 'content', itemType: 'code', text: 'Heading'}],
319
+ headings: [{key: 'content', valueType: 'code', label: 'Heading'}],
320
320
  items: [{content: 'code snippet'}],
321
321
  };
322
322
 
@@ -334,7 +334,7 @@ describe('DetailsRenderer', () => {
334
334
 
335
335
  const details = {
336
336
  type: 'table',
337
- headings: [{key: 'content', itemType: 'code', text: 'Heading'}],
337
+ headings: [{key: 'content', valueType: 'code', label: 'Heading'}],
338
338
  items: [{content: code}],
339
339
  };
340
340
 
@@ -347,7 +347,7 @@ describe('DetailsRenderer', () => {
347
347
  it('renders thumbnail values', () => {
348
348
  const details = {
349
349
  type: 'table',
350
- headings: [{key: 'content', itemType: 'thumbnail', text: 'Heading'}],
350
+ headings: [{key: 'content', valueType: 'thumbnail', label: 'Heading'}],
351
351
  items: [{content: 'http://example.com/my-image.jpg'}],
352
352
  };
353
353
 
@@ -369,7 +369,7 @@ describe('DetailsRenderer', () => {
369
369
  };
370
370
  const details = {
371
371
  type: 'table',
372
- headings: [{key: 'content', itemType: 'link', text: 'Heading'}],
372
+ headings: [{key: 'content', valueType: 'link', label: 'Heading'}],
373
373
  items: [{content: link}],
374
374
  };
375
375
 
@@ -391,7 +391,7 @@ describe('DetailsRenderer', () => {
391
391
  };
392
392
  const details = {
393
393
  type: 'table',
394
- headings: [{key: 'content', itemType: 'link', text: 'Heading'}],
394
+ headings: [{key: 'content', valueType: 'link', label: 'Heading'}],
395
395
  items: [{content: link}],
396
396
  };
397
397
 
@@ -411,7 +411,7 @@ describe('DetailsRenderer', () => {
411
411
  };
412
412
  const details = {
413
413
  type: 'table',
414
- headings: [{key: 'content', itemType: 'link', text: 'Heading'}],
414
+ headings: [{key: 'content', valueType: 'link', label: 'Heading'}],
415
415
  items: [{content: link}],
416
416
  };
417
417
 
@@ -431,7 +431,7 @@ describe('DetailsRenderer', () => {
431
431
  };
432
432
  const details = {
433
433
  type: 'table',
434
- headings: [{key: 'content', itemType: 'node', text: 'Heading'}],
434
+ headings: [{key: 'content', valueType: 'node', label: 'Heading'}],
435
435
  items: [{content: node}],
436
436
  };
437
437
 
@@ -466,7 +466,7 @@ describe('DetailsRenderer', () => {
466
466
  };
467
467
  const details = {
468
468
  type: 'table',
469
- headings: [{key: 'content', itemType: 'node', text: 'Heading'}],
469
+ headings: [{key: 'content', valueType: 'node', label: 'Heading'}],
470
470
  items: [{content: node}],
471
471
  };
472
472
 
@@ -498,7 +498,7 @@ describe('DetailsRenderer', () => {
498
498
  };
499
499
  const details = {
500
500
  type: 'table',
501
- headings: [{key: 'content', itemType: 'node', text: 'Heading'}],
501
+ headings: [{key: 'content', valueType: 'node', label: 'Heading'}],
502
502
  items: [{content: node}],
503
503
  };
504
504
 
@@ -519,7 +519,7 @@ describe('DetailsRenderer', () => {
519
519
  };
520
520
  const details = {
521
521
  type: 'table',
522
- headings: [{key: 'content', itemType: 'source-location', text: 'Heading'}],
522
+ headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
523
523
  items: [{content: sourceLocation}],
524
524
  };
525
525
 
@@ -549,7 +549,7 @@ describe('DetailsRenderer', () => {
549
549
  };
550
550
  const details = {
551
551
  type: 'table',
552
- headings: [{key: 'content', itemType: 'source-location', text: 'Heading'}],
552
+ headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
553
553
  items: [{content: sourceLocation}],
554
554
  };
555
555
 
@@ -575,7 +575,7 @@ describe('DetailsRenderer', () => {
575
575
  };
576
576
  const details = {
577
577
  type: 'table',
578
- headings: [{key: 'content', itemType: 'source-location', text: 'Heading'}],
578
+ headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
579
579
  items: [{content: sourceLocation}],
580
580
  };
581
581
 
@@ -600,7 +600,7 @@ describe('DetailsRenderer', () => {
600
600
  };
601
601
  const details = {
602
602
  type: 'table',
603
- headings: [{key: 'content', itemType: 'source-location', text: 'Heading'}],
603
+ headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
604
604
  items: [{content: sourceLocation}],
605
605
  };
606
606
 
@@ -625,7 +625,7 @@ describe('DetailsRenderer', () => {
625
625
  };
626
626
  const details = {
627
627
  type: 'table',
628
- headings: [{key: 'content', itemType: 'source-location', text: 'Heading'}],
628
+ headings: [{key: 'content', valueType: 'source-location', label: 'Heading'}],
629
629
  items: [{content: sourceLocation}],
630
630
  };
631
631
 
@@ -646,7 +646,7 @@ describe('DetailsRenderer', () => {
646
646
 
647
647
  const details = {
648
648
  type: 'table',
649
- headings: [{key: 'content', itemType: 'url', text: 'Heading'}],
649
+ headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
650
650
  items: [{content: urlText}],
651
651
  };
652
652
 
@@ -673,7 +673,7 @@ describe('DetailsRenderer', () => {
673
673
 
674
674
  const details = {
675
675
  type: 'table',
676
- headings: [{key: 'content', itemType: 'url', text: 'Heading'}],
676
+ headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
677
677
  items: [{content: url}],
678
678
  overallSavingsMs: 100,
679
679
  };
@@ -693,7 +693,7 @@ describe('DetailsRenderer', () => {
693
693
 
694
694
  const details = {
695
695
  type: 'table',
696
- headings: [{key: 'content', itemType: 'url', text: 'Heading'}],
696
+ headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
697
697
  items: [{content: urlText}],
698
698
  };
699
699
 
@@ -712,11 +712,11 @@ describe('DetailsRenderer', () => {
712
712
  console.error = consoleError;
713
713
  });
714
714
 
715
- it('renders an unknown heading itemType', () => {
715
+ it('renders an unknown heading valueType', () => {
716
716
  // Disallowed by type system, but test that we get an error message out just in case.
717
717
  const details = {
718
718
  type: 'table',
719
- headings: [{key: 'content', itemType: 'notRealValueType', text: 'Heading'}],
719
+ headings: [{key: 'content', valueType: 'notRealValueType', label: 'Heading'}],
720
720
  items: [{content: 'some string'}],
721
721
  };
722
722
 
@@ -737,7 +737,7 @@ describe('DetailsRenderer', () => {
737
737
 
738
738
  const details = {
739
739
  type: 'table',
740
- headings: [{key: 'content', itemType: 'url', text: 'Heading'}],
740
+ headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
741
741
  items: [{content: item}],
742
742
  };
743
743
 
@@ -753,8 +753,8 @@ describe('DetailsRenderer', () => {
753
753
  it('uses the item\'s type over the heading type', () => {
754
754
  const details = {
755
755
  type: 'table',
756
- // itemType is overriden by code object
757
- headings: [{key: 'content', itemType: 'url', text: 'Heading'}],
756
+ // valueType is overriden by code object
757
+ headings: [{key: 'content', valueType: 'url', label: 'Heading'}],
758
758
  items: [
759
759
  {content: {type: 'code', value: 'https://codeobject.com'}},
760
760
  {content: 'https://example.com'},
@@ -790,7 +790,7 @@ describe('DetailsRenderer', () => {
790
790
  const details = {
791
791
  type: 'table',
792
792
  headings: [
793
- {key: 'url', itemType: 'url', subItemsHeading: {key: 'source', itemType: 'code'}},
793
+ {key: 'url', valueType: 'url', subItemsHeading: {key: 'source', valueType: 'code'}},
794
794
  ],
795
795
  items: [
796
796
  {
@@ -832,7 +832,7 @@ describe('DetailsRenderer', () => {
832
832
  it('renders, uses heading properties as fallback', () => {
833
833
  const details = {
834
834
  type: 'table',
835
- headings: [{key: 'url', itemType: 'url', subItemsHeading: {key: 'source'}}],
835
+ headings: [{key: 'url', valueType: 'url', subItemsHeading: {key: 'source'}}],
836
836
  items: [
837
837
  {
838
838
  url: 'https://www.example.com',
@@ -176,6 +176,31 @@ describe('util helpers', () => {
176
176
  assert.deepStrictEqual(clonedPreparedResult.categories, preparedResult.categories);
177
177
  assert.deepStrictEqual(clonedPreparedResult.categoryGroups, preparedResult.categoryGroups);
178
178
  });
179
+
180
+ it('converts old opportunity table column headings to consolidated table headings', () => {
181
+ const clonedSampleResult = JSON.parse(JSON.stringify(sampleResult));
182
+
183
+ const auditsWithTableDetails = Object.values(clonedSampleResult.audits)
184
+ .filter(audit => audit.details?.type === 'table');
185
+ assert.notEqual(auditsWithTableDetails.length, 0);
186
+ for (const audit of auditsWithTableDetails) {
187
+ for (const heading of audit.details.headings) {
188
+ heading.itemType = heading.valueType;
189
+ heading.text = heading.label;
190
+ delete heading.valueType;
191
+ delete heading.label;
192
+
193
+ if (heading.subItemsHeading) {
194
+ heading.subItemsHeading.itemType = heading.subItemsHeading.valueType;
195
+ // @ts-expect-error
196
+ delete heading.subItemsHeading.valueType;
197
+ }
198
+ }
199
+ }
200
+
201
+ const preparedResult = Util.prepareReportResult(clonedSampleResult);
202
+ assert.deepStrictEqual(sampleResult.audits, preparedResult.audits);
203
+ });
179
204
  });
180
205
 
181
206
  it('appends stack pack descriptions to auditRefs', () => {