From 157934085f4c47abb3ee595858d88d5bce574ccb Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 15 Apr 2022 10:53:25 -0700 Subject: [PATCH 1/9] first pass --- .../audits/accessibility/axe-audit.js | 11 +++++++++- .../audits/accessibility/color-contrast.js | 6 +++++ .../audits/accessibility/duplicate-id-aria.js | 6 +++++ .../byte-efficiency/duplicated-javascript.js | 2 ++ .../byte-efficiency/legacy-javascript.js | 1 + .../byte-efficiency/unused-javascript.js | 1 + lighthouse-core/audits/csp-xss.js | 1 + .../audits/dobetterweb/inspector-issues.js | 4 ++++ .../audits/non-composited-animations.js | 1 + lighthouse-core/audits/seo/hreflang.js | 3 ++- lighthouse-core/audits/third-party-facades.js | 6 ++++- lighthouse-core/lib/i18n/i18n.js | 4 ++++ .../reports/sample-flow-result.json | 22 +++++++++++++++++++ lighthouse-core/test/results/sample_v2.json | 8 +++++++ report/renderer/details-renderer.js | 11 +++++++++- shared/localization/locales/en-US.json | 15 +++++++++++++ shared/localization/locales/en-XL.json | 15 +++++++++++++ types/lhr/audit-details.d.ts | 1 + 18 files changed, 114 insertions(+), 4 deletions(-) diff --git a/lighthouse-core/audits/accessibility/axe-audit.js b/lighthouse-core/audits/accessibility/axe-audit.js index 0c701e16afee..15497e82608d 100644 --- a/lighthouse-core/audits/accessibility/axe-audit.js +++ b/lighthouse-core/audits/accessibility/axe-audit.js @@ -16,11 +16,17 @@ const i18n = require('../../lib/i18n/i18n.js'); const UIStrings = { /** Label of a table column that identifies HTML elements that have failed an audit. */ failingElementsHeader: 'Failing Elements', + /** Label of a table column that identifies HTML elements that are related to a failure in an audit. */ + relatedElementsHeader: 'Related Elements', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); class AxeAudit extends Audit { + static get relatedNodesLabel() { + return str_(UIStrings.relatedElementsHeader); + } + /** * Base class for audit rules which reflect assessment performed by the aXe accessibility library * See https://github.com/dequelabs/axe-core/blob/6b444546cff492a62a70a74a8fc3c62bd4729400/doc/API.md#results-object for result type and format details @@ -81,7 +87,10 @@ class AxeAudit extends Audit { }, subItems: axeNode.relatedNodes.length ? { type: 'subitems', - items: axeNode.relatedNodes.map(node => ({relatedNode: Audit.makeNodeItem(node)})), + label: this.relatedNodesLabel, + items: axeNode.relatedNodes.map(node => ({ + relatedNode: Audit.makeNodeItem(node), + })), } : undefined, })); } diff --git a/lighthouse-core/audits/accessibility/color-contrast.js b/lighthouse-core/audits/accessibility/color-contrast.js index dbced6ea0731..da7c9ea3f332 100644 --- a/lighthouse-core/audits/accessibility/color-contrast.js +++ b/lighthouse-core/audits/accessibility/color-contrast.js @@ -23,6 +23,8 @@ const UIStrings = { /** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */ description: 'Low-contrast text is difficult or impossible for many users to read. ' + '[Learn more](https://web.dev/color-contrast/).', + /** Label of a table column that identifies a single HTML element that is the background of another HTML element. */ + backgroundElementHeader: 'Background Element', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); @@ -40,6 +42,10 @@ class ColorContrast extends AxeAudit { requiredArtifacts: ['Accessibility'], }; } + + static get relatedNodesLabel() { + return str_(UIStrings.backgroundElementHeader); + } } module.exports = ColorContrast; diff --git a/lighthouse-core/audits/accessibility/duplicate-id-aria.js b/lighthouse-core/audits/accessibility/duplicate-id-aria.js index 335ade51a722..043f2d6b0147 100644 --- a/lighthouse-core/audits/accessibility/duplicate-id-aria.js +++ b/lighthouse-core/audits/accessibility/duplicate-id-aria.js @@ -20,6 +20,8 @@ const UIStrings = { failureTitle: 'ARIA IDs are not unique', /** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */ description: 'The value of an ARIA ID must be unique to prevent other instances from being overlooked by assistive technologies. [Learn more](https://web.dev/duplicate-id-aria/).', + /** Label of a table column that identifies HTML elements that are duplicates of another HTML element. */ + relatedElementsHeader: 'Duplicate Elements', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); @@ -37,6 +39,10 @@ class DuplicateIdAria extends AxeAudit { requiredArtifacts: ['Accessibility'], }; } + + static get relatedNodesLabel() { + return str_(UIStrings.relatedElementsHeader); + } } module.exports = DuplicateIdAria; diff --git a/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js b/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js index 1cb02931e468..c25ca7076bb2 100644 --- a/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +++ b/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js @@ -208,6 +208,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit { totalBytes: 0, subItems: { type: 'subitems', + label: str_(i18n.UIStrings.scriptResourceType), items: subItems, }, }); @@ -221,6 +222,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit { totalBytes: 0, subItems: { type: 'subitems', + label: str_(i18n.UIStrings.scriptResourceType), items: Array.from(overflowUrls).map(url => ({url})), }, }); diff --git a/lighthouse-core/audits/byte-efficiency/legacy-javascript.js b/lighthouse-core/audits/byte-efficiency/legacy-javascript.js index 1c8ce232e5fd..4f859ba96b25 100644 --- a/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +++ b/lighthouse-core/audits/byte-efficiency/legacy-javascript.js @@ -420,6 +420,7 @@ class LegacyJavascript extends ByteEfficiencyAudit { url: script.url, wastedBytes, subItems: { + label: str_(i18n.UIStrings.columnLocation), type: 'subitems', items: [], }, diff --git a/lighthouse-core/audits/byte-efficiency/unused-javascript.js b/lighthouse-core/audits/byte-efficiency/unused-javascript.js index a416f9300e02..aa7a62dc9382 100644 --- a/lighthouse-core/audits/byte-efficiency/unused-javascript.js +++ b/lighthouse-core/audits/byte-efficiency/unused-javascript.js @@ -136,6 +136,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit { const commonSourcePrefix = commonPrefix([...bundle.map.sourceInfos.keys()]); item.subItems = { type: 'subitems', + label: str_(i18n.UIStrings.module), items: topUnusedSourceSizes.map(({source, unused, total}) => { return { source: trimCommonPrefix(source, commonSourcePrefix), diff --git a/lighthouse-core/audits/csp-xss.js b/lighthouse-core/audits/csp-xss.js index e45fb0d8f2a9..b648422d5531 100644 --- a/lighthouse-core/audits/csp-xss.js +++ b/lighthouse-core/audits/csp-xss.js @@ -109,6 +109,7 @@ class CspXss extends Audit { }, subItems: { type: 'subitems', + label: str_(i18n.UIStrings.reasons), items, }, }); diff --git a/lighthouse-core/audits/dobetterweb/inspector-issues.js b/lighthouse-core/audits/dobetterweb/inspector-issues.js index 781b8f48b70a..71887ad2f27a 100644 --- a/lighthouse-core/audits/dobetterweb/inspector-issues.js +++ b/lighthouse-core/audits/dobetterweb/inspector-issues.js @@ -65,6 +65,7 @@ class IssuesPanelEntries extends Audit { issueType: 'Mixed content', subItems: { type: 'subitems', + label: str_(i18n.UIStrings.columnURL), items: Array.from(requestUrls).map(url => ({url})), }, }; @@ -86,6 +87,7 @@ class IssuesPanelEntries extends Audit { issueType: 'Cookie', subItems: { type: 'subitems', + label: str_(i18n.UIStrings.columnURL), items: Array.from(requestUrls).map(url => { return { url, @@ -111,6 +113,7 @@ class IssuesPanelEntries extends Audit { issueType: str_(UIStrings.issueTypeBlockedByResponse), subItems: { type: 'subitems', + label: str_(i18n.UIStrings.columnURL), items: Array.from(requestUrls).map(url => { return { url, @@ -136,6 +139,7 @@ class IssuesPanelEntries extends Audit { issueType: 'Content security policy', subItems: { type: 'subitems', + label: str_(i18n.UIStrings.columnURL), items: Array.from(requestUrls).map(url => { return { url, diff --git a/lighthouse-core/audits/non-composited-animations.js b/lighthouse-core/audits/non-composited-animations.js index b0691e4ed273..6fcf74cc0b81 100644 --- a/lighthouse-core/audits/non-composited-animations.js +++ b/lighthouse-core/audits/non-composited-animations.js @@ -166,6 +166,7 @@ class NonCompositedAnimations extends Audit { node: Audit.makeNodeItem(element.node), subItems: { type: 'subitems', + label: str_(i18n.UIStrings.reasons), items: allFailureReasons, }, }); diff --git a/lighthouse-core/audits/seo/hreflang.js b/lighthouse-core/audits/seo/hreflang.js index 3749560332f1..f752af430fed 100644 --- a/lighthouse-core/audits/seo/hreflang.js +++ b/lighthouse-core/audits/seo/hreflang.js @@ -6,7 +6,7 @@ 'use strict'; /** @typedef {string|LH.Audit.Details.NodeValue|undefined} Source */ -/** @typedef {{source: Source, subItems: {type: 'subitems', items: SubItem[]}}} InvalidHreflang */ +/** @typedef {{source: Source, subItems: {type: 'subitems', label: LH.IcuMessage, items: SubItem[]}}} InvalidHreflang */ /** @typedef {{reason: LH.IcuMessage}} SubItem */ const Audit = require('../audit.js'); @@ -120,6 +120,7 @@ class Hreflang extends Audit { source, subItems: { type: 'subitems', + label: str_(i18n.UIStrings.reasons), items: reasons.map(reason => ({reason})), }, }); diff --git a/lighthouse-core/audits/third-party-facades.js b/lighthouse-core/audits/third-party-facades.js index 286e8a1837d3..72046ae053ac 100644 --- a/lighthouse-core/audits/third-party-facades.js +++ b/lighthouse-core/audits/third-party-facades.js @@ -187,7 +187,11 @@ class ThirdPartyFacades extends Audit { product: productWithCategory, transferSize: entitySummary.transferSize, blockingTime: entitySummary.blockingTime, - subItems: {type: 'subitems', items}, + subItems: { + type: 'subitems', + label: str_(i18n.UIStrings.scriptResourceType), + items, + }, }); } diff --git a/lighthouse-core/lib/i18n/i18n.js b/lighthouse-core/lib/i18n/i18n.js index e8c8ce5438bc..53029b365a86 100644 --- a/lighthouse-core/lib/i18n/i18n.js +++ b/lighthouse-core/lib/i18n/i18n.js @@ -89,6 +89,10 @@ const UIStrings = { thirdPartyResourceType: 'Third-party', /** Label used to identify a value in a table where many individual values are aggregated to a single value, for brevity. "Other resources" could also be read as "the rest of the resources". Resource refers to network resources requested by the browser. */ otherResourcesLabel: 'Other resources', + /** Label for a row in a data table; 'Module' refers to a JavaScript module file. */ + module: 'Module', + /** Label for a row in a data table; What follows are reasons why something failed or occurred. */ + reasons: 'Reasons', /** The name of the metric that marks the time at which the first text or image is painted by the browser. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit. */ firstContentfulPaintMetric: 'First Contentful Paint', /** The name of the metric that marks the time at which the page is fully loaded and is able to quickly respond to user input (clicks, taps, and keypresses feel responsive). Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit. */ diff --git a/lighthouse-core/test/fixtures/fraggle-rock/reports/sample-flow-result.json b/lighthouse-core/test/fixtures/fraggle-rock/reports/sample-flow-result.json index 10804549238f..d260a325ab15 100644 --- a/lighthouse-core/test/fixtures/fraggle-rock/reports/sample-flow-result.json +++ b/lighthouse-core/test/fixtures/fraggle-rock/reports/sample-flow-result.json @@ -3214,6 +3214,7 @@ "url": "https://www.mikescerealshack.co/_next/static/chunks/commons.49455e4fa8cc3f51203f.js", "wastedBytes": 57, "subItems": { + "label": "Location", "type": "subitems", "items": [ { @@ -7019,6 +7020,9 @@ "path": "audits[legacy-javascript].displayValue" } ], + "lighthouse-core/lib/i18n/i18n.js | columnLocation": [ + "audits[legacy-javascript].details.items[0].subItems.label" + ], "lighthouse-core/audits/dobetterweb/doctype.js | title": [ "audits.doctype.title" ], @@ -11245,6 +11249,7 @@ }, "subItems": { "type": "subitems", + "label": "Background Element", "items": [ { "relatedNode": { @@ -11287,6 +11292,7 @@ }, "subItems": { "type": "subitems", + "label": "Background Element", "items": [ { "relatedNode": { @@ -11329,6 +11335,7 @@ }, "subItems": { "type": "subitems", + "label": "Background Element", "items": [ { "relatedNode": { @@ -11371,6 +11378,7 @@ }, "subItems": { "type": "subitems", + "label": "Background Element", "items": [ { "relatedNode": { @@ -11413,6 +11421,7 @@ }, "subItems": { "type": "subitems", + "label": "Background Element", "items": [ { "relatedNode": { @@ -11455,6 +11464,7 @@ }, "subItems": { "type": "subitems", + "label": "Background Element", "items": [ { "relatedNode": { @@ -13614,6 +13624,14 @@ "audits[color-contrast].details.headings[0].text", "audits[image-alt].details.headings[0].text" ], + "lighthouse-core/audits/accessibility/color-contrast.js | backgroundElementHeader": [ + "audits[color-contrast].details.items[0].subItems.label", + "audits[color-contrast].details.items[1].subItems.label", + "audits[color-contrast].details.items[2].subItems.label", + "audits[color-contrast].details.items[3].subItems.label", + "audits[color-contrast].details.items[4].subItems.label", + "audits[color-contrast].details.items[5].subItems.label" + ], "lighthouse-core/audits/accessibility/definition-list.js | title": [ "audits[definition-list].title" ], @@ -17285,6 +17303,7 @@ "url": "https://www.mikescerealshack.co/_next/static/chunks/commons.49455e4fa8cc3f51203f.js", "wastedBytes": 0, "subItems": { + "label": "Location", "type": "subitems", "items": [ { @@ -21136,6 +21155,9 @@ "lighthouse-core/audits/byte-efficiency/legacy-javascript.js | description": [ "audits[legacy-javascript].description" ], + "lighthouse-core/lib/i18n/i18n.js | columnLocation": [ + "audits[legacy-javascript].details.items[0].subItems.label" + ], "lighthouse-core/audits/dobetterweb/doctype.js | title": [ "audits.doctype.title" ], diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 31d0ce027bf5..03d617c27372 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -2445,6 +2445,7 @@ }, "subItems": { "type": "subitems", + "label": "Reasons", "items": [ { "failureReason": "Unsupported CSS Properties: margin-left, height", @@ -5140,6 +5141,7 @@ "url": "http://localhost:10200/dobetterweb/third_party/aggressive-promise-polyfill.js", "wastedBytes": 21189, "subItems": { + "label": "Location", "type": "subitems", "items": [ { @@ -9508,6 +9510,9 @@ "path": "audits[non-composited-animations].displayValue" } ], + "lighthouse-core/lib/i18n/i18n.js | reasons": [ + "audits[non-composited-animations].details.items[0].subItems.label" + ], "lighthouse-core/audits/non-composited-animations.js | unsupportedCSSProperty": [ { "values": { @@ -10019,6 +10024,9 @@ "lighthouse-core/audits/byte-efficiency/legacy-javascript.js | description": [ "audits[legacy-javascript].description" ], + "lighthouse-core/lib/i18n/i18n.js | columnLocation": [ + "audits[legacy-javascript].details.items[0].subItems.label" + ], "lighthouse-core/audits/dobetterweb/doctype.js | title": [ "audits.doctype.title" ], diff --git a/report/renderer/details-renderer.js b/report/renderer/details-renderer.js index ae58b027c390..8367474e8784 100644 --- a/report/renderer/details-renderer.js +++ b/report/renderer/details-renderer.js @@ -424,7 +424,16 @@ export class DetailsRenderer { if (!item.subItems) return fragment; const subItemsHeadings = headings.map(this._getDerivedsubItemsHeading); - if (!subItemsHeadings.some(Boolean)) return fragment; + if (!subItemsHeadings.some(Boolean) || !item.subItems.items.length) return fragment; + + if (item.subItems.label) { + const rowEl = this._dom.createElement('tr'); + const tdEl = this._dom.createChildOf(rowEl, 'td'); + tdEl.append(this._renderText(item.subItems.label)); + tdEl.append(this._dom.createElement('hr')); + rowEl.classList.add('lh-sub-item-row'); + fragment.append(rowEl); + } for (const subItem of item.subItems.items) { const rowEl = this._renderTableRow(subItem, subItemsHeadings); diff --git a/shared/localization/locales/en-US.json b/shared/localization/locales/en-US.json index 2e1233088078..8963744b4853 100644 --- a/shared/localization/locales/en-US.json +++ b/shared/localization/locales/en-US.json @@ -287,6 +287,9 @@ "lighthouse-core/audits/accessibility/axe-audit.js | failingElementsHeader": { "message": "Failing Elements" }, + "lighthouse-core/audits/accessibility/axe-audit.js | relatedElementsHeader": { + "message": "Related Elements" + }, "lighthouse-core/audits/accessibility/button-name.js | description": { "message": "When a button doesn't have an accessible name, screen readers announce it as \"button\", making it unusable for users who rely on screen readers. [Learn more](https://web.dev/button-name/)." }, @@ -305,6 +308,9 @@ "lighthouse-core/audits/accessibility/bypass.js | title": { "message": "The page contains a heading, skip link, or landmark region" }, + "lighthouse-core/audits/accessibility/color-contrast.js | backgroundElementHeader": { + "message": "Background Element" + }, "lighthouse-core/audits/accessibility/color-contrast.js | description": { "message": "Low-contrast text is difficult or impossible for many users to read. [Learn more](https://web.dev/color-contrast/)." }, @@ -356,6 +362,9 @@ "lighthouse-core/audits/accessibility/duplicate-id-aria.js | failureTitle": { "message": "ARIA IDs are not unique" }, + "lighthouse-core/audits/accessibility/duplicate-id-aria.js | relatedElementsHeader": { + "message": "Duplicate Elements" + }, "lighthouse-core/audits/accessibility/duplicate-id-aria.js | title": { "message": "ARIA IDs are unique" }, @@ -1979,6 +1988,9 @@ "lighthouse-core/lib/i18n/i18n.js | mediaResourceType": { "message": "Media" }, + "lighthouse-core/lib/i18n/i18n.js | module": { + "message": "Module" + }, "lighthouse-core/lib/i18n/i18n.js | ms": { "message": "{timeInMs, number, milliseconds} ms" }, @@ -1988,6 +2000,9 @@ "lighthouse-core/lib/i18n/i18n.js | otherResourceType": { "message": "Other" }, + "lighthouse-core/lib/i18n/i18n.js | reasons": { + "message": "Reasons" + }, "lighthouse-core/lib/i18n/i18n.js | scriptResourceType": { "message": "Script" }, diff --git a/shared/localization/locales/en-XL.json b/shared/localization/locales/en-XL.json index a80fcec78ce3..13558d65ff1f 100644 --- a/shared/localization/locales/en-XL.json +++ b/shared/localization/locales/en-XL.json @@ -287,6 +287,9 @@ "lighthouse-core/audits/accessibility/axe-audit.js | failingElementsHeader": { "message": "F̂áîĺîńĝ Él̂ém̂én̂t́ŝ" }, + "lighthouse-core/audits/accessibility/axe-audit.js | relatedElementsHeader": { + "message": "R̂él̂át̂éd̂ Él̂ém̂én̂t́ŝ" + }, "lighthouse-core/audits/accessibility/button-name.js | description": { "message": "Ŵh́êń â b́ût́t̂ón̂ d́ôéŝń't̂ h́âv́ê án̂ áĉćêśŝíb̂ĺê ńâḿê, śĉŕêén̂ ŕêád̂ér̂ś âńn̂óûńĉé ît́ âś \"b̂út̂t́ôń\", m̂ák̂ín̂ǵ ît́ ûńûśâb́l̂é f̂ór̂ úŝér̂ś ŵh́ô ŕêĺŷ ón̂ śĉŕêén̂ ŕêád̂ér̂ś. [L̂éâŕn̂ ḿôŕê](https://web.dev/button-name/)." }, @@ -305,6 +308,9 @@ "lighthouse-core/audits/accessibility/bypass.js | title": { "message": "T̂h́ê ṕâǵê ćôńt̂áîńŝ á ĥéâd́îńĝ, śk̂íp̂ ĺîńk̂, ór̂ ĺâńd̂ḿâŕk̂ ŕêǵîón̂" }, + "lighthouse-core/audits/accessibility/color-contrast.js | backgroundElementHeader": { + "message": "B̂áĉḱĝŕôún̂d́ Êĺêḿêńt̂" + }, "lighthouse-core/audits/accessibility/color-contrast.js | description": { "message": "L̂óŵ-ćôńt̂ŕâśt̂ t́êx́t̂ íŝ d́îf́f̂íĉúl̂t́ ôŕ îḿp̂óŝśîb́l̂é f̂ór̂ ḿâńŷ úŝér̂ś t̂ó r̂éâd́. [L̂éâŕn̂ ḿôŕê](https://web.dev/color-contrast/)." }, @@ -356,6 +362,9 @@ "lighthouse-core/audits/accessibility/duplicate-id-aria.js | failureTitle": { "message": "ÂŔÎÁ ÎD́ŝ ár̂é n̂ót̂ ún̂íq̂úê" }, + "lighthouse-core/audits/accessibility/duplicate-id-aria.js | relatedElementsHeader": { + "message": "D̂úp̂ĺîćât́ê Él̂ém̂én̂t́ŝ" + }, "lighthouse-core/audits/accessibility/duplicate-id-aria.js | title": { "message": "ÂŔÎÁ ÎD́ŝ ár̂é ûńîq́ûé" }, @@ -1979,6 +1988,9 @@ "lighthouse-core/lib/i18n/i18n.js | mediaResourceType": { "message": "M̂éd̂íâ" }, + "lighthouse-core/lib/i18n/i18n.js | module": { + "message": "M̂ód̂úl̂é" + }, "lighthouse-core/lib/i18n/i18n.js | ms": { "message": "{timeInMs, number, milliseconds} m̂ś" }, @@ -1988,6 +2000,9 @@ "lighthouse-core/lib/i18n/i18n.js | otherResourceType": { "message": "Ôt́ĥér̂" }, + "lighthouse-core/lib/i18n/i18n.js | reasons": { + "message": "R̂éâśôńŝ" + }, "lighthouse-core/lib/i18n/i18n.js | scriptResourceType": { "message": "Ŝćr̂íp̂t́" }, diff --git a/types/lhr/audit-details.d.ts b/types/lhr/audit-details.d.ts index 2743a1f9876e..9ffa588a7f62 100644 --- a/types/lhr/audit-details.d.ts +++ b/types/lhr/audit-details.d.ts @@ -117,6 +117,7 @@ declare module Details { interface TableSubItems { type: 'subitems'; items: TableItem[]; + label?: string | IcuMessage; } /** From 56491ceb2193e57b5f750247fb3643cc4f89506f Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 15 Apr 2022 10:59:09 -0700 Subject: [PATCH 2/9] snapshots --- .../duplicated-javascript-test.js | 519 ++++++++++-------- .../byte-efficiency/legacy-javascript-test.js | 45 +- .../byte-efficiency/unused-javascript-test.js | 79 +-- 3 files changed, 359 insertions(+), 284 deletions(-) diff --git a/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js index 74623c66212b..aabc53d3fd3b 100644 --- a/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js @@ -62,262 +62,327 @@ describe('DuplicatedJavascript computed artifact', () => { }; const networkRecords = [{url: 'https://example.com', resourceType: 'Document'}]; const results = await DuplicatedJavascript.audit_(artifacts, networkRecords, context); - expect({items: results.items, wastedBytesByUrl: results.wastedBytesByUrl}) - .toMatchInlineSnapshot(` - Object { + expect({ items: results.items, wastedBytesByUrl: results.wastedBytesByUrl }). +toMatchInlineSnapshot(` +Object { + "items": Array [ + Object { + "source": "Control/assets/js/vendor/ng/select/select.js", + "subItems": Object { + "items": Array [ + Object { + "sourceTransferBytes": 16009, + "url": "https://example.com/coursehero-bundle-1.js", + }, + Object { + "sourceTransferBytes": 16009, + "url": "https://example.com/coursehero-bundle-2.js", + }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 16009, + }, + Object { + "source": "Control/assets/js/vendor/ng/select/angular-sanitize.js", + "subItems": Object { "items": Array [ Object { - "source": "Control/assets/js/vendor/ng/select/select.js", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 16009, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 16009, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 16009, + "sourceTransferBytes": 3015, + "url": "https://example.com/coursehero-bundle-1.js", + }, + Object { + "sourceTransferBytes": 3015, + "url": "https://example.com/coursehero-bundle-2.js", }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 3015, + }, + Object { + "source": "node_modules/@babel/runtime", + "subItems": Object { + "items": Array [ Object { - "source": "Control/assets/js/vendor/ng/select/angular-sanitize.js", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 3015, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 3015, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 3015, + "sourceTransferBytes": 502, + "url": "https://example.com/coursehero-bundle-1.js", }, Object { - "source": "node_modules/@babel/runtime", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 502, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 502, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 502, + "sourceTransferBytes": 502, + "url": "https://example.com/coursehero-bundle-2.js", }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 502, + }, + Object { + "source": "js/src/utils/service/amplitude-service.ts", + "subItems": Object { + "items": Array [ Object { - "source": "js/src/utils/service/amplitude-service.ts", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 445, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 437, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 437, + "sourceTransferBytes": 445, + "url": "https://example.com/coursehero-bundle-1.js", }, Object { - "source": "js/src/search/results/store/filter-actions.ts", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 315, - "url": "https://example.com/coursehero-bundle-2.js", - }, - Object { - "sourceTransferBytes": 312, - "url": "https://example.com/coursehero-bundle-1.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 312, + "sourceTransferBytes": 437, + "url": "https://example.com/coursehero-bundle-2.js", }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 437, + }, + Object { + "source": "js/src/search/results/store/filter-actions.ts", + "subItems": Object { + "items": Array [ Object { - "source": "js/src/search/results/store/item/resource-types.ts", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 258, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 256, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 256, + "sourceTransferBytes": 315, + "url": "https://example.com/coursehero-bundle-2.js", }, Object { - "source": "js/src/search/results/store/filter-store.ts", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 4197, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 4175, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 4175, + "sourceTransferBytes": 312, + "url": "https://example.com/coursehero-bundle-1.js", }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 312, + }, + Object { + "source": "js/src/search/results/store/item/resource-types.ts", + "subItems": Object { + "items": Array [ Object { - "source": "js/src/search/results/view/filter/autocomplete-list.tsx", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 377, - "url": "https://example.com/coursehero-bundle-2.js", - }, - Object { - "sourceTransferBytes": 374, - "url": "https://example.com/coursehero-bundle-1.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 374, + "sourceTransferBytes": 258, + "url": "https://example.com/coursehero-bundle-1.js", }, Object { - "source": "js/src/search/results/view/filter/autocomplete-filter.tsx", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 1262, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 1258, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 1258, + "sourceTransferBytes": 256, + "url": "https://example.com/coursehero-bundle-2.js", }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 256, + }, + Object { + "source": "js/src/search/results/store/filter-store.ts", + "subItems": Object { + "items": Array [ Object { - "source": "js/src/search/results/view/filter/autocomplete-filter-with-icon.tsx", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 890, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 889, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 889, + "sourceTransferBytes": 4197, + "url": "https://example.com/coursehero-bundle-1.js", }, Object { - "source": "js/src/common/component/school-search.tsx", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 1927, - "url": "https://example.com/coursehero-bundle-2.js", - }, - Object { - "sourceTransferBytes": 1754, - "url": "https://example.com/coursehero-bundle-1.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 1754, + "sourceTransferBytes": 4175, + "url": "https://example.com/coursehero-bundle-2.js", }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 4175, + }, + Object { + "source": "js/src/search/results/view/filter/autocomplete-list.tsx", + "subItems": Object { + "items": Array [ Object { - "source": "js/src/common/component/search/abstract-taxonomy-search.tsx", - "subItems": Object { - "items": Array [ - Object { - "sourceTransferBytes": 1024, - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "sourceTransferBytes": 1022, - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 1022, + "sourceTransferBytes": 377, + "url": "https://example.com/coursehero-bundle-2.js", }, Object { - "source": "Other", - "subItems": Object { - "items": Array [ - Object { - "url": "https://example.com/coursehero-bundle-1.js", - }, - Object { - "url": "https://example.com/coursehero-bundle-2.js", - }, - ], - "type": "subitems", - }, - "totalBytes": 0, - "url": "", - "wastedBytes": 542, + "sourceTransferBytes": 374, + "url": "https://example.com/coursehero-bundle-1.js", }, ], - "wastedBytesByUrl": Map { - "https://example.com/coursehero-bundle-2.js" => 27925, - "https://example.com/coursehero-bundle-1.js" => 2620, + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, }, - } - `); + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 374, + }, + Object { + "source": "js/src/search/results/view/filter/autocomplete-filter.tsx", + "subItems": Object { + "items": Array [ + Object { + "sourceTransferBytes": 1262, + "url": "https://example.com/coursehero-bundle-1.js", + }, + Object { + "sourceTransferBytes": 1258, + "url": "https://example.com/coursehero-bundle-2.js", + }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 1258, + }, + Object { + "source": "js/src/search/results/view/filter/autocomplete-filter-with-icon.tsx", + "subItems": Object { + "items": Array [ + Object { + "sourceTransferBytes": 890, + "url": "https://example.com/coursehero-bundle-1.js", + }, + Object { + "sourceTransferBytes": 889, + "url": "https://example.com/coursehero-bundle-2.js", + }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 889, + }, + Object { + "source": "js/src/common/component/school-search.tsx", + "subItems": Object { + "items": Array [ + Object { + "sourceTransferBytes": 1927, + "url": "https://example.com/coursehero-bundle-2.js", + }, + Object { + "sourceTransferBytes": 1754, + "url": "https://example.com/coursehero-bundle-1.js", + }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 1754, + }, + Object { + "source": "js/src/common/component/search/abstract-taxonomy-search.tsx", + "subItems": Object { + "items": Array [ + Object { + "sourceTransferBytes": 1024, + "url": "https://example.com/coursehero-bundle-1.js", + }, + Object { + "sourceTransferBytes": 1022, + "url": "https://example.com/coursehero-bundle-2.js", + }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 1022, + }, + Object { + "source": "Other", + "subItems": Object { + "items": Array [ + Object { + "url": "https://example.com/coursehero-bundle-1.js", + }, + Object { + "url": "https://example.com/coursehero-bundle-2.js", + }, + ], + "label": Object { + "formattedDefault": "Script", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "", + "wastedBytes": 542, + }, + ], + "wastedBytesByUrl": Map { + "https://example.com/coursehero-bundle-2.js" => 27925, + "https://example.com/coursehero-bundle-1.js" => 2620, + }, +} +`); }); it('.audit', async () => { diff --git a/lighthouse-core/test/audits/byte-efficiency/legacy-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/legacy-javascript-test.js index 0efdf1e02d6c..4ee8d6f10e7b 100644 --- a/lighthouse-core/test/audits/byte-efficiency/legacy-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/legacy-javascript-test.js @@ -98,28 +98,33 @@ describe('LegacyJavaScript audit', () => { ]); expect(result.items).toHaveLength(1); expect(result.items[0]).toMatchInlineSnapshot(` +Object { + "subItems": Object { + "items": Array [ Object { - "subItems": Object { - "items": Array [ - Object { - "location": Object { - "column": 0, - "line": 0, - "original": undefined, - "type": "source-location", - "url": "https://www.googletagmanager.com/a.js", - "urlProvider": "network", - }, - "signal": "String.prototype.repeat", - }, - ], - "type": "subitems", + "location": Object { + "column": 0, + "line": 0, + "original": undefined, + "type": "source-location", + "url": "https://www.googletagmanager.com/a.js", + "urlProvider": "network", }, - "totalBytes": 0, - "url": "https://www.googletagmanager.com/a.js", - "wastedBytes": 20104, - } - `); + "signal": "String.prototype.repeat", + }, + ], + "label": Object { + "formattedDefault": "Location", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | columnLocation", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 0, + "url": "https://www.googletagmanager.com/a.js", + "wastedBytes": 20104, +} +`); expect(result.wastedBytesByUrl).toMatchInlineSnapshot(`Map {}`); }); diff --git a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js index 8621b0b7d094..bcc6af3b6382 100644 --- a/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/unused-javascript-test.js @@ -135,44 +135,49 @@ describe('UnusedJavaScript audit', () => { const result = await UnusedJavaScript.audit_(artifacts, networkRecords, context); expect(result.items).toMatchInlineSnapshot(` - Array [ +Array [ + Object { + "subItems": Object { + "items": Array [ Object { - "subItems": Object { - "items": Array [ - Object { - "source": "(unmapped)", - "sourceBytes": 10062, - "sourceWastedBytes": 3760, - }, - Object { - "source": "…src/codecs/webp/encoder-meta.ts", - "sourceBytes": 660, - "sourceWastedBytes": 660, - }, - Object { - "source": "…src/lib/util.ts", - "sourceBytes": 4043, - "sourceWastedBytes": 500, - }, - Object { - "source": "…src/custom-els/RangeInput/index.ts", - "sourceBytes": 2138, - "sourceWastedBytes": 293, - }, - Object { - "source": "…node_modules/comlink/comlink.js", - "sourceBytes": 4117, - "sourceWastedBytes": 256, - }, - ], - "type": "subitems", - }, - "totalBytes": 83748, - "url": "https://squoosh.app/main-app.js", - "wastedBytes": 6961, - "wastedPercent": 8.312435814764395, + "source": "(unmapped)", + "sourceBytes": 10062, + "sourceWastedBytes": 3760, }, - ] - `); + Object { + "source": "…src/codecs/webp/encoder-meta.ts", + "sourceBytes": 660, + "sourceWastedBytes": 660, + }, + Object { + "source": "…src/lib/util.ts", + "sourceBytes": 4043, + "sourceWastedBytes": 500, + }, + Object { + "source": "…src/custom-els/RangeInput/index.ts", + "sourceBytes": 2138, + "sourceWastedBytes": 293, + }, + Object { + "source": "…node_modules/comlink/comlink.js", + "sourceBytes": 4117, + "sourceWastedBytes": 256, + }, + ], + "label": Object { + "formattedDefault": "Module", + "i18nId": "lighthouse-core/lib/i18n/i18n.js | module", + "values": undefined, + }, + "type": "subitems", + }, + "totalBytes": 83748, + "url": "https://squoosh.app/main-app.js", + "wastedBytes": 6961, + "wastedPercent": 8.312435814764395, + }, +] +`); }); }); From e40607708e051a21de0e373168aba97ca2de14e5 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 15 Apr 2022 11:28:58 -0700 Subject: [PATCH 3/9] fix td --- report/renderer/details-renderer.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/report/renderer/details-renderer.js b/report/renderer/details-renderer.js index 8367474e8784..223737745b55 100644 --- a/report/renderer/details-renderer.js +++ b/report/renderer/details-renderer.js @@ -432,6 +432,11 @@ export class DetailsRenderer { tdEl.append(this._renderText(item.subItems.label)); tdEl.append(this._dom.createElement('hr')); rowEl.classList.add('lh-sub-item-row'); + // Need a `td` per column so that background color of the table will + // span the width of the label row. + for (let i = 1; i < headings.length; i++) { + this._dom.createChildOf(rowEl, 'td'); + } fragment.append(rowEl); } From 7d25e8f61f69b4d2e6d8082de1c3a366cea6104e Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 15 Apr 2022 14:19:36 -0700 Subject: [PATCH 4/9] lint --- .../test/audits/byte-efficiency/duplicated-javascript-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js b/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js index aabc53d3fd3b..8fa830d51023 100644 --- a/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/duplicated-javascript-test.js @@ -62,7 +62,7 @@ describe('DuplicatedJavascript computed artifact', () => { }; const networkRecords = [{url: 'https://example.com', resourceType: 'Document'}]; const results = await DuplicatedJavascript.audit_(artifacts, networkRecords, context); - expect({ items: results.items, wastedBytesByUrl: results.wastedBytesByUrl }). + expect({items: results.items, wastedBytesByUrl: results.wastedBytesByUrl}). toMatchInlineSnapshot(` Object { "items": Array [ From f12f5508e18c9b54d6ec69292febce52b843a787 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 5 Dec 2022 14:02:24 -0800 Subject: [PATCH 5/9] table subItemsLabel --- core/audits/audit.js | 5 +- .../byte-efficiency/byte-efficiency-audit.js | 4 +- .../byte-efficiency/duplicated-javascript.js | 3 +- .../byte-efficiency/legacy-javascript.js | 2 +- .../byte-efficiency/unused-javascript.js | 2 +- core/audits/csp-xss.js | 2 +- core/audits/non-composited-animations.js | 2 +- core/audits/seo/hreflang.js | 2 +- core/audits/third-party-facades.js | 6 +- .../reports/sample-flow-result.json | 244 ++++++++++++++---- core/test/results/sample_v2.json | 123 ++++++--- report/renderer/details-renderer.js | 13 +- types/lhr/audit-details.d.ts | 2 + 13 files changed, 302 insertions(+), 108 deletions(-) diff --git a/core/audits/audit.js b/core/audits/audit.js index a4fdafceee98..5a92e433a22b 100644 --- a/core/audits/audit.js +++ b/core/audits/audit.js @@ -213,17 +213,20 @@ class Audit { /** * @param {LH.Audit.Details.Opportunity['headings']} headings + * @param {LH.Audit.Details.Table['subItemsLabel']} subItemsLabel * @param {LH.Audit.Details.Opportunity['items']} items * @param {number} overallSavingsMs * @param {number=} overallSavingsBytes * @return {LH.Audit.Details.Opportunity} */ - static makeOpportunityDetails(headings, items, overallSavingsMs, overallSavingsBytes) { + static makeOpportunityDetails( + headings, subItemsLabel, items, overallSavingsMs, overallSavingsBytes) { Audit.assertHeadingKeysExist(headings, items); return { type: 'opportunity', headings: items.length === 0 ? [] : headings, + subItemsLabel, items, overallSavingsMs, overallSavingsBytes, diff --git a/core/audits/byte-efficiency/byte-efficiency-audit.js b/core/audits/byte-efficiency/byte-efficiency-audit.js index 91df17ab5ae7..9f46f92af523 100644 --- a/core/audits/byte-efficiency/byte-efficiency-audit.js +++ b/core/audits/byte-efficiency/byte-efficiency-audit.js @@ -26,6 +26,7 @@ const WASTED_MS_FOR_SCORE_OF_ZERO = 5000; * @property {Array} items * @property {Map=} wastedBytesByUrl * @property {LH.Audit.Details.Opportunity['headings']} headings + * @property {LH.Audit.Details.Table['subItemsLabel']} subItemsLabel * @property {LH.IcuMessage} [displayValue] * @property {LH.IcuMessage} [explanation] * @property {Array} [warnings] @@ -228,7 +229,8 @@ class ByteEfficiencyAudit extends Audit { displayValue = str_(i18n.UIStrings.displayValueByteSavings, {wastedBytes}); } - const details = Audit.makeOpportunityDetails(result.headings, results, wastedMs, wastedBytes); + const details = Audit.makeOpportunityDetails( + result.headings, result.subItemsLabel, results, wastedMs, wastedBytes); return { explanation: result.explanation, diff --git a/core/audits/byte-efficiency/duplicated-javascript.js b/core/audits/byte-efficiency/duplicated-javascript.js index a5d7bd19c22a..0249fd76f9f7 100644 --- a/core/audits/byte-efficiency/duplicated-javascript.js +++ b/core/audits/byte-efficiency/duplicated-javascript.js @@ -207,7 +207,6 @@ class DuplicatedJavascript extends ByteEfficiencyAudit { totalBytes: 0, subItems: { type: 'subitems', - label: str_(i18n.UIStrings.scriptResourceType), items: subItems, }, }); @@ -221,7 +220,6 @@ class DuplicatedJavascript extends ByteEfficiencyAudit { totalBytes: 0, subItems: { type: 'subitems', - label: str_(i18n.UIStrings.scriptResourceType), items: Array.from(overflowUrls).map(url => ({url})), }, }); @@ -240,6 +238,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit { return { items, headings, + subItemsLabel: str_(i18n.UIStrings.scriptResourceType), wastedBytesByUrl, }; } diff --git a/core/audits/byte-efficiency/legacy-javascript.js b/core/audits/byte-efficiency/legacy-javascript.js index b67963c0041c..bae161d29c15 100644 --- a/core/audits/byte-efficiency/legacy-javascript.js +++ b/core/audits/byte-efficiency/legacy-javascript.js @@ -426,7 +426,6 @@ class LegacyJavascript extends ByteEfficiencyAudit { url: script.url, wastedBytes, subItems: { - label: str_(i18n.UIStrings.columnLocation), type: 'subitems', items: [], }, @@ -468,6 +467,7 @@ class LegacyJavascript extends ByteEfficiencyAudit { return { items, headings, + subItemsLabel: str_(i18n.UIStrings.columnLocation), wastedBytesByUrl, }; } diff --git a/core/audits/byte-efficiency/unused-javascript.js b/core/audits/byte-efficiency/unused-javascript.js index 38e013365d81..91674610896a 100644 --- a/core/audits/byte-efficiency/unused-javascript.js +++ b/core/audits/byte-efficiency/unused-javascript.js @@ -135,7 +135,6 @@ class UnusedJavaScript extends ByteEfficiencyAudit { const commonSourcePrefix = commonPrefix(bundle.map.sourceURLs()); item.subItems = { type: 'subitems', - label: str_(i18n.UIStrings.module), items: topUnusedSourceSizes.map(({source, unused, total}) => { return { source: trimCommonPrefix(source, commonSourcePrefix), @@ -156,6 +155,7 @@ class UnusedJavaScript extends ByteEfficiencyAudit { {key: 'wastedBytes', valueType: 'bytes', subItemsHeading: {key: 'sourceWastedBytes'}, label: str_(i18n.UIStrings.columnWastedBytes)}, /* eslint-enable max-len */ ], + subItemsLabel: str_(i18n.UIStrings.module), }; } } diff --git a/core/audits/csp-xss.js b/core/audits/csp-xss.js index 410b71c3200d..47a8c4cc5fee 100644 --- a/core/audits/csp-xss.js +++ b/core/audits/csp-xss.js @@ -106,7 +106,6 @@ class CspXss extends Audit { }, subItems: { type: 'subitems', - label: str_(i18n.UIStrings.reasons), items, }, }); @@ -174,6 +173,7 @@ class CspXss extends Audit { /* eslint-enable max-len */ ]; const details = Audit.makeTableDetails(headings, results); + details.subItemsLabel = str_(i18n.UIStrings.reasons); return { score, diff --git a/core/audits/non-composited-animations.js b/core/audits/non-composited-animations.js index 5f16acf3a115..c90c448dde2b 100644 --- a/core/audits/non-composited-animations.js +++ b/core/audits/non-composited-animations.js @@ -165,7 +165,6 @@ class NonCompositedAnimations extends Audit { node: Audit.makeNodeItem(element.node), subItems: { type: 'subitems', - label: str_(i18n.UIStrings.reasons), items: allFailureReasons, }, }); @@ -187,6 +186,7 @@ class NonCompositedAnimations extends Audit { } const details = Audit.makeTableDetails(headings, results); + details.subItemsLabel = str_(i18n.UIStrings.reasons); let displayValue; if (results.length > 0) { diff --git a/core/audits/seo/hreflang.js b/core/audits/seo/hreflang.js index 1c3f835e9727..0be38c7ecd6a 100644 --- a/core/audits/seo/hreflang.js +++ b/core/audits/seo/hreflang.js @@ -119,7 +119,6 @@ class Hreflang extends Audit { source, subItems: { type: 'subitems', - label: str_(i18n.UIStrings.reasons), items: reasons.map(reason => ({reason})), }, }); @@ -137,6 +136,7 @@ class Hreflang extends Audit { }]; const details = Audit.makeTableDetails(headings, invalidHreflangs); + details.subItemsLabel = str_(i18n.UIStrings.reasons); return { score: Number(invalidHreflangs.length === 0), diff --git a/core/audits/third-party-facades.js b/core/audits/third-party-facades.js index ca9a1ee678d6..1fe59f655a2b 100644 --- a/core/audits/third-party-facades.js +++ b/core/audits/third-party-facades.js @@ -189,7 +189,6 @@ class ThirdPartyFacades extends Audit { blockingTime: entitySummary.blockingTime, subItems: { type: 'subitems', - label: str_(i18n.UIStrings.scriptResourceType), items, }, }); @@ -216,7 +215,10 @@ class ThirdPartyFacades extends Audit { displayValue: str_(UIStrings.displayValue, { itemCount: results.length, }), - details: Audit.makeTableDetails(headings, results), + details: { + ...Audit.makeTableDetails(headings, results), + subItemsLabel: str_(i18n.UIStrings.scriptResourceType), + }, }; } } diff --git a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json index d0e2fa7c40ef..5ec15ee09d14 100644 --- a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json +++ b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json @@ -237,13 +237,13 @@ "label": "Time Spent" } ], - "items": [ + "subItemsLabel": [ { "url": "https://www.mikescerealshack.co/", "responseTime": 236.03100000000006 } ], - "overallSavingsMs": 136.03100000000006 + "items": 136.03100000000006 } }, "interactive": { @@ -353,9 +353,20 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Time Spent" + } + ], + "subItemsLabel": [], + "items": 0 } }, "installable-manifest": { @@ -622,8 +633,8 @@ "details": { "type": "opportunity", "headings": [], - "items": [], - "overallSavingsMs": 0 + "subItemsLabel": [], + "items": 0 } }, "uses-rel-preconnect": { @@ -638,9 +649,20 @@ "warnings": [], "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Potential Savings" + } + ], + "subItemsLabel": [], + "items": 0 } }, "font-display": { @@ -1730,7 +1752,8 @@ "details": { "type": "table", "headings": [], - "items": [] + "items": [], + "subItemsLabel": "Reasons" } }, "unsized-images": { @@ -1816,7 +1839,7 @@ "label": "Potential Savings" } ], - "items": [ + "subItemsLabel": [ { "node": { "type": "node", @@ -1838,7 +1861,7 @@ "wastedMs": 0 } ], - "overallSavingsMs": 0, + "items": 0, "debugData": { "type": "debugdata", "initiatorPath": [ @@ -1894,7 +1917,8 @@ "severity": "High", "description": "No CSP found in enforcement mode" } - ] + ], + "subItemsLabel": "Reasons" } }, "full-page-screenshot": { @@ -3058,9 +3082,25 @@ "numericUnit": "millisecond", "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Potential Savings" + } + ], + "subItemsLabel": [], + "items": 0 } }, "unminified-css": { @@ -3127,6 +3167,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Module", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -3231,6 +3272,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -3271,12 +3313,12 @@ "label": "Potential Savings" } ], + "subItemsLabel": "Location", "items": [ { "url": "https://www.mikescerealshack.co/_next/static/chunks/commons.49455e4fa8cc3f51203f.js", "wastedBytes": 57, "subItems": { - "label": "Location", "type": "subitems", "items": [ { @@ -3660,7 +3702,8 @@ "details": { "type": "table", "headings": [], - "items": [] + "items": [], + "subItemsLabel": "Reasons" } }, "plugins": { @@ -6253,17 +6296,21 @@ ], "core/lib/i18n/i18n.js | columnURL": [ "audits[server-response-time].details.headings[0].label", + "audits.redirects.details.headings[0].label", "audits[bootup-time].details.headings[0].label", + "audits[uses-rel-preconnect].details.headings[0].label", "audits[network-rtt].details.headings[0].label", "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", "audits[unsized-images].details.headings[1].label", "audits[preload-lcp-image].details.headings[1].label", "audits[total-byte-weight].details.headings[0].label", + "audits[render-blocking-resources].details.headings[0].label", "audits[legacy-javascript].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", + "audits.redirects.details.headings[1].label", "audits[mainthread-work-breakdown].details.headings[1].label", "audits[network-rtt].details.headings[1].label", "audits[network-server-latency].details.headings[1].label" @@ -6419,6 +6466,12 @@ "core/audits/uses-rel-preconnect.js | description": [ "audits[uses-rel-preconnect].description" ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[uses-rel-preconnect].details.headings[1].label", + "audits[preload-lcp-image].details.headings[2].label", + "audits[render-blocking-resources].details.headings[2].label", + "audits[legacy-javascript].details.headings[2].label" + ], "core/audits/font-display.js | title": [ "audits[font-display].title" ], @@ -6473,13 +6526,15 @@ "core/lib/i18n/i18n.js | columnTransferSize": [ "audits[resource-summary].details.headings[2].label", "audits[third-party-summary].details.headings[1].label", - "audits[total-byte-weight].details.headings[1].label" + "audits[total-byte-weight].details.headings[1].label", + "audits[render-blocking-resources].details.headings[1].label" ], "core/lib/i18n/i18n.js | totalResourceType": [ "audits[resource-summary].details.items[0].label" ], "core/lib/i18n/i18n.js | scriptResourceType": [ - "audits[resource-summary].details.items[1].label" + "audits[resource-summary].details.items[1].label", + "audits[duplicated-javascript].details.subItemsLabel" ], "core/lib/i18n/i18n.js | fontResourceType": [ "audits[resource-summary].details.items[2].label" @@ -6595,6 +6650,11 @@ "core/audits/non-composited-animations.js | description": [ "audits[non-composited-animations].description" ], + "core/lib/i18n/i18n.js | reasons": [ + "audits[non-composited-animations].details.subItemsLabel", + "audits[csp-xss].details.subItemsLabel", + "audits.hreflang.details.subItemsLabel" + ], "core/audits/unsized-images.js | failureTitle": [ "audits[unsized-images].title" ], @@ -6613,10 +6673,6 @@ "core/audits/preload-lcp-image.js | description": [ "audits[preload-lcp-image].description" ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[preload-lcp-image].details.headings[2].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/csp-xss.js | title": [ "audits[csp-xss].title" ], @@ -6984,6 +7040,9 @@ "core/audits/byte-efficiency/unused-javascript.js | description": [ "audits[unused-javascript].description" ], + "core/lib/i18n/i18n.js | module": [ + "audits[unused-javascript].details.subItemsLabel" + ], "core/audits/byte-efficiency/modern-image-formats.js | title": [ "audits[modern-image-formats].title" ], @@ -7035,7 +7094,7 @@ } ], "core/lib/i18n/i18n.js | columnLocation": [ - "audits[legacy-javascript].details.items[0].subItems.label" + "audits[legacy-javascript].details.subItemsLabel" ], "core/audits/dobetterweb/doctype.js | title": [ "audits.doctype.title" @@ -8587,7 +8646,8 @@ "details": { "type": "table", "headings": [], - "items": [] + "items": [], + "subItemsLabel": "Reasons" } }, "unsized-images": { @@ -8942,6 +9002,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Module", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -9046,6 +9107,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -9063,6 +9125,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Location", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -10256,7 +10319,8 @@ "audits[resource-summary].details.items[2].label" ], "core/lib/i18n/i18n.js | scriptResourceType": [ - "audits[resource-summary].details.items[3].label" + "audits[resource-summary].details.items[3].label", + "audits[duplicated-javascript].details.subItemsLabel" ], "core/lib/i18n/i18n.js | stylesheetResourceType": [ "audits[resource-summary].details.items[4].label" @@ -10339,6 +10403,9 @@ "core/audits/non-composited-animations.js | description": [ "audits[non-composited-animations].description" ], + "core/lib/i18n/i18n.js | reasons": [ + "audits[non-composited-animations].details.subItemsLabel" + ], "core/audits/unsized-images.js | failureTitle": [ "audits[unsized-images].title" ], @@ -10406,6 +10473,9 @@ "core/audits/byte-efficiency/unused-javascript.js | description": [ "audits[unused-javascript].description" ], + "core/lib/i18n/i18n.js | module": [ + "audits[unused-javascript].details.subItemsLabel" + ], "core/audits/byte-efficiency/modern-image-formats.js | title": [ "audits[modern-image-formats].title" ], @@ -10448,6 +10518,9 @@ "core/audits/byte-efficiency/legacy-javascript.js | description": [ "audits[legacy-javascript].description" ], + "core/lib/i18n/i18n.js | columnLocation": [ + "audits[legacy-javascript].details.subItemsLabel" + ], "core/audits/dobetterweb/inspector-issues.js | title": [ "audits[inspector-issues].title" ], @@ -14418,13 +14491,13 @@ "label": "Time Spent" } ], - "items": [ + "subItemsLabel": [ { "url": "https://www.mikescerealshack.co/corrections", "responseTime": 115.003 } ], - "overallSavingsMs": 15.003 + "items": 15.003 } }, "interactive": { @@ -14534,9 +14607,20 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Time Spent" + } + ], + "subItemsLabel": [], + "items": 0 } }, "installable-manifest": { @@ -14808,8 +14892,8 @@ "details": { "type": "opportunity", "headings": [], - "items": [], - "overallSavingsMs": 0 + "subItemsLabel": [], + "items": 0 } }, "uses-rel-preconnect": { @@ -14824,9 +14908,20 @@ "warnings": [], "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Potential Savings" + } + ], + "subItemsLabel": [], + "items": 0 } }, "font-display": { @@ -15740,7 +15835,8 @@ "details": { "type": "table", "headings": [], - "items": [] + "items": [], + "subItemsLabel": "Reasons" } }, "unsized-images": { @@ -15845,7 +15941,7 @@ "label": "Potential Savings" } ], - "items": [ + "subItemsLabel": [ { "node": { "type": "node", @@ -15867,7 +15963,7 @@ "wastedMs": 0 } ], - "overallSavingsMs": 0, + "items": 0, "debugData": { "type": "debugdata", "initiatorPath": [ @@ -15923,7 +16019,8 @@ "severity": "High", "description": "No CSP found in enforcement mode" } - ] + ], + "subItemsLabel": "Reasons" } }, "full-page-screenshot": { @@ -17088,9 +17185,25 @@ "numericUnit": "millisecond", "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Potential Savings" + } + ], + "subItemsLabel": [], + "items": 0 } }, "unminified-css": { @@ -17157,6 +17270,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Module", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -17351,6 +17465,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -17391,12 +17506,12 @@ "label": "Potential Savings" } ], + "subItemsLabel": "Location", "items": [ { "url": "https://www.mikescerealshack.co/_next/static/chunks/commons.49455e4fa8cc3f51203f.js", "wastedBytes": 0, "subItems": { - "label": "Location", "type": "subitems", "items": [ { @@ -17837,7 +17952,8 @@ "details": { "type": "table", "headings": [], - "items": [] + "items": [], + "subItemsLabel": "Reasons" } }, "plugins": { @@ -20412,19 +20528,23 @@ ], "core/lib/i18n/i18n.js | columnURL": [ "audits[server-response-time].details.headings[0].label", + "audits.redirects.details.headings[0].label", "audits[bootup-time].details.headings[0].label", + "audits[uses-rel-preconnect].details.headings[0].label", "audits[network-rtt].details.headings[0].label", "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", "audits[unsized-images].details.headings[1].label", "audits[preload-lcp-image].details.headings[1].label", "audits[total-byte-weight].details.headings[0].label", + "audits[render-blocking-resources].details.headings[0].label", "audits[modern-image-formats].details.headings[1].label", "audits[uses-responsive-images].details.headings[1].label", "audits[legacy-javascript].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", + "audits.redirects.details.headings[1].label", "audits[mainthread-work-breakdown].details.headings[1].label", "audits[network-rtt].details.headings[1].label", "audits[network-server-latency].details.headings[1].label" @@ -20580,6 +20700,14 @@ "core/audits/uses-rel-preconnect.js | description": [ "audits[uses-rel-preconnect].description" ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[uses-rel-preconnect].details.headings[1].label", + "audits[preload-lcp-image].details.headings[2].label", + "audits[render-blocking-resources].details.headings[2].label", + "audits[modern-image-formats].details.headings[3].label", + "audits[uses-responsive-images].details.headings[3].label", + "audits[legacy-javascript].details.headings[2].label" + ], "core/audits/font-display.js | title": [ "audits[font-display].title" ], @@ -20634,7 +20762,8 @@ "core/lib/i18n/i18n.js | columnTransferSize": [ "audits[resource-summary].details.headings[2].label", "audits[third-party-summary].details.headings[1].label", - "audits[total-byte-weight].details.headings[1].label" + "audits[total-byte-weight].details.headings[1].label", + "audits[render-blocking-resources].details.headings[1].label" ], "core/lib/i18n/i18n.js | totalResourceType": [ "audits[resource-summary].details.items[0].label" @@ -20646,7 +20775,8 @@ "audits[resource-summary].details.items[2].label" ], "core/lib/i18n/i18n.js | scriptResourceType": [ - "audits[resource-summary].details.items[3].label" + "audits[resource-summary].details.items[3].label", + "audits[duplicated-javascript].details.subItemsLabel" ], "core/lib/i18n/i18n.js | fontResourceType": [ "audits[resource-summary].details.items[4].label" @@ -20746,6 +20876,11 @@ "core/audits/non-composited-animations.js | description": [ "audits[non-composited-animations].description" ], + "core/lib/i18n/i18n.js | reasons": [ + "audits[non-composited-animations].details.subItemsLabel", + "audits[csp-xss].details.subItemsLabel", + "audits.hreflang.details.subItemsLabel" + ], "core/audits/unsized-images.js | failureTitle": [ "audits[unsized-images].title" ], @@ -20764,12 +20899,6 @@ "core/audits/preload-lcp-image.js | description": [ "audits[preload-lcp-image].description" ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[preload-lcp-image].details.headings[2].label", - "audits[modern-image-formats].details.headings[3].label", - "audits[uses-responsive-images].details.headings[3].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/csp-xss.js | title": [ "audits[csp-xss].title" ], @@ -21140,6 +21269,9 @@ "core/audits/byte-efficiency/unused-javascript.js | description": [ "audits[unused-javascript].description" ], + "core/lib/i18n/i18n.js | module": [ + "audits[unused-javascript].details.subItemsLabel" + ], "core/audits/byte-efficiency/modern-image-formats.js | title": [ "audits[modern-image-formats].title" ], @@ -21201,7 +21333,7 @@ "audits[legacy-javascript].description" ], "core/lib/i18n/i18n.js | columnLocation": [ - "audits[legacy-javascript].details.items[0].subItems.label" + "audits[legacy-javascript].details.subItemsLabel" ], "core/audits/dobetterweb/doctype.js | title": [ "audits.doctype.title" diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index 6ee3b63c2a6d..a446dd8c9c50 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -340,13 +340,13 @@ "label": "Time Spent" } ], - "items": [ + "subItemsLabel": [ { "url": "http://localhost:10200/dobetterweb/dbw_tester.html", "responseTime": 568.468 } ], - "overallSavingsMs": 468.46799999999996 + "items": 468.46799999999996 } }, "interactive": { @@ -775,9 +775,20 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Time Spent" + } + ], + "subItemsLabel": [], + "items": 0 } }, "installable-manifest": { @@ -1166,8 +1177,8 @@ "details": { "type": "opportunity", "headings": [], - "items": [], - "overallSavingsMs": 0 + "subItemsLabel": [], + "items": 0 } }, "uses-rel-preconnect": { @@ -1182,9 +1193,20 @@ "warnings": [], "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Potential Savings" + } + ], + "subItemsLabel": [], + "items": 0 } }, "font-display": { @@ -2496,7 +2518,6 @@ }, "subItems": { "type": "subitems", - "label": "Reasons", "items": [ { "failureReason": "Unsupported CSS Properties: margin-left, height", @@ -2505,7 +2526,8 @@ ] } } - ] + ], + "subItemsLabel": "Reasons" } }, "unsized-images": { @@ -2628,7 +2650,8 @@ "severity": "High", "description": "No CSP found in enforcement mode" } - ] + ], + "subItemsLabel": "Reasons" } }, "full-page-screenshot": { @@ -4565,7 +4588,7 @@ "label": "Potential Savings" } ], - "items": [ + "subItemsLabel": [ { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", "totalBytes": 903, @@ -4597,7 +4620,7 @@ "wastedMs": 879 } ], - "overallSavingsMs": 0 + "items": 0 } }, "unminified-css": { @@ -4713,6 +4736,7 @@ "label": "Potential Savings" } ], + "subItemsLabel": "Module", "items": [ { "url": "http://localhost:10200/dobetterweb/third_party/aggressive-promise-polyfill.js", @@ -5133,6 +5157,7 @@ "details": { "type": "opportunity", "headings": [], + "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, "overallSavingsBytes": 0 @@ -5173,12 +5198,12 @@ "label": "Potential Savings" } ], + "subItemsLabel": "Location", "items": [ { "url": "http://localhost:10200/dobetterweb/third_party/aggressive-promise-polyfill.js", "wastedBytes": 21189, "subItems": { - "label": "Location", "type": "subitems", "items": [ { @@ -5548,9 +5573,20 @@ "numericUnit": "millisecond", "details": { "type": "opportunity", - "headings": [], - "items": [], - "overallSavingsMs": 0 + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + } + ], + "subItemsLabel": [], + "items": 0 } }, "uses-passive-event-listeners": { @@ -5805,7 +5841,8 @@ "details": { "type": "table", "headings": [], - "items": [] + "items": [], + "subItemsLabel": "Reasons" } }, "plugins": { @@ -8546,9 +8583,11 @@ ], "core/lib/i18n/i18n.js | columnURL": [ "audits[server-response-time].details.headings[0].label", + "audits.redirects.details.headings[0].label", "audits[image-aspect-ratio].details.headings[1].label", "audits[image-size-responsive].details.headings[1].label", "audits[bootup-time].details.headings[0].label", + "audits[uses-rel-preconnect].details.headings[0].label", "audits[network-rtt].details.headings[0].label", "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", @@ -8562,10 +8601,12 @@ "audits[uses-text-compression].details.headings[0].label", "audits[uses-responsive-images].details.headings[1].label", "audits[efficient-animated-content].details.headings[0].label", - "audits[legacy-javascript].details.headings[0].label" + "audits[legacy-javascript].details.headings[0].label", + "audits[uses-http2].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", + "audits.redirects.details.headings[1].label", "audits[mainthread-work-breakdown].details.headings[1].label", "audits[network-rtt].details.headings[1].label", "audits[network-server-latency].details.headings[1].label" @@ -8757,6 +8798,17 @@ "core/audits/uses-rel-preconnect.js | description": [ "audits[uses-rel-preconnect].description" ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[uses-rel-preconnect].details.headings[1].label", + "audits[render-blocking-resources].details.headings[2].label", + "audits[unminified-javascript].details.headings[2].label", + "audits[unused-javascript].details.headings[2].label", + "audits[modern-image-formats].details.headings[3].label", + "audits[uses-text-compression].details.headings[2].label", + "audits[uses-responsive-images].details.headings[3].label", + "audits[efficient-animated-content].details.headings[2].label", + "audits[legacy-javascript].details.headings[2].label" + ], "core/audits/font-display.js | title": [ "audits[font-display].title" ], @@ -8840,7 +8892,8 @@ ], "core/lib/i18n/i18n.js | scriptResourceType": [ "audits[performance-budget].details.items[2].label", - "audits[resource-summary].details.items[2].label" + "audits[resource-summary].details.items[2].label", + "audits[duplicated-javascript].details.subItemsLabel" ], "core/lib/i18n/i18n.js | otherResourceType": [ "audits[performance-budget].details.items[3].label", @@ -8994,9 +9047,6 @@ "path": "audits[non-composited-animations].displayValue" } ], - "core/lib/i18n/i18n.js | reasons": [ - "audits[non-composited-animations].details.items[0].subItems.label" - ], "core/audits/non-composited-animations.js | unsupportedCSSProperty": [ { "values": { @@ -9006,6 +9056,11 @@ "path": "audits[non-composited-animations].details.items[0].subItems.items[0].failureReason" } ], + "core/lib/i18n/i18n.js | reasons": [ + "audits[non-composited-animations].details.subItemsLabel", + "audits[csp-xss].details.subItemsLabel", + "audits.hreflang.details.subItemsLabel" + ], "core/audits/unsized-images.js | failureTitle": [ "audits[unsized-images].title" ], @@ -9383,16 +9438,6 @@ "path": "audits[render-blocking-resources].displayValue" } ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[render-blocking-resources].details.headings[2].label", - "audits[unminified-javascript].details.headings[2].label", - "audits[unused-javascript].details.headings[2].label", - "audits[modern-image-formats].details.headings[3].label", - "audits[uses-text-compression].details.headings[2].label", - "audits[uses-responsive-images].details.headings[3].label", - "audits[efficient-animated-content].details.headings[2].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/byte-efficiency/unminified-css.js | title": [ "audits[unminified-css].title" ], @@ -9461,6 +9506,9 @@ "core/audits/byte-efficiency/unused-javascript.js | description": [ "audits[unused-javascript].description" ], + "core/lib/i18n/i18n.js | module": [ + "audits[unused-javascript].details.subItemsLabel" + ], "core/audits/byte-efficiency/modern-image-formats.js | title": [ "audits[modern-image-formats].title" ], @@ -9509,7 +9557,7 @@ "audits[legacy-javascript].description" ], "core/lib/i18n/i18n.js | columnLocation": [ - "audits[legacy-javascript].details.items[0].subItems.label" + "audits[legacy-javascript].details.subItemsLabel" ], "core/audits/dobetterweb/doctype.js | title": [ "audits.doctype.title" @@ -9597,6 +9645,9 @@ "core/audits/dobetterweb/uses-http2.js | description": [ "audits[uses-http2].description" ], + "core/audits/dobetterweb/uses-http2.js | columnProtocol": [ + "audits[uses-http2].details.headings[1].label" + ], "core/audits/dobetterweb/uses-passive-event-listeners.js | failureTitle": [ "audits[uses-passive-event-listeners].title" ], diff --git a/report/renderer/details-renderer.js b/report/renderer/details-renderer.js index d0af27043457..1c8e6feb91f6 100644 --- a/report/renderer/details-renderer.js +++ b/report/renderer/details-renderer.js @@ -356,8 +356,9 @@ export class DetailsRenderer { * expand into multiple rows, if there is a subItemsHeading. * @param {TableItem} item * @param {LH.Audit.Details.TableColumnHeading[]} headings + * @param {string | undefined} defaultSubItemsLabel */ - _renderTableRowsFromItem(item, headings) { + _renderTableRowsFromItem(item, headings, defaultSubItemsLabel) { const fragment = this._dom.createFragment(); fragment.append(this._renderTableRow(item, headings)); @@ -366,10 +367,11 @@ export class DetailsRenderer { const subItemsHeadings = headings.map(this._getDerivedSubItemsHeading); if (!subItemsHeadings.some(Boolean) || !item.subItems.items.length) return fragment; - if (item.subItems.label) { + const label = item.subItems.label ?? defaultSubItemsLabel; + if (label) { const rowEl = this._dom.createElement('tr'); const tdEl = this._dom.createChildOf(rowEl, 'td'); - tdEl.append(this._renderText(item.subItems.label)); + tdEl.append(this._renderText(label)); tdEl.append(this._dom.createElement('hr')); rowEl.classList.add('lh-sub-item-row'); // Need a `td` per column so that background color of the table will @@ -390,7 +392,7 @@ export class DetailsRenderer { } /** - * @param {{headings: TableColumnHeading[], items: TableItem[]}} details + * @param {{headings: TableColumnHeading[], items: TableItem[], subItemsLabel?: string}} details * @return {Element} */ _renderTable(details) { @@ -411,7 +413,8 @@ export class DetailsRenderer { const tbodyElem = this._dom.createChildOf(tableElem, 'tbody'); let even = true; for (const item of details.items) { - const rowsFragment = this._renderTableRowsFromItem(item, details.headings); + const rowsFragment = + this._renderTableRowsFromItem(item, details.headings, details.subItemsLabel); for (const rowEl of this._dom.findAll('tr', rowsFragment)) { // For zebra styling. rowEl.classList.add(even ? 'lh-row--even' : 'lh-row--odd'); diff --git a/types/lhr/audit-details.d.ts b/types/lhr/audit-details.d.ts index a0dd2f3c6b4a..3c2b218d8557 100644 --- a/types/lhr/audit-details.d.ts +++ b/types/lhr/audit-details.d.ts @@ -68,6 +68,7 @@ declare module Details { overallSavingsMs: number; overallSavingsBytes?: number; headings: TableColumnHeading[]; + subItemsLabel?: string | IcuMessage; items: OpportunityItem[]; debugData?: DebugData; } @@ -107,6 +108,7 @@ declare module Details { interface Table { type: 'table'; headings: TableColumnHeading[]; + subItemsLabel?: string | IcuMessage; items: TableItem[]; summary?: { wastedMs?: number; From fea87457823a1071621431816d5db8b4a32c1b9c Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 5 Dec 2022 15:36:47 -0800 Subject: [PATCH 6/9] fix opps --- core/audits/audit.js | 5 +- .../byte-efficiency/byte-efficiency-audit.js | 6 +- core/audits/seo/hreflang.js | 2 +- .../reports/sample-flow-result.json | 210 +++++------------- core/test/results/sample_v2.json | 105 +++------ 5 files changed, 97 insertions(+), 231 deletions(-) diff --git a/core/audits/audit.js b/core/audits/audit.js index 5a92e433a22b..a4fdafceee98 100644 --- a/core/audits/audit.js +++ b/core/audits/audit.js @@ -213,20 +213,17 @@ class Audit { /** * @param {LH.Audit.Details.Opportunity['headings']} headings - * @param {LH.Audit.Details.Table['subItemsLabel']} subItemsLabel * @param {LH.Audit.Details.Opportunity['items']} items * @param {number} overallSavingsMs * @param {number=} overallSavingsBytes * @return {LH.Audit.Details.Opportunity} */ - static makeOpportunityDetails( - headings, subItemsLabel, items, overallSavingsMs, overallSavingsBytes) { + static makeOpportunityDetails(headings, items, overallSavingsMs, overallSavingsBytes) { Audit.assertHeadingKeysExist(headings, items); return { type: 'opportunity', headings: items.length === 0 ? [] : headings, - subItemsLabel, items, overallSavingsMs, overallSavingsBytes, diff --git a/core/audits/byte-efficiency/byte-efficiency-audit.js b/core/audits/byte-efficiency/byte-efficiency-audit.js index 9f46f92af523..a2d9b83542d8 100644 --- a/core/audits/byte-efficiency/byte-efficiency-audit.js +++ b/core/audits/byte-efficiency/byte-efficiency-audit.js @@ -26,7 +26,7 @@ const WASTED_MS_FOR_SCORE_OF_ZERO = 5000; * @property {Array} items * @property {Map=} wastedBytesByUrl * @property {LH.Audit.Details.Opportunity['headings']} headings - * @property {LH.Audit.Details.Table['subItemsLabel']} subItemsLabel + * @property {LH.Audit.Details.Table['subItemsLabel']=} subItemsLabel * @property {LH.IcuMessage} [displayValue] * @property {LH.IcuMessage} [explanation] * @property {Array} [warnings] @@ -229,8 +229,8 @@ class ByteEfficiencyAudit extends Audit { displayValue = str_(i18n.UIStrings.displayValueByteSavings, {wastedBytes}); } - const details = Audit.makeOpportunityDetails( - result.headings, result.subItemsLabel, results, wastedMs, wastedBytes); + const details = Audit.makeOpportunityDetails(result.headings, results, wastedMs, wastedBytes); + details.subItemsLabel = result.subItemsLabel; return { explanation: result.explanation, diff --git a/core/audits/seo/hreflang.js b/core/audits/seo/hreflang.js index 0be38c7ecd6a..e49c50ecd108 100644 --- a/core/audits/seo/hreflang.js +++ b/core/audits/seo/hreflang.js @@ -5,7 +5,7 @@ */ /** @typedef {string|LH.Audit.Details.NodeValue|undefined} Source */ -/** @typedef {{source: Source, subItems: {type: 'subitems', label: LH.IcuMessage, items: SubItem[]}}} InvalidHreflang */ +/** @typedef {{source: Source, subItems: {type: 'subitems', items: SubItem[]}}} InvalidHreflang */ /** @typedef {{reason: LH.IcuMessage}} SubItem */ import {Audit} from '../audit.js'; diff --git a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json index 5ec15ee09d14..4add00ee4025 100644 --- a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json +++ b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json @@ -237,13 +237,13 @@ "label": "Time Spent" } ], - "subItemsLabel": [ + "items": [ { "url": "https://www.mikescerealshack.co/", "responseTime": 236.03100000000006 } ], - "items": 136.03100000000006 + "overallSavingsMs": 136.03100000000006 } }, "interactive": { @@ -353,20 +353,9 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Time Spent" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "installable-manifest": { @@ -633,8 +622,8 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": [], - "items": 0 + "items": [], + "overallSavingsMs": 0 } }, "uses-rel-preconnect": { @@ -649,20 +638,9 @@ "warnings": [], "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Potential Savings" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "font-display": { @@ -1839,7 +1817,7 @@ "label": "Potential Savings" } ], - "subItemsLabel": [ + "items": [ { "node": { "type": "node", @@ -1861,7 +1839,7 @@ "wastedMs": 0 } ], - "items": 0, + "overallSavingsMs": 0, "debugData": { "type": "debugdata", "initiatorPath": [ @@ -3082,25 +3060,9 @@ "numericUnit": "millisecond", "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "totalBytes", - "valueType": "bytes", - "label": "Transfer Size" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Potential Savings" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "unminified-css": { @@ -3167,10 +3129,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Module", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Module" } }, "modern-image-formats": { @@ -3272,10 +3234,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Script" } }, "legacy-javascript": { @@ -3313,7 +3275,6 @@ "label": "Potential Savings" } ], - "subItemsLabel": "Location", "items": [ { "url": "https://www.mikescerealshack.co/_next/static/chunks/commons.49455e4fa8cc3f51203f.js", @@ -3337,7 +3298,8 @@ } ], "overallSavingsMs": 0, - "overallSavingsBytes": 57 + "overallSavingsBytes": 57, + "subItemsLabel": "Location" } }, "doctype": { @@ -6296,21 +6258,17 @@ ], "core/lib/i18n/i18n.js | columnURL": [ "audits[server-response-time].details.headings[0].label", - "audits.redirects.details.headings[0].label", "audits[bootup-time].details.headings[0].label", - "audits[uses-rel-preconnect].details.headings[0].label", "audits[network-rtt].details.headings[0].label", "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", "audits[unsized-images].details.headings[1].label", "audits[preload-lcp-image].details.headings[1].label", "audits[total-byte-weight].details.headings[0].label", - "audits[render-blocking-resources].details.headings[0].label", "audits[legacy-javascript].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", - "audits.redirects.details.headings[1].label", "audits[mainthread-work-breakdown].details.headings[1].label", "audits[network-rtt].details.headings[1].label", "audits[network-server-latency].details.headings[1].label" @@ -6466,12 +6424,6 @@ "core/audits/uses-rel-preconnect.js | description": [ "audits[uses-rel-preconnect].description" ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[uses-rel-preconnect].details.headings[1].label", - "audits[preload-lcp-image].details.headings[2].label", - "audits[render-blocking-resources].details.headings[2].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/font-display.js | title": [ "audits[font-display].title" ], @@ -6526,8 +6478,7 @@ "core/lib/i18n/i18n.js | columnTransferSize": [ "audits[resource-summary].details.headings[2].label", "audits[third-party-summary].details.headings[1].label", - "audits[total-byte-weight].details.headings[1].label", - "audits[render-blocking-resources].details.headings[1].label" + "audits[total-byte-weight].details.headings[1].label" ], "core/lib/i18n/i18n.js | totalResourceType": [ "audits[resource-summary].details.items[0].label" @@ -6673,6 +6624,10 @@ "core/audits/preload-lcp-image.js | description": [ "audits[preload-lcp-image].description" ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[preload-lcp-image].details.headings[2].label", + "audits[legacy-javascript].details.headings[2].label" + ], "core/audits/csp-xss.js | title": [ "audits[csp-xss].title" ], @@ -9002,10 +8957,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Module", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Module" } }, "modern-image-formats": { @@ -9107,10 +9062,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Script" } }, "legacy-javascript": { @@ -9125,10 +9080,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Location", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Location" } }, "inspector-issues": { @@ -14491,13 +14446,13 @@ "label": "Time Spent" } ], - "subItemsLabel": [ + "items": [ { "url": "https://www.mikescerealshack.co/corrections", "responseTime": 115.003 } ], - "items": 15.003 + "overallSavingsMs": 15.003 } }, "interactive": { @@ -14607,20 +14562,9 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Time Spent" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "installable-manifest": { @@ -14892,8 +14836,8 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": [], - "items": 0 + "items": [], + "overallSavingsMs": 0 } }, "uses-rel-preconnect": { @@ -14908,20 +14852,9 @@ "warnings": [], "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Potential Savings" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "font-display": { @@ -15941,7 +15874,7 @@ "label": "Potential Savings" } ], - "subItemsLabel": [ + "items": [ { "node": { "type": "node", @@ -15963,7 +15896,7 @@ "wastedMs": 0 } ], - "items": 0, + "overallSavingsMs": 0, "debugData": { "type": "debugdata", "initiatorPath": [ @@ -17185,25 +17118,9 @@ "numericUnit": "millisecond", "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "totalBytes", - "valueType": "bytes", - "label": "Transfer Size" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Potential Savings" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "unminified-css": { @@ -17270,10 +17187,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Module", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Module" } }, "modern-image-formats": { @@ -17465,10 +17382,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Script" } }, "legacy-javascript": { @@ -17506,7 +17423,6 @@ "label": "Potential Savings" } ], - "subItemsLabel": "Location", "items": [ { "url": "https://www.mikescerealshack.co/_next/static/chunks/commons.49455e4fa8cc3f51203f.js", @@ -17530,7 +17446,8 @@ } ], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Location" } }, "doctype": { @@ -20528,23 +20445,19 @@ ], "core/lib/i18n/i18n.js | columnURL": [ "audits[server-response-time].details.headings[0].label", - "audits.redirects.details.headings[0].label", "audits[bootup-time].details.headings[0].label", - "audits[uses-rel-preconnect].details.headings[0].label", "audits[network-rtt].details.headings[0].label", "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", "audits[unsized-images].details.headings[1].label", "audits[preload-lcp-image].details.headings[1].label", "audits[total-byte-weight].details.headings[0].label", - "audits[render-blocking-resources].details.headings[0].label", "audits[modern-image-formats].details.headings[1].label", "audits[uses-responsive-images].details.headings[1].label", "audits[legacy-javascript].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", - "audits.redirects.details.headings[1].label", "audits[mainthread-work-breakdown].details.headings[1].label", "audits[network-rtt].details.headings[1].label", "audits[network-server-latency].details.headings[1].label" @@ -20700,14 +20613,6 @@ "core/audits/uses-rel-preconnect.js | description": [ "audits[uses-rel-preconnect].description" ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[uses-rel-preconnect].details.headings[1].label", - "audits[preload-lcp-image].details.headings[2].label", - "audits[render-blocking-resources].details.headings[2].label", - "audits[modern-image-formats].details.headings[3].label", - "audits[uses-responsive-images].details.headings[3].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/font-display.js | title": [ "audits[font-display].title" ], @@ -20762,8 +20667,7 @@ "core/lib/i18n/i18n.js | columnTransferSize": [ "audits[resource-summary].details.headings[2].label", "audits[third-party-summary].details.headings[1].label", - "audits[total-byte-weight].details.headings[1].label", - "audits[render-blocking-resources].details.headings[1].label" + "audits[total-byte-weight].details.headings[1].label" ], "core/lib/i18n/i18n.js | totalResourceType": [ "audits[resource-summary].details.items[0].label" @@ -20899,6 +20803,12 @@ "core/audits/preload-lcp-image.js | description": [ "audits[preload-lcp-image].description" ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[preload-lcp-image].details.headings[2].label", + "audits[modern-image-formats].details.headings[3].label", + "audits[uses-responsive-images].details.headings[3].label", + "audits[legacy-javascript].details.headings[2].label" + ], "core/audits/csp-xss.js | title": [ "audits[csp-xss].title" ], diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index a446dd8c9c50..0f47e745049d 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -340,13 +340,13 @@ "label": "Time Spent" } ], - "subItemsLabel": [ + "items": [ { "url": "http://localhost:10200/dobetterweb/dbw_tester.html", "responseTime": 568.468 } ], - "items": 468.46799999999996 + "overallSavingsMs": 468.46799999999996 } }, "interactive": { @@ -775,20 +775,9 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Time Spent" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "installable-manifest": { @@ -1177,8 +1166,8 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": [], - "items": 0 + "items": [], + "overallSavingsMs": 0 } }, "uses-rel-preconnect": { @@ -1193,20 +1182,9 @@ "warnings": [], "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Potential Savings" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "font-display": { @@ -4588,7 +4566,7 @@ "label": "Potential Savings" } ], - "subItemsLabel": [ + "items": [ { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", "totalBytes": 903, @@ -4620,7 +4598,7 @@ "wastedMs": 879 } ], - "items": 0 + "overallSavingsMs": 0 } }, "unminified-css": { @@ -4736,7 +4714,6 @@ "label": "Potential Savings" } ], - "subItemsLabel": "Module", "items": [ { "url": "http://localhost:10200/dobetterweb/third_party/aggressive-promise-polyfill.js", @@ -4752,7 +4729,8 @@ } ], "overallSavingsMs": 600, - "overallSavingsBytes": 65111 + "overallSavingsBytes": 65111, + "subItemsLabel": "Module" } }, "modern-image-formats": { @@ -5157,10 +5135,10 @@ "details": { "type": "opportunity", "headings": [], - "subItemsLabel": "Script", "items": [], "overallSavingsMs": 0, - "overallSavingsBytes": 0 + "overallSavingsBytes": 0, + "subItemsLabel": "Script" } }, "legacy-javascript": { @@ -5198,7 +5176,6 @@ "label": "Potential Savings" } ], - "subItemsLabel": "Location", "items": [ { "url": "http://localhost:10200/dobetterweb/third_party/aggressive-promise-polyfill.js", @@ -5232,7 +5209,8 @@ } ], "overallSavingsMs": 150, - "overallSavingsBytes": 21189 + "overallSavingsBytes": 21189, + "subItemsLabel": "Location" } }, "doctype": { @@ -5573,20 +5551,9 @@ "numericUnit": "millisecond", "details": { "type": "opportunity", - "headings": [ - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "protocol", - "valueType": "text", - "label": "Protocol" - } - ], - "subItemsLabel": [], - "items": 0 + "headings": [], + "items": [], + "overallSavingsMs": 0 } }, "uses-passive-event-listeners": { @@ -8583,11 +8550,9 @@ ], "core/lib/i18n/i18n.js | columnURL": [ "audits[server-response-time].details.headings[0].label", - "audits.redirects.details.headings[0].label", "audits[image-aspect-ratio].details.headings[1].label", "audits[image-size-responsive].details.headings[1].label", "audits[bootup-time].details.headings[0].label", - "audits[uses-rel-preconnect].details.headings[0].label", "audits[network-rtt].details.headings[0].label", "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", @@ -8601,12 +8566,10 @@ "audits[uses-text-compression].details.headings[0].label", "audits[uses-responsive-images].details.headings[1].label", "audits[efficient-animated-content].details.headings[0].label", - "audits[legacy-javascript].details.headings[0].label", - "audits[uses-http2].details.headings[0].label" + "audits[legacy-javascript].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", - "audits.redirects.details.headings[1].label", "audits[mainthread-work-breakdown].details.headings[1].label", "audits[network-rtt].details.headings[1].label", "audits[network-server-latency].details.headings[1].label" @@ -8798,17 +8761,6 @@ "core/audits/uses-rel-preconnect.js | description": [ "audits[uses-rel-preconnect].description" ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[uses-rel-preconnect].details.headings[1].label", - "audits[render-blocking-resources].details.headings[2].label", - "audits[unminified-javascript].details.headings[2].label", - "audits[unused-javascript].details.headings[2].label", - "audits[modern-image-formats].details.headings[3].label", - "audits[uses-text-compression].details.headings[2].label", - "audits[uses-responsive-images].details.headings[3].label", - "audits[efficient-animated-content].details.headings[2].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/font-display.js | title": [ "audits[font-display].title" ], @@ -9438,6 +9390,16 @@ "path": "audits[render-blocking-resources].displayValue" } ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[render-blocking-resources].details.headings[2].label", + "audits[unminified-javascript].details.headings[2].label", + "audits[unused-javascript].details.headings[2].label", + "audits[modern-image-formats].details.headings[3].label", + "audits[uses-text-compression].details.headings[2].label", + "audits[uses-responsive-images].details.headings[3].label", + "audits[efficient-animated-content].details.headings[2].label", + "audits[legacy-javascript].details.headings[2].label" + ], "core/audits/byte-efficiency/unminified-css.js | title": [ "audits[unminified-css].title" ], @@ -9645,9 +9607,6 @@ "core/audits/dobetterweb/uses-http2.js | description": [ "audits[uses-http2].description" ], - "core/audits/dobetterweb/uses-http2.js | columnProtocol": [ - "audits[uses-http2].details.headings[1].label" - ], "core/audits/dobetterweb/uses-passive-event-listeners.js | failureTitle": [ "audits[uses-passive-event-listeners].title" ], From 8eb2316963ddf99cfe3936af2a24e16d7c06914e Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 5 Dec 2022 17:58:46 -0800 Subject: [PATCH 7/9] update --- .../duplicated-javascript-test.js | 67 +------------------ .../byte-efficiency/legacy-javascript-test.js | 5 -- .../byte-efficiency/unused-javascript-test.js | 5 -- .../test/renderer/report-ui-features-test.js | 4 ++ 4 files changed, 5 insertions(+), 76 deletions(-) diff --git a/core/test/audits/byte-efficiency/duplicated-javascript-test.js b/core/test/audits/byte-efficiency/duplicated-javascript-test.js index 473b92d5d7cd..1cc5902eaca8 100644 --- a/core/test/audits/byte-efficiency/duplicated-javascript-test.js +++ b/core/test/audits/byte-efficiency/duplicated-javascript-test.js @@ -60,7 +60,7 @@ describe('DuplicatedJavascript computed artifact', () => { }; const networkRecords = [{url: 'https://example.com', resourceType: 'Document'}]; const results = await DuplicatedJavascript.audit_(artifacts, networkRecords, context); - expect({items: results.items, wastedBytesByUrl: results.wastedBytesByUrl}). + expect({ items: results.items, wastedBytesByUrl: results.wastedBytesByUrl }). toMatchInlineSnapshot(` Object { "items": Array [ @@ -77,11 +77,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -101,11 +96,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -125,11 +115,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -149,11 +134,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -173,11 +153,6 @@ Object { "url": "https://example.com/coursehero-bundle-1.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -197,11 +172,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -221,11 +191,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -245,11 +210,6 @@ Object { "url": "https://example.com/coursehero-bundle-1.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -269,11 +229,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -293,11 +248,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -317,11 +267,6 @@ Object { "url": "https://example.com/coursehero-bundle-1.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -341,11 +286,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, @@ -363,11 +303,6 @@ Object { "url": "https://example.com/coursehero-bundle-2.js", }, ], - "label": Object { - "formattedDefault": "Script", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | scriptResourceType", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, diff --git a/core/test/audits/byte-efficiency/legacy-javascript-test.js b/core/test/audits/byte-efficiency/legacy-javascript-test.js index fd301e7c1f9c..49df55450f23 100644 --- a/core/test/audits/byte-efficiency/legacy-javascript-test.js +++ b/core/test/audits/byte-efficiency/legacy-javascript-test.js @@ -111,11 +111,6 @@ Object { "signal": "String.prototype.repeat", }, ], - "label": Object { - "formattedDefault": "Location", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | columnLocation", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 0, diff --git a/core/test/audits/byte-efficiency/unused-javascript-test.js b/core/test/audits/byte-efficiency/unused-javascript-test.js index f3267c1d01da..e5065eabd158 100644 --- a/core/test/audits/byte-efficiency/unused-javascript-test.js +++ b/core/test/audits/byte-efficiency/unused-javascript-test.js @@ -163,11 +163,6 @@ Array [ "sourceWastedBytes": 256, }, ], - "label": Object { - "formattedDefault": "Module", - "i18nId": "lighthouse-core/lib/i18n/i18n.js | module", - "values": undefined, - }, "type": "subitems", }, "totalBytes": 83748, diff --git a/report/test/renderer/report-ui-features-test.js b/report/test/renderer/report-ui-features-test.js index d023ffdb50a3..7a54b4ab7cc5 100644 --- a/report/test/renderer/report-ui-features-test.js +++ b/report/test/renderer/report-ui-features-test.js @@ -235,12 +235,15 @@ describe('ReportUIFeatures', () => { const initialExpected = [ '/script1.js(www.cdn.com)24.0 KiB8.8 KiB', + 'Module', '10.0 KiB0.0 KiB', '20.0 KiB0.0 KiB', '/script2.js(www.example.com)24.0 KiB8.8 KiB', + 'Module', '30.0 KiB0.0 KiB', '40.0 KiB0.0 KiB', '/script3.js(www.notexample.com)24.0 KiB8.8 KiB', + 'Module', '50.0 KiB0.0 KiB', '60.0 KiB0.0 KiB', ]; @@ -249,6 +252,7 @@ describe('ReportUIFeatures', () => { filterCheckbox.click(); expect(getRowIdentifiers()).toEqual([ '/script2.js(www.example.com)24.0 KiB8.8 KiB', + 'Module', '30.0 KiB0.0 KiB', '40.0 KiB0.0 KiB', ]); From 0fcc356f84aa187f07ef9777bcc35018c1100da9 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 6 Dec 2022 10:21:03 -0800 Subject: [PATCH 8/9] lint --- core/test/audits/byte-efficiency/duplicated-javascript-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/audits/byte-efficiency/duplicated-javascript-test.js b/core/test/audits/byte-efficiency/duplicated-javascript-test.js index 1cc5902eaca8..b68b238f9684 100644 --- a/core/test/audits/byte-efficiency/duplicated-javascript-test.js +++ b/core/test/audits/byte-efficiency/duplicated-javascript-test.js @@ -60,7 +60,7 @@ describe('DuplicatedJavascript computed artifact', () => { }; const networkRecords = [{url: 'https://example.com', resourceType: 'Document'}]; const results = await DuplicatedJavascript.audit_(artifacts, networkRecords, context); - expect({ items: results.items, wastedBytesByUrl: results.wastedBytesByUrl }). + expect({items: results.items, wastedBytesByUrl: results.wastedBytesByUrl}). toMatchInlineSnapshot(` Object { "items": Array [ From 59d56536830ad5ee777da52835bebc7f9aa07209 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Dec 2022 14:50:09 -0800 Subject: [PATCH 9/9] make line thinner --- report/assets/styles.css | 4 ++++ report/renderer/components.js | 2 +- report/renderer/details-renderer.js | 6 +----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/report/assets/styles.css b/report/assets/styles.css index ab891bef1061..b0d7f6d8fbd7 100644 --- a/report/assets/styles.css +++ b/report/assets/styles.css @@ -1515,6 +1515,10 @@ word-break: normal; } +.lh-table hr { + border-width: 0.5px; +} + .lh-row--even { background-color: var(--table-higlight-background-color); } diff --git a/report/renderer/components.js b/report/renderer/components.js index cd4b97f4df95..25d7243b7b48 100644 --- a/report/renderer/components.js +++ b/report/renderer/components.js @@ -520,7 +520,7 @@ function createSnippetLineComponent(dom) { function createStylesComponent(dom) { const el0 = dom.createFragment(); const el1 = dom.createElement("style"); - el1.append("/**\n * @license\n * Copyright 2017 The Lighthouse Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS-IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n Naming convention:\n\n If a variable is used for a specific component: --{component}-{property name}-{modifier}\n\n Both {component} and {property name} should be kebab-case. If the target is the entire page,\n use 'report' for the component. The property name should not be abbreviated. Use the\n property name the variable is intended for - if it's used for multiple, a common descriptor\n is fine (ex: 'size' for a variable applied to 'width' and 'height'). If a variable is shared\n across multiple components, either create more variables or just drop the \"{component}-\"\n part of the name. Append any modifiers at the end (ex: 'big', 'dark').\n\n For colors: --color-{hue}-{intensity}\n\n {intensity} is the Material Design tag - 700, A700, etc.\n*/\n.lh-vars {\n /* Palette using Material Design Colors\n * https://www.materialui.co/colors */\n --color-amber-50: #FFF8E1;\n --color-blue-200: #90CAF9;\n --color-blue-900: #0D47A1;\n --color-blue-A700: #2962FF;\n --color-blue-primary: #06f;\n --color-cyan-500: #00BCD4;\n --color-gray-100: #F5F5F5;\n --color-gray-300: #CFCFCF;\n --color-gray-200: #E0E0E0;\n --color-gray-400: #BDBDBD;\n --color-gray-50: #FAFAFA;\n --color-gray-500: #9E9E9E;\n --color-gray-600: #757575;\n --color-gray-700: #616161;\n --color-gray-800: #424242;\n --color-gray-900: #212121;\n --color-gray: #000000;\n --color-green-700: #080;\n --color-green: #0c6;\n --color-lime-400: #D3E156;\n --color-orange-50: #FFF3E0;\n --color-orange-700: #C33300;\n --color-orange: #fa3;\n --color-red-700: #c00;\n --color-red: #f33;\n --color-teal-600: #00897B;\n --color-white: #FFFFFF;\n\n /* Context-specific colors */\n --color-average-secondary: var(--color-orange-700);\n --color-average: var(--color-orange);\n --color-fail-secondary: var(--color-red-700);\n --color-fail: var(--color-red);\n --color-hover: var(--color-gray-50);\n --color-informative: var(--color-blue-900);\n --color-pass-secondary: var(--color-green-700);\n --color-pass: var(--color-green);\n --color-not-applicable: var(--color-gray-600);\n\n /* Component variables */\n --audit-description-padding-left: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right));\n --audit-explanation-line-height: 16px;\n --audit-group-margin-bottom: calc(var(--default-padding) * 6);\n --audit-group-padding-vertical: 8px;\n --audit-margin-horizontal: 5px;\n --audit-padding-vertical: 8px;\n --category-padding: calc(var(--default-padding) * 6) var(--edge-gap-padding) calc(var(--default-padding) * 4);\n --chevron-line-stroke: var(--color-gray-600);\n --chevron-size: 12px;\n --default-padding: 8px;\n --edge-gap-padding: calc(var(--default-padding) * 4);\n --env-item-background-color: var(--color-gray-100);\n --env-item-font-size: 28px;\n --env-item-line-height: 36px;\n --env-item-padding: 10px 0px;\n --env-name-min-width: 220px;\n --footer-padding-vertical: 16px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 48px;\n --gauge-circle-size-sm: 32px;\n --gauge-label-font-size-big: 18px;\n --gauge-label-font-size: var(--report-font-size-secondary);\n --gauge-label-line-height-big: 24px;\n --gauge-label-line-height: var(--report-line-height-secondary);\n --gauge-percentage-font-size-big: 38px;\n --gauge-percentage-font-size: var(--report-font-size-secondary);\n --gauge-wrapper-width: 120px;\n --header-line-height: 24px;\n --highlighter-background-color: var(--report-text-color);\n --icon-square-size: calc(var(--score-icon-size) * 0.88);\n --image-preview-size: 48px;\n --link-color: var(--color-blue-primary);\n --locale-selector-background-color: var(--color-white);\n --metric-toggle-lines-fill: #7F7F7F;\n --metric-value-font-size: calc(var(--report-font-size) * 1.8);\n --metrics-toggle-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-white);\n --plugin-badge-size-big: calc(var(--gauge-circle-size-big) / 2.7);\n --plugin-badge-size: calc(var(--gauge-circle-size) / 2.7);\n --plugin-icon-size: 65%;\n --pwa-icon-margin: 0 var(--default-padding);\n --pwa-icon-size: var(--topbar-logo-size);\n --report-background-color: #fff;\n --report-border-color-secondary: #ebebeb;\n --report-font-family-monospace: 'Roboto Mono', 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: Roboto, Helvetica, Arial, sans-serif;\n --report-font-size: 14px;\n --report-font-size-secondary: 12px;\n --report-icon-size: var(--score-icon-background-size);\n --report-line-height: 24px;\n --report-line-height-secondary: 20px;\n --report-monospace-font-size: calc(var(--report-font-size) * 0.85);\n --report-text-color-secondary: var(--color-gray-800);\n --report-text-color: var(--color-gray-900);\n --report-content-max-width: calc(60 * var(--report-font-size)); /* defaults to 840px */\n --report-content-min-width: 360px;\n --report-content-max-width-minus-edge-gap: calc(var(--report-content-max-width) - var(--edge-gap-padding) * 2);\n --score-container-padding: 8px;\n --score-icon-background-size: 24px;\n --score-icon-margin-left: 6px;\n --score-icon-margin-right: 14px;\n --score-icon-margin: 0 var(--score-icon-margin-right) 0 var(--score-icon-margin-left);\n --score-icon-size: 12px;\n --score-icon-size-big: 16px;\n --screenshot-overlay-background: rgba(0, 0, 0, 0.3);\n --section-padding-vertical: calc(var(--default-padding) * 6);\n --snippet-background-color: var(--color-gray-50);\n --snippet-color: #0938C2;\n --sparkline-height: 5px;\n --stackpack-padding-horizontal: 10px;\n --sticky-header-background-color: var(--report-background-color);\n --table-higlight-background-color: hsla(210, 17%, 77%, 0.1);\n --tools-icon-color: var(--color-gray-600);\n --topbar-background-color: var(--color-white);\n --topbar-height: 32px;\n --topbar-logo-size: 24px;\n --topbar-padding: 0 8px;\n --toplevel-warning-background-color: hsla(30, 100%, 75%, 10%);\n --toplevel-warning-message-text-color: var(--color-average-secondary);\n --toplevel-warning-padding: 18px;\n --toplevel-warning-text-color: var(--report-text-color);\n\n /* SVGs */\n --plugin-icon-url-dark: url('data:image/svg+xml;utf8,');\n --plugin-icon-url: url('data:image/svg+xml;utf8,');\n\n --pass-icon-url: url('data:image/svg+xml;utf8,check');\n --average-icon-url: url('data:image/svg+xml;utf8,info');\n --fail-icon-url: url('data:image/svg+xml;utf8,warn');\n\n --pwa-installable-gray-url: url('data:image/svg+xml;utf8,');\n --pwa-optimized-gray-url: url('data:image/svg+xml;utf8,');\n\n --pwa-installable-gray-url-dark: url('data:image/svg+xml;utf8,');\n --pwa-optimized-gray-url-dark: url('data:image/svg+xml;utf8,');\n\n --pwa-installable-color-url: url('data:image/svg+xml;utf8,');\n --pwa-optimized-color-url: url('data:image/svg+xml;utf8,');\n\n --swap-locale-icon-url: url('data:image/svg+xml;utf8,');\n}\n\n@media not print {\n .lh-dark {\n /* Pallete */\n --color-gray-200: var(--color-gray-800);\n --color-gray-300: #616161;\n --color-gray-400: var(--color-gray-600);\n --color-gray-700: var(--color-gray-400);\n --color-gray-50: #757575;\n --color-gray-600: var(--color-gray-500);\n --color-green-700: var(--color-green);\n --color-orange-700: var(--color-orange);\n --color-red-700: var(--color-red);\n --color-teal-600: var(--color-cyan-500);\n\n /* Context-specific colors */\n --color-hover: rgba(0, 0, 0, 0.2);\n --color-informative: var(--color-blue-200);\n\n /* Component variables */\n --env-item-background-color: #393535;\n --link-color: var(--color-blue-200);\n --locale-selector-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-gray-800);\n --report-background-color: var(--color-gray-900);\n --report-border-color-secondary: var(--color-gray-200);\n --report-text-color-secondary: var(--color-gray-400);\n --report-text-color: var(--color-gray-100);\n --snippet-color: var(--color-cyan-500);\n --topbar-background-color: var(--color-gray);\n --toplevel-warning-background-color: hsl(33deg 14% 18%);\n --toplevel-warning-message-text-color: var(--color-orange-700);\n --toplevel-warning-text-color: var(--color-gray-100);\n\n /* SVGs */\n --plugin-icon-url: var(--plugin-icon-url-dark);\n --pwa-installable-gray-url: var(--pwa-installable-gray-url-dark);\n --pwa-optimized-gray-url: var(--pwa-optimized-gray-url-dark);\n }\n}\n\n@media only screen and (max-width: 480px) {\n .lh-vars {\n --audit-group-margin-bottom: 20px;\n --edge-gap-padding: var(--default-padding);\n --env-name-min-width: 120px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 72px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 112px;\n --header-padding: 16px 0 16px 0;\n --image-preview-size: 24px;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-size: 14px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --topbar-height: 28px;\n --topbar-logo-size: 20px;\n }\n\n /* Not enough space to adequately show the relative savings bars. */\n .lh-sparkline {\n display: none;\n }\n}\n\n.lh-vars.lh-devtools {\n --audit-explanation-line-height: 14px;\n --audit-group-margin-bottom: 20px;\n --audit-group-padding-vertical: 12px;\n --audit-padding-vertical: 4px;\n --category-padding: 12px;\n --default-padding: 12px;\n --env-name-min-width: 120px;\n --footer-padding-vertical: 8px;\n --gauge-circle-size-big: 72px;\n --gauge-circle-size: 64px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 97px;\n --header-line-height: 20px;\n --header-padding: 16px 0 16px 0;\n --screenshot-overlay-background: transparent;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-family-monospace: 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n --report-font-size: 12px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --section-padding-vertical: 8px;\n}\n\n.lh-devtools.lh-root {\n height: 100%;\n}\n.lh-devtools.lh-root img {\n /* Override devtools default 'min-width: 0' so svg without size in a flexbox isn't collapsed. */\n min-width: auto;\n}\n.lh-devtools .lh-container {\n overflow-y: scroll;\n height: calc(100% - var(--topbar-height));\n}\n@media print {\n .lh-devtools .lh-container {\n overflow: unset;\n }\n}\n.lh-devtools .lh-sticky-header {\n /* This is normally the height of the topbar, but we want it to stick to the top of our scroll container .lh-container` */\n top: 0;\n}\n.lh-devtools .lh-element-screenshot__overlay {\n position: absolute;\n}\n\n@keyframes fadeIn {\n 0% { opacity: 0;}\n 100% { opacity: 0.6;}\n}\n\n.lh-root *, .lh-root *::before, .lh-root *::after {\n box-sizing: border-box;\n}\n\n.lh-root {\n font-family: var(--report-font-family);\n font-size: var(--report-font-size);\n margin: 0;\n line-height: var(--report-line-height);\n background: var(--report-background-color);\n color: var(--report-text-color);\n}\n\n.lh-root :focus-visible {\n outline: -webkit-focus-ring-color auto 3px;\n}\n.lh-root summary:focus {\n outline: none;\n box-shadow: 0 0 0 1px hsl(217, 89%, 61%);\n}\n\n.lh-root [hidden] {\n display: none !important;\n}\n\n.lh-root pre {\n margin: 0;\n}\n\n.lh-root details > summary {\n cursor: pointer;\n}\n\n.lh-hidden {\n display: none !important;\n}\n\n.lh-container {\n /*\n Text wrapping in the report is so much FUN!\n We have a `word-break: break-word;` globally here to prevent a few common scenarios, namely\n long non-breakable text (usually URLs) found in:\n 1. The footer\n 2. .lh-node (outerHTML)\n 3. .lh-code\n\n With that sorted, the next challenge is appropriate column sizing and text wrapping inside our\n .lh-details tables. Even more fun.\n * We don't want table headers (\"Potential Savings (ms)\") to wrap or their column values, but\n we'd be happy for the URL column to wrap if the URLs are particularly long.\n * We want the narrow columns to remain narrow, providing the most column width for URL\n * We don't want the table to extend past 100% width.\n * Long URLs in the URL column can wrap. Util.getURLDisplayName maxes them out at 64 characters,\n but they do not get any overflow:ellipsis treatment.\n */\n word-break: break-word;\n}\n\n.lh-audit-group a,\n.lh-category-header__description a,\n.lh-audit__description a,\n.lh-warnings a,\n.lh-footer a,\n.lh-table-column--link a {\n color: var(--link-color);\n}\n\n.lh-audit__description, .lh-audit__stackpack {\n --inner-audit-padding-right: var(--stackpack-padding-horizontal);\n padding-left: var(--audit-description-padding-left);\n padding-right: var(--inner-audit-padding-right);\n padding-top: 8px;\n padding-bottom: 8px;\n}\n\n.lh-details {\n margin-top: var(--default-padding);\n margin-bottom: var(--default-padding);\n margin-left: var(--audit-description-padding-left);\n /* whatever the .lh-details side margins are */\n width: 100%;\n}\n\n.lh-audit__stackpack {\n display: flex;\n align-items: center;\n}\n\n.lh-audit__stackpack__img {\n max-width: 30px;\n margin-right: var(--default-padding)\n}\n\n/* Report header */\n\n.lh-report-icon {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n cursor: pointer;\n}\n.lh-report-icon[disabled] {\n opacity: 0.3;\n pointer-events: none;\n}\n\n.lh-report-icon::before {\n content: \"\";\n margin: 4px;\n background-repeat: no-repeat;\n width: var(--report-icon-size);\n height: var(--report-icon-size);\n opacity: 0.7;\n display: inline-block;\n vertical-align: middle;\n}\n.lh-report-icon:hover::before {\n opacity: 1;\n}\n.lh-dark .lh-report-icon::before {\n filter: invert(1);\n}\n.lh-report-icon--print::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--copy::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--open::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--download::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--dark::before {\n background-image:url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--treemap::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--date::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--devices::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--world::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--stopwatch::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--networkspeed::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--samples-one::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--samples-many::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--chrome::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n\n\n\n.lh-buttons {\n display: flex;\n flex-wrap: wrap;\n margin: var(--default-padding) 0;\n}\n.lh-button {\n height: 32px;\n border: 1px solid var(--report-border-color-secondary);\n border-radius: 3px;\n color: var(--link-color);\n background-color: var(--report-background-color);\n margin: 5px;\n}\n\n.lh-button:first-of-type {\n margin-left: 0;\n}\n\n/* Node */\n.lh-node__snippet {\n font-family: var(--report-font-family-monospace);\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n line-height: 20px;\n}\n\n/* Score */\n\n.lh-audit__score-icon {\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n margin: var(--score-icon-margin);\n}\n\n.lh-audit--pass .lh-audit__display-text {\n color: var(--color-pass-secondary);\n}\n.lh-audit--pass .lh-audit__score-icon,\n.lh-scorescale-range--pass::before {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-audit__display-text {\n color: var(--color-average-secondary);\n}\n.lh-audit--average .lh-audit__score-icon,\n.lh-scorescale-range--average::before {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-audit--fail .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n.lh-audit--fail .lh-audit__score-icon,\n.lh-audit--error .lh-audit__score-icon,\n.lh-scorescale-range--fail::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-audit--manual .lh-audit__display-text,\n.lh-audit--notapplicable .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n.lh-audit--manual .lh-audit__score-icon,\n.lh-audit--notapplicable .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n background: none;\n}\n\n.lh-audit--informative .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n\n.lh-audit--informative .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n}\n\n.lh-audit__description,\n.lh-audit__stackpack {\n color: var(--report-text-color-secondary);\n}\n.lh-audit__adorn {\n border: 1px solid slategray;\n border-radius: 3px;\n margin: 0 3px;\n padding: 0 2px;\n line-height: 1.1;\n display: inline-block;\n font-size: 90%;\n}\n\n.lh-category-header__description {\n text-align: center;\n color: var(--color-gray-700);\n margin: 0px auto;\n max-width: 400px;\n}\n\n\n.lh-audit__display-text,\n.lh-load-opportunity__sparkline,\n.lh-chevron-container {\n margin: 0 var(--audit-margin-horizontal);\n}\n.lh-chevron-container {\n margin-right: 0;\n}\n\n.lh-audit__title-and-text {\n flex: 1;\n}\n\n.lh-audit__title-and-text code {\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n}\n\n/* Prepend display text with em dash separator. But not in Opportunities. */\n.lh-audit__display-text:not(:empty):before {\n content: '—';\n margin-right: var(--audit-margin-horizontal);\n}\n.lh-audit-group.lh-audit-group--load-opportunities .lh-audit__display-text:not(:empty):before {\n display: none;\n}\n\n/* Expandable Details (Audit Groups, Audits) */\n.lh-audit__header {\n display: flex;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-audit--load-opportunity .lh-audit__header {\n display: block;\n}\n\n\n.lh-metricfilter {\n display: grid;\n justify-content: end;\n align-items: center;\n grid-auto-flow: column;\n gap: 4px;\n color: var(--color-gray-700);\n}\n\n.lh-metricfilter__radio {\n position: absolute;\n left: -9999px;\n}\n.lh-metricfilter input[type='radio']:focus-visible + label {\n outline: -webkit-focus-ring-color auto 1px;\n}\n\n.lh-metricfilter__label {\n display: inline-flex;\n padding: 0 4px;\n height: 16px;\n text-decoration: underline;\n align-items: center;\n cursor: pointer;\n font-size: 90%;\n}\n\n.lh-metricfilter__label--active {\n background: var(--color-blue-primary);\n color: var(--color-white);\n border-radius: 3px;\n text-decoration: none;\n}\n/* Give the 'All' choice a more muted display */\n.lh-metricfilter__label--active[for=\"metric-All\"] {\n background-color: var(--color-blue-200) !important;\n color: black !important;\n}\n\n.lh-metricfilter__text {\n margin-right: 8px;\n}\n\n/* If audits are filtered, hide the itemcount for Passed Audits… */\n.lh-category--filtered .lh-audit-group .lh-audit-group__itemcount {\n display: none;\n}\n\n\n.lh-audit__header:hover {\n background-color: var(--color-hover);\n}\n\n/* We want to hide the browser's default arrow marker on summary elements. Admittedly, it's complicated. */\n.lh-root details > summary {\n /* Blink 89+ and Firefox will hide the arrow when display is changed from (new) default of `list-item` to block. https://chromestatus.com/feature/6730096436051968*/\n display: block;\n}\n/* Safari and Blink <=88 require using the -webkit-details-marker selector */\n.lh-root details > summary::-webkit-details-marker {\n display: none;\n}\n\n/* Perf Metric */\n\n.lh-metrics-container {\n display: grid;\n grid-auto-rows: 1fr;\n grid-template-columns: 1fr 1fr;\n grid-column-gap: var(--report-line-height);\n margin-bottom: var(--default-padding);\n}\n\n.lh-metric {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(-n+2) {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-metric__innerwrap {\n display: grid;\n /**\n * Icon -- Metric Name\n * -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 1fr;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-metric__details {\n order: -1;\n}\n\n.lh-metric__title {\n flex: 1;\n}\n\n.lh-calclink {\n padding-left: calc(1ex / 3);\n}\n\n.lh-metric__description {\n display: none;\n grid-column-start: 2;\n grid-column-end: 4;\n color: var(--report-text-color-secondary);\n}\n\n.lh-metric__value {\n font-size: var(--metric-value-font-size);\n margin: calc(var(--default-padding) / 2) 0;\n white-space: nowrap; /* No wrapping between metric value and the icon */\n grid-column-start: 2;\n}\n\n\n@media screen and (max-width: 535px) {\n .lh-metrics-container {\n display: block;\n }\n\n .lh-metric {\n border-bottom: none !important;\n }\n .lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(1) {\n border-bottom: 1px solid var(--report-border-color-secondary) !important;\n }\n\n /* Change the grid to 3 columns for narrow viewport. */\n .lh-metric__innerwrap {\n /**\n * Icon -- Metric Name -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 2fr 1fr;\n }\n .lh-metric__value {\n justify-self: end;\n grid-column-start: unset;\n }\n}\n\n/* No-JS toggle switch */\n/* Keep this selector sync'd w/ `magicSelector` in report-ui-features-test.js */\n .lh-metrics-toggle__input:checked ~ .lh-metrics-container .lh-metric__description {\n display: block;\n}\n\n/* TODO get rid of the SVGS and clean up these some more */\n.lh-metrics-toggle__input {\n opacity: 0;\n position: absolute;\n right: 0;\n top: 0px;\n}\n\n.lh-metrics-toggle__input + div > label > .lh-metrics-toggle__labeltext--hide,\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--show {\n display: none;\n}\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--hide {\n display: inline;\n}\n.lh-metrics-toggle__input:focus + div > label {\n outline: -webkit-focus-ring-color auto 3px;\n}\n\n.lh-metrics-toggle__label {\n cursor: pointer;\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n/* Pushes the metric description toggle button to the right. */\n.lh-audit-group--metrics .lh-audit-group__header {\n display: flex;\n justify-content: space-between;\n}\n\n.lh-metric__icon,\n.lh-scorescale-range::before {\n content: '';\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n display: inline-block;\n margin: var(--score-icon-margin);\n}\n\n.lh-metric--pass .lh-metric__value {\n color: var(--color-pass-secondary);\n}\n.lh-metric--pass .lh-metric__icon {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-metric--average .lh-metric__value {\n color: var(--color-average-secondary);\n}\n.lh-metric--average .lh-metric__icon {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-metric--fail .lh-metric__value {\n color: var(--color-fail-secondary);\n}\n.lh-metric--fail .lh-metric__icon,\n.lh-metric--error .lh-metric__icon {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-metric--error .lh-metric__value,\n.lh-metric--error .lh-metric__description {\n color: var(--color-fail-secondary);\n}\n\n/* Perf load opportunity */\n\n.lh-load-opportunity__cols {\n display: flex;\n align-items: flex-start;\n}\n\n.lh-load-opportunity__header .lh-load-opportunity__col {\n color: var(--color-gray-600);\n display: unset;\n line-height: calc(2.3 * var(--report-font-size));\n}\n\n.lh-load-opportunity__col {\n display: flex;\n}\n\n.lh-load-opportunity__col--one {\n flex: 5;\n align-items: center;\n margin-right: 2px;\n}\n.lh-load-opportunity__col--two {\n flex: 4;\n text-align: right;\n}\n\n.lh-audit--load-opportunity .lh-audit__display-text {\n text-align: right;\n flex: 0 0 calc(4 * var(--report-font-size));\n}\n\n\n/* Sparkline */\n\n.lh-load-opportunity__sparkline {\n flex: 1;\n margin-top: calc((var(--report-line-height) - var(--sparkline-height)) / 2);\n}\n\n.lh-sparkline {\n height: var(--sparkline-height);\n width: 100%;\n}\n\n.lh-sparkline__bar {\n height: 100%;\n float: right;\n}\n\n.lh-audit--pass .lh-sparkline__bar {\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-sparkline__bar {\n background: var(--color-average);\n}\n\n.lh-audit--fail .lh-sparkline__bar {\n background: var(--color-fail);\n}\n\n/* Filmstrip */\n\n.lh-filmstrip-container {\n /* smaller gap between metrics and filmstrip */\n margin: -8px auto 0 auto;\n}\n\n.lh-filmstrip {\n display: grid;\n justify-content: space-between;\n padding-bottom: var(--default-padding);\n width: 100%;\n grid-template-columns: repeat(auto-fit, 60px);\n}\n\n.lh-filmstrip__frame {\n text-align: right;\n position: relative;\n}\n\n.lh-filmstrip__thumbnail {\n border: 1px solid var(--report-border-color-secondary);\n max-height: 100px;\n max-width: 60px;\n}\n\n/* Audit */\n\n.lh-audit {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n/* Apply border-top to just the first audit. */\n.lh-audit {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n.lh-audit ~ .lh-audit {\n border-top: none;\n}\n\n\n.lh-audit--error .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n\n/* Audit Group */\n\n.lh-audit-group {\n margin-bottom: var(--audit-group-margin-bottom);\n position: relative;\n}\n.lh-audit-group--metrics {\n margin-bottom: calc(var(--audit-group-margin-bottom) / 2);\n}\n\n.lh-audit-group__header::before {\n /* By default, groups don't get an icon */\n content: none;\n width: var(--pwa-icon-size);\n height: var(--pwa-icon-size);\n margin: var(--pwa-icon-margin);\n display: inline-block;\n vertical-align: middle;\n}\n\n/* Style the \"over budget\" columns red. */\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(4),\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(5),\n.lh-audit-group--budgets #timing-budget tbody tr td:nth-child(3) {\n color: var(--color-red-700);\n}\n\n/* Align the \"over budget request count\" text to be close to the \"over budget bytes\" column. */\n.lh-audit-group--budgets .lh-table tbody tr td:nth-child(4){\n text-align: right;\n}\n\n.lh-audit-group--budgets .lh-details--budget {\n width: 100%;\n margin: 0 0 var(--default-padding);\n}\n\n.lh-audit-group--pwa-installable .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-installable-gray-url);\n}\n.lh-audit-group--pwa-optimized .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-optimized-gray-url);\n}\n.lh-audit-group--pwa-installable.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-installable-color-url);\n}\n.lh-audit-group--pwa-optimized.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-optimized-color-url);\n}\n\n.lh-audit-group--metrics .lh-audit-group__summary {\n margin-top: 0;\n margin-bottom: 0;\n}\n\n.lh-audit-group__summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.lh-audit-group__header .lh-chevron {\n margin-top: calc((var(--report-line-height) - 5px) / 2);\n}\n\n.lh-audit-group__header {\n letter-spacing: 0.8px;\n padding: var(--default-padding);\n padding-left: 0;\n}\n\n.lh-audit-group__header, .lh-audit-group__summary {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n.lh-audit-group__title {\n text-transform: uppercase;\n font-weight: 500;\n}\n\n.lh-audit-group__itemcount {\n color: var(--color-gray-600);\n}\n\n.lh-audit-group__footer {\n color: var(--color-gray-600);\n display: block;\n margin-top: var(--default-padding);\n}\n\n.lh-details,\n.lh-category-header__description,\n.lh-load-opportunity__header,\n.lh-audit-group__footer {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n}\n\n.lh-audit-explanation {\n margin: var(--audit-padding-vertical) 0 calc(var(--audit-padding-vertical) / 2) var(--audit-margin-horizontal);\n line-height: var(--audit-explanation-line-height);\n display: inline-block;\n}\n\n.lh-audit--fail .lh-audit-explanation {\n color: var(--color-fail-secondary);\n}\n\n/* Report */\n.lh-list > :not(:last-child) {\n margin-bottom: calc(var(--default-padding) * 2);\n}\n\n.lh-header-container {\n display: block;\n margin: 0 auto;\n position: relative;\n word-wrap: break-word;\n}\n\n.lh-header-container .lh-scores-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n\n\n.lh-report {\n min-width: var(--report-content-min-width);\n}\n\n.lh-exception {\n font-size: large;\n}\n\n.lh-code {\n white-space: normal;\n margin-top: 0;\n font-size: var(--report-monospace-font-size);\n}\n\n.lh-warnings {\n --item-margin: calc(var(--report-line-height) / 6);\n color: var(--color-average-secondary);\n margin: var(--audit-padding-vertical) 0;\n padding: var(--default-padding)\n var(--default-padding)\n var(--default-padding)\n calc(var(--audit-description-padding-left));\n background-color: var(--toplevel-warning-background-color);\n}\n.lh-warnings span {\n font-weight: bold;\n}\n\n.lh-warnings--toplevel {\n --item-margin: calc(var(--header-line-height) / 4);\n color: var(--toplevel-warning-text-color);\n margin-left: auto;\n margin-right: auto;\n max-width: var(--report-content-max-width-minus-edge-gap);\n padding: var(--toplevel-warning-padding);\n border-radius: 8px;\n}\n\n.lh-warnings__msg {\n color: var(--toplevel-warning-message-text-color);\n margin: 0;\n}\n\n.lh-warnings ul {\n margin: 0;\n}\n.lh-warnings li {\n margin: var(--item-margin) 0;\n}\n.lh-warnings li:last-of-type {\n margin-bottom: 0;\n}\n\n.lh-scores-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n}\n.lh-scores-header__solo {\n padding: 0;\n border: 0;\n}\n\n/* Gauge */\n\n.lh-gauge__wrapper--pass {\n color: var(--color-pass-secondary);\n fill: var(--color-pass);\n stroke: var(--color-pass);\n}\n\n.lh-gauge__wrapper--average {\n color: var(--color-average-secondary);\n fill: var(--color-average);\n stroke: var(--color-average);\n}\n\n.lh-gauge__wrapper--fail {\n color: var(--color-fail-secondary);\n fill: var(--color-fail);\n stroke: var(--color-fail);\n}\n\n.lh-gauge__wrapper--not-applicable {\n color: var(--color-not-applicable);\n fill: var(--color-not-applicable);\n stroke: var(--color-not-applicable);\n}\n\n.lh-fraction__wrapper .lh-fraction__content::before {\n content: '';\n height: var(--score-icon-size);\n width: var(--score-icon-size);\n margin: var(--score-icon-margin);\n display: inline-block;\n}\n.lh-fraction__wrapper--pass .lh-fraction__content {\n color: var(--color-pass-secondary);\n}\n.lh-fraction__wrapper--pass .lh-fraction__background {\n background-color: var(--color-pass);\n}\n.lh-fraction__wrapper--pass .lh-fraction__content::before {\n background-color: var(--color-pass);\n border-radius: 50%;\n}\n.lh-fraction__wrapper--average .lh-fraction__content {\n color: var(--color-average-secondary);\n}\n.lh-fraction__wrapper--average .lh-fraction__background,\n.lh-fraction__wrapper--average .lh-fraction__content::before {\n background-color: var(--color-average);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content {\n color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__background {\n background-color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n.lh-fraction__wrapper--null .lh-fraction__content {\n color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__background {\n background-color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__content::before {\n border-radius: 50%;\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-700);\n}\n\n.lh-fraction__background {\n position: absolute;\n height: 100%;\n width: 100%;\n border-radius: calc(var(--gauge-circle-size) / 2);\n opacity: 0.1;\n z-index: -1;\n}\n\n.lh-fraction__content-wrapper {\n height: var(--gauge-circle-size);\n display: flex;\n align-items: center;\n}\n\n.lh-fraction__content {\n display: flex;\n position: relative;\n align-items: center;\n justify-content: center;\n font-size: calc(0.3 * var(--gauge-circle-size));\n line-height: calc(0.4 * var(--gauge-circle-size));\n width: max-content;\n min-width: calc(1.5 * var(--gauge-circle-size));\n padding: calc(0.1 * var(--gauge-circle-size)) calc(0.2 * var(--gauge-circle-size));\n --score-icon-size: calc(0.21 * var(--gauge-circle-size));\n --score-icon-margin: 0 calc(0.15 * var(--gauge-circle-size)) 0 0;\n}\n\n.lh-gauge {\n stroke-linecap: round;\n width: var(--gauge-circle-size);\n height: var(--gauge-circle-size);\n}\n\n.lh-category .lh-gauge {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n.lh-gauge-base {\n opacity: 0.1;\n}\n\n.lh-gauge-arc {\n fill: none;\n transform-origin: 50% 50%;\n animation: load-gauge var(--transition-length) ease both;\n animation-delay: 250ms;\n}\n\n.lh-gauge__svg-wrapper {\n position: relative;\n height: var(--gauge-circle-size);\n}\n.lh-category .lh-gauge__svg-wrapper,\n.lh-category .lh-fraction__wrapper {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n/* The plugin badge overlay */\n.lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size);\n height: var(--plugin-badge-size);\n background-color: var(--plugin-badge-background-color);\n background-image: var(--plugin-icon-url);\n background-repeat: no-repeat;\n background-size: var(--plugin-icon-size);\n background-position: 58% 50%;\n content: \"\";\n position: absolute;\n right: -6px;\n bottom: 0px;\n display: block;\n z-index: 100;\n box-shadow: 0 0 4px rgba(0,0,0,.2);\n border-radius: 25%;\n}\n.lh-category .lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size-big);\n height: var(--plugin-badge-size-big);\n}\n\n@keyframes load-gauge {\n from { stroke-dasharray: 0 352; }\n}\n\n.lh-gauge__percentage {\n width: 100%;\n height: var(--gauge-circle-size);\n position: absolute;\n font-family: var(--report-font-family-monospace);\n font-size: calc(var(--gauge-circle-size) * 0.34 + 1.3px);\n line-height: 0;\n text-align: center;\n top: calc(var(--score-container-padding) + var(--gauge-circle-size) / 2);\n}\n\n.lh-category .lh-gauge__percentage {\n --gauge-circle-size: var(--gauge-circle-size-big);\n --gauge-percentage-font-size: var(--gauge-percentage-font-size-big);\n}\n\n.lh-gauge__wrapper,\n.lh-fraction__wrapper {\n position: relative;\n display: flex;\n align-items: center;\n flex-direction: column;\n text-decoration: none;\n padding: var(--score-container-padding);\n\n --transition-length: 1s;\n\n /* Contain the layout style paint & layers during animation*/\n contain: content;\n will-change: opacity; /* Only using for layer promotion */\n}\n\n.lh-gauge__label,\n.lh-fraction__label {\n font-size: var(--gauge-label-font-size);\n font-weight: 500;\n line-height: var(--gauge-label-line-height);\n margin-top: 10px;\n text-align: center;\n color: var(--report-text-color);\n word-break: keep-all;\n}\n\n/* TODO(#8185) use more BEM (.lh-gauge__label--big) instead of relying on descendant selector */\n.lh-category .lh-gauge__label,\n.lh-category .lh-fraction__label {\n --gauge-label-font-size: var(--gauge-label-font-size-big);\n --gauge-label-line-height: var(--gauge-label-line-height-big);\n margin-top: 14px;\n}\n\n.lh-scores-header .lh-gauge__wrapper,\n.lh-scores-header .lh-fraction__wrapper,\n.lh-scores-header .lh-gauge--pwa__wrapper,\n.lh-sticky-header .lh-gauge__wrapper,\n.lh-sticky-header .lh-fraction__wrapper,\n.lh-sticky-header .lh-gauge--pwa__wrapper {\n width: var(--gauge-wrapper-width);\n}\n\n.lh-scorescale {\n display: inline-flex;\n\n gap: calc(var(--default-padding) * 4);\n margin: 16px auto 0 auto;\n font-size: var(--report-font-size-secondary);\n color: var(--color-gray-700);\n\n}\n\n.lh-scorescale-range {\n display: flex;\n align-items: center;\n font-family: var(--report-font-family-monospace);\n white-space: nowrap;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale {\n border: 0;\n display: flex;\n justify-content: center;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale-range {\n font-family: unset;\n font-size: 12px;\n}\n\n.lh-scorescale-wrap {\n display: contents;\n}\n\n/* Hide category score gauages if it's a single category report */\n.lh-header--solo-category .lh-scores-wrapper {\n display: none;\n}\n\n\n.lh-categories {\n width: 100%;\n}\n\n.lh-category {\n padding: var(--category-padding);\n max-width: var(--report-content-max-width);\n margin: 0 auto;\n\n --sticky-header-height: calc(var(--gauge-circle-size-sm) + var(--score-container-padding) * 2);\n --topbar-plus-sticky-header: calc(var(--topbar-height) + var(--sticky-header-height));\n scroll-margin-top: var(--topbar-plus-sticky-header);\n\n /* Faster recalc style & layout of the report. https://web.dev/content-visibility/ */\n content-visibility: auto;\n contain-intrinsic-size: 1000px;\n}\n\n.lh-category-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n.lh-category-wrapper:last-of-type {\n border-bottom: 0;\n}\n\n.lh-category-header {\n margin-bottom: var(--section-padding-vertical);\n}\n\n.lh-category-header .lh-score__gauge {\n max-width: 400px;\n width: auto;\n margin: 0px auto;\n}\n\n.lh-category-header__finalscreenshot {\n display: grid;\n grid-template: none / 1fr 1px 1fr;\n justify-items: center;\n align-items: center;\n gap: var(--report-line-height);\n min-height: 288px;\n margin-bottom: var(--default-padding);\n}\n\n.lh-final-ss-image {\n /* constrain the size of the image to not be too large */\n max-height: calc(var(--gauge-circle-size-big) * 2.8);\n max-width: calc(var(--gauge-circle-size-big) * 3.5);\n border: 1px solid var(--color-gray-200);\n padding: 4px;\n border-radius: 3px;\n display: block;\n}\n\n.lh-category-headercol--separator {\n background: var(--color-gray-200);\n width: 1px;\n height: var(--gauge-circle-size-big);\n}\n\n@media screen and (max-width: 780px) {\n .lh-category-header__finalscreenshot {\n grid-template: 1fr 1fr / none\n }\n .lh-category-headercol--separator {\n display: none;\n }\n}\n\n\n/* 964 fits the min-width of the filmstrip */\n@media screen and (max-width: 964px) {\n .lh-report {\n margin-left: 0;\n width: 100%;\n }\n}\n\n@media print {\n body {\n -webkit-print-color-adjust: exact; /* print background colors */\n }\n .lh-container {\n display: block;\n }\n .lh-report {\n margin-left: 0;\n padding-top: 0;\n }\n .lh-categories {\n margin-top: 0;\n }\n}\n\n.lh-table {\n position: relative;\n border-collapse: separate;\n border-spacing: 0;\n /* Can't assign padding to table, so shorten the width instead. */\n width: calc(100% - var(--audit-description-padding-left) - var(--stackpack-padding-horizontal));\n border: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-table thead th {\n position: sticky;\n top: calc(var(--topbar-plus-sticky-header) + 1em);\n z-index: 1;\n background-color: var(--report-background-color);\n border-bottom: 1px solid var(--report-border-color-secondary);\n font-weight: normal;\n color: var(--color-gray-600);\n /* See text-wrapping comment on .lh-container. */\n word-break: normal;\n}\n\n.lh-row--even {\n background-color: var(--table-higlight-background-color);\n}\n.lh-row--hidden {\n display: none;\n}\n\n.lh-table th,\n.lh-table td {\n padding: var(--default-padding);\n}\n\n.lh-table tr {\n vertical-align: middle;\n}\n\n/* Looks unnecessary, but mostly for keeping the s left-aligned */\n.lh-table-column--text,\n.lh-table-column--source-location,\n.lh-table-column--url,\n/* .lh-table-column--thumbnail, */\n/* .lh-table-column--empty,*/\n.lh-table-column--code,\n.lh-table-column--node {\n text-align: left;\n}\n\n.lh-table-column--code {\n min-width: 100px;\n}\n\n.lh-table-column--bytes,\n.lh-table-column--timespanMs,\n.lh-table-column--ms,\n.lh-table-column--numeric {\n text-align: right;\n word-break: normal;\n}\n\n\n\n.lh-table .lh-table-column--thumbnail {\n width: var(--image-preview-size);\n}\n\n.lh-table-column--url {\n min-width: 250px;\n}\n\n.lh-table-column--text {\n min-width: 80px;\n}\n\n/* Keep columns narrow if they follow the URL column */\n/* 12% was determined to be a decent narrow width, but wide enough for column headings */\n.lh-table-column--url + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--ms,\n.lh-table-column--url + .lh-table-column--ms + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--timespanMs {\n width: 12%;\n}\n\n.lh-text__url-host {\n display: inline;\n}\n\n.lh-text__url-host {\n margin-left: calc(var(--report-font-size) / 2);\n opacity: 0.6;\n font-size: 90%\n}\n\n.lh-thumbnail {\n object-fit: cover;\n width: var(--image-preview-size);\n height: var(--image-preview-size);\n display: block;\n}\n\n.lh-unknown pre {\n overflow: scroll;\n border: solid 1px var(--color-gray-200);\n}\n\n.lh-text__url > a {\n color: inherit;\n text-decoration: none;\n}\n\n.lh-text__url > a:hover {\n text-decoration: underline dotted #999;\n}\n\n.lh-sub-item-row {\n margin-left: 20px;\n margin-bottom: 0;\n color: var(--color-gray-700);\n}\n.lh-sub-item-row td {\n padding-top: 4px;\n padding-bottom: 4px;\n padding-left: 20px;\n}\n\n/* Chevron\n https://codepen.io/paulirish/pen/LmzEmK\n */\n.lh-chevron {\n --chevron-angle: 42deg;\n /* Edge doesn't support transform: rotate(calc(...)), so we define it here */\n --chevron-angle-right: -42deg;\n width: var(--chevron-size);\n height: var(--chevron-size);\n margin-top: calc((var(--report-line-height) - 12px) / 2);\n}\n\n.lh-chevron__lines {\n transition: transform 0.4s;\n transform: translateY(var(--report-line-height));\n}\n.lh-chevron__line {\n stroke: var(--chevron-line-stroke);\n stroke-width: var(--chevron-size);\n stroke-linecap: square;\n transform-origin: 50%;\n transform: rotate(var(--chevron-angle));\n transition: transform 300ms, stroke 300ms;\n}\n\n.lh-expandable-details .lh-chevron__line-right,\n.lh-expandable-details[open] .lh-chevron__line-left {\n transform: rotate(var(--chevron-angle-right));\n}\n\n.lh-expandable-details[open] .lh-chevron__line-right {\n transform: rotate(var(--chevron-angle));\n}\n\n\n.lh-expandable-details[open] .lh-chevron__lines {\n transform: translateY(calc(var(--chevron-size) * -1));\n}\n\n.lh-expandable-details[open] {\n animation: 300ms openDetails forwards;\n padding-bottom: var(--default-padding);\n}\n\n@keyframes openDetails {\n from {\n outline: 1px solid var(--report-background-color);\n }\n to {\n outline: 1px solid;\n box-shadow: 0 2px 4px rgba(0, 0, 0, .24);\n }\n}\n\n@media screen and (max-width: 780px) {\n /* no black outline if we're not confident the entire table can be displayed within bounds */\n .lh-expandable-details[open] {\n animation: none;\n }\n}\n\n.lh-expandable-details[open] summary, details.lh-clump > summary {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\ndetails.lh-clump[open] > summary {\n border-bottom-width: 0;\n}\n\n\n\ndetails .lh-clump-toggletext--hide,\ndetails[open] .lh-clump-toggletext--show { display: none; }\ndetails[open] .lh-clump-toggletext--hide { display: block;}\n\n\n/* Tooltip */\n.lh-tooltip-boundary {\n position: relative;\n}\n\n.lh-tooltip {\n position: absolute;\n display: none; /* Don't retain these layers when not needed */\n opacity: 0;\n background: #ffffff;\n white-space: pre-line; /* Render newlines in the text */\n min-width: 246px;\n max-width: 275px;\n padding: 15px;\n border-radius: 5px;\n text-align: initial;\n line-height: 1.4;\n}\n/* shrink tooltips to not be cutoff on left edge of narrow viewports\n 45vw is chosen to be ~= width of the left column of metrics\n*/\n@media screen and (max-width: 535px) {\n .lh-tooltip {\n min-width: 45vw;\n padding: 3vw;\n }\n}\n\n.lh-tooltip-boundary:hover .lh-tooltip {\n display: block;\n animation: fadeInTooltip 250ms;\n animation-fill-mode: forwards;\n animation-delay: 850ms;\n bottom: 100%;\n z-index: 1;\n will-change: opacity;\n right: 0;\n pointer-events: none;\n}\n\n.lh-tooltip::before {\n content: \"\";\n border: solid transparent;\n border-bottom-color: #fff;\n border-width: 10px;\n position: absolute;\n bottom: -20px;\n right: 6px;\n transform: rotate(180deg);\n pointer-events: none;\n}\n\n@keyframes fadeInTooltip {\n 0% { opacity: 0; }\n 75% { opacity: 1; }\n 100% { opacity: 1; filter: drop-shadow(1px 0px 1px #aaa) drop-shadow(0px 2px 4px hsla(206, 6%, 25%, 0.15)); pointer-events: auto; }\n}\n\n/* Element screenshot */\n.lh-element-screenshot {\n float: left;\n margin-right: 20px;\n}\n.lh-element-screenshot__content {\n overflow: hidden;\n min-width: 110px;\n display: flex;\n justify-content: center;\n background-color: var(--report-background-color);\n}\n.lh-element-screenshot__image {\n position: relative;\n /* Set by ElementScreenshotRenderer.installFullPageScreenshotCssVariable */\n background-image: var(--element-screenshot-url);\n outline: 2px solid #777;\n background-color: white;\n background-repeat: no-repeat;\n}\n.lh-element-screenshot__mask {\n position: absolute;\n background: #555;\n opacity: 0.8;\n}\n.lh-element-screenshot__element-marker {\n position: absolute;\n outline: 2px solid var(--color-lime-400);\n}\n.lh-element-screenshot__overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 2000; /* .lh-topbar is 1000 */\n background: var(--screenshot-overlay-background);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: zoom-out;\n}\n\n.lh-element-screenshot__overlay .lh-element-screenshot {\n margin-right: 0; /* clearing margin used in thumbnail case */\n outline: 1px solid var(--color-gray-700);\n}\n\n.lh-screenshot-overlay--enabled .lh-element-screenshot {\n cursor: zoom-out;\n}\n.lh-screenshot-overlay--enabled .lh-node .lh-element-screenshot {\n cursor: zoom-in;\n}\n\n\n.lh-meta__items {\n --meta-icon-size: calc(var(--report-icon-size) * 0.667);\n padding: var(--default-padding);\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n background-color: var(--env-item-background-color);\n border-radius: 3px;\n margin: 0 0 var(--default-padding) 0;\n font-size: 12px;\n column-gap: var(--default-padding);\n color: var(--color-gray-700);\n}\n\n.lh-meta__item {\n display: block;\n list-style-type: none;\n position: relative;\n padding: 0 0 0 calc(var(--meta-icon-size) + var(--default-padding) * 2);\n cursor: unset; /* disable pointer cursor from report-icon */\n}\n\n.lh-meta__item.lh-tooltip-boundary {\n text-decoration: dotted underline var(--color-gray-500);\n cursor: help;\n}\n\n.lh-meta__item.lh-report-icon::before {\n position: absolute;\n left: var(--default-padding);\n width: var(--meta-icon-size);\n height: var(--meta-icon-size);\n}\n\n.lh-meta__item.lh-report-icon:hover::before {\n opacity: 0.7;\n}\n\n.lh-meta__item .lh-tooltip {\n color: var(--color-gray-800);\n}\n\n.lh-meta__item .lh-tooltip::before {\n right: auto; /* Set the tooltip arrow to the leftside */\n left: 6px;\n}\n\n/* Change the grid for narrow viewport. */\n@media screen and (max-width: 640px) {\n .lh-meta__items {\n grid-template-columns: 1fr 1fr;\n }\n}\n@media screen and (max-width: 535px) {\n .lh-meta__items {\n display: block;\n }\n}\n\n\n/*# sourceURL=report-styles.css */\n"); + el1.append("/**\n * @license\n * Copyright 2017 The Lighthouse Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS-IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n Naming convention:\n\n If a variable is used for a specific component: --{component}-{property name}-{modifier}\n\n Both {component} and {property name} should be kebab-case. If the target is the entire page,\n use 'report' for the component. The property name should not be abbreviated. Use the\n property name the variable is intended for - if it's used for multiple, a common descriptor\n is fine (ex: 'size' for a variable applied to 'width' and 'height'). If a variable is shared\n across multiple components, either create more variables or just drop the \"{component}-\"\n part of the name. Append any modifiers at the end (ex: 'big', 'dark').\n\n For colors: --color-{hue}-{intensity}\n\n {intensity} is the Material Design tag - 700, A700, etc.\n*/\n.lh-vars {\n /* Palette using Material Design Colors\n * https://www.materialui.co/colors */\n --color-amber-50: #FFF8E1;\n --color-blue-200: #90CAF9;\n --color-blue-900: #0D47A1;\n --color-blue-A700: #2962FF;\n --color-blue-primary: #06f;\n --color-cyan-500: #00BCD4;\n --color-gray-100: #F5F5F5;\n --color-gray-300: #CFCFCF;\n --color-gray-200: #E0E0E0;\n --color-gray-400: #BDBDBD;\n --color-gray-50: #FAFAFA;\n --color-gray-500: #9E9E9E;\n --color-gray-600: #757575;\n --color-gray-700: #616161;\n --color-gray-800: #424242;\n --color-gray-900: #212121;\n --color-gray: #000000;\n --color-green-700: #080;\n --color-green: #0c6;\n --color-lime-400: #D3E156;\n --color-orange-50: #FFF3E0;\n --color-orange-700: #C33300;\n --color-orange: #fa3;\n --color-red-700: #c00;\n --color-red: #f33;\n --color-teal-600: #00897B;\n --color-white: #FFFFFF;\n\n /* Context-specific colors */\n --color-average-secondary: var(--color-orange-700);\n --color-average: var(--color-orange);\n --color-fail-secondary: var(--color-red-700);\n --color-fail: var(--color-red);\n --color-hover: var(--color-gray-50);\n --color-informative: var(--color-blue-900);\n --color-pass-secondary: var(--color-green-700);\n --color-pass: var(--color-green);\n --color-not-applicable: var(--color-gray-600);\n\n /* Component variables */\n --audit-description-padding-left: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right));\n --audit-explanation-line-height: 16px;\n --audit-group-margin-bottom: calc(var(--default-padding) * 6);\n --audit-group-padding-vertical: 8px;\n --audit-margin-horizontal: 5px;\n --audit-padding-vertical: 8px;\n --category-padding: calc(var(--default-padding) * 6) var(--edge-gap-padding) calc(var(--default-padding) * 4);\n --chevron-line-stroke: var(--color-gray-600);\n --chevron-size: 12px;\n --default-padding: 8px;\n --edge-gap-padding: calc(var(--default-padding) * 4);\n --env-item-background-color: var(--color-gray-100);\n --env-item-font-size: 28px;\n --env-item-line-height: 36px;\n --env-item-padding: 10px 0px;\n --env-name-min-width: 220px;\n --footer-padding-vertical: 16px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 48px;\n --gauge-circle-size-sm: 32px;\n --gauge-label-font-size-big: 18px;\n --gauge-label-font-size: var(--report-font-size-secondary);\n --gauge-label-line-height-big: 24px;\n --gauge-label-line-height: var(--report-line-height-secondary);\n --gauge-percentage-font-size-big: 38px;\n --gauge-percentage-font-size: var(--report-font-size-secondary);\n --gauge-wrapper-width: 120px;\n --header-line-height: 24px;\n --highlighter-background-color: var(--report-text-color);\n --icon-square-size: calc(var(--score-icon-size) * 0.88);\n --image-preview-size: 48px;\n --link-color: var(--color-blue-primary);\n --locale-selector-background-color: var(--color-white);\n --metric-toggle-lines-fill: #7F7F7F;\n --metric-value-font-size: calc(var(--report-font-size) * 1.8);\n --metrics-toggle-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-white);\n --plugin-badge-size-big: calc(var(--gauge-circle-size-big) / 2.7);\n --plugin-badge-size: calc(var(--gauge-circle-size) / 2.7);\n --plugin-icon-size: 65%;\n --pwa-icon-margin: 0 var(--default-padding);\n --pwa-icon-size: var(--topbar-logo-size);\n --report-background-color: #fff;\n --report-border-color-secondary: #ebebeb;\n --report-font-family-monospace: 'Roboto Mono', 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: Roboto, Helvetica, Arial, sans-serif;\n --report-font-size: 14px;\n --report-font-size-secondary: 12px;\n --report-icon-size: var(--score-icon-background-size);\n --report-line-height: 24px;\n --report-line-height-secondary: 20px;\n --report-monospace-font-size: calc(var(--report-font-size) * 0.85);\n --report-text-color-secondary: var(--color-gray-800);\n --report-text-color: var(--color-gray-900);\n --report-content-max-width: calc(60 * var(--report-font-size)); /* defaults to 840px */\n --report-content-min-width: 360px;\n --report-content-max-width-minus-edge-gap: calc(var(--report-content-max-width) - var(--edge-gap-padding) * 2);\n --score-container-padding: 8px;\n --score-icon-background-size: 24px;\n --score-icon-margin-left: 6px;\n --score-icon-margin-right: 14px;\n --score-icon-margin: 0 var(--score-icon-margin-right) 0 var(--score-icon-margin-left);\n --score-icon-size: 12px;\n --score-icon-size-big: 16px;\n --screenshot-overlay-background: rgba(0, 0, 0, 0.3);\n --section-padding-vertical: calc(var(--default-padding) * 6);\n --snippet-background-color: var(--color-gray-50);\n --snippet-color: #0938C2;\n --sparkline-height: 5px;\n --stackpack-padding-horizontal: 10px;\n --sticky-header-background-color: var(--report-background-color);\n --table-higlight-background-color: hsla(210, 17%, 77%, 0.1);\n --tools-icon-color: var(--color-gray-600);\n --topbar-background-color: var(--color-white);\n --topbar-height: 32px;\n --topbar-logo-size: 24px;\n --topbar-padding: 0 8px;\n --toplevel-warning-background-color: hsla(30, 100%, 75%, 10%);\n --toplevel-warning-message-text-color: var(--color-average-secondary);\n --toplevel-warning-padding: 18px;\n --toplevel-warning-text-color: var(--report-text-color);\n\n /* SVGs */\n --plugin-icon-url-dark: url('data:image/svg+xml;utf8,');\n --plugin-icon-url: url('data:image/svg+xml;utf8,');\n\n --pass-icon-url: url('data:image/svg+xml;utf8,check');\n --average-icon-url: url('data:image/svg+xml;utf8,info');\n --fail-icon-url: url('data:image/svg+xml;utf8,warn');\n\n --pwa-installable-gray-url: url('data:image/svg+xml;utf8,');\n --pwa-optimized-gray-url: url('data:image/svg+xml;utf8,');\n\n --pwa-installable-gray-url-dark: url('data:image/svg+xml;utf8,');\n --pwa-optimized-gray-url-dark: url('data:image/svg+xml;utf8,');\n\n --pwa-installable-color-url: url('data:image/svg+xml;utf8,');\n --pwa-optimized-color-url: url('data:image/svg+xml;utf8,');\n\n --swap-locale-icon-url: url('data:image/svg+xml;utf8,');\n}\n\n@media not print {\n .lh-dark {\n /* Pallete */\n --color-gray-200: var(--color-gray-800);\n --color-gray-300: #616161;\n --color-gray-400: var(--color-gray-600);\n --color-gray-700: var(--color-gray-400);\n --color-gray-50: #757575;\n --color-gray-600: var(--color-gray-500);\n --color-green-700: var(--color-green);\n --color-orange-700: var(--color-orange);\n --color-red-700: var(--color-red);\n --color-teal-600: var(--color-cyan-500);\n\n /* Context-specific colors */\n --color-hover: rgba(0, 0, 0, 0.2);\n --color-informative: var(--color-blue-200);\n\n /* Component variables */\n --env-item-background-color: #393535;\n --link-color: var(--color-blue-200);\n --locale-selector-background-color: var(--color-gray-200);\n --plugin-badge-background-color: var(--color-gray-800);\n --report-background-color: var(--color-gray-900);\n --report-border-color-secondary: var(--color-gray-200);\n --report-text-color-secondary: var(--color-gray-400);\n --report-text-color: var(--color-gray-100);\n --snippet-color: var(--color-cyan-500);\n --topbar-background-color: var(--color-gray);\n --toplevel-warning-background-color: hsl(33deg 14% 18%);\n --toplevel-warning-message-text-color: var(--color-orange-700);\n --toplevel-warning-text-color: var(--color-gray-100);\n\n /* SVGs */\n --plugin-icon-url: var(--plugin-icon-url-dark);\n --pwa-installable-gray-url: var(--pwa-installable-gray-url-dark);\n --pwa-optimized-gray-url: var(--pwa-optimized-gray-url-dark);\n }\n}\n\n@media only screen and (max-width: 480px) {\n .lh-vars {\n --audit-group-margin-bottom: 20px;\n --edge-gap-padding: var(--default-padding);\n --env-name-min-width: 120px;\n --gauge-circle-size-big: 96px;\n --gauge-circle-size: 72px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 112px;\n --header-padding: 16px 0 16px 0;\n --image-preview-size: 24px;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-size: 14px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --topbar-height: 28px;\n --topbar-logo-size: 20px;\n }\n\n /* Not enough space to adequately show the relative savings bars. */\n .lh-sparkline {\n display: none;\n }\n}\n\n.lh-vars.lh-devtools {\n --audit-explanation-line-height: 14px;\n --audit-group-margin-bottom: 20px;\n --audit-group-padding-vertical: 12px;\n --audit-padding-vertical: 4px;\n --category-padding: 12px;\n --default-padding: 12px;\n --env-name-min-width: 120px;\n --footer-padding-vertical: 8px;\n --gauge-circle-size-big: 72px;\n --gauge-circle-size: 64px;\n --gauge-label-font-size-big: 22px;\n --gauge-label-font-size: 14px;\n --gauge-label-line-height-big: 26px;\n --gauge-label-line-height: 20px;\n --gauge-percentage-font-size-big: 34px;\n --gauge-percentage-font-size: 26px;\n --gauge-wrapper-width: 97px;\n --header-line-height: 20px;\n --header-padding: 16px 0 16px 0;\n --screenshot-overlay-background: transparent;\n --plugin-icon-size: 75%;\n --pwa-icon-margin: 0 7px 0 -3px;\n --report-font-family-monospace: 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace;\n --report-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n --report-font-size: 12px;\n --report-line-height: 20px;\n --score-icon-margin-left: 2px;\n --score-icon-size: 10px;\n --section-padding-vertical: 8px;\n}\n\n.lh-devtools.lh-root {\n height: 100%;\n}\n.lh-devtools.lh-root img {\n /* Override devtools default 'min-width: 0' so svg without size in a flexbox isn't collapsed. */\n min-width: auto;\n}\n.lh-devtools .lh-container {\n overflow-y: scroll;\n height: calc(100% - var(--topbar-height));\n}\n@media print {\n .lh-devtools .lh-container {\n overflow: unset;\n }\n}\n.lh-devtools .lh-sticky-header {\n /* This is normally the height of the topbar, but we want it to stick to the top of our scroll container .lh-container` */\n top: 0;\n}\n.lh-devtools .lh-element-screenshot__overlay {\n position: absolute;\n}\n\n@keyframes fadeIn {\n 0% { opacity: 0;}\n 100% { opacity: 0.6;}\n}\n\n.lh-root *, .lh-root *::before, .lh-root *::after {\n box-sizing: border-box;\n}\n\n.lh-root {\n font-family: var(--report-font-family);\n font-size: var(--report-font-size);\n margin: 0;\n line-height: var(--report-line-height);\n background: var(--report-background-color);\n color: var(--report-text-color);\n}\n\n.lh-root :focus-visible {\n outline: -webkit-focus-ring-color auto 3px;\n}\n.lh-root summary:focus {\n outline: none;\n box-shadow: 0 0 0 1px hsl(217, 89%, 61%);\n}\n\n.lh-root [hidden] {\n display: none !important;\n}\n\n.lh-root pre {\n margin: 0;\n}\n\n.lh-root details > summary {\n cursor: pointer;\n}\n\n.lh-hidden {\n display: none !important;\n}\n\n.lh-container {\n /*\n Text wrapping in the report is so much FUN!\n We have a `word-break: break-word;` globally here to prevent a few common scenarios, namely\n long non-breakable text (usually URLs) found in:\n 1. The footer\n 2. .lh-node (outerHTML)\n 3. .lh-code\n\n With that sorted, the next challenge is appropriate column sizing and text wrapping inside our\n .lh-details tables. Even more fun.\n * We don't want table headers (\"Potential Savings (ms)\") to wrap or their column values, but\n we'd be happy for the URL column to wrap if the URLs are particularly long.\n * We want the narrow columns to remain narrow, providing the most column width for URL\n * We don't want the table to extend past 100% width.\n * Long URLs in the URL column can wrap. Util.getURLDisplayName maxes them out at 64 characters,\n but they do not get any overflow:ellipsis treatment.\n */\n word-break: break-word;\n}\n\n.lh-audit-group a,\n.lh-category-header__description a,\n.lh-audit__description a,\n.lh-warnings a,\n.lh-footer a,\n.lh-table-column--link a {\n color: var(--link-color);\n}\n\n.lh-audit__description, .lh-audit__stackpack {\n --inner-audit-padding-right: var(--stackpack-padding-horizontal);\n padding-left: var(--audit-description-padding-left);\n padding-right: var(--inner-audit-padding-right);\n padding-top: 8px;\n padding-bottom: 8px;\n}\n\n.lh-details {\n margin-top: var(--default-padding);\n margin-bottom: var(--default-padding);\n margin-left: var(--audit-description-padding-left);\n /* whatever the .lh-details side margins are */\n width: 100%;\n}\n\n.lh-audit__stackpack {\n display: flex;\n align-items: center;\n}\n\n.lh-audit__stackpack__img {\n max-width: 30px;\n margin-right: var(--default-padding)\n}\n\n/* Report header */\n\n.lh-report-icon {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n cursor: pointer;\n}\n.lh-report-icon[disabled] {\n opacity: 0.3;\n pointer-events: none;\n}\n\n.lh-report-icon::before {\n content: \"\";\n margin: 4px;\n background-repeat: no-repeat;\n width: var(--report-icon-size);\n height: var(--report-icon-size);\n opacity: 0.7;\n display: inline-block;\n vertical-align: middle;\n}\n.lh-report-icon:hover::before {\n opacity: 1;\n}\n.lh-dark .lh-report-icon::before {\n filter: invert(1);\n}\n.lh-report-icon--print::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--copy::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--open::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--download::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--dark::before {\n background-image:url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--treemap::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--date::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--devices::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--world::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--stopwatch::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--networkspeed::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--samples-one::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--samples-many::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n.lh-report-icon--chrome::before {\n background-image: url('data:image/svg+xml;utf8,');\n}\n\n\n\n.lh-buttons {\n display: flex;\n flex-wrap: wrap;\n margin: var(--default-padding) 0;\n}\n.lh-button {\n height: 32px;\n border: 1px solid var(--report-border-color-secondary);\n border-radius: 3px;\n color: var(--link-color);\n background-color: var(--report-background-color);\n margin: 5px;\n}\n\n.lh-button:first-of-type {\n margin-left: 0;\n}\n\n/* Node */\n.lh-node__snippet {\n font-family: var(--report-font-family-monospace);\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n line-height: 20px;\n}\n\n/* Score */\n\n.lh-audit__score-icon {\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n margin: var(--score-icon-margin);\n}\n\n.lh-audit--pass .lh-audit__display-text {\n color: var(--color-pass-secondary);\n}\n.lh-audit--pass .lh-audit__score-icon,\n.lh-scorescale-range--pass::before {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-audit__display-text {\n color: var(--color-average-secondary);\n}\n.lh-audit--average .lh-audit__score-icon,\n.lh-scorescale-range--average::before {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-audit--fail .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n.lh-audit--fail .lh-audit__score-icon,\n.lh-audit--error .lh-audit__score-icon,\n.lh-scorescale-range--fail::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-audit--manual .lh-audit__display-text,\n.lh-audit--notapplicable .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n.lh-audit--manual .lh-audit__score-icon,\n.lh-audit--notapplicable .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n background: none;\n}\n\n.lh-audit--informative .lh-audit__display-text {\n color: var(--color-gray-600);\n}\n\n.lh-audit--informative .lh-audit__score-icon {\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-400);\n border-radius: 100%;\n}\n\n.lh-audit__description,\n.lh-audit__stackpack {\n color: var(--report-text-color-secondary);\n}\n.lh-audit__adorn {\n border: 1px solid slategray;\n border-radius: 3px;\n margin: 0 3px;\n padding: 0 2px;\n line-height: 1.1;\n display: inline-block;\n font-size: 90%;\n}\n\n.lh-category-header__description {\n text-align: center;\n color: var(--color-gray-700);\n margin: 0px auto;\n max-width: 400px;\n}\n\n\n.lh-audit__display-text,\n.lh-load-opportunity__sparkline,\n.lh-chevron-container {\n margin: 0 var(--audit-margin-horizontal);\n}\n.lh-chevron-container {\n margin-right: 0;\n}\n\n.lh-audit__title-and-text {\n flex: 1;\n}\n\n.lh-audit__title-and-text code {\n color: var(--snippet-color);\n font-size: var(--report-monospace-font-size);\n}\n\n/* Prepend display text with em dash separator. But not in Opportunities. */\n.lh-audit__display-text:not(:empty):before {\n content: '—';\n margin-right: var(--audit-margin-horizontal);\n}\n.lh-audit-group.lh-audit-group--load-opportunities .lh-audit__display-text:not(:empty):before {\n display: none;\n}\n\n/* Expandable Details (Audit Groups, Audits) */\n.lh-audit__header {\n display: flex;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-audit--load-opportunity .lh-audit__header {\n display: block;\n}\n\n\n.lh-metricfilter {\n display: grid;\n justify-content: end;\n align-items: center;\n grid-auto-flow: column;\n gap: 4px;\n color: var(--color-gray-700);\n}\n\n.lh-metricfilter__radio {\n position: absolute;\n left: -9999px;\n}\n.lh-metricfilter input[type='radio']:focus-visible + label {\n outline: -webkit-focus-ring-color auto 1px;\n}\n\n.lh-metricfilter__label {\n display: inline-flex;\n padding: 0 4px;\n height: 16px;\n text-decoration: underline;\n align-items: center;\n cursor: pointer;\n font-size: 90%;\n}\n\n.lh-metricfilter__label--active {\n background: var(--color-blue-primary);\n color: var(--color-white);\n border-radius: 3px;\n text-decoration: none;\n}\n/* Give the 'All' choice a more muted display */\n.lh-metricfilter__label--active[for=\"metric-All\"] {\n background-color: var(--color-blue-200) !important;\n color: black !important;\n}\n\n.lh-metricfilter__text {\n margin-right: 8px;\n}\n\n/* If audits are filtered, hide the itemcount for Passed Audits… */\n.lh-category--filtered .lh-audit-group .lh-audit-group__itemcount {\n display: none;\n}\n\n\n.lh-audit__header:hover {\n background-color: var(--color-hover);\n}\n\n/* We want to hide the browser's default arrow marker on summary elements. Admittedly, it's complicated. */\n.lh-root details > summary {\n /* Blink 89+ and Firefox will hide the arrow when display is changed from (new) default of `list-item` to block. https://chromestatus.com/feature/6730096436051968*/\n display: block;\n}\n/* Safari and Blink <=88 require using the -webkit-details-marker selector */\n.lh-root details > summary::-webkit-details-marker {\n display: none;\n}\n\n/* Perf Metric */\n\n.lh-metrics-container {\n display: grid;\n grid-auto-rows: 1fr;\n grid-template-columns: 1fr 1fr;\n grid-column-gap: var(--report-line-height);\n margin-bottom: var(--default-padding);\n}\n\n.lh-metric {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(-n+2) {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-metric__innerwrap {\n display: grid;\n /**\n * Icon -- Metric Name\n * -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 1fr;\n align-items: center;\n padding: var(--default-padding);\n}\n\n.lh-metric__details {\n order: -1;\n}\n\n.lh-metric__title {\n flex: 1;\n}\n\n.lh-calclink {\n padding-left: calc(1ex / 3);\n}\n\n.lh-metric__description {\n display: none;\n grid-column-start: 2;\n grid-column-end: 4;\n color: var(--report-text-color-secondary);\n}\n\n.lh-metric__value {\n font-size: var(--metric-value-font-size);\n margin: calc(var(--default-padding) / 2) 0;\n white-space: nowrap; /* No wrapping between metric value and the icon */\n grid-column-start: 2;\n}\n\n\n@media screen and (max-width: 535px) {\n .lh-metrics-container {\n display: block;\n }\n\n .lh-metric {\n border-bottom: none !important;\n }\n .lh-category:not(.lh--hoisted-meta) .lh-metric:nth-last-child(1) {\n border-bottom: 1px solid var(--report-border-color-secondary) !important;\n }\n\n /* Change the grid to 3 columns for narrow viewport. */\n .lh-metric__innerwrap {\n /**\n * Icon -- Metric Name -- Metric Value\n */\n grid-template-columns: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)) 2fr 1fr;\n }\n .lh-metric__value {\n justify-self: end;\n grid-column-start: unset;\n }\n}\n\n/* No-JS toggle switch */\n/* Keep this selector sync'd w/ `magicSelector` in report-ui-features-test.js */\n .lh-metrics-toggle__input:checked ~ .lh-metrics-container .lh-metric__description {\n display: block;\n}\n\n/* TODO get rid of the SVGS and clean up these some more */\n.lh-metrics-toggle__input {\n opacity: 0;\n position: absolute;\n right: 0;\n top: 0px;\n}\n\n.lh-metrics-toggle__input + div > label > .lh-metrics-toggle__labeltext--hide,\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--show {\n display: none;\n}\n.lh-metrics-toggle__input:checked + div > label > .lh-metrics-toggle__labeltext--hide {\n display: inline;\n}\n.lh-metrics-toggle__input:focus + div > label {\n outline: -webkit-focus-ring-color auto 3px;\n}\n\n.lh-metrics-toggle__label {\n cursor: pointer;\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n/* Pushes the metric description toggle button to the right. */\n.lh-audit-group--metrics .lh-audit-group__header {\n display: flex;\n justify-content: space-between;\n}\n\n.lh-metric__icon,\n.lh-scorescale-range::before {\n content: '';\n width: var(--score-icon-size);\n height: var(--score-icon-size);\n display: inline-block;\n margin: var(--score-icon-margin);\n}\n\n.lh-metric--pass .lh-metric__value {\n color: var(--color-pass-secondary);\n}\n.lh-metric--pass .lh-metric__icon {\n border-radius: 100%;\n background: var(--color-pass);\n}\n\n.lh-metric--average .lh-metric__value {\n color: var(--color-average-secondary);\n}\n.lh-metric--average .lh-metric__icon {\n background: var(--color-average);\n width: var(--icon-square-size);\n height: var(--icon-square-size);\n}\n\n.lh-metric--fail .lh-metric__value {\n color: var(--color-fail-secondary);\n}\n.lh-metric--fail .lh-metric__icon,\n.lh-metric--error .lh-metric__icon {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n\n.lh-metric--error .lh-metric__value,\n.lh-metric--error .lh-metric__description {\n color: var(--color-fail-secondary);\n}\n\n/* Perf load opportunity */\n\n.lh-load-opportunity__cols {\n display: flex;\n align-items: flex-start;\n}\n\n.lh-load-opportunity__header .lh-load-opportunity__col {\n color: var(--color-gray-600);\n display: unset;\n line-height: calc(2.3 * var(--report-font-size));\n}\n\n.lh-load-opportunity__col {\n display: flex;\n}\n\n.lh-load-opportunity__col--one {\n flex: 5;\n align-items: center;\n margin-right: 2px;\n}\n.lh-load-opportunity__col--two {\n flex: 4;\n text-align: right;\n}\n\n.lh-audit--load-opportunity .lh-audit__display-text {\n text-align: right;\n flex: 0 0 calc(4 * var(--report-font-size));\n}\n\n\n/* Sparkline */\n\n.lh-load-opportunity__sparkline {\n flex: 1;\n margin-top: calc((var(--report-line-height) - var(--sparkline-height)) / 2);\n}\n\n.lh-sparkline {\n height: var(--sparkline-height);\n width: 100%;\n}\n\n.lh-sparkline__bar {\n height: 100%;\n float: right;\n}\n\n.lh-audit--pass .lh-sparkline__bar {\n background: var(--color-pass);\n}\n\n.lh-audit--average .lh-sparkline__bar {\n background: var(--color-average);\n}\n\n.lh-audit--fail .lh-sparkline__bar {\n background: var(--color-fail);\n}\n\n/* Filmstrip */\n\n.lh-filmstrip-container {\n /* smaller gap between metrics and filmstrip */\n margin: -8px auto 0 auto;\n}\n\n.lh-filmstrip {\n display: grid;\n justify-content: space-between;\n padding-bottom: var(--default-padding);\n width: 100%;\n grid-template-columns: repeat(auto-fit, 60px);\n}\n\n.lh-filmstrip__frame {\n text-align: right;\n position: relative;\n}\n\n.lh-filmstrip__thumbnail {\n border: 1px solid var(--report-border-color-secondary);\n max-height: 100px;\n max-width: 60px;\n}\n\n/* Audit */\n\n.lh-audit {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\n\n/* Apply border-top to just the first audit. */\n.lh-audit {\n border-top: 1px solid var(--report-border-color-secondary);\n}\n.lh-audit ~ .lh-audit {\n border-top: none;\n}\n\n\n.lh-audit--error .lh-audit__display-text {\n color: var(--color-fail-secondary);\n}\n\n/* Audit Group */\n\n.lh-audit-group {\n margin-bottom: var(--audit-group-margin-bottom);\n position: relative;\n}\n.lh-audit-group--metrics {\n margin-bottom: calc(var(--audit-group-margin-bottom) / 2);\n}\n\n.lh-audit-group__header::before {\n /* By default, groups don't get an icon */\n content: none;\n width: var(--pwa-icon-size);\n height: var(--pwa-icon-size);\n margin: var(--pwa-icon-margin);\n display: inline-block;\n vertical-align: middle;\n}\n\n/* Style the \"over budget\" columns red. */\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(4),\n.lh-audit-group--budgets #performance-budget tbody tr td:nth-child(5),\n.lh-audit-group--budgets #timing-budget tbody tr td:nth-child(3) {\n color: var(--color-red-700);\n}\n\n/* Align the \"over budget request count\" text to be close to the \"over budget bytes\" column. */\n.lh-audit-group--budgets .lh-table tbody tr td:nth-child(4){\n text-align: right;\n}\n\n.lh-audit-group--budgets .lh-details--budget {\n width: 100%;\n margin: 0 0 var(--default-padding);\n}\n\n.lh-audit-group--pwa-installable .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-installable-gray-url);\n}\n.lh-audit-group--pwa-optimized .lh-audit-group__header::before {\n content: '';\n background-image: var(--pwa-optimized-gray-url);\n}\n.lh-audit-group--pwa-installable.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-installable-color-url);\n}\n.lh-audit-group--pwa-optimized.lh-badged .lh-audit-group__header::before {\n background-image: var(--pwa-optimized-color-url);\n}\n\n.lh-audit-group--metrics .lh-audit-group__summary {\n margin-top: 0;\n margin-bottom: 0;\n}\n\n.lh-audit-group__summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.lh-audit-group__header .lh-chevron {\n margin-top: calc((var(--report-line-height) - 5px) / 2);\n}\n\n.lh-audit-group__header {\n letter-spacing: 0.8px;\n padding: var(--default-padding);\n padding-left: 0;\n}\n\n.lh-audit-group__header, .lh-audit-group__summary {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n color: var(--color-gray-700);\n}\n\n.lh-audit-group__title {\n text-transform: uppercase;\n font-weight: 500;\n}\n\n.lh-audit-group__itemcount {\n color: var(--color-gray-600);\n}\n\n.lh-audit-group__footer {\n color: var(--color-gray-600);\n display: block;\n margin-top: var(--default-padding);\n}\n\n.lh-details,\n.lh-category-header__description,\n.lh-load-opportunity__header,\n.lh-audit-group__footer {\n font-size: var(--report-font-size-secondary);\n line-height: var(--report-line-height-secondary);\n}\n\n.lh-audit-explanation {\n margin: var(--audit-padding-vertical) 0 calc(var(--audit-padding-vertical) / 2) var(--audit-margin-horizontal);\n line-height: var(--audit-explanation-line-height);\n display: inline-block;\n}\n\n.lh-audit--fail .lh-audit-explanation {\n color: var(--color-fail-secondary);\n}\n\n/* Report */\n.lh-list > :not(:last-child) {\n margin-bottom: calc(var(--default-padding) * 2);\n}\n\n.lh-header-container {\n display: block;\n margin: 0 auto;\n position: relative;\n word-wrap: break-word;\n}\n\n.lh-header-container .lh-scores-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n\n\n.lh-report {\n min-width: var(--report-content-min-width);\n}\n\n.lh-exception {\n font-size: large;\n}\n\n.lh-code {\n white-space: normal;\n margin-top: 0;\n font-size: var(--report-monospace-font-size);\n}\n\n.lh-warnings {\n --item-margin: calc(var(--report-line-height) / 6);\n color: var(--color-average-secondary);\n margin: var(--audit-padding-vertical) 0;\n padding: var(--default-padding)\n var(--default-padding)\n var(--default-padding)\n calc(var(--audit-description-padding-left));\n background-color: var(--toplevel-warning-background-color);\n}\n.lh-warnings span {\n font-weight: bold;\n}\n\n.lh-warnings--toplevel {\n --item-margin: calc(var(--header-line-height) / 4);\n color: var(--toplevel-warning-text-color);\n margin-left: auto;\n margin-right: auto;\n max-width: var(--report-content-max-width-minus-edge-gap);\n padding: var(--toplevel-warning-padding);\n border-radius: 8px;\n}\n\n.lh-warnings__msg {\n color: var(--toplevel-warning-message-text-color);\n margin: 0;\n}\n\n.lh-warnings ul {\n margin: 0;\n}\n.lh-warnings li {\n margin: var(--item-margin) 0;\n}\n.lh-warnings li:last-of-type {\n margin-bottom: 0;\n}\n\n.lh-scores-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n}\n.lh-scores-header__solo {\n padding: 0;\n border: 0;\n}\n\n/* Gauge */\n\n.lh-gauge__wrapper--pass {\n color: var(--color-pass-secondary);\n fill: var(--color-pass);\n stroke: var(--color-pass);\n}\n\n.lh-gauge__wrapper--average {\n color: var(--color-average-secondary);\n fill: var(--color-average);\n stroke: var(--color-average);\n}\n\n.lh-gauge__wrapper--fail {\n color: var(--color-fail-secondary);\n fill: var(--color-fail);\n stroke: var(--color-fail);\n}\n\n.lh-gauge__wrapper--not-applicable {\n color: var(--color-not-applicable);\n fill: var(--color-not-applicable);\n stroke: var(--color-not-applicable);\n}\n\n.lh-fraction__wrapper .lh-fraction__content::before {\n content: '';\n height: var(--score-icon-size);\n width: var(--score-icon-size);\n margin: var(--score-icon-margin);\n display: inline-block;\n}\n.lh-fraction__wrapper--pass .lh-fraction__content {\n color: var(--color-pass-secondary);\n}\n.lh-fraction__wrapper--pass .lh-fraction__background {\n background-color: var(--color-pass);\n}\n.lh-fraction__wrapper--pass .lh-fraction__content::before {\n background-color: var(--color-pass);\n border-radius: 50%;\n}\n.lh-fraction__wrapper--average .lh-fraction__content {\n color: var(--color-average-secondary);\n}\n.lh-fraction__wrapper--average .lh-fraction__background,\n.lh-fraction__wrapper--average .lh-fraction__content::before {\n background-color: var(--color-average);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content {\n color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__background {\n background-color: var(--color-fail);\n}\n.lh-fraction__wrapper--fail .lh-fraction__content::before {\n border-left: calc(var(--score-icon-size) / 2) solid transparent;\n border-right: calc(var(--score-icon-size) / 2) solid transparent;\n border-bottom: var(--score-icon-size) solid var(--color-fail);\n}\n.lh-fraction__wrapper--null .lh-fraction__content {\n color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__background {\n background-color: var(--color-gray-700);\n}\n.lh-fraction__wrapper--null .lh-fraction__content::before {\n border-radius: 50%;\n border: calc(0.2 * var(--score-icon-size)) solid var(--color-gray-700);\n}\n\n.lh-fraction__background {\n position: absolute;\n height: 100%;\n width: 100%;\n border-radius: calc(var(--gauge-circle-size) / 2);\n opacity: 0.1;\n z-index: -1;\n}\n\n.lh-fraction__content-wrapper {\n height: var(--gauge-circle-size);\n display: flex;\n align-items: center;\n}\n\n.lh-fraction__content {\n display: flex;\n position: relative;\n align-items: center;\n justify-content: center;\n font-size: calc(0.3 * var(--gauge-circle-size));\n line-height: calc(0.4 * var(--gauge-circle-size));\n width: max-content;\n min-width: calc(1.5 * var(--gauge-circle-size));\n padding: calc(0.1 * var(--gauge-circle-size)) calc(0.2 * var(--gauge-circle-size));\n --score-icon-size: calc(0.21 * var(--gauge-circle-size));\n --score-icon-margin: 0 calc(0.15 * var(--gauge-circle-size)) 0 0;\n}\n\n.lh-gauge {\n stroke-linecap: round;\n width: var(--gauge-circle-size);\n height: var(--gauge-circle-size);\n}\n\n.lh-category .lh-gauge {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n.lh-gauge-base {\n opacity: 0.1;\n}\n\n.lh-gauge-arc {\n fill: none;\n transform-origin: 50% 50%;\n animation: load-gauge var(--transition-length) ease both;\n animation-delay: 250ms;\n}\n\n.lh-gauge__svg-wrapper {\n position: relative;\n height: var(--gauge-circle-size);\n}\n.lh-category .lh-gauge__svg-wrapper,\n.lh-category .lh-fraction__wrapper {\n --gauge-circle-size: var(--gauge-circle-size-big);\n}\n\n/* The plugin badge overlay */\n.lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size);\n height: var(--plugin-badge-size);\n background-color: var(--plugin-badge-background-color);\n background-image: var(--plugin-icon-url);\n background-repeat: no-repeat;\n background-size: var(--plugin-icon-size);\n background-position: 58% 50%;\n content: \"\";\n position: absolute;\n right: -6px;\n bottom: 0px;\n display: block;\n z-index: 100;\n box-shadow: 0 0 4px rgba(0,0,0,.2);\n border-radius: 25%;\n}\n.lh-category .lh-gauge__wrapper--plugin .lh-gauge__svg-wrapper::before {\n width: var(--plugin-badge-size-big);\n height: var(--plugin-badge-size-big);\n}\n\n@keyframes load-gauge {\n from { stroke-dasharray: 0 352; }\n}\n\n.lh-gauge__percentage {\n width: 100%;\n height: var(--gauge-circle-size);\n position: absolute;\n font-family: var(--report-font-family-monospace);\n font-size: calc(var(--gauge-circle-size) * 0.34 + 1.3px);\n line-height: 0;\n text-align: center;\n top: calc(var(--score-container-padding) + var(--gauge-circle-size) / 2);\n}\n\n.lh-category .lh-gauge__percentage {\n --gauge-circle-size: var(--gauge-circle-size-big);\n --gauge-percentage-font-size: var(--gauge-percentage-font-size-big);\n}\n\n.lh-gauge__wrapper,\n.lh-fraction__wrapper {\n position: relative;\n display: flex;\n align-items: center;\n flex-direction: column;\n text-decoration: none;\n padding: var(--score-container-padding);\n\n --transition-length: 1s;\n\n /* Contain the layout style paint & layers during animation*/\n contain: content;\n will-change: opacity; /* Only using for layer promotion */\n}\n\n.lh-gauge__label,\n.lh-fraction__label {\n font-size: var(--gauge-label-font-size);\n font-weight: 500;\n line-height: var(--gauge-label-line-height);\n margin-top: 10px;\n text-align: center;\n color: var(--report-text-color);\n word-break: keep-all;\n}\n\n/* TODO(#8185) use more BEM (.lh-gauge__label--big) instead of relying on descendant selector */\n.lh-category .lh-gauge__label,\n.lh-category .lh-fraction__label {\n --gauge-label-font-size: var(--gauge-label-font-size-big);\n --gauge-label-line-height: var(--gauge-label-line-height-big);\n margin-top: 14px;\n}\n\n.lh-scores-header .lh-gauge__wrapper,\n.lh-scores-header .lh-fraction__wrapper,\n.lh-scores-header .lh-gauge--pwa__wrapper,\n.lh-sticky-header .lh-gauge__wrapper,\n.lh-sticky-header .lh-fraction__wrapper,\n.lh-sticky-header .lh-gauge--pwa__wrapper {\n width: var(--gauge-wrapper-width);\n}\n\n.lh-scorescale {\n display: inline-flex;\n\n gap: calc(var(--default-padding) * 4);\n margin: 16px auto 0 auto;\n font-size: var(--report-font-size-secondary);\n color: var(--color-gray-700);\n\n}\n\n.lh-scorescale-range {\n display: flex;\n align-items: center;\n font-family: var(--report-font-family-monospace);\n white-space: nowrap;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale {\n border: 0;\n display: flex;\n justify-content: center;\n}\n\n.lh-category-header__finalscreenshot .lh-scorescale-range {\n font-family: unset;\n font-size: 12px;\n}\n\n.lh-scorescale-wrap {\n display: contents;\n}\n\n/* Hide category score gauages if it's a single category report */\n.lh-header--solo-category .lh-scores-wrapper {\n display: none;\n}\n\n\n.lh-categories {\n width: 100%;\n}\n\n.lh-category {\n padding: var(--category-padding);\n max-width: var(--report-content-max-width);\n margin: 0 auto;\n\n --sticky-header-height: calc(var(--gauge-circle-size-sm) + var(--score-container-padding) * 2);\n --topbar-plus-sticky-header: calc(var(--topbar-height) + var(--sticky-header-height));\n scroll-margin-top: var(--topbar-plus-sticky-header);\n\n /* Faster recalc style & layout of the report. https://web.dev/content-visibility/ */\n content-visibility: auto;\n contain-intrinsic-size: 1000px;\n}\n\n.lh-category-wrapper {\n border-bottom: 1px solid var(--color-gray-200);\n}\n.lh-category-wrapper:last-of-type {\n border-bottom: 0;\n}\n\n.lh-category-header {\n margin-bottom: var(--section-padding-vertical);\n}\n\n.lh-category-header .lh-score__gauge {\n max-width: 400px;\n width: auto;\n margin: 0px auto;\n}\n\n.lh-category-header__finalscreenshot {\n display: grid;\n grid-template: none / 1fr 1px 1fr;\n justify-items: center;\n align-items: center;\n gap: var(--report-line-height);\n min-height: 288px;\n margin-bottom: var(--default-padding);\n}\n\n.lh-final-ss-image {\n /* constrain the size of the image to not be too large */\n max-height: calc(var(--gauge-circle-size-big) * 2.8);\n max-width: calc(var(--gauge-circle-size-big) * 3.5);\n border: 1px solid var(--color-gray-200);\n padding: 4px;\n border-radius: 3px;\n display: block;\n}\n\n.lh-category-headercol--separator {\n background: var(--color-gray-200);\n width: 1px;\n height: var(--gauge-circle-size-big);\n}\n\n@media screen and (max-width: 780px) {\n .lh-category-header__finalscreenshot {\n grid-template: 1fr 1fr / none\n }\n .lh-category-headercol--separator {\n display: none;\n }\n}\n\n\n/* 964 fits the min-width of the filmstrip */\n@media screen and (max-width: 964px) {\n .lh-report {\n margin-left: 0;\n width: 100%;\n }\n}\n\n@media print {\n body {\n -webkit-print-color-adjust: exact; /* print background colors */\n }\n .lh-container {\n display: block;\n }\n .lh-report {\n margin-left: 0;\n padding-top: 0;\n }\n .lh-categories {\n margin-top: 0;\n }\n}\n\n.lh-table {\n position: relative;\n border-collapse: separate;\n border-spacing: 0;\n /* Can't assign padding to table, so shorten the width instead. */\n width: calc(100% - var(--audit-description-padding-left) - var(--stackpack-padding-horizontal));\n border: 1px solid var(--report-border-color-secondary);\n}\n\n.lh-table thead th {\n position: sticky;\n top: calc(var(--topbar-plus-sticky-header) + 1em);\n z-index: 1;\n background-color: var(--report-background-color);\n border-bottom: 1px solid var(--report-border-color-secondary);\n font-weight: normal;\n color: var(--color-gray-600);\n /* See text-wrapping comment on .lh-container. */\n word-break: normal;\n}\n\n.lh-table hr {\n border-width: 0.5px;\n}\n\n.lh-row--even {\n background-color: var(--table-higlight-background-color);\n}\n.lh-row--hidden {\n display: none;\n}\n\n.lh-table th,\n.lh-table td {\n padding: var(--default-padding);\n}\n\n.lh-table tr {\n vertical-align: middle;\n}\n\n/* Looks unnecessary, but mostly for keeping the s left-aligned */\n.lh-table-column--text,\n.lh-table-column--source-location,\n.lh-table-column--url,\n/* .lh-table-column--thumbnail, */\n/* .lh-table-column--empty,*/\n.lh-table-column--code,\n.lh-table-column--node {\n text-align: left;\n}\n\n.lh-table-column--code {\n min-width: 100px;\n}\n\n.lh-table-column--bytes,\n.lh-table-column--timespanMs,\n.lh-table-column--ms,\n.lh-table-column--numeric {\n text-align: right;\n word-break: normal;\n}\n\n\n\n.lh-table .lh-table-column--thumbnail {\n width: var(--image-preview-size);\n}\n\n.lh-table-column--url {\n min-width: 250px;\n}\n\n.lh-table-column--text {\n min-width: 80px;\n}\n\n/* Keep columns narrow if they follow the URL column */\n/* 12% was determined to be a decent narrow width, but wide enough for column headings */\n.lh-table-column--url + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--ms,\n.lh-table-column--url + .lh-table-column--ms + th.lh-table-column--bytes,\n.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--timespanMs {\n width: 12%;\n}\n\n.lh-text__url-host {\n display: inline;\n}\n\n.lh-text__url-host {\n margin-left: calc(var(--report-font-size) / 2);\n opacity: 0.6;\n font-size: 90%\n}\n\n.lh-thumbnail {\n object-fit: cover;\n width: var(--image-preview-size);\n height: var(--image-preview-size);\n display: block;\n}\n\n.lh-unknown pre {\n overflow: scroll;\n border: solid 1px var(--color-gray-200);\n}\n\n.lh-text__url > a {\n color: inherit;\n text-decoration: none;\n}\n\n.lh-text__url > a:hover {\n text-decoration: underline dotted #999;\n}\n\n.lh-sub-item-row {\n margin-left: 20px;\n margin-bottom: 0;\n color: var(--color-gray-700);\n}\n.lh-sub-item-row td {\n padding-top: 4px;\n padding-bottom: 4px;\n padding-left: 20px;\n}\n\n/* Chevron\n https://codepen.io/paulirish/pen/LmzEmK\n */\n.lh-chevron {\n --chevron-angle: 42deg;\n /* Edge doesn't support transform: rotate(calc(...)), so we define it here */\n --chevron-angle-right: -42deg;\n width: var(--chevron-size);\n height: var(--chevron-size);\n margin-top: calc((var(--report-line-height) - 12px) / 2);\n}\n\n.lh-chevron__lines {\n transition: transform 0.4s;\n transform: translateY(var(--report-line-height));\n}\n.lh-chevron__line {\n stroke: var(--chevron-line-stroke);\n stroke-width: var(--chevron-size);\n stroke-linecap: square;\n transform-origin: 50%;\n transform: rotate(var(--chevron-angle));\n transition: transform 300ms, stroke 300ms;\n}\n\n.lh-expandable-details .lh-chevron__line-right,\n.lh-expandable-details[open] .lh-chevron__line-left {\n transform: rotate(var(--chevron-angle-right));\n}\n\n.lh-expandable-details[open] .lh-chevron__line-right {\n transform: rotate(var(--chevron-angle));\n}\n\n\n.lh-expandable-details[open] .lh-chevron__lines {\n transform: translateY(calc(var(--chevron-size) * -1));\n}\n\n.lh-expandable-details[open] {\n animation: 300ms openDetails forwards;\n padding-bottom: var(--default-padding);\n}\n\n@keyframes openDetails {\n from {\n outline: 1px solid var(--report-background-color);\n }\n to {\n outline: 1px solid;\n box-shadow: 0 2px 4px rgba(0, 0, 0, .24);\n }\n}\n\n@media screen and (max-width: 780px) {\n /* no black outline if we're not confident the entire table can be displayed within bounds */\n .lh-expandable-details[open] {\n animation: none;\n }\n}\n\n.lh-expandable-details[open] summary, details.lh-clump > summary {\n border-bottom: 1px solid var(--report-border-color-secondary);\n}\ndetails.lh-clump[open] > summary {\n border-bottom-width: 0;\n}\n\n\n\ndetails .lh-clump-toggletext--hide,\ndetails[open] .lh-clump-toggletext--show { display: none; }\ndetails[open] .lh-clump-toggletext--hide { display: block;}\n\n\n/* Tooltip */\n.lh-tooltip-boundary {\n position: relative;\n}\n\n.lh-tooltip {\n position: absolute;\n display: none; /* Don't retain these layers when not needed */\n opacity: 0;\n background: #ffffff;\n white-space: pre-line; /* Render newlines in the text */\n min-width: 246px;\n max-width: 275px;\n padding: 15px;\n border-radius: 5px;\n text-align: initial;\n line-height: 1.4;\n}\n/* shrink tooltips to not be cutoff on left edge of narrow viewports\n 45vw is chosen to be ~= width of the left column of metrics\n*/\n@media screen and (max-width: 535px) {\n .lh-tooltip {\n min-width: 45vw;\n padding: 3vw;\n }\n}\n\n.lh-tooltip-boundary:hover .lh-tooltip {\n display: block;\n animation: fadeInTooltip 250ms;\n animation-fill-mode: forwards;\n animation-delay: 850ms;\n bottom: 100%;\n z-index: 1;\n will-change: opacity;\n right: 0;\n pointer-events: none;\n}\n\n.lh-tooltip::before {\n content: \"\";\n border: solid transparent;\n border-bottom-color: #fff;\n border-width: 10px;\n position: absolute;\n bottom: -20px;\n right: 6px;\n transform: rotate(180deg);\n pointer-events: none;\n}\n\n@keyframes fadeInTooltip {\n 0% { opacity: 0; }\n 75% { opacity: 1; }\n 100% { opacity: 1; filter: drop-shadow(1px 0px 1px #aaa) drop-shadow(0px 2px 4px hsla(206, 6%, 25%, 0.15)); pointer-events: auto; }\n}\n\n/* Element screenshot */\n.lh-element-screenshot {\n float: left;\n margin-right: 20px;\n}\n.lh-element-screenshot__content {\n overflow: hidden;\n min-width: 110px;\n display: flex;\n justify-content: center;\n background-color: var(--report-background-color);\n}\n.lh-element-screenshot__image {\n position: relative;\n /* Set by ElementScreenshotRenderer.installFullPageScreenshotCssVariable */\n background-image: var(--element-screenshot-url);\n outline: 2px solid #777;\n background-color: white;\n background-repeat: no-repeat;\n}\n.lh-element-screenshot__mask {\n position: absolute;\n background: #555;\n opacity: 0.8;\n}\n.lh-element-screenshot__element-marker {\n position: absolute;\n outline: 2px solid var(--color-lime-400);\n}\n.lh-element-screenshot__overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 2000; /* .lh-topbar is 1000 */\n background: var(--screenshot-overlay-background);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: zoom-out;\n}\n\n.lh-element-screenshot__overlay .lh-element-screenshot {\n margin-right: 0; /* clearing margin used in thumbnail case */\n outline: 1px solid var(--color-gray-700);\n}\n\n.lh-screenshot-overlay--enabled .lh-element-screenshot {\n cursor: zoom-out;\n}\n.lh-screenshot-overlay--enabled .lh-node .lh-element-screenshot {\n cursor: zoom-in;\n}\n\n\n.lh-meta__items {\n --meta-icon-size: calc(var(--report-icon-size) * 0.667);\n padding: var(--default-padding);\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n background-color: var(--env-item-background-color);\n border-radius: 3px;\n margin: 0 0 var(--default-padding) 0;\n font-size: 12px;\n column-gap: var(--default-padding);\n color: var(--color-gray-700);\n}\n\n.lh-meta__item {\n display: block;\n list-style-type: none;\n position: relative;\n padding: 0 0 0 calc(var(--meta-icon-size) + var(--default-padding) * 2);\n cursor: unset; /* disable pointer cursor from report-icon */\n}\n\n.lh-meta__item.lh-tooltip-boundary {\n text-decoration: dotted underline var(--color-gray-500);\n cursor: help;\n}\n\n.lh-meta__item.lh-report-icon::before {\n position: absolute;\n left: var(--default-padding);\n width: var(--meta-icon-size);\n height: var(--meta-icon-size);\n}\n\n.lh-meta__item.lh-report-icon:hover::before {\n opacity: 0.7;\n}\n\n.lh-meta__item .lh-tooltip {\n color: var(--color-gray-800);\n}\n\n.lh-meta__item .lh-tooltip::before {\n right: auto; /* Set the tooltip arrow to the leftside */\n left: 6px;\n}\n\n/* Change the grid for narrow viewport. */\n@media screen and (max-width: 640px) {\n .lh-meta__items {\n grid-template-columns: 1fr 1fr;\n }\n}\n@media screen and (max-width: 535px) {\n .lh-meta__items {\n display: block;\n }\n}\n\n\n/*# sourceURL=report-styles.css */\n"); el0.append(el1); return el0; } diff --git a/report/renderer/details-renderer.js b/report/renderer/details-renderer.js index 1c8e6feb91f6..8bd23b5080cd 100644 --- a/report/renderer/details-renderer.js +++ b/report/renderer/details-renderer.js @@ -371,14 +371,10 @@ export class DetailsRenderer { if (label) { const rowEl = this._dom.createElement('tr'); const tdEl = this._dom.createChildOf(rowEl, 'td'); + tdEl.setAttribute('colspan', headings.length.toString()); tdEl.append(this._renderText(label)); tdEl.append(this._dom.createElement('hr')); rowEl.classList.add('lh-sub-item-row'); - // Need a `td` per column so that background color of the table will - // span the width of the label row. - for (let i = 1; i < headings.length; i++) { - this._dom.createChildOf(rowEl, 'td'); - } fragment.append(rowEl); }