diff --git a/lighthouse-core/audits/accessibility/axe-audit.js b/lighthouse-core/audits/accessibility/axe-audit.js index 1b2fefa6f963..cef8c989696d 100644 --- a/lighthouse-core/audits/accessibility/axe-audit.js +++ b/lighthouse-core/audits/accessibility/axe-audit.js @@ -44,11 +44,11 @@ class AxeAudit extends Audit { const impact = rule && rule.impact; const tags = rule && rule.tags; - /** @type {Array<{node: LH.Audit.DetailsRendererNodeDetailsJSON}>} */ + /** @type {LH.Audit.Details.Table['items']}>} */ let items = []; if (rule && rule.nodes) { items = rule.nodes.map(node => ({ - node: /** @type {LH.Audit.DetailsRendererNodeDetailsJSON} */ ({ + node: /** @type {LH.Audit.Details.NodeValue} */ ({ type: 'node', selector: Array.isArray(node.target) ? node.target.join(' ') : '', path: node.path, @@ -58,16 +58,27 @@ class AxeAudit extends Audit { })); } + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'node', itemType: 'node', text: str_(UIStrings.failingElementsHeader)}, ]; + /** @type {LH.Audit.Details.Diagnostic|undefined} */ + let diagnostic; + if (impact || tags) { + diagnostic = { + type: 'diagnostic', + impact, + tags, + }; + } + return { rawValue: typeof rule === 'undefined', extendedInfo: { value: rule, }, - details: {...Audit.makeTableDetails(headings, items), impact, tags}, + details: {...Audit.makeTableDetails(headings, items), diagnostic}, }; } } diff --git a/lighthouse-core/audits/audit.js b/lighthouse-core/audits/audit.js index 98b44ba30757..ce6188de9559 100644 --- a/lighthouse-core/audits/audit.js +++ b/lighthouse-core/audits/audit.js @@ -102,10 +102,10 @@ class Audit { } /** - * @param {Array} headings - * @param {Array>} results - * @param {LH.Audit.DetailsRendererDetailsSummary=} summary - * @return {LH.Audit.DetailsRendererDetailsJSON} + * @param {LH.Audit.Details.Table['headings']} headings + * @param {LH.Audit.Details.Table['items']} results + * @param {LH.Audit.Details.Table['summary']=} summary + * @return {LH.Audit.Details.Table} */ static makeTableDetails(headings, results, summary) { if (results.length === 0) { @@ -126,8 +126,8 @@ class Audit { } /** - * @param {Array} headings - * @param {Array} items + * @param {LH.Audit.Details.Opportunity['headings']} headings + * @param {LH.Audit.Details.Opportunity['items']} items * @param {number} overallSavingsMs * @param {number=} overallSavingsBytes * @return {LH.Audit.Details.Opportunity} diff --git a/lighthouse-core/audits/bootup-time.js b/lighthouse-core/audits/bootup-time.js index 1c74d3ac0141..c709e5e03325 100644 --- a/lighthouse-core/audits/bootup-time.js +++ b/lighthouse-core/audits/bootup-time.js @@ -159,6 +159,7 @@ class BootupTime extends Audit { const summary = {wastedMs: totalBootupTime}; + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)}, {key: 'total', granularity: 1, itemType: 'ms', text: str_(UIStrings.columnTotal)}, diff --git a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js index 1a796153e1b8..3cfc199c5c9c 100644 --- a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js +++ b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js @@ -86,6 +86,7 @@ class TotalByteWeight extends ByteEfficiencyAudit { context.options.scoreMedian ); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)}, {key: 'totalBytes', itemType: 'bytes', text: str_(i18n.UIStrings.columnSize)}, diff --git a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js index d866ca6d8a38..4fd21e9f3f76 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js +++ b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js @@ -237,12 +237,19 @@ class CacheHeaders extends Audit { totalWastedBytes += wastedBytes; if (url.includes('?')) queryStringCount++; + // Include cacheControl info (if it exists) per url as a diagnostic. + /** @type {LH.Audit.Details.Diagnostic|undefined} */ + let diagnostic; + if (cacheControl) { + diagnostic = { + type: 'diagnostic', + ...cacheControl, + }; + } + results.push({ url, - // Include cacheControl in results, but cast as any so table types - // are happy. cacheControl is not shown in the table so this is OK. - // TODO(bckenny): fix DetailsItem - cacheControl: /** @type {any} */ (cacheControl), + diagnostic, cacheLifetimeMs: cacheLifetimeInSeconds * 1000, cacheHitProbability, totalBytes, @@ -260,6 +267,7 @@ class CacheHeaders extends Audit { context.options.scoreMedian ); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)}, // TODO(i18n): pre-compute localized duration diff --git a/lighthouse-core/audits/critical-request-chains.js b/lighthouse-core/audits/critical-request-chains.js index 3b02ddd40812..08a45ddff3fc 100644 --- a/lighthouse-core/audits/critical-request-chains.js +++ b/lighthouse-core/audits/critical-request-chains.js @@ -211,7 +211,7 @@ class CriticalRequestChains extends Audit { }, }, details: { - type: 'criticalrequestchain', + type: /** @type {'criticalrequestchain'} */('criticalrequestchain'), chains: flattenedChains, longestChain, }, diff --git a/lighthouse-core/audits/deprecations.js b/lighthouse-core/audits/deprecations.js index 0df9375c9464..3a47a712f791 100644 --- a/lighthouse-core/audits/deprecations.js +++ b/lighthouse-core/audits/deprecations.js @@ -45,6 +45,7 @@ class Deprecations extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'value', itemType: 'code', text: 'Deprecation / Warning'}, {key: 'url', itemType: 'url', text: 'URL'}, diff --git a/lighthouse-core/audits/diagnostics.js b/lighthouse-core/audits/diagnostics.js index 481bb1bfbd69..e4c17c78898e 100644 --- a/lighthouse-core/audits/diagnostics.js +++ b/lighthouse-core/audits/diagnostics.js @@ -67,7 +67,11 @@ class Diagnostics extends Audit { return { score: 1, rawValue: 1, - details: {items: [diagnostics]}, + details: { + type: 'diagnostic', + // TODO: Consider not nesting diagnostics under `items`. + items: [diagnostics], + }, }; } } diff --git a/lighthouse-core/audits/dobetterweb/dom-size.js b/lighthouse-core/audits/dobetterweb/dom-size.js index fa34ae8b7bf8..046d5a42c94b 100644 --- a/lighthouse-core/audits/dobetterweb/dom-size.js +++ b/lighthouse-core/audits/dobetterweb/dom-size.js @@ -101,13 +101,14 @@ class DOMSize extends Audit { context.options.scoreMedian ); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'statistic', itemType: 'text', text: str_(UIStrings.columnStatistic)}, {key: 'element', itemType: 'code', text: str_(UIStrings.columnElement)}, {key: 'value', itemType: 'numeric', text: str_(UIStrings.columnValue)}, ]; - /** @type {Array>} */ + /** @type {LH.Audit.Details.Table['items']} */ const items = [ { statistic: str_(UIStrings.statisticDOMNodes), diff --git a/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js b/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js index d518d8a7ee85..a599dde47703 100644 --- a/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +++ b/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js @@ -55,6 +55,7 @@ class ExternalAnchorsUseRelNoopenerAudit extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'href', itemType: 'url', text: 'URL'}, {key: 'target', itemType: 'text', text: 'Target'}, diff --git a/lighthouse-core/audits/dobetterweb/geolocation-on-start.js b/lighthouse-core/audits/dobetterweb/geolocation-on-start.js index 56d727d95be4..1269e29cc0e6 100644 --- a/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +++ b/lighthouse-core/audits/dobetterweb/geolocation-on-start.js @@ -37,6 +37,7 @@ class GeolocationOnStart extends ViolationAudit { // 'Only request geolocation information in response to a user gesture.' const results = ViolationAudit.getViolationResults(artifacts, /geolocation/); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, {key: 'label', itemType: 'text', text: 'Location'}, diff --git a/lighthouse-core/audits/dobetterweb/js-libraries.js b/lighthouse-core/audits/dobetterweb/js-libraries.js index 3a970ff7f17e..9e24a02aa77c 100644 --- a/lighthouse-core/audits/dobetterweb/js-libraries.js +++ b/lighthouse-core/audits/dobetterweb/js-libraries.js @@ -32,10 +32,11 @@ class JsLibrariesAudit extends Audit { static audit(artifacts) { const libDetails = artifacts.JSLibraries.map(lib => ({ name: lib.name, - version: lib.version, // null if not detected - npm: lib.npmPkgName || null, // ~70% of libs come with this field + version: lib.version || undefined, // null if not detected + npm: lib.npmPkgName || undefined, // ~70% of libs come with this field })); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'name', itemType: 'text', text: 'Name'}, {key: 'version', itemType: 'text', text: 'Version'}, diff --git a/lighthouse-core/audits/dobetterweb/no-document-write.js b/lighthouse-core/audits/dobetterweb/no-document-write.js index fa741a343ce8..d78c5bb42d6c 100644 --- a/lighthouse-core/audits/dobetterweb/no-document-write.js +++ b/lighthouse-core/audits/dobetterweb/no-document-write.js @@ -35,6 +35,7 @@ class NoDocWriteAudit extends ViolationAudit { static audit(artifacts) { const results = ViolationAudit.getViolationResults(artifacts, /document\.write/); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, {key: 'label', itemType: 'text', text: 'Location'}, diff --git a/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js b/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js index cfa7fdf4b8ff..1230e5c69dcb 100644 --- a/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js +++ b/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js @@ -135,7 +135,7 @@ class NoVulnerableLibrariesAudit extends Audit { } let totalVulns = 0; - /** @type {Array<{highestSeverity: string, vulnCount: number, detectedLib: LH.Audit.DetailsRendererLinkDetailsJSON}>} */ + /** @type {Array<{highestSeverity: string, vulnCount: number, detectedLib: LH.Audit.Details.LinkValue}>} */ const vulnerabilityResults = []; const libraryVulns = foundLibraries.map(lib => { @@ -175,6 +175,7 @@ class NoVulnerableLibrariesAudit extends Audit { displayValue = `${totalVulns} vulnerability detected`; } + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'detectedLib', itemType: 'link', text: 'Library Version'}, {key: 'vulnCount', itemType: 'text', text: 'Vulnerability Count'}, diff --git a/lighthouse-core/audits/dobetterweb/notification-on-start.js b/lighthouse-core/audits/dobetterweb/notification-on-start.js index 67d260315154..101e0a7b65e6 100644 --- a/lighthouse-core/audits/dobetterweb/notification-on-start.js +++ b/lighthouse-core/audits/dobetterweb/notification-on-start.js @@ -36,6 +36,7 @@ class NotificationOnStart extends ViolationAudit { static audit(artifacts) { const results = ViolationAudit.getViolationResults(artifacts, /notification permission/); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, {key: 'label', itemType: 'text', text: 'Location'}, diff --git a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js index bac18a9e77b1..27c090a89564 100644 --- a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js +++ b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js @@ -29,7 +29,7 @@ class PasswordInputsCanBePastedIntoAudit extends Audit { static audit(artifacts) { const passwordInputsWithPreventedPaste = artifacts.PasswordInputsWithPreventedPaste; - /** @type {Array<{node: LH.Audit.DetailsRendererNodeDetailsJSON}>} */ + /** @type {LH.Audit.Details.Table['items']} */ const items = []; passwordInputsWithPreventedPaste.forEach(input => { items.push({ @@ -37,6 +37,7 @@ class PasswordInputsCanBePastedIntoAudit extends Audit { }); }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'node', itemType: 'node', text: 'Failing Elements'}, ]; diff --git a/lighthouse-core/audits/dobetterweb/uses-http2.js b/lighthouse-core/audits/dobetterweb/uses-http2.js index fd11ed12c38f..998167b6ff3d 100644 --- a/lighthouse-core/audits/dobetterweb/uses-http2.js +++ b/lighthouse-core/audits/dobetterweb/uses-http2.js @@ -68,6 +68,7 @@ class UsesHTTP2Audit extends Audit { displayValue = `${resources.length} request not served via HTTP/2`; } + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, {key: 'protocol', itemType: 'text', text: 'Protocol'}, diff --git a/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js b/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js index eb84b183ef15..7d966aa13668 100644 --- a/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +++ b/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js @@ -36,6 +36,7 @@ class PassiveEventsAudit extends ViolationAudit { static audit(artifacts) { const results = ViolationAudit.getViolationResults(artifacts, /passive event listener/); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, {key: 'label', itemType: 'text', text: 'Location'}, diff --git a/lighthouse-core/audits/errors-in-console.js b/lighthouse-core/audits/errors-in-console.js index 644ed4aa5568..74195a8e6335 100644 --- a/lighthouse-core/audits/errors-in-console.js +++ b/lighthouse-core/audits/errors-in-console.js @@ -60,6 +60,7 @@ class ErrorLogs extends Audit { const tableRows = consoleRows.concat(runtimeExRows); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, {key: 'description', itemType: 'code', text: 'Description'}, diff --git a/lighthouse-core/audits/font-display.js b/lighthouse-core/audits/font-display.js index ae985bae2f57..2a1ce26099fe 100644 --- a/lighthouse-core/audits/font-display.js +++ b/lighthouse-core/audits/font-display.js @@ -123,6 +123,7 @@ class FontDisplay extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)}, {key: 'wastedMs', itemType: 'ms', text: str_(i18n.UIStrings.columnWastedMs)}, diff --git a/lighthouse-core/audits/image-aspect-ratio.js b/lighthouse-core/audits/image-aspect-ratio.js index 37dafc981454..a3fc6bdece1f 100644 --- a/lighthouse-core/audits/image-aspect-ratio.js +++ b/lighthouse-core/audits/image-aspect-ratio.js @@ -93,6 +93,7 @@ class ImageAspectRatio extends Audit { if (!processed.doRatiosMatch) results.push(processed); }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'thumbnail', text: ''}, {key: 'url', itemType: 'url', text: 'URL'}, diff --git a/lighthouse-core/audits/is-on-https.js b/lighthouse-core/audits/is-on-https.js index ccdecf0e0e17..d64cf8ed5c8e 100644 --- a/lighthouse-core/audits/is-on-https.js +++ b/lighthouse-core/audits/is-on-https.js @@ -62,6 +62,7 @@ class HTTPS extends Audit { const items = Array.from(new Set(insecureURLs)).map(url => ({url})); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'Insecure URL'}, ]; diff --git a/lighthouse-core/audits/main-thread-tasks.js b/lighthouse-core/audits/main-thread-tasks.js index 8b044c20cd24..437b0bbef946 100644 --- a/lighthouse-core/audits/main-thread-tasks.js +++ b/lighthouse-core/audits/main-thread-tasks.js @@ -41,6 +41,7 @@ class MainThreadTasks extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'type', itemType: 'text', text: 'Task Type'}, {key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'}, diff --git a/lighthouse-core/audits/mainthread-work-breakdown.js b/lighthouse-core/audits/mainthread-work-breakdown.js index 9ebb5686323c..9325693aebf6 100644 --- a/lighthouse-core/audits/mainthread-work-breakdown.js +++ b/lighthouse-core/audits/mainthread-work-breakdown.js @@ -105,6 +105,7 @@ class MainThreadWorkBreakdown extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'groupLabel', itemType: 'text', text: str_(UIStrings.columnCategory)}, {key: 'duration', itemType: 'ms', granularity: 1, text: str_(i18n.UIStrings.columnTimeSpent)}, diff --git a/lighthouse-core/audits/metrics.js b/lighthouse-core/audits/metrics.js index 06f22c2e10c6..d81691a348a3 100644 --- a/lighthouse-core/audits/metrics.js +++ b/lighthouse-core/audits/metrics.js @@ -108,8 +108,12 @@ class Metrics extends Audit { } } - /** @type {MetricsDetails} */ - const details = {items: [metrics]}; + /** @type {LH.Audit.Details.Diagnostic} */ + const details = { + type: 'diagnostic', + // TODO: Consider not nesting metrics under `items`. + items: [metrics], + }; return { score: 1, @@ -155,6 +159,4 @@ class Metrics extends Audit { * @property {number} observedSpeedIndexTs */ -/** @typedef {{items: [UberMetricsItem]}} MetricsDetails */ - module.exports = Metrics; diff --git a/lighthouse-core/audits/mixed-content.js b/lighthouse-core/audits/mixed-content.js index f495f204a5e7..979377518d0b 100644 --- a/lighthouse-core/audits/mixed-content.js +++ b/lighthouse-core/audits/mixed-content.js @@ -129,6 +129,7 @@ class MixedContent extends Audit { const displayValue = `${Util.formatNumber(upgradeableResources.length)} ${upgradeableResources.length === 1 ? 'request' : 'requests'}`; + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'fullUrl', itemType: 'url', text: 'URL'}, ]; diff --git a/lighthouse-core/audits/multi-check-audit.js b/lighthouse-core/audits/multi-check-audit.js index a41cb33e045a..249d0c1e10a4 100644 --- a/lighthouse-core/audits/multi-check-audit.js +++ b/lighthouse-core/audits/multi-check-audit.js @@ -41,7 +41,13 @@ class MultiCheckAudit extends Audit { }); } - const details = {items: [detailsItem]}; + // Include the detailed pass/fail checklist as a diagnostic. + /** @type {LH.Audit.Details.Diagnostic} */ + const details = { + type: 'diagnostic', + // TODO: Consider not nesting detailsItem under `items`. + items: [detailsItem], + }; // If we fail, share the failures if (result.failures.length > 0) { diff --git a/lighthouse-core/audits/network-requests.js b/lighthouse-core/audits/network-requests.js index 5ef3c2e88d4b..dd7c3da0f5c5 100644 --- a/lighthouse-core/audits/network-requests.js +++ b/lighthouse-core/audits/network-requests.js @@ -53,6 +53,7 @@ class NetworkRequests extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, {key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'}, diff --git a/lighthouse-core/audits/network-rtt.js b/lighthouse-core/audits/network-rtt.js index 9afa8ce17bcb..49986ca85925 100644 --- a/lighthouse-core/audits/network-rtt.js +++ b/lighthouse-core/audits/network-rtt.js @@ -59,6 +59,7 @@ class NetworkRTT extends Audit { results.sort((a, b) => b.rtt - a.rtt); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'origin', itemType: 'text', text: str_(i18n.UIStrings.columnURL)}, {key: 'rtt', itemType: 'ms', granularity: 1, text: str_(i18n.UIStrings.columnTimeSpent)}, diff --git a/lighthouse-core/audits/network-server-latency.js b/lighthouse-core/audits/network-server-latency.js index 0a1748dd8f78..1f3b378530c6 100644 --- a/lighthouse-core/audits/network-server-latency.js +++ b/lighthouse-core/audits/network-server-latency.js @@ -57,6 +57,7 @@ class NetworkServerLatency extends Audit { results.sort((a, b) => b.serverReponseTime - a.serverReponseTime); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'origin', itemType: 'text', text: str_(i18n.UIStrings.columnURL)}, {key: 'serverReponseTime', itemType: 'ms', granularity: 1, diff --git a/lighthouse-core/audits/predictive-perf.js b/lighthouse-core/audits/predictive-perf.js index 313b1432b463..7051a8f7cb0d 100644 --- a/lighthouse-core/audits/predictive-perf.js +++ b/lighthouse-core/audits/predictive-perf.js @@ -90,7 +90,11 @@ class PredictivePerf extends Audit { score, rawValue: values.roughEstimateOfTTI, displayValue: Util.formatMilliseconds(values.roughEstimateOfTTI), - details: {items: [values]}, + details: { + type: 'diagnostic', + // TODO: Consider not nesting values under `items`. + items: [values], + }, }; } } diff --git a/lighthouse-core/audits/screenshot-thumbnails.js b/lighthouse-core/audits/screenshot-thumbnails.js index 1e65c3555ac8..edb57026046f 100644 --- a/lighthouse-core/audits/screenshot-thumbnails.js +++ b/lighthouse-core/audits/screenshot-thumbnails.js @@ -117,8 +117,9 @@ class ScreenshotThumbnails extends Audit { }); } let base64Data; - if (cachedThumbnails.has(frameForTimestamp)) { - base64Data = cachedThumbnails.get(frameForTimestamp); + const cachedThumbnail = cachedThumbnails.get(frameForTimestamp); + if (cachedThumbnail) { + base64Data = cachedThumbnail; } else { const imageData = frameForTimestamp.getParsedImage(); const thumbnailImageData = ScreenshotThumbnails.scaleImageToThumbnail(imageData); diff --git a/lighthouse-core/audits/seo/font-size.js b/lighthouse-core/audits/seo/font-size.js index 591288ba9a52..bd569d01ef21 100644 --- a/lighthouse-core/audits/seo/font-size.js +++ b/lighthouse-core/audits/seo/font-size.js @@ -236,6 +236,7 @@ class FontSize extends Audit { (visitedTextLength - failingTextLength) / visitedTextLength * 100; const pageUrl = artifacts.URL.finalUrl; + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'source', itemType: 'url', text: 'Source'}, {key: 'selector', itemType: 'code', text: 'Selector'}, diff --git a/lighthouse-core/audits/seo/hreflang.js b/lighthouse-core/audits/seo/hreflang.js index 2c1784171391..b7749725fc1f 100644 --- a/lighthouse-core/audits/seo/hreflang.js +++ b/lighthouse-core/audits/seo/hreflang.js @@ -105,6 +105,7 @@ class Hreflang extends Audit { .filter(h => h.name.toLowerCase() === LINK_HEADER && !headerHasValidHreflangs(h.value)) .forEach(h => invalidHreflangs.push({source: `${h.name}: ${h.value}`})); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'source', itemType: 'code', text: 'Source'}, ]; diff --git a/lighthouse-core/audits/seo/is-crawlable.js b/lighthouse-core/audits/seo/is-crawlable.js index 12454799f3d8..a61b6ff75df6 100644 --- a/lighthouse-core/audits/seo/is-crawlable.js +++ b/lighthouse-core/audits/seo/is-crawlable.js @@ -84,7 +84,7 @@ class IsCrawlable extends Audit { return MainResource.request({devtoolsLog, URL: artifacts.URL}, context) .then(mainResource => { - /** @type {Array>} */ + /** @type {LH.Audit.Details.Table['items']} */ const blockingDirectives = []; if (metaRobots) { @@ -120,6 +120,7 @@ class IsCrawlable extends Audit { } } + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'source', itemType: 'code', text: 'Blocking Directive Source'}, ]; diff --git a/lighthouse-core/audits/seo/link-text.js b/lighthouse-core/audits/seo/link-text.js index d979e96fe13e..fc95ebd28c75 100644 --- a/lighthouse-core/audits/seo/link-text.js +++ b/lighthouse-core/audits/seo/link-text.js @@ -59,6 +59,7 @@ class LinkText extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'href', itemType: 'url', text: 'Link destination'}, {key: 'text', itemType: 'text', text: 'Link Text'}, diff --git a/lighthouse-core/audits/seo/plugins.js b/lighthouse-core/audits/seo/plugins.js index 30eff7e93adf..18bc35a1f861 100644 --- a/lighthouse-core/audits/seo/plugins.js +++ b/lighthouse-core/audits/seo/plugins.js @@ -140,6 +140,7 @@ class Plugins extends Audit { }; }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'source', itemType: 'code', text: 'Element source'}, ]; diff --git a/lighthouse-core/audits/seo/robots-txt.js b/lighthouse-core/audits/seo/robots-txt.js index bc7cdd25ede6..cd13d74315dc 100644 --- a/lighthouse-core/audits/seo/robots-txt.js +++ b/lighthouse-core/audits/seo/robots-txt.js @@ -205,6 +205,7 @@ class RobotsTxt extends Audit { const validationErrors = validateRobots(content); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'index', itemType: 'text', text: 'Line #'}, {key: 'line', itemType: 'code', text: 'Content'}, diff --git a/lighthouse-core/audits/seo/tap-targets.js b/lighthouse-core/audits/seo/tap-targets.js index b82e51fdb8bf..63bdb29c6fef 100644 --- a/lighthouse-core/audits/seo/tap-targets.js +++ b/lighthouse-core/audits/seo/tap-targets.js @@ -243,7 +243,7 @@ function getTableItems(overlapFailures) { /** * @param {LH.Artifacts.TapTarget} target - * @returns {LH.Audit.DetailsRendererNodeDetailsJSON} + * @returns {LH.Audit.Details.NodeValue} */ function targetToTableNode(target) { return { @@ -289,6 +289,7 @@ class TapTargets extends Audit { const overlapFailuresForDisplay = mergeSymmetricFailures(overlapFailures); const tableItems = getTableItems(overlapFailuresForDisplay); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'tapTarget', itemType: 'node', text: str_(UIStrings.tapTargetHeader)}, {key: 'size', itemType: 'text', text: str_(UIStrings.sizeHeader)}, @@ -339,8 +340,8 @@ module.exports.UIStrings = UIStrings; }} BoundedTapTarget */ /** @typedef {{ - tapTarget: LH.Audit.DetailsRendererNodeDetailsJSON; - overlappingTarget: LH.Audit.DetailsRendererNodeDetailsJSON; + tapTarget: LH.Audit.Details.NodeValue; + overlappingTarget: LH.Audit.Details.NodeValue; size: string; overlapScoreRatio: number; height: number; diff --git a/lighthouse-core/audits/user-timings.js b/lighthouse-core/audits/user-timings.js index a9f1295c4cce..339b528a56e2 100644 --- a/lighthouse-core/audits/user-timings.js +++ b/lighthouse-core/audits/user-timings.js @@ -94,6 +94,7 @@ class UserTimings extends Audit { } }); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'name', itemType: 'text', text: str_(UIStrings.columnName)}, {key: 'timingType', itemType: 'text', text: str_(UIStrings.columnType)}, diff --git a/lighthouse-core/lib/traces/pwmetrics-events.js b/lighthouse-core/lib/traces/pwmetrics-events.js index 33fccecc640f..4f57e83545a3 100644 --- a/lighthouse-core/lib/traces/pwmetrics-events.js +++ b/lighthouse-core/lib/traces/pwmetrics-events.js @@ -15,7 +15,7 @@ const log = require('lighthouse-logger'); */ function getUberMetrics(auditResults) { const metricsAudit = auditResults.metrics; - if (!metricsAudit || !metricsAudit.details || !metricsAudit.details.items) return; + if (!metricsAudit || !metricsAudit.details || !('items' in metricsAudit.details)) return; return metricsAudit.details.items[0]; } diff --git a/lighthouse-core/report/html/renderer/category-renderer.js b/lighthouse-core/report/html/renderer/category-renderer.js index c20523bcf26b..8bbb753a7cd7 100644 --- a/lighthouse-core/report/html/renderer/category-renderer.js +++ b/lighthouse-core/report/html/renderer/category-renderer.js @@ -86,9 +86,12 @@ class CategoryRenderer { const header = /** @type {HTMLDetailsElement} */ (this.dom.find('details', auditEl)); if (audit.result.details && audit.result.details.type) { + // @ts-ignore TODO(bckenny): fix detailsRenderer.render input type const elem = this.detailsRenderer.render(audit.result.details); - elem.classList.add('lh-details'); - header.appendChild(elem); + if (elem) { + elem.classList.add('lh-details'); + header.appendChild(elem); + } } this.dom.find('.lh-audit__index', auditEl).textContent = `${index + 1}`; diff --git a/lighthouse-core/report/html/renderer/details-renderer.js b/lighthouse-core/report/html/renderer/details-renderer.js index 9526be1303e5..91a8384b125f 100644 --- a/lighthouse-core/report/html/renderer/details-renderer.js +++ b/lighthouse-core/report/html/renderer/details-renderer.js @@ -44,7 +44,7 @@ class DetailsRenderer { /** * @param {DetailsJSON|OpportunityDetails} details - * @return {Element} + * @return {Element|null} */ render(details) { switch (details.type) { @@ -81,6 +81,12 @@ class DetailsRenderer { return this._renderOpportunityTable(details); case 'numeric': return this._renderNumeric(/** @type {StringDetailsJSON} */ (details)); + + // Internal-only details, not for rendering. + case 'screenshot': + case 'diagnostic': + return null; + default: { throw new Error(`Unknown type: ${details.type}`); } @@ -218,6 +224,7 @@ class DetailsRenderer { for (const heading of details.headings) { const itemType = heading.itemType || 'text'; const classes = `lh-table-column--${itemType}`; + // @ts-ignore TODO(bckenny): this can never be null this._dom.createChildOf(theadTrElem, 'th', classes).appendChild(this.render({ type: 'text', value: heading.text || '', @@ -241,6 +248,7 @@ class DetailsRenderer { if (value.type) { const valueAsDetails = /** @type {DetailsJSON} */ (value); const classes = `lh-table-column--${valueAsDetails.type}`; + // @ts-ignore TODO(bckenny): this can never be null this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(valueAsDetails)); continue; } @@ -257,6 +265,7 @@ class DetailsRenderer { // @ts-ignore - TODO(bckenny): handle with refactoring above const valueType = value.type; const classes = `lh-table-column--${valueType || heading.itemType}`; + // @ts-ignore TODO(bckenny): this can never be null this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(item)); } } diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js index 8d63094bc40e..0fe9f1da7495 100644 --- a/lighthouse-core/report/html/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js @@ -160,8 +160,9 @@ class PerformanceCategoryRenderer extends CategoryRenderer { const thumbnailResult = thumbnailAudit && thumbnailAudit.result; if (thumbnailResult && thumbnailResult.details) { timelineEl.id = thumbnailResult.id; + // @ts-ignore TODO(bckenny): fix detailsRenderer.render input type const filmstripEl = this.detailsRenderer.render(thumbnailResult.details); - timelineEl.appendChild(filmstripEl); + filmstripEl && timelineEl.appendChild(filmstripEl); } // Opportunities diff --git a/lighthouse-core/report/html/renderer/psi.js b/lighthouse-core/report/html/renderer/psi.js index a1b063cc7298..35c4d104e6cc 100644 --- a/lighthouse-core/report/html/renderer/psi.js +++ b/lighthouse-core/report/html/renderer/psi.js @@ -74,7 +74,7 @@ function prepareLabData(LHResult, document) { function _getFinalScreenshot(perfCategory) { const auditRef = perfCategory.auditRefs.find(audit => audit.id === 'final-screenshot'); if (!auditRef || !auditRef.result || auditRef.result.scoreDisplayMode === 'error') return null; - return auditRef.result.details.data; + return /** @type {LH.Audit.Details.Screenshot} */ (auditRef.result.details).data; } if (typeof module !== 'undefined' && module.exports) { diff --git a/lighthouse-core/report/html/renderer/report-renderer.js b/lighthouse-core/report/html/renderer/report-renderer.js index cb013ef06347..325c27dc58f2 100644 --- a/lighthouse-core/report/html/renderer/report-renderer.js +++ b/lighthouse-core/report/html/renderer/report-renderer.js @@ -24,7 +24,6 @@ */ /** @typedef {import('./dom.js')} DOM */ -/** @typedef {import('./details-renderer.js').DetailsJSON} DetailsJSON */ /* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer, PwaCategoryRenderer */ diff --git a/lighthouse-core/test/audits/dobetterweb/js-libraries-test.js b/lighthouse-core/test/audits/dobetterweb/js-libraries-test.js index 65bb77641b14..9b3975c95796 100644 --- a/lighthouse-core/test/audits/dobetterweb/js-libraries-test.js +++ b/lighthouse-core/test/audits/dobetterweb/js-libraries-test.js @@ -55,7 +55,7 @@ describe('Returns detected front-end JavaScript libraries', () => { { name: 'lib2', npm: 'lib2', - version: null, + version: undefined, }, ]; assert.equal(auditResult.rawValue, true); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 3e2ee84cdd1c..950cfedddf29 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -478,6 +478,7 @@ "rawValue": false, "explanation": "Failures: No manifest was fetched.", "details": { + "type": "diagnostic", "items": [ { "failures": [ @@ -498,6 +499,7 @@ "rawValue": false, "explanation": "Failures: No manifest was fetched.", "details": { + "type": "diagnostic", "items": [ { "failures": [ @@ -518,6 +520,7 @@ "rawValue": false, "explanation": "Failures: No manifest was fetched,\nNo `` tag found.", "details": { + "type": "diagnostic", "items": [ { "failures": [ @@ -804,6 +807,7 @@ "scoreDisplayMode": "informative", "rawValue": 1, "details": { + "type": "diagnostic", "items": [ { "numRequests": 18, @@ -1373,6 +1377,7 @@ "scoreDisplayMode": "informative", "rawValue": 4927.278, "details": { + "type": "diagnostic", "items": [ { "firstContentfulPaint": 3969, @@ -1564,12 +1569,15 @@ } } ], - "impact": "serious", - "tags": [ - "cat.color", - "wcag2aa", - "wcag143" - ] + "diagnostic": { + "type": "diagnostic", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143" + ] + } } }, "definition-list": { @@ -1649,12 +1657,15 @@ } } ], - "impact": "serious", - "tags": [ - "cat.language", - "wcag2a", - "wcag311" - ] + "diagnostic": { + "type": "diagnostic", + "impact": "serious", + "tags": [ + "cat.language", + "wcag2a", + "wcag311" + ] + } } }, "html-lang-valid": { @@ -1719,14 +1730,17 @@ } } ], - "impact": "critical", - "tags": [ - "cat.text-alternatives", - "wcag2a", - "wcag111", - "section508", - "section508.22.a" - ] + "diagnostic": { + "type": "diagnostic", + "impact": "critical", + "tags": [ + "cat.text-alternatives", + "wcag2a", + "wcag111", + "section508", + "section508.22.a" + ] + } } }, "input-image-alt": { @@ -1782,15 +1796,18 @@ } } ], - "impact": "critical", - "tags": [ - "cat.forms", - "wcag2a", - "wcag332", - "wcag131", - "section508", - "section508.22.n" - ] + "diagnostic": { + "type": "diagnostic", + "impact": "critical", + "tags": [ + "cat.forms", + "wcag2a", + "wcag332", + "wcag131", + "section508", + "section508.22.n" + ] + } } }, "layout-table": { @@ -1837,15 +1854,18 @@ } } ], - "impact": "serious", - "tags": [ - "cat.name-role-value", - "wcag2a", - "wcag412", - "wcag244", - "section508", - "section508.22.a" - ] + "diagnostic": { + "type": "diagnostic", + "impact": "serious", + "tags": [ + "cat.name-role-value", + "wcag2a", + "wcag412", + "wcag244", + "section508", + "section508.22.a" + ] + } } }, "list": { @@ -1921,14 +1941,17 @@ } } ], - "impact": "serious", - "tags": [ - "cat.text-alternatives", - "wcag2a", - "wcag111", - "section508", - "section508.22.a" - ] + "diagnostic": { + "type": "diagnostic", + "impact": "serious", + "tags": [ + "cat.text-alternatives", + "wcag2a", + "wcag111", + "section508", + "section508.22.a" + ] + } } }, "tabindex": { @@ -2108,7 +2131,6 @@ "items": [ { "url": "http://localhost:10200/zone.js", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 71654, @@ -2116,7 +2138,6 @@ }, { "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 24741, @@ -2124,7 +2145,6 @@ }, { "url": "http://localhost:10200/dobetterweb/dbw_tester.js", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 1703, @@ -2132,7 +2152,6 @@ }, { "url": "http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 1108, @@ -2140,7 +2159,6 @@ }, { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 821, @@ -2148,7 +2166,6 @@ }, { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 821, @@ -2156,7 +2173,6 @@ }, { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 821, @@ -2164,7 +2180,6 @@ }, { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 821, @@ -2172,7 +2187,6 @@ }, { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 821, @@ -2180,7 +2194,6 @@ }, { "url": "http://localhost:10200/dobetterweb/empty_module.js?delay=500", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 144, @@ -2188,7 +2201,6 @@ }, { "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", - "cacheControl": null, "cacheLifetimeMs": 0, "cacheHitProbability": 0, "totalBytes": 0, diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json index 983063b7ca94..f365ce24c794 100644 --- a/proto/sample_v2_round_trip.json +++ b/proto/sample_v2_round_trip.json @@ -159,6 +159,15 @@ "color-contrast": { "description": "Low-contrast text is difficult or impossible for many users to read. [Learn more](https://dequeuniversity.com/rules/axe/3.1/color-contrast?application=lighthouse).", "details": { + "diagnostic": { + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143" + ], + "type": "diagnostic" + }, "headings": [ { "itemType": "node", @@ -166,7 +175,6 @@ "text": "Failing Elements" } ], - "impact": "serious", "items": [ { "node": { @@ -187,11 +195,6 @@ } } ], - "tags": [ - "cat.color", - "wcag2aa", - "wcag143" - ], "type": "table" }, "id": "color-contrast", @@ -442,7 +445,8 @@ "totalByteWeight": 160738.0, "totalTaskTime": 1548.5690000000002 } - ] + ], + "type": "diagnostic" }, "id": "diagnostics", "score": null, @@ -817,6 +821,15 @@ "html-has-lang": { "description": "If a page doesn't specify a lang attribute, a screen reader assumes that the page is in the default language that the user chose when setting up the screen reader. If the page isn't actually in the default language, then the screen reader might not announce the page's text correctly. [Learn more](https://dequeuniversity.com/rules/axe/3.1/html-has-lang?application=lighthouse).", "details": { + "diagnostic": { + "impact": "serious", + "tags": [ + "cat.language", + "wcag2a", + "wcag311" + ], + "type": "diagnostic" + }, "headings": [ { "itemType": "node", @@ -824,7 +837,6 @@ "text": "Failing Elements" } ], - "impact": "serious", "items": [ { "node": { @@ -836,11 +848,6 @@ } } ], - "tags": [ - "cat.language", - "wcag2a", - "wcag311" - ], "type": "table" }, "id": "html-has-lang", @@ -865,6 +872,17 @@ "image-alt": { "description": "Informative elements should aim for short, descriptive alternate text. Decorative elements can be ignored with an empty alt attribute. [Learn more](https://dequeuniversity.com/rules/axe/3.1/image-alt?application=lighthouse).", "details": { + "diagnostic": { + "impact": "critical", + "tags": [ + "cat.text-alternatives", + "wcag2a", + "wcag111", + "section508", + "section508.22.a" + ], + "type": "diagnostic" + }, "headings": [ { "itemType": "node", @@ -872,7 +890,6 @@ "text": "Failing Elements" } ], - "impact": "critical", "items": [ { "node": { @@ -911,13 +928,6 @@ } } ], - "tags": [ - "cat.text-alternatives", - "wcag2a", - "wcag111", - "section508", - "section508.22.a" - ], "type": "table" }, "id": "image-alt", @@ -983,7 +993,8 @@ "isParseFailure": true, "parseFailureReason": "No manifest was fetched" } - ] + ], + "type": "diagnostic" }, "explanation": "Failures: No manifest was fetched.", "id": "installable-manifest", @@ -1070,6 +1081,18 @@ "label": { "description": "Labels ensure that form controls are announced properly by assistive technologies, like screen readers. [Learn more](https://dequeuniversity.com/rules/axe/3.1/label?application=lighthouse).", "details": { + "diagnostic": { + "impact": "critical", + "tags": [ + "cat.forms", + "wcag2a", + "wcag332", + "wcag131", + "section508", + "section508.22.n" + ], + "type": "diagnostic" + }, "headings": [ { "itemType": "node", @@ -1077,7 +1100,6 @@ "text": "Failing Elements" } ], - "impact": "critical", "items": [ { "node": { @@ -1107,14 +1129,6 @@ } } ], - "tags": [ - "cat.forms", - "wcag2a", - "wcag332", - "wcag131", - "section508", - "section508.22.n" - ], "type": "table" }, "id": "label", @@ -1132,6 +1146,18 @@ "link-name": { "description": "Link text (and alternate text for images, when used as links) that is discernible, unique, and focusable improves the navigation experience for screen reader users. [Learn more](https://dequeuniversity.com/rules/axe/3.1/link-name?application=lighthouse).", "details": { + "diagnostic": { + "impact": "serious", + "tags": [ + "cat.name-role-value", + "wcag2a", + "wcag412", + "wcag244", + "section508", + "section508.22.a" + ], + "type": "diagnostic" + }, "headings": [ { "itemType": "node", @@ -1139,7 +1165,6 @@ "text": "Failing Elements" } ], - "impact": "serious", "items": [ { "node": { @@ -1160,14 +1185,6 @@ } } ], - "tags": [ - "cat.name-role-value", - "wcag2a", - "wcag412", - "wcag244", - "section508", - "section508.22.a" - ], "type": "table" }, "id": "link-name", @@ -1581,7 +1598,8 @@ "speedIndex": 4417.0, "speedIndexTs": 185607736912.0 } - ] + ], + "type": "diagnostic" }, "id": "metrics", "score": null, @@ -2010,6 +2028,17 @@ "object-alt": { "description": "Screen readers cannot translate non-text content. Adding alt text to `` elements helps screen readers convey meaning to users. [Learn more](https://dequeuniversity.com/rules/axe/3.1/object-alt?application=lighthouse).", "details": { + "diagnostic": { + "impact": "serious", + "tags": [ + "cat.text-alternatives", + "wcag2a", + "wcag111", + "section508", + "section508.22.a" + ], + "type": "diagnostic" + }, "headings": [ { "itemType": "node", @@ -2017,7 +2046,6 @@ "text": "Failing Elements" } ], - "impact": "serious", "items": [ { "node": { @@ -2038,13 +2066,6 @@ } } ], - "tags": [ - "cat.text-alternatives", - "wcag2a", - "wcag111", - "section508", - "section508.22.a" - ], "type": "table" }, "id": "object-alt", @@ -2319,7 +2340,8 @@ "isParseFailure": true, "parseFailureReason": "No manifest was fetched" } - ] + ], + "type": "diagnostic" }, "explanation": "Failures: No manifest was fetched.", "id": "splash-screen", @@ -2376,7 +2398,8 @@ "parseFailureReason": "No manifest was fetched", "themeColor": null } - ] + ], + "type": "diagnostic" }, "explanation": "Failures: No manifest was fetched,\nNo `` tag found.", "id": "themed-omnibox", @@ -2659,7 +2682,6 @@ ], "items": [ { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 71654.0, @@ -2667,7 +2689,6 @@ "wastedBytes": 71654.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 24741.0, @@ -2675,7 +2696,6 @@ "wastedBytes": 24741.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 1703.0, @@ -2683,7 +2703,6 @@ "wastedBytes": 1703.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 1108.0, @@ -2691,7 +2710,6 @@ "wastedBytes": 1108.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 821.0, @@ -2699,7 +2717,6 @@ "wastedBytes": 821.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 821.0, @@ -2707,7 +2724,6 @@ "wastedBytes": 821.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 821.0, @@ -2715,7 +2731,6 @@ "wastedBytes": 821.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 821.0, @@ -2723,7 +2738,6 @@ "wastedBytes": 821.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 821.0, @@ -2731,7 +2745,6 @@ "wastedBytes": 821.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 144.0, @@ -2739,7 +2752,6 @@ "wastedBytes": 144.0 }, { - "cacheControl": null, "cacheHitProbability": 0.0, "cacheLifetimeMs": 0.0, "totalBytes": 0.0, diff --git a/types/audit-details.d.ts b/types/audit-details.d.ts index dda07549bad2..f2dd8a329ee0 100644 --- a/types/audit-details.d.ts +++ b/types/audit-details.d.ts @@ -5,63 +5,166 @@ */ declare global { - module LH.Audit.Details { - export interface Filmstrip { - type: 'filmstrip'; - scale: number; - items: { - /** The relative time from navigationStart to this frame, in milliseconds. */ - timing: number; - /** The raw timestamp of this frame, in microseconds. */ + module LH.Audit { + export type Details = + Details.CriticalRequestChain | + Details.Diagnostic | + Details.Filmstrip | + Details.Opportunity | + Details.Screenshot | + Details.Table; + + // Details namespace. + export module Details { + export interface CriticalRequestChain { + type: 'criticalrequestchain'; + longestChain: { + duration: number; + length: number; + transferSize: number; + }; + chains: Audit.SimpleCriticalRequestNode; + } + + export interface Filmstrip { + type: 'filmstrip'; + scale: number; + items: { + /** The relative time from navigationStart to this frame, in milliseconds. */ + timing: number; + /** The raw timestamp of this frame, in microseconds. */ + timestamp: number; + /** The data URL encoding of this frame. */ + data: string; + }[]; + } + + export interface Opportunity { + type: 'opportunity'; + overallSavingsMs: number; + overallSavingsBytes?: number; + headings: OpportunityColumnHeading[]; + items: OpportunityItem[]; + diagnostic?: Diagnostic; + } + + export interface Screenshot { + type: 'screenshot'; timestamp: number; - /** The data URL encoding of this frame. */ data: string; - }[]; - } + } - export interface Screenshot { - type: 'screenshot'; - timestamp: number; - data: string; - } + export interface Table { + type: 'table'; + headings: TableColumnHeading[]; + items: TableItem[]; + summary?: { + wastedMs?: number; + wastedBytes?: number; + }; + diagnostic?: Diagnostic; + } - export interface Opportunity { - type: 'opportunity'; - overallSavingsMs: number; - overallSavingsBytes?: number; - headings: OpportunityColumnHeading[]; - items: OpportunityItem[]; - } + /** + * A details type that is not rendered in the final report; usually used + * for including diagnostic information in the LHR. Can contain anything. + */ + export interface Diagnostic { + type: 'diagnostic'; + [p: string]: any; + } - export interface CriticalRequestChain { - type: 'criticalrequestchain'; - longestChain: { - duration: number; - length: number; - transferSize: number; - }; - chains: Audit.SimpleCriticalRequestNode; - } + /** Possible types of values found within table items. */ + type ItemValueTypes = 'bytes' | 'code' | 'link' | 'ms' | 'node' | 'numeric' | 'text' | 'thumbnail' | 'timespanMs' | 'url'; - // Contents of details below here + // TODO(bckenny): unify Table/Opportunity headings and items on next breaking change. - export interface OpportunityColumnHeading { - /** The name of the property within items being described. */ - key: string; - /** Readable text label of the field. */ - label: string; - /** The data format of the column of values being described. */ - valueType: string; - } - - export interface OpportunityItem { - url: string; - wastedBytes?: number; - totalBytes?: number; - wastedMs?: number; - [p: string]: number | boolean | string | undefined; - } + export interface TableColumnHeading { + /** The name of the property within items being described. */ + key: string; + /** Readable text label of the field. */ + text: string; + /** + * The data format of the column of values being described. Usually + * those values will be primitives rendered as this type, but the values + * could also be objects with their own type to override this field. + */ + itemType: ItemValueTypes; + displayUnit?: string; + granularity?: number; + } + + export type TableItem = { + diagnostic?: Diagnostic; + [p: string]: string | number | boolean | undefined | Diagnostic | NodeValue | LinkValue | UrlValue | CodeValue; + } + + export interface OpportunityColumnHeading { + /** The name of the property within items being described. */ + key: string; + /** Readable text label of the field. */ + label: string; + /** + * The data format of the column of values being described. Usually + * those values will be primitives rendered as this type, but the values + * could also be objects with their own type to override this field. + */ + valueType: ItemValueTypes; + + // NOTE: not used by opportunity details, but used in the renderer until table/opportunity unification. + displayUnit?: string; + granularity?: number; + } + + export interface OpportunityItem { + url: string; + wastedBytes?: number; + totalBytes?: number; + wastedMs?: number; + diagnostic?: Diagnostic; + [p: string]: number | boolean | string | undefined | Diagnostic; + } + + /** + * A value used within a details object, intended to be displayed as code, + * regardless of the controlling heading's valueType. + */ + export interface CodeValue { + type: 'code'; + value: string; + } + + /** + * A value used within a details object, intended to be displayed as a + * link with text, regardless of the controlling heading's valueType. + */ + export interface LinkValue { + type: 'link', + text: string; + url: string; + } + + /** + * A value used within a details object, intended to be displayed an HTML + * node, regardless of the controlling heading's valueType. + */ + export interface NodeValue { + type: 'node'; + path?: string; + selector?: string; + snippet?: string; + } + + /** + * A value used within a details object, intended to be displayed as a + * linkified URL, regardless of the controlling heading's valueType. + */ + export interface UrlValue { + type: 'url'; + value: string; + } + } } } diff --git a/types/audit.d.ts b/types/audit.d.ts index b57bb71a98aa..41cceacad7fb 100644 --- a/types/audit.d.ts +++ b/types/audit.d.ts @@ -55,14 +55,6 @@ declare global { scoreDisplayMode?: Audit.ScoreDisplayMode; } - export interface Heading { - key: string; - itemType: string; - text: string; - displayUnit?: string; - granularity?: number; - } - export interface ByteEfficiencyItem extends Audit.Details.OpportunityItem { url: string; wastedBytes: number; @@ -70,51 +62,6 @@ declare global { wastedPercent?: number; } - // TODO: placeholder typedefs until Details are typed - export interface DetailsRendererDetailsSummary { - wastedMs?: number; - wastedBytes?: number; - } - - export interface DetailsObject { - [x: string]: DetailsItem - } - - // TODO: placeholder typedefs until Details are typed - export interface DetailsRendererDetailsJSON { - type: 'table'; - headings: Array; - items: Array; - summary?: DetailsRendererDetailsSummary; - } - - export interface DetailsRendererCodeDetailJSON { - type: 'code', - value: string; - } - - export type DetailsItem = string | number | DetailsRendererNodeDetailsJSON | - DetailsRendererLinkDetailsJSON | DetailsRendererCodeDetailJSON | undefined | - boolean | DetailsRendererUrlDetailsJSON | null; - - export interface DetailsRendererNodeDetailsJSON { - type: 'node'; - path?: string; - selector?: string; - snippet?: string; - } - - export interface DetailsRendererLinkDetailsJSON { - type: 'link'; - text: string; - url: string; - } - - export interface DetailsRendererUrlDetailsJSON { - type: 'url'; - value: string; - } - // Type returned by Audit.audit(). Only rawValue is required. export interface Product { rawValue: boolean | number | null; @@ -126,8 +73,7 @@ declare global { extendedInfo?: {[p: string]: any}; /** Overrides scoreDisplayMode with notApplicable if set to true */ notApplicable?: boolean; - // TODO(bckenny): define details - details?: object; + details?: Audit.Details; } /* Audit result returned in Lighthouse report. All audits offer a description and score of 0-1 */ @@ -155,7 +101,7 @@ declare global { /** A more detailed description that describes why the audit is important and links to Lighthouse documentation on the audit; markdown links supported. */ description: string; // TODO(bckenny): define details - details?: any; + details?: Audit.Details; } export interface Results {