diff --git a/lighthouse-core/audits/bootup-time.js b/lighthouse-core/audits/bootup-time.js index 16d30f994460..6c6dbff51cf3 100644 --- a/lighthouse-core/audits/bootup-time.js +++ b/lighthouse-core/audits/bootup-time.js @@ -77,60 +77,59 @@ class BootupTime extends Audit { * @param {LH.Audit.Context} context * @return {Promise} */ - static audit(artifacts, context) { + static async audit(artifacts, context) { const trace = artifacts.traces[BootupTime.DEFAULT_PASS]; - return artifacts.requestDevtoolsTimelineModel(trace).then(devtoolsTimelineModel => { - const executionTimings = BootupTime.getExecutionTimingsByURL(devtoolsTimelineModel); - let totalBootupTime = 0; - /** @type {Object>} */ - const extendedInfo = {}; + const devtoolsTimelineModel = await artifacts.requestDevtoolsTimelineModel(trace); + const executionTimings = BootupTime.getExecutionTimingsByURL(devtoolsTimelineModel); + let totalBootupTime = 0; + /** @type {Object>} */ + const extendedInfo = {}; - const headings = [ - {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'scripting', itemType: 'text', text: groupIdToName.scripting}, - {key: 'scriptParseCompile', itemType: 'text', text: groupIdToName.scriptParseCompile}, - ]; + const headings = [ + {key: 'url', itemType: 'url', text: 'URL'}, + {key: 'scripting', itemType: 'text', text: groupIdToName.scripting}, + {key: 'scriptParseCompile', itemType: 'text', text: groupIdToName.scriptParseCompile}, + ]; - // map data in correct format to create a table - const results = Array.from(executionTimings) - .map(([url, groups]) => { - // Add up the totalBootupTime for all the taskGroups - totalBootupTime += Object.keys(groups).reduce((sum, name) => sum += groups[name], 0); - extendedInfo[url] = groups; + // map data in correct format to create a table + const results = Array.from(executionTimings) + .map(([url, groups]) => { + // Add up the totalBootupTime for all the taskGroups + totalBootupTime += Object.keys(groups).reduce((sum, name) => sum += groups[name], 0); + extendedInfo[url] = groups; - const scriptingTotal = groups[groupIdToName.scripting] || 0; - const parseCompileTotal = groups[groupIdToName.scriptParseCompile] || 0; - return { - url: url, - sum: scriptingTotal + parseCompileTotal, - // Only reveal the javascript task costs - // Later we can account for forced layout costs, etc. - scripting: Util.formatMilliseconds(scriptingTotal, 1), - scriptParseCompile: Util.formatMilliseconds(parseCompileTotal, 1), - }; - }) - .filter(result => result.sum >= THRESHOLD_IN_MS) - .sort((a, b) => b.sum - a.sum); + const scriptingTotal = groups[groupIdToName.scripting] || 0; + const parseCompileTotal = groups[groupIdToName.scriptParseCompile] || 0; + return { + url: url, + sum: scriptingTotal + parseCompileTotal, + // Only reveal the javascript task costs + // Later we can account for forced layout costs, etc. + scripting: Util.formatMilliseconds(scriptingTotal, 1), + scriptParseCompile: Util.formatMilliseconds(parseCompileTotal, 1), + }; + }) + .filter(result => result.sum >= THRESHOLD_IN_MS) + .sort((a, b) => b.sum - a.sum); - const summary = {wastedMs: totalBootupTime}; - const details = BootupTime.makeTableDetails(headings, results, summary); + const summary = {wastedMs: totalBootupTime}; + const details = BootupTime.makeTableDetails(headings, results, summary); - const score = Audit.computeLogNormalScore( - totalBootupTime, - context.options.scorePODR, - context.options.scoreMedian - ); + const score = Audit.computeLogNormalScore( + totalBootupTime, + context.options.scorePODR, + context.options.scoreMedian + ); - return { - score, - rawValue: totalBootupTime, - displayValue: [Util.MS_DISPLAY_VALUE, totalBootupTime], - details, - extendedInfo: { - value: extendedInfo, - }, - }; - }); + return { + score, + rawValue: totalBootupTime, + displayValue: [Util.MS_DISPLAY_VALUE, totalBootupTime], + details, + extendedInfo: { + value: extendedInfo, + }, + }; } } diff --git a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js index ec5ee06d64aa..c21a75fa9554 100644 --- a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js +++ b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js @@ -42,68 +42,68 @@ class TotalByteWeight extends ByteEfficiencyAudit { * @param {LH.Audit.Context} context * @return {Promise} */ - static audit(artifacts, context) { + static async audit(artifacts, context) { const devtoolsLogs = artifacts.devtoolsLogs[ByteEfficiencyAudit.DEFAULT_PASS]; - return Promise.all([ + const [networkRecords, networkThroughput] = await Promise.all([ artifacts.requestNetworkRecords(devtoolsLogs), artifacts.requestNetworkThroughput(devtoolsLogs), - ]).then(([networkRecords, networkThroughput]) => { - let totalBytes = 0; - /** @type {Array<{url: string, totalBytes: number, totalMs: number}>} */ - let results = []; - networkRecords.forEach(record => { - // exclude data URIs since their size is reflected in other resources - // exclude unfinished requests since they won't have transfer size information - if (record.parsedURL.scheme === 'data' || !record.finished) return; + ]); - const result = { - url: record.url, - totalBytes: record.transferSize, - totalMs: ByteEfficiencyAudit.bytesToMs(record.transferSize, networkThroughput), - }; + let totalBytes = 0; + /** @type {Array<{url: string, totalBytes: number, totalMs: number}>} */ + let results = []; + networkRecords.forEach(record => { + // exclude data URIs since their size is reflected in other resources + // exclude unfinished requests since they won't have transfer size information + if (record.parsedURL.scheme === 'data' || !record.finished) return; - totalBytes += result.totalBytes; - results.push(result); - }); - const totalCompletedRequests = results.length; - results = results.sort((itemA, itemB) => itemB.totalBytes - itemA.totalBytes).slice(0, 10); + const result = { + url: record.url, + totalBytes: record.transferSize, + totalMs: ByteEfficiencyAudit.bytesToMs(record.transferSize, networkThroughput), + }; + + totalBytes += result.totalBytes; + results.push(result); + }); + const totalCompletedRequests = results.length; + results = results.sort((itemA, itemB) => itemB.totalBytes - itemA.totalBytes).slice(0, 10); - const score = ByteEfficiencyAudit.computeLogNormalScore( - totalBytes, - context.options.scorePODR, - context.options.scoreMedian - ); + const score = ByteEfficiencyAudit.computeLogNormalScore( + totalBytes, + context.options.scorePODR, + context.options.scoreMedian + ); - const headings = [ - {key: 'url', itemType: 'url', text: 'URL'}, - { - key: 'totalBytes', - itemType: 'bytes', - displayUnit: 'kb', - granularity: 1, - text: 'Total Size', - }, - {key: 'totalMs', itemType: 'ms', text: 'Transfer Time'}, - ]; + const headings = [ + {key: 'url', itemType: 'url', text: 'URL'}, + { + key: 'totalBytes', + itemType: 'bytes', + displayUnit: 'kb', + granularity: 1, + text: 'Total Size', + }, + {key: 'totalMs', itemType: 'ms', text: 'Transfer Time'}, + ]; - const tableDetails = ByteEfficiencyAudit.makeTableDetails(headings, results); + const tableDetails = ByteEfficiencyAudit.makeTableDetails(headings, results); - return { - score, - rawValue: totalBytes, - displayValue: [ - 'Total size was %d\xa0KB', - totalBytes / 1024, - ], - extendedInfo: { - value: { - results, - totalCompletedRequests, - }, + return { + score, + rawValue: totalBytes, + displayValue: [ + 'Total size was %d\xa0KB', + totalBytes / 1024, + ], + extendedInfo: { + value: { + results, + totalCompletedRequests, }, - details: tableDetails, - }; - }); + }, + details: tableDetails, + }; } } diff --git a/lighthouse-core/audits/mainthread-work-breakdown.js b/lighthouse-core/audits/mainthread-work-breakdown.js index 2dc4d94b8ef3..70518693a50a 100644 --- a/lighthouse-core/audits/mainthread-work-breakdown.js +++ b/lighthouse-core/audits/mainthread-work-breakdown.js @@ -61,58 +61,56 @@ class MainThreadWorkBreakdown extends Audit { * @param {LH.Audit.Context} context * @return {Promise} */ - static audit(artifacts, context) { + static async audit(artifacts, context) { const trace = artifacts.traces[MainThreadWorkBreakdown.DEFAULT_PASS]; - return artifacts.requestDevtoolsTimelineModel(trace) - .then(devtoolsTimelineModel => { - const executionTimings = MainThreadWorkBreakdown.getExecutionTimingsByCategory( - devtoolsTimelineModel - ); - let totalExecutionTime = 0; - - const extendedInfo = {}; - const categoryTotals = {}; - const results = Array.from(executionTimings).map(([eventName, duration]) => { - totalExecutionTime += duration; - extendedInfo[eventName] = duration; - const groupName = taskToGroup[eventName]; - - const categoryTotal = categoryTotals[groupName] || 0; - categoryTotals[groupName] = categoryTotal + duration; - - return { - category: eventName, - group: groupName, - duration: Util.formatMilliseconds(duration, 1), - }; - }); - - const headings = [ - {key: 'group', itemType: 'text', text: 'Category'}, - {key: 'category', itemType: 'text', text: 'Work'}, - {key: 'duration', itemType: 'text', text: 'Time spent'}, - ]; - // @ts-ignore - stableSort added to Array by WebInspector - results.stableSort((a, b) => categoryTotals[b.group] - categoryTotals[a.group]); - const tableDetails = MainThreadWorkBreakdown.makeTableDetails(headings, results); - - const score = Audit.computeLogNormalScore( - totalExecutionTime, - context.options.scorePODR, - context.options.scoreMedian - ); - - return { - score, - rawValue: totalExecutionTime, - displayValue: ['%d\xa0ms', totalExecutionTime], - details: tableDetails, - extendedInfo: { - value: extendedInfo, - }, - }; - }); + const devtoolsTimelineModel = await artifacts.requestDevtoolsTimelineModel(trace); + const executionTimings = MainThreadWorkBreakdown.getExecutionTimingsByCategory( + devtoolsTimelineModel + ); + let totalExecutionTime = 0; + + const extendedInfo = {}; + const categoryTotals = {}; + const results = Array.from(executionTimings).map(([eventName, duration]) => { + totalExecutionTime += duration; + extendedInfo[eventName] = duration; + const groupName = taskToGroup[eventName]; + + const categoryTotal = categoryTotals[groupName] || 0; + categoryTotals[groupName] = categoryTotal + duration; + + return { + category: eventName, + group: groupName, + duration: Util.formatMilliseconds(duration, 1), + }; + }); + + const headings = [ + {key: 'group', itemType: 'text', text: 'Category'}, + {key: 'category', itemType: 'text', text: 'Work'}, + {key: 'duration', itemType: 'text', text: 'Time spent'}, + ]; + // @ts-ignore - stableSort added to Array by WebInspector + results.stableSort((a, b) => categoryTotals[b.group] - categoryTotals[a.group]); + const tableDetails = MainThreadWorkBreakdown.makeTableDetails(headings, results); + + const score = Audit.computeLogNormalScore( + totalExecutionTime, + context.options.scorePODR, + context.options.scoreMedian + ); + + return { + score, + rawValue: totalExecutionTime, + displayValue: ['%d\xa0ms', totalExecutionTime], + details: tableDetails, + extendedInfo: { + value: extendedInfo, + }, + }; } } diff --git a/lighthouse-core/audits/uses-rel-preload.js b/lighthouse-core/audits/uses-rel-preload.js index 0309a3267678..2724d26e808a 100644 --- a/lighthouse-core/audits/uses-rel-preload.js +++ b/lighthouse-core/audits/uses-rel-preload.js @@ -140,55 +140,55 @@ class UsesRelPreloadAudit extends Audit { * @param {LH.Audit.Context} context * @return {Promise} */ - static audit(artifacts, context) { + static async audit(artifacts, context) { const trace = artifacts.traces[UsesRelPreloadAudit.DEFAULT_PASS]; const devtoolsLog = artifacts.devtoolsLogs[UsesRelPreloadAudit.DEFAULT_PASS]; const URL = artifacts.URL; const simulatorOptions = {trace, devtoolsLog, settings: context.settings}; - return Promise.all([ + const [critChains, mainResource, graph, simulator] = await Promise.all([ // TODO(phulce): eliminate dependency on CRC artifacts.requestCriticalRequestChains({devtoolsLog, URL}), artifacts.requestMainResource({devtoolsLog, URL}), artifacts.requestPageDependencyGraph({trace, devtoolsLog}), artifacts.requestLoadSimulator(simulatorOptions), - ]).then(([critChains, mainResource, graph, simulator]) => { - // get all critical requests 2 + mainResourceIndex levels deep - const mainResourceIndex = mainResource.redirects ? mainResource.redirects.length : 0; - - const criticalRequests = UsesRelPreloadAudit._flattenRequests(critChains, - 3 + mainResourceIndex, 2 + mainResourceIndex); - - /** @type {Set} */ - const urls = new Set(); - for (const networkRecord of criticalRequests) { - if (!networkRecord._isLinkPreload && networkRecord.protocol !== 'data') { - urls.add(networkRecord._url); - } + ]); + + // get all critical requests 2 + mainResourceIndex levels deep + const mainResourceIndex = mainResource.redirects ? mainResource.redirects.length : 0; + + const criticalRequests = UsesRelPreloadAudit._flattenRequests(critChains, + 3 + mainResourceIndex, 2 + mainResourceIndex); + + /** @type {Set} */ + const urls = new Set(); + for (const networkRecord of criticalRequests) { + if (!networkRecord._isLinkPreload && networkRecord.protocol !== 'data') { + urls.add(networkRecord._url); } + } - const {results, wastedMs} = UsesRelPreloadAudit.computeWasteWithGraph(urls, graph, simulator, - mainResource); - // sort results by wastedTime DESC - results.sort((a, b) => b.wastedMs - a.wastedMs); - - const headings = [ - {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'wastedMs', itemType: 'ms', text: 'Potential Savings', granularity: 10}, - ]; - const summary = {wastedMs}; - const details = Audit.makeTableDetails(headings, results, summary); - - return { - score: UnusedBytes.scoreForWastedMs(wastedMs), - rawValue: wastedMs, - displayValue: ['Potential savings of %10d\xa0ms', wastedMs], - extendedInfo: { - value: results, - }, - details, - }; - }); + const {results, wastedMs} = UsesRelPreloadAudit.computeWasteWithGraph(urls, graph, simulator, + mainResource); + // sort results by wastedTime DESC + results.sort((a, b) => b.wastedMs - a.wastedMs); + + const headings = [ + {key: 'url', itemType: 'url', text: 'URL'}, + {key: 'wastedMs', itemType: 'ms', text: 'Potential Savings', granularity: 10}, + ]; + const summary = {wastedMs}; + const details = Audit.makeTableDetails(headings, results, summary); + + return { + score: UnusedBytes.scoreForWastedMs(wastedMs), + rawValue: wastedMs, + displayValue: ['Potential savings of %10d\xa0ms', wastedMs], + extendedInfo: { + value: results, + }, + details, + }; } } diff --git a/lighthouse-core/report/html/renderer/category-renderer.js b/lighthouse-core/report/html/renderer/category-renderer.js index 39e52f6a033b..443267a9a7bd 100644 --- a/lighthouse-core/report/html/renderer/category-renderer.js +++ b/lighthouse-core/report/html/renderer/category-renderer.js @@ -25,29 +25,33 @@ class CategoryRenderer { /** * @param {!ReportRenderer.AuditJSON} audit + * @param {number} index * @return {!Element} */ - renderAudit(audit) { + renderAudit(audit, index) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-audit', this.templateContext); const auditEl = this.dom.find('.lh-audit', tmpl); auditEl.id = audit.result.name; const scoreDisplayMode = audit.result.scoreDisplayMode; - let title = audit.result.description; + if (audit.result.displayValue) { - title += `: ${Util.formatDisplayValue(audit.result.displayValue)}`; + const displayValue = Util.formatDisplayValue(audit.result.displayValue); + this.dom.find('.lh-audit__display-text', auditEl).textContent = displayValue; } this.dom.find('.lh-audit__title', auditEl).appendChild( - this.dom.convertMarkdownCodeSnippets(title)); + this.dom.convertMarkdownCodeSnippets(audit.result.description)); this.dom.find('.lh-audit__description', auditEl) .appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.helpText)); // Append audit details to header section so the entire audit is within a
. - const header = /** @type {!HTMLDetailsElement} */ (this.dom.find('.lh-audit__header', auditEl)); + const header = /** @type {!HTMLDetailsElement} */ (this.dom.find('details', auditEl)); if (audit.result.details && audit.result.details.type) { header.appendChild(this.detailsRenderer.render(audit.result.details)); } + this.dom.find('.lh-audit__index', auditEl).textContent = `${index + 1}`; + if (audit.result.informative) { auditEl.classList.add('lh-audit--informative'); } @@ -55,14 +59,14 @@ class CategoryRenderer { auditEl.classList.add('lh-audit--manual'); } - this._populateScore(auditEl, audit.result.score, scoreDisplayMode, audit.result.error); + this._setRatingClass(auditEl, audit.result.score, scoreDisplayMode, audit.result.error); if (audit.result.error) { auditEl.classList.add(`lh-audit--error`); - const valueEl = this.dom.find('.lh-score__value', auditEl); - valueEl.textContent = 'Error'; - valueEl.classList.add('tooltip-boundary'); - const tooltip = this.dom.createChildOf(valueEl, 'div', 'lh-error-tooltip-content tooltip'); + const textEl = this.dom.find('.lh-audit__display-text', auditEl); + textEl.textContent = 'Error!'; + textEl.classList.add('tooltip-boundary'); + const tooltip = this.dom.createChildOf(textEl, 'div', 'lh-error-tooltip-content tooltip'); tooltip.textContent = audit.result.debugString || 'Report error: no audit information'; } else if (audit.result.debugString) { const debugStrEl = auditEl.appendChild(this.dom.createElement('div', 'lh-debug')); @@ -72,21 +76,18 @@ class CategoryRenderer { } /** - * @param {!DocumentFragment|!Element} element DOM node to populate with values. + * @param {!Element} element DOM node to populate with values. * @param {number} score * @param {string} scoreDisplayMode * @param {boolean} isError * @return {!Element} */ - _populateScore(element, score, scoreDisplayMode, isError) { - const scoreOutOf100 = Math.round(score * 100); - const valueEl = this.dom.find('.lh-score__value', element); - valueEl.textContent = Util.formatNumber(scoreOutOf100); + _setRatingClass(element, score, scoreDisplayMode, isError) { // FIXME(paulirish): this'll have to deal with null scores and scoreDisplayMode stuff.. const rating = isError ? 'error' : Util.calculateRating(score); - valueEl.classList.add(`lh-score__value--${rating}`, `lh-score__value--${scoreDisplayMode}`); - - return /** @type {!Element} **/ (element); + element.classList.add(`lh-audit--${rating}`, + `lh-audit--${scoreDisplayMode}`); + return element; } /** @@ -105,34 +106,38 @@ class CategoryRenderer { this.dom.find('.lh-category-header__description', tmpl) .appendChild(this.dom.convertMarkdownLinkSnippets(category.description)); - return this._populateScore(tmpl, category.score, 'numeric', false); + return /** @type {!Element} */ (tmpl.firstElementChild); } /** * Renders the group container for a group of audits. Individual audit elements can be added * directly to the returned element. * @param {!ReportRenderer.GroupJSON} group - * @param {{expandable: boolean}} opts + * @param {{expandable: boolean, itemCount: (number|undefined)}} opts * @return {!Element} */ renderAuditGroup(group, opts) { const expandable = opts.expandable; - const element = this.dom.createElement(expandable ? 'details' : 'div', 'lh-audit-group'); - const summmaryEl = this.dom.createChildOf(element, 'summary', 'lh-audit-group__summary'); + const groupEl = this.dom.createElement(expandable ? 'details' : 'div', 'lh-audit-group'); + const summmaryEl = this.dom.createChildOf(groupEl, 'summary', 'lh-audit-group__summary'); const headerEl = this.dom.createChildOf(summmaryEl, 'div', 'lh-audit-group__header'); + const itemCountEl = this.dom.createChildOf(summmaryEl, 'div', 'lh-audit-group__itemcount'); this.dom.createChildOf(summmaryEl, 'div', `lh-toggle-arrow ${expandable ? '' : ' lh-toggle-arrow-unexpandable'}`, { - title: 'See audits', + title: 'Show audits', }); if (group.description) { const auditGroupDescription = this.dom.createElement('div', 'lh-audit-group__description'); auditGroupDescription.appendChild(this.dom.convertMarkdownLinkSnippets(group.description)); - element.appendChild(auditGroupDescription); + groupEl.appendChild(auditGroupDescription); } headerEl.textContent = group.title; - return element; + if (opts.itemCount) { + itemCountEl.textContent = `${opts.itemCount} audits`; + } + return groupEl; } /** @@ -161,8 +166,8 @@ class CategoryRenderer { */ _renderFailedAuditsSection(elements) { const failedElem = this.renderAuditGroup({ - title: `${this._getTotalAuditsLength(elements)} Failed Audits`, - }, {expandable: false}); + title: `Failed audits`, + }, {expandable: false, itemCount: this._getTotalAuditsLength(elements)}); failedElem.classList.add('lh-failed-audits'); elements.forEach(elem => failedElem.appendChild(elem)); return failedElem; @@ -174,8 +179,8 @@ class CategoryRenderer { */ renderPassedAuditsSection(elements) { const passedElem = this.renderAuditGroup({ - title: `${this._getTotalAuditsLength(elements)} Passed Audits`, - }, {expandable: true}); + title: `Passed audits`, + }, {expandable: true, itemCount: this._getTotalAuditsLength(elements)}); passedElem.classList.add('lh-passed-audits'); elements.forEach(elem => passedElem.appendChild(elem)); return passedElem; @@ -187,8 +192,8 @@ class CategoryRenderer { */ _renderNotApplicableAuditsSection(elements) { const notApplicableElem = this.renderAuditGroup({ - title: `${this._getTotalAuditsLength(elements)} Not Applicable Audits`, - }, {expandable: true}); + title: `Not applicable`, + }, {expandable: true, itemCount: this._getTotalAuditsLength(elements)}); notApplicableElem.classList.add('lh-audit-group--notapplicable'); elements.forEach(elem => notApplicableElem.appendChild(elem)); return notApplicableElem; @@ -197,20 +202,17 @@ class CategoryRenderer { /** * @param {!Array} manualAudits * @param {string} manualDescription - * @param {!Element} element Parent container to add the manual audits to. + * @return {!Element} */ - _renderManualAudits(manualAudits, manualDescription, element) { - if (!manualAudits.length) return; - + _renderManualAudits(manualAudits, manualDescription) { const group = {title: 'Additional items to manually check', description: manualDescription}; - const auditGroupElem = this.renderAuditGroup(group, {expandable: true}); + const auditGroupElem = this.renderAuditGroup(group, + {expandable: true, itemCount: manualAudits.length}); auditGroupElem.classList.add('lh-audit-group--manual'); - - manualAudits.forEach(audit => { - auditGroupElem.appendChild(this.renderAudit(audit)); + manualAudits.forEach((audit, i) => { + auditGroupElem.appendChild(this.renderAudit(audit, i)); }); - - element.appendChild(auditGroupElem); + return auditGroupElem; } /** @@ -291,12 +293,12 @@ class CategoryRenderer { const passedElements = /** @type {!Array} */ ([]); const notApplicableElements = /** @type {!Array} */ ([]); - auditsUngrouped.failed.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit) => - failedElements.push(this.renderAudit(audit))); - auditsUngrouped.passed.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit) => - passedElements.push(this.renderAudit(audit))); - auditsUngrouped.notApplicable.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit) => - notApplicableElements.push(this.renderAudit(audit))); + auditsUngrouped.failed.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit, i) => + failedElements.push(this.renderAudit(audit, i))); + auditsUngrouped.passed.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit, i) => + passedElements.push(this.renderAudit(audit, i))); + auditsUngrouped.notApplicable.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit, i) => + notApplicableElements.push(this.renderAudit(audit, i))); let hasFailedGroups = false; @@ -306,7 +308,7 @@ class CategoryRenderer { if (groups.failed.length) { const auditGroupElem = this.renderAuditGroup(group, {expandable: false}); - groups.failed.forEach(item => auditGroupElem.appendChild(this.renderAudit(item))); + groups.failed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i))); auditGroupElem.open = true; failedElements.push(auditGroupElem); @@ -315,13 +317,14 @@ class CategoryRenderer { if (groups.passed.length) { const auditGroupElem = this.renderAuditGroup(group, {expandable: true}); - groups.passed.forEach(item => auditGroupElem.appendChild(this.renderAudit(item))); + groups.passed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i))); passedElements.push(auditGroupElem); } if (groups.notApplicable.length) { const auditGroupElem = this.renderAuditGroup(group, {expandable: true}); - groups.notApplicable.forEach(item => auditGroupElem.appendChild(this.renderAudit(item))); + groups.notApplicable.forEach((item, i) => + auditGroupElem.appendChild(this.renderAudit(item, i))); notApplicableElements.push(auditGroupElem); } }); @@ -336,6 +339,11 @@ class CategoryRenderer { } } + if (manualAudits.length) { + const manualEl = this._renderManualAudits(manualAudits, category.manualDescription); + element.appendChild(manualEl); + } + if (passedElements.length) { const passedElem = this.renderPassedAuditsSection(passedElements); element.appendChild(passedElem); @@ -346,9 +354,6 @@ class CategoryRenderer { element.appendChild(notApplicableElem); } - // Render manual audits after passing. - this._renderManualAudits(manualAudits, category.manualDescription, element); - return element; } diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js index 498640b713ac..5f6f68ccade5 100644 --- a/lighthouse-core/report/html/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js @@ -42,10 +42,11 @@ class PerformanceCategoryRenderer extends CategoryRenderer { /** * @param {!ReportRenderer.AuditJSON} audit + * @param {number} index * @param {number} scale * @return {!Element} */ - _renderOpportunity(audit, scale) { + _renderOpportunity(audit, index, scale) { const element = this.dom.createElement('details', [ 'lh-load-opportunity', `lh-load-opportunity--${Util.calculateRating(audit.result.score)}`, @@ -53,6 +54,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { ].join(' ')); element.id = audit.result.name; + // TODO(paulirish): use a template instead. const summary = this.dom.createChildOf(element, 'summary', 'lh-load-opportunity__summary ' + 'lh-expandable-details__summary'); const titleEl = this.dom.createChildOf(summary, 'div', 'lh-load-opportunity__title'); @@ -161,17 +163,24 @@ class PerformanceCategoryRenderer extends CategoryRenderer { const maxWaste = Math.max(...opportunityAudits.map(audit => audit.result.rawValue)); const scale = Math.ceil(maxWaste / 1000) * 1000; const groupEl = this.renderAuditGroup(groups['load-opportunities'], {expandable: false}); - opportunityAudits.forEach(item => groupEl.appendChild(this._renderOpportunity(item, scale))); + opportunityAudits.forEach((item, i) => + groupEl.appendChild(this._renderOpportunity(item, i, scale))); groupEl.open = true; element.appendChild(groupEl); } // Diagnostics const diagnosticAudits = category.audits - .filter(audit => audit.group === 'diagnostics' && audit.result.score < 1); + .filter(audit => audit.group === 'diagnostics' && audit.result.score < 1) + .sort((a, b) => { + const scoreA = a.result.informative ? 100 : a.result.score; + const scoreB = b.result.informative ? 100 : b.result.score; + return scoreA - scoreB; + }); + if (diagnosticAudits.length) { const groupEl = this.renderAuditGroup(groups['diagnostics'], {expandable: false}); - diagnosticAudits.forEach(item => groupEl.appendChild(this.renderAudit(item))); + diagnosticAudits.forEach((item, i) => groupEl.appendChild(this.renderAudit(item, i))); groupEl.open = true; element.appendChild(groupEl); } @@ -179,7 +188,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { const passedElements = category.audits .filter(audit => (audit.group === 'load-opportunities' || audit.group === 'diagnostics') && audit.result.score === 1) - .map(audit => this.renderAudit(audit)); + .map((audit, i) => this.renderAudit(audit, i)); if (!passedElements.length) return element; diff --git a/lighthouse-core/report/html/renderer/report-renderer.js b/lighthouse-core/report/html/renderer/report-renderer.js index a0868287e6ce..ae882b3f6d61 100644 --- a/lighthouse-core/report/html/renderer/report-renderer.js +++ b/lighthouse-core/report/html/renderer/report-renderer.js @@ -106,7 +106,7 @@ class ReportRenderer { this._dom.find('.leftnav-item__category', navItem).textContent = category.name; const score = this._dom.find('.leftnav-item__score', navItem); - score.classList.add(`lh-score__value--${Util.calculateRating(category.score)}`); + score.classList.add(`lh-audit--${Util.calculateRating(category.score)}`); score.textContent = Math.round(100 * category.score); nav.appendChild(navItem); } diff --git a/lighthouse-core/report/html/report-styles.css b/lighthouse-core/report/html/report-styles.css index 7de75e20c653..649a2b1f370f 100644 --- a/lighthouse-core/report/html/report-styles.css +++ b/lighthouse-core/report/html/report-styles.css @@ -35,6 +35,7 @@ --report-border-color: #ccc; --report-secondary-border-color: #ebebeb; --metric-timeline-rule-color: #b3b3b3; + --display-value-gray: hsl(216, 5%, 39%); --report-width: calc(60 * var(--body-font-size)); --report-menu-width: calc(20 * var(--body-font-size)); --report-content-width: calc(var(--report-width) + var(--report-menu-width)); @@ -50,16 +51,17 @@ --lh-sparkline-height: 5px; --lh-sparkline-thin-height: 3px; --lh-filmstrip-thumbnail-width: 60px; - --lh-audit-score-width: calc(5 * var(--body-font-size)); + --lh-score-icon-width: calc(1.5 * var(--body-font-size)); --lh-category-score-width: calc(5 * var(--body-font-size)); --lh-audit-vpadding: 8px; + --lh-audit-index-width: 1.3em; --lh-audit-hgap: 12px; --lh-audit-group-vpadding: 12px; --lh-section-vpadding: 12px; - --pass-icon-url: url('data:image/svg+xml;utf8,'); - --fail-icon-url: url('data:image/svg+xml;utf8,'); - --collapsed-icon-url: url('data:image/svg+xml;utf8,'); - --expanded-icon-url: url('data:image/svg+xml;utf8,'); + --pass-icon-url: url('data:image/svg+xml;utf8,check'); + --average-icon-url: url('data:image/svg+xml;utf8,info'); + --fail-icon-url: url('data:image/svg+xml;utf8,warn'); + --chevron-icon-url: url('data:image/svg+xml;utf8,'); } .lh-vars.lh-devtools { @@ -114,11 +116,12 @@ display: none !important; } -a { +.lh-audit-group a, +.lh-category-header__description a { color: #0c50c7; } -summary { +.lh-root details summary { cursor: pointer; } @@ -182,94 +185,78 @@ summary { /* Score */ -.lh-score__value { - float: right; +.lh-audit__score-icon { margin-left: var(--lh-score-margin); - width: calc(var(--lh-audit-score-width) - var(--lh-score-margin)); - position: relative; - font-weight: bold; - top: 1px; - text-align: right; -} - -.lh-score__value::after { - content: ''; - position: absolute; - right: 0; - top: 0; - bottom: 0; - border-radius: inherit; - width: 16px; -} - -.lh-audit--informative .lh-score__value, -.lh-audit--manual .lh-score__value { - display: none; + width: var(--lh-score-icon-width); + background: none no-repeat center center / contain; } -/* No icon for audits with number scores. */ -.lh-score__value:not(.lh-score__value--binary)::after { - content: none; -} - -.lh-score__value--pass { +.lh-audit--pass .lh-audit__display-text { color: var(--pass-color); } - -.lh-score__value--pass::after { - background: var(--pass-icon-url) no-repeat center center / 12px 12px; +.lh-audit--pass .lh-audit__score-icon { + background-image: var(--pass-icon-url); } -.lh-score__value--average { +.lh-audit--average .lh-audit__display-text { color: var(--average-color); } - -.lh-score__value--average::after { - background: none; - content: '!'; - color: var(--average-color); - display: flex; - justify-content: center; - align-items: center; - font-weight: 500; - font-size: 15px; +.lh-audit--average .lh-audit__score-icon { + background-image: var(--average-icon-url); } -.lh-score__value--fail { +.lh-audit--fail .lh-audit__display-text { color: var(--fail-color); } +.lh-audit--fail .lh-audit__score-icon { + background-image: var(--fail-icon-url); +} -.lh-score__value--fail::after { - background: var(--fail-icon-url) no-repeat center center / 12px 12px; +.lh-audit--informative .lh-audit__display-text { + color: var(--display-value-gray); } -/* This must override the above styles */ -.lh-audit .lh-score__value--binary { - color: transparent; +.lh-audit--informative .lh-audit__score-icon, +.lh-audit--manual .lh-audit__score-icon { + visibility: hidden; } +.lh-audit--error .lh-audit__score-icon { + display: none; +} + + .lh-audit__description, .lh-category-header__description { + margin-top: 5px; font-size: var(--body-font-size); color: var(--secondary-text-color); line-height: var(--body-line-height); } +.lh-audit__header > div, +.lh-audit__header > span { + margin: 0 5px; +} + +.lh-audit__header .lh-audit__index { + margin-left: var(--text-indent); + width: var(--lh-audit-index-width); +} + .lh-audit__title { flex: 1; } .lh-toggle-arrow { - background: var(--collapsed-icon-url) no-repeat center center / 12px 12px; - background-color: transparent; + background: var(--chevron-icon-url) no-repeat center center / 20px 20px; width: 12px; - height: 12px; flex: none; transition: transform 150ms ease-in-out; cursor: pointer; border: none; - order: -1; + transform: rotateZ(90deg); margin-right: calc(var(--expandable-indent) - 12px); - align-self: flex-start; + margin-top: calc((var(--body-line-height) - 12px) / 2); } .lh-toggle-arrow-unexpandable { @@ -283,15 +270,17 @@ summary { } .lh-expandable-details__summary { - display: flex; - align-items: center; cursor: pointer; margin-left: calc(0px - var(--expandable-indent)); } +.lh-audit__header { + display: flex; +} + .lh-audit-group[open] > .lh-audit-group__summary > .lh-toggle-arrow, .lh-expandable-details[open] > .lh-expandable-details__summary > .lh-toggle-arrow { - background-image: var(--expanded-icon-url); + transform: rotateZ(-90deg); } .lh-audit-group__summary::-webkit-details-marker, @@ -299,10 +288,6 @@ summary { display: none; } -.lh-audit__header .lh-toggle-arrow { - margin-top: calc((var(--body-line-height) - 12px) / 2); -} - /* Perf Timeline */ .lh-timeline-container { @@ -386,7 +371,7 @@ summary { } .lh-metric--pass .lh-metric__value::after { - background: url('data:image/svg+xml;utf8,check') no-repeat 50% 50%; + background: var(--pass-icon-url) no-repeat 50% 50%; } @@ -394,7 +379,7 @@ summary { color: var(--average-color); } .lh-metric--average .lh-metric__value::after { - background: url('data:image/svg+xml;utf8,info') no-repeat 50% 50%; + background: var(--average-icon-url) no-repeat 50% 50%; } @@ -403,7 +388,7 @@ summary { } .lh-metric--fail .lh-metric__value::after { - background: url('data:image/svg+xml;utf8,warn') no-repeat 50% 50%; + background: var(--fail-icon-url) no-repeat 50% 50%; } .lh-metric--error .lh-metric__value, @@ -462,7 +447,7 @@ summary { .lh-load-opportunity__stats { text-align: right; - flex: 0 0 var(--lh-audit-score-width); + flex: 0 0 calc(5 * var(--body-font-size)); } .lh-load-opportunity__primary-stat { @@ -589,13 +574,12 @@ summary { } .lh-audit .lh-debug { - margin-left: var(--expandable-indent); - margin-right: var(--lh-audit-score-width); + margin-left: calc(var(--expandable-indent) + var(--lh-audit-index-width)); + margin-right: var(--lh-score-icon-width); } -.lh-audit--error .lh-score__value { +.lh-audit--error .lh-audit__display-text { color: var(--fail-color); - font-weight: normal; } /* Audit Group */ @@ -609,15 +593,21 @@ summary { .lh-audit-group__header { font-size: var(--subheader-font-size); line-height: var(--subheader-line-height); + flex: 1; } .lh-audit-group__summary { display: flex; - align-items: center; + justify-content: space-between; margin-bottom: var(--lh-audit-group-vpadding); margin-left: calc(0px - var(--expandable-indent)); } +.lh-audit-group__itemcount { + color: var(--display-value-gray); + margin: 0 10px; +} + .lh-audit-group__summary .lh-toggle-arrow { margin-top: calc((var(--subheader-line-height) - 12px) / 2); } @@ -777,7 +767,6 @@ summary { margin-bottom: var(--lh-section-vpadding); } -.lh-category-header .lh-score__value, .lh-category-header .lh-score__gauge .lh-gauge__label { display: none; } diff --git a/lighthouse-core/report/html/templates.html b/lighthouse-core/report/html/templates.html index bdb6c00d8c63..37a9c39d7d6c 100644 --- a/lighthouse-core/report/html/templates.html +++ b/lighthouse-core/report/html/templates.html @@ -19,7 +19,6 @@