Commit 5a003efc authored by Paul Irish's avatar Paul Irish Committed by Commit Bot

DevTools: Roll Lighthouse to 4.0.0-beta

Bug: 772558
Change-Id: Ie1ef89f7a189687e600e5e22d376c53dac242079
Reviewed-on: https://chromium-review.googlesource.com/c/1355176Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Commit-Queue: Paul Irish <paulirish@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612481}
parent 298c4d34
...@@ -38,7 +38,7 @@ all_devtools_files = [ ...@@ -38,7 +38,7 @@ all_devtools_files = [
"front_end/audits2_worker.js", "front_end/audits2_worker.js",
"front_end/audits2_worker.json", "front_end/audits2_worker.json",
"front_end/audits2_worker/Audits2Service.js", "front_end/audits2_worker/Audits2Service.js",
"front_end/audits2_worker/lighthouse/lighthouse-background.js", "front_end/audits2_worker/lighthouse/lighthouse-dt-bundle.js",
"front_end/audits2_worker/module.json", "front_end/audits2_worker/module.json",
"front_end/audits2/Audits2Panel.js", "front_end/audits2/Audits2Panel.js",
"front_end/audits2/Audits2Controller.js", "front_end/audits2/Audits2Controller.js",
...@@ -55,6 +55,7 @@ all_devtools_files = [ ...@@ -55,6 +55,7 @@ all_devtools_files = [
"front_end/audits2/lighthouse/renderer/details-renderer.js", "front_end/audits2/lighthouse/renderer/details-renderer.js",
"front_end/audits2/lighthouse/renderer/category-renderer.js", "front_end/audits2/lighthouse/renderer/category-renderer.js",
"front_end/audits2/lighthouse/renderer/performance-category-renderer.js", "front_end/audits2/lighthouse/renderer/performance-category-renderer.js",
"front_end/audits2/lighthouse/renderer/pwa-category-renderer.js",
"front_end/audits2/lighthouse/renderer/crc-details-renderer.js", "front_end/audits2/lighthouse/renderer/crc-details-renderer.js",
"front_end/audits2/lighthouse/renderer/report-renderer.js", "front_end/audits2/lighthouse/renderer/report-renderer.js",
"front_end/audits2/lighthouse/renderer/util.js", "front_end/audits2/lighthouse/renderer/util.js",
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
/** @typedef {import('./report-renderer.js')} ReportRenderer */ /** @typedef {import('./report-renderer.js')} ReportRenderer */
/** @typedef {import('./details-renderer.js')} DetailsRenderer */ /** @typedef {import('./details-renderer.js')} DetailsRenderer */
/** @typedef {import('./util.js')} Util */ /** @typedef {import('./util.js')} Util */
/** @typedef {'failed'|'manual'|'passed'|'not-applicable'} TopLevelClumpId */
class CategoryRenderer { class CategoryRenderer {
/** /**
...@@ -39,6 +40,29 @@ class CategoryRenderer { ...@@ -39,6 +40,29 @@ class CategoryRenderer {
this.detailsRenderer.setTemplateContext(this.templateContext); this.detailsRenderer.setTemplateContext(this.templateContext);
} }
/**
* Display info per top-level clump. Define on class to avoid race with Util init.
*/
get _clumpDisplayInfo() {
return {
'failed': {
className: 'lh-clump--failed',
},
'manual': {
title: Util.UIStrings.manualAuditsGroupTitle,
className: 'lh-clump--manual',
},
'passed': {
title: Util.UIStrings.passedAuditsGroupTitle,
className: 'lh-clump--passed',
},
'not-applicable': {
title: Util.UIStrings.notApplicableAuditsGroupTitle,
className: 'lh-clump--not-applicable',
},
};
}
/** /**
* @param {LH.ReportResult.AuditRef} audit * @param {LH.ReportResult.AuditRef} audit
* @param {number} index * @param {number} index
...@@ -164,11 +188,12 @@ class CategoryRenderer { ...@@ -164,11 +188,12 @@ class CategoryRenderer {
renderAuditGroup(group, opts) { renderAuditGroup(group, opts) {
const expandable = opts.expandable; const expandable = opts.expandable;
const groupEl = this.dom.createElement(expandable ? 'details' : 'div', 'lh-audit-group'); const groupEl = this.dom.createElement(expandable ? 'details' : 'div', 'lh-audit-group');
const summmaryEl = this.dom.createChildOf(groupEl, 'summary', 'lh-audit-group__summary'); const summaryEl = this.dom.createChildOf(groupEl, expandable ? 'summary' : 'div');
const headerEl = this.dom.createChildOf(summmaryEl, 'div', 'lh-audit-group__header'); const summaryInnerEl = this.dom.createChildOf(summaryEl, 'div', 'lh-audit-group__summary');
const itemCountEl = this.dom.createChildOf(summmaryEl, 'div', 'lh-audit-group__itemcount'); const headerEl = this.dom.createChildOf(summaryInnerEl, 'div', 'lh-audit-group__header');
const itemCountEl = this.dom.createChildOf(summaryInnerEl, 'div', 'lh-audit-group__itemcount');
if (expandable) { if (expandable) {
const chevronEl = summmaryEl.appendChild(this._createChevron()); const chevronEl = summaryInnerEl.appendChild(this._createChevron());
chevronEl.title = Util.UIStrings.auditGroupExpandTooltip; chevronEl.title = Util.UIStrings.auditGroupExpandTooltip;
} }
...@@ -187,76 +212,111 @@ class CategoryRenderer { ...@@ -187,76 +212,111 @@ class CategoryRenderer {
} }
/** /**
* Find the total number of audits contained within a section. * Takes an array of auditRefs, groups them if requested, then returns an
* Accounts for nested subsections like Accessibility. * array of audit and audit-group elements.
* @param {Array<Element>} elements * @param {Array<LH.ReportResult.AuditRef>} auditRefs
* @return {number} * @param {Object<string, LH.Result.ReportGroup>} groupDefinitions
* @param {{expandable: boolean}} opts
* @return {Array<Element>}
*/ */
_getTotalAuditsLength(elements) { _renderGroupedAudits(auditRefs, groupDefinitions, opts) {
// Create a scratch element to append sections to so we can reuse querySelectorAll(). // Audits grouped by their group (or under notAGroup).
const scratch = this.dom.createElement('div'); /** @type {Map<string, Array<LH.ReportResult.AuditRef>>} */
elements.forEach(function(element) { const grouped = new Map();
scratch.appendChild(element);
}); // Add audits without a group first so they will appear first.
const subAudits = scratch.querySelectorAll('.lh-audit'); const notAGroup = 'NotAGroup';
if (subAudits.length) { grouped.set(notAGroup, []);
return subAudits.length;
} else { for (const auditRef of auditRefs) {
return elements.length; const groupId = auditRef.group || notAGroup;
const groupAuditRefs = grouped.get(groupId) || [];
groupAuditRefs.push(auditRef);
grouped.set(groupId, groupAuditRefs);
} }
/** @type {Array<Element>} */
const auditElements = [];
// Continuous numbering across all groups.
let index = 0;
for (const [groupId, groupAuditRefs] of grouped) {
if (groupId === notAGroup) {
// Push not-grouped audits individually.
for (const auditRef of groupAuditRefs) {
auditElements.push(this.renderAudit(auditRef, index++));
}
continue;
} }
/** // Push grouped audits as a group.
* @param {Array<Element>} elements const groupDef = groupDefinitions[groupId];
* @return {Element} const auditGroupElem = this.renderAuditGroup(groupDef, opts);
*/ for (const auditRef of groupAuditRefs) {
_renderFailedAuditsSection(elements) { auditGroupElem.appendChild(this.renderAudit(auditRef, index++));
const failedElem = this.dom.createElement('div'); }
failedElem.classList.add('lh-failed-audits'); auditGroupElem.classList.add(`lh-audit-group--${groupId}`);
elements.forEach(elem => failedElem.appendChild(elem)); auditElements.push(auditGroupElem);
return failedElem;
} }
/** return auditElements;
* @param {Array<Element>} elements
* @return {Element}
*/
renderPassedAuditsSection(elements) {
const passedElem = this.renderAuditGroup({
title: Util.UIStrings.passedAuditsGroupTitle,
}, {expandable: true, itemCount: this._getTotalAuditsLength(elements)});
passedElem.classList.add('lh-passed-audits');
elements.forEach(elem => passedElem.appendChild(elem));
return passedElem;
} }
/** /**
* @param {Array<Element>} elements * Take a set of audits, group them if they have groups, then render in a top-level
* clump that can't be expanded/collapsed.
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
* @param {Object<string, LH.Result.ReportGroup>} groupDefinitions
* @return {Element} * @return {Element}
*/ */
_renderNotApplicableAuditsSection(elements) { renderUnexpandableClump(auditRefs, groupDefinitions) {
const notApplicableElem = this.renderAuditGroup({ const clumpElement = this.dom.createElement('div');
title: Util.UIStrings.notApplicableAuditsGroupTitle, const elements = this._renderGroupedAudits(auditRefs, groupDefinitions, {expandable: false});
}, {expandable: true, itemCount: this._getTotalAuditsLength(elements)}); elements.forEach(elem => clumpElement.appendChild(elem));
notApplicableElem.classList.add('lh-audit-group--not-applicable'); return clumpElement;
elements.forEach(elem => notApplicableElem.appendChild(elem));
return notApplicableElem;
} }
/** /**
* @param {Array<LH.ReportResult.AuditRef>} manualAudits * Renders a clump (a grouping of groups), under a status of failed, manual,
* @param {string} [manualDescription] * passed, or not-applicable. The result ends up something like:
*
* clump (e.g. 'failed')
* ├── audit 1 (w/o group)
* ├── audit 2 (w/o group)
* ├── audit group
* | ├── audit 3
* | └── audit 4
* └── audit group
* ├── audit 5
* └── audit 6
* clump (e.g. 'manual')
* ├── …
* ⋮
* @param {TopLevelClumpId} clumpId
* @param {{auditRefs: Array<LH.ReportResult.AuditRef>, groupDefinitions: Object<string, LH.Result.ReportGroup>, description?: string}} clumpOpts
* @return {Element} * @return {Element}
*/ */
_renderManualAudits(manualAudits, manualDescription) { renderClump(clumpId, {auditRefs, groupDefinitions, description}) {
const group = {title: Util.UIStrings.manualAuditsGroupTitle, description: manualDescription}; if (clumpId === 'failed') {
const auditGroupElem = this.renderAuditGroup(group, // Failed audit clump is always expanded and not nested in an lh-audit-group.
{expandable: true, itemCount: manualAudits.length}); const failedElem = this.renderUnexpandableClump(auditRefs, groupDefinitions);
auditGroupElem.classList.add('lh-audit-group--manual'); failedElem.classList.add('lh-clump', this._clumpDisplayInfo.failed.className);
manualAudits.forEach((audit, i) => { return failedElem;
auditGroupElem.appendChild(this.renderAudit(audit, i)); }
});
return auditGroupElem; const expandable = true;
const elements = this._renderGroupedAudits(auditRefs, groupDefinitions, {expandable});
const clumpInfo = this._clumpDisplayInfo[clumpId];
// TODO: renderAuditGroup shouldn't be used to render a clump (since it *contains* audit groups).
const groupDef = {title: clumpInfo.title, description};
const opts = {expandable, itemCount: auditRefs.length};
const clumpElem = this.renderAuditGroup(groupDef, opts);
clumpElem.classList.add('lh-clump', clumpInfo.className);
elements.forEach(elem => clumpElem.appendChild(elem));
return clumpElem;
} }
/** /**
...@@ -302,104 +362,57 @@ class CategoryRenderer { ...@@ -302,104 +362,57 @@ class CategoryRenderer {
} }
/** /**
* @param {LH.ReportResult.Category} category * Returns the id of the top-level clump to put this audit in.
* @param {Object<string, LH.Result.ReportGroup>} [groupDefinitions] * @param {LH.ReportResult.AuditRef} auditRef
* @return {Element} * @return {TopLevelClumpId}
*/ */
render(category, groupDefinitions) { _getClumpIdForAuditRef(auditRef) {
const element = this.dom.createElement('div', 'lh-category'); const scoreDisplayMode = auditRef.result.scoreDisplayMode;
this.createPermalinkSpan(element, category.id); if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'not-applicable') {
element.appendChild(this.renderCategoryHeader(category)); return scoreDisplayMode;
const auditRefs = category.auditRefs;
const manualAudits = auditRefs.filter(audit => audit.result.scoreDisplayMode === 'manual');
const nonManualAudits = auditRefs.filter(audit => !manualAudits.includes(audit));
/** @type {Object<string, {passed: Array<LH.ReportResult.AuditRef>, failed: Array<LH.ReportResult.AuditRef>, notApplicable: Array<LH.ReportResult.AuditRef>}>} */
const auditsGroupedByGroup = {};
const auditsUngrouped = {passed: [], failed: [], notApplicable: []};
nonManualAudits.forEach(auditRef => {
let group;
if (auditRef.group) {
const groupId = auditRef.group;
if (auditsGroupedByGroup[groupId]) {
group = auditsGroupedByGroup[groupId];
} else {
group = {passed: [], failed: [], notApplicable: []};
auditsGroupedByGroup[groupId] = group;
}
} else {
group = auditsUngrouped;
} }
if (auditRef.result.scoreDisplayMode === 'not-applicable') { if (Util.showAsPassed(auditRef.result)) {
group.notApplicable.push(auditRef); return 'passed';
} else if (Util.showAsPassed(auditRef.result)) {
group.passed.push(auditRef);
} else { } else {
group.failed.push(auditRef); return 'failed';
}
});
const failedElements = /** @type {Array<Element>} */ ([]);
const passedElements = /** @type {Array<Element>} */ ([]);
const notApplicableElements = /** @type {Array<Element>} */ ([]);
auditsUngrouped.failed.forEach((audit, i) => failedElements.push(this.renderAudit(audit, i)));
auditsUngrouped.passed.forEach((audit, i) => passedElements.push(this.renderAudit(audit, i)));
auditsUngrouped.notApplicable.forEach((audit, i) => notApplicableElements.push(
this.renderAudit(audit, i)));
Object.keys(auditsGroupedByGroup).forEach(groupId => {
if (!groupDefinitions) return; // We never reach here if there aren't groups, but TSC needs convincing
const group = groupDefinitions[groupId];
const groups = auditsGroupedByGroup[groupId];
if (groups.failed.length) {
const auditGroupElem = this.renderAuditGroup(group, {expandable: false});
groups.failed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i)));
auditGroupElem.classList.add('lh-audit-group--unadorned');
failedElements.push(auditGroupElem);
} }
if (groups.passed.length) {
const auditGroupElem = this.renderAuditGroup(group, {expandable: true});
groups.passed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i)));
auditGroupElem.classList.add('lh-audit-group--unadorned');
passedElements.push(auditGroupElem);
}
if (groups.notApplicable.length) {
const auditGroupElem = this.renderAuditGroup(group, {expandable: true});
groups.notApplicable.forEach((item, i) =>
auditGroupElem.appendChild(this.renderAudit(item, i)));
auditGroupElem.classList.add('lh-audit-group--unadorned');
notApplicableElements.push(auditGroupElem);
}
});
if (failedElements.length) {
const failedElem = this._renderFailedAuditsSection(failedElements);
element.appendChild(failedElem);
}
if (manualAudits.length) {
const manualEl = this._renderManualAudits(manualAudits, category.manualDescription);
element.appendChild(manualEl);
} }
if (passedElements.length) { /**
const passedElem = this.renderPassedAuditsSection(passedElements); * @param {LH.ReportResult.Category} category
element.appendChild(passedElem); * @param {Object<string, LH.Result.ReportGroup>} [groupDefinitions]
} * @return {Element}
*/
render(category, groupDefinitions = {}) {
const element = this.dom.createElement('div', 'lh-category');
this.createPermalinkSpan(element, category.id);
element.appendChild(this.renderCategoryHeader(category));
if (notApplicableElements.length) { // Top level clumps for audits, in order they will appear in the report.
const notApplicableElem = this._renderNotApplicableAuditsSection(notApplicableElements); /** @type {Map<TopLevelClumpId, Array<LH.ReportResult.AuditRef>>} */
element.appendChild(notApplicableElem); const clumps = new Map();
clumps.set('failed', []);
clumps.set('manual', []);
clumps.set('passed', []);
clumps.set('not-applicable', []);
// Sort audits into clumps.
for (const auditRef of category.auditRefs) {
const clumpId = this._getClumpIdForAuditRef(auditRef);
const clump = /** @type {Array<LH.ReportResult.AuditRef>} */ (clumps.get(clumpId)); // already defined
clump.push(auditRef);
clumps.set(clumpId, clump);
}
// Render each clump.
for (const [clumpId, clumpRefs] of clumps) {
if (clumpRefs.length === 0) continue;
const description = clumpId === 'manual' ? category.manualDescription : undefined;
const clumpElem = this.renderClump(clumpId, {auditRefs: clumpRefs, groupDefinitions,
description});
element.appendChild(clumpElem);
} }
return element; return element;
......
...@@ -59,7 +59,7 @@ class CriticalRequestChainRenderer { ...@@ -59,7 +59,7 @@ class CriticalRequestChainRenderer {
const node = parent[id]; const node = parent[id];
const siblings = Object.keys(parent); const siblings = Object.keys(parent);
const isLastChild = siblings.indexOf(id) === (siblings.length - 1); const isLastChild = siblings.indexOf(id) === (siblings.length - 1);
const hasChildren = Object.keys(node.children).length > 0; const hasChildren = !!node.children && Object.keys(node.children).length > 0;
// Copy the tree markers so that we don't change by reference. // Copy the tree markers so that we don't change by reference.
const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : []; const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : [];
...@@ -149,13 +149,14 @@ class CriticalRequestChainRenderer { ...@@ -149,13 +149,14 @@ class CriticalRequestChainRenderer {
*/ */
static buildTree(dom, tmpl, segment, elem, details) { static buildTree(dom, tmpl, segment, elem, details) {
elem.appendChild(CriticalRequestChainRenderer.createChainNode(dom, tmpl, segment)); elem.appendChild(CriticalRequestChainRenderer.createChainNode(dom, tmpl, segment));
if (segment.node.children) {
for (const key of Object.keys(segment.node.children)) { for (const key of Object.keys(segment.node.children)) {
const childSegment = CriticalRequestChainRenderer.createSegment(segment.node.children, key, const childSegment = CriticalRequestChainRenderer.createSegment(segment.node.children, key,
segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild); segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild);
CriticalRequestChainRenderer.buildTree(dom, tmpl, childSegment, elem, details); CriticalRequestChainRenderer.buildTree(dom, tmpl, childSegment, elem, details);
} }
} }
}
/** /**
* @param {DOM} dom * @param {DOM} dom
......
...@@ -80,6 +80,8 @@ class DetailsRenderer { ...@@ -80,6 +80,8 @@ class DetailsRenderer {
case 'opportunity': case 'opportunity':
// @ts-ignore - TODO(bckenny): Fix type hierarchy // @ts-ignore - TODO(bckenny): Fix type hierarchy
return this._renderOpportunityTable(details); return this._renderOpportunityTable(details);
case 'numeric':
return this._renderNumeric(/** @type {StringDetailsJSON} */ (details));
default: { default: {
throw new Error(`Unknown type: ${details.type}`); throw new Error(`Unknown type: ${details.type}`);
} }
...@@ -124,14 +126,11 @@ class DetailsRenderer { ...@@ -124,14 +126,11 @@ class DetailsRenderer {
displayedPath = parsed.file === '/' ? parsed.origin : parsed.file; displayedPath = parsed.file === '/' ? parsed.origin : parsed.file;
displayedHost = parsed.file === '/' ? '' : `(${parsed.hostname})`; displayedHost = parsed.file === '/' ? '' : `(${parsed.hostname})`;
title = url; title = url;
} catch (/** @type {!Error} */ e) { } catch (e) {
if (!e.name.startsWith('TypeError')) {
throw e;
}
displayedPath = url; displayedPath = url;
} }
const element = /** @type {HTMLElement} */ (this._dom.createElement('div', 'lh-text__url')); const element = this._dom.createElement('div', 'lh-text__url');
element.appendChild(this._renderText({ element.appendChild(this._renderText({
value: displayedPath, value: displayedPath,
})); }));
...@@ -162,7 +161,7 @@ class DetailsRenderer { ...@@ -162,7 +161,7 @@ class DetailsRenderer {
}); });
} }
const a = /** @type {HTMLAnchorElement} */ (this._dom.createElement('a')); const a = this._dom.createElement('a');
a.rel = 'noopener'; a.rel = 'noopener';
a.target = '_blank'; a.target = '_blank';
a.textContent = details.text; a.textContent = details.text;
...@@ -181,6 +180,16 @@ class DetailsRenderer { ...@@ -181,6 +180,16 @@ class DetailsRenderer {
return element; return element;
} }
/**
* @param {{value: string}} text
* @return {Element}
*/
_renderNumeric(text) {
const element = this._dom.createElement('div', 'lh-numeric');
element.textContent = text.value;
return element;
}
/** /**
* Create small thumbnail with scaled down image asset. * Create small thumbnail with scaled down image asset.
* If the supplied details doesn't have an image/* mimeType, then an empty span is returned. * If the supplied details doesn't have an image/* mimeType, then an empty span is returned.
...@@ -188,7 +197,7 @@ class DetailsRenderer { ...@@ -188,7 +197,7 @@ class DetailsRenderer {
* @return {Element} * @return {Element}
*/ */
_renderThumbnail(details) { _renderThumbnail(details) {
const element = /** @type {HTMLImageElement}*/ (this._dom.createElement('img', 'lh-thumbnail')); const element = this._dom.createElement('img', 'lh-thumbnail');
const strValue = details.value; const strValue = details.value;
element.src = strValue; element.src = strValue;
element.title = strValue; element.title = strValue;
...@@ -337,7 +346,7 @@ class DetailsRenderer { ...@@ -337,7 +346,7 @@ class DetailsRenderer {
* @protected * @protected
*/ */
renderNode(item) { renderNode(item) {
const element = /** @type {HTMLSpanElement} */ (this._dom.createElement('span', 'lh-node')); const element = this._dom.createElement('span', 'lh-node');
if (item.snippet) { if (item.snippet) {
element.textContent = item.snippet; element.textContent = item.snippet;
} }
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
/* globals URL self */ /* globals URL self */
/** @typedef {HTMLElementTagNameMap & {[id: string]: HTMLElement}} HTMLElmentByTagName */
class DOM { class DOM {
/** /**
* @param {Document} document * @param {Document} document
...@@ -27,14 +29,14 @@ class DOM { ...@@ -27,14 +29,14 @@ class DOM {
this._document = document; this._document = document;
} }
// TODO(bckenny): can pass along `createElement`'s inferred type
/** /**
* @param {string} name * @template {string} T
* @param {T} name
* @param {string=} className * @param {string=} className
* @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs. * @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs.
* Note: if an attribute key has an undefined value, this method does not * Note: if an attribute key has an undefined value, this method does not
* set the attribute on the node. * set the attribute on the node.
* @return {Element} * @return {HTMLElmentByTagName[T]}
*/ */
createElement(name, className, attrs = {}) { createElement(name, className, attrs = {}) {
const element = this._document.createElement(name); const element = this._document.createElement(name);
...@@ -58,13 +60,14 @@ class DOM { ...@@ -58,13 +60,14 @@ class DOM {
} }
/** /**
* @template {string} T
* @param {Element} parentElem * @param {Element} parentElem
* @param {string} elementName * @param {T} elementName
* @param {string=} className * @param {string=} className
* @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs. * @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs.
* Note: if an attribute key has an undefined value, this method does not * Note: if an attribute key has an undefined value, this method does not
* set the attribute on the node. * set the attribute on the node.
* @return {Element} * @return {HTMLElmentByTagName[T]}
*/ */
createChildOf(parentElem, elementName, className, attrs) { createChildOf(parentElem, elementName, className, attrs) {
const element = this.createElement(elementName, className, attrs); const element = this.createElement(elementName, className, attrs);
...@@ -122,7 +125,7 @@ class DOM { ...@@ -122,7 +125,7 @@ class DOM {
// Append link if there are any. // Append link if there are any.
if (linkText && linkHref) { if (linkText && linkHref) {
const a = /** @type {HTMLAnchorElement} */ (this.createElement('a')); const a = this.createElement('a');
a.rel = 'noopener'; a.rel = 'noopener';
a.target = '_blank'; a.target = '_blank';
a.textContent = linkText; a.textContent = linkText;
...@@ -147,7 +150,7 @@ class DOM { ...@@ -147,7 +150,7 @@ class DOM {
const [preambleText, codeText] = parts.splice(0, 2); const [preambleText, codeText] = parts.splice(0, 2);
element.appendChild(this._document.createTextNode(preambleText)); element.appendChild(this._document.createTextNode(preambleText));
if (codeText) { if (codeText) {
const pre = /** @type {HTMLPreElement} */ (this.createElement('code')); const pre = this.createElement('code');
pre.textContent = codeText; pre.textContent = codeText;
element.appendChild(pre); element.appendChild(pre);
} }
......
...@@ -188,7 +188,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { ...@@ -188,7 +188,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
groupEl.appendChild(headerEl); groupEl.appendChild(headerEl);
opportunityAudits.forEach((item, i) => opportunityAudits.forEach((item, i) =>
groupEl.appendChild(this._renderOpportunity(item, i, scale))); groupEl.appendChild(this._renderOpportunity(item, i, scale)));
groupEl.classList.add('lh-audit-group--opportunities'); groupEl.classList.add('lh-audit-group--load-opportunities');
element.appendChild(groupEl); element.appendChild(groupEl);
} }
...@@ -209,14 +209,17 @@ class PerformanceCategoryRenderer extends CategoryRenderer { ...@@ -209,14 +209,17 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
} }
// Passed audits // Passed audits
const passedElements = category.auditRefs const passedAudits = category.auditRefs
.filter(audit => (audit.group === 'load-opportunities' || audit.group === 'diagnostics') && .filter(audit => (audit.group === 'load-opportunities' || audit.group === 'diagnostics') &&
Util.showAsPassed(audit.result)) Util.showAsPassed(audit.result));
.map((audit, i) => this.renderAudit(audit, i));
if (!passedElements.length) return element; if (!passedAudits.length) return element;
const passedElem = this.renderPassedAuditsSection(passedElements); const clumpOpts = {
auditRefs: passedAudits,
groupDefinitions: groups,
};
const passedElem = this.renderClump('passed', clumpOpts);
element.appendChild(passedElem); element.appendChild(passedElem);
return element; return element;
} }
......
/**
* @license
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/* globals self, Util, CategoryRenderer */
class PwaCategoryRenderer extends CategoryRenderer {
/**
* @param {LH.ReportResult.Category} category
* @param {Object<string, LH.Result.ReportGroup>} [groupDefinitions]
* @return {Element}
*/
render(category, groupDefinitions = {}) {
const categoryElem = this.dom.createElement('div', 'lh-category');
this.createPermalinkSpan(categoryElem, category.id);
categoryElem.appendChild(this.renderCategoryHeader(category));
const auditRefs = category.auditRefs;
// Regular audits aren't split up into pass/fail/not-applicable clumps, they're
// all put in a top-level clump that isn't expandable/collapsable.
const regularAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode !== 'manual');
const auditsElem = this._renderAudits(regularAuditRefs, groupDefinitions);
categoryElem.appendChild(auditsElem);
// Manual audits are still in a manual clump.
const manualAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode === 'manual');
const manualElem = this.renderClump('manual',
{auditRefs: manualAuditRefs, groupDefinitions, description: category.manualDescription});
categoryElem.appendChild(manualElem);
return categoryElem;
}
/**
* @param {LH.ReportResult.Category} category
* @return {DocumentFragment}
*/
renderScoreGauge(category) {
// Defer to parent-gauge style if category error.
if (category.score === null) {
return super.renderScoreGauge(category);
}
const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge--pwa', this.templateContext);
const wrapper = /** @type {HTMLAnchorElement} */ (this.dom.find('.lh-gauge--pwa__wrapper',
tmpl));
wrapper.href = `#${category.id}`;
const allGroups = this._getGroupIds(category.auditRefs);
const passingGroupIds = this._getPassingGroupIds(category.auditRefs);
if (passingGroupIds.size === allGroups.size) {
wrapper.classList.add('lh-badged--all');
} else {
for (const passingGroupId of passingGroupIds) {
wrapper.classList.add(`lh-badged--${passingGroupId}`);
}
}
this.dom.find('.lh-gauge__label', tmpl).textContent = category.title;
return tmpl;
}
/**
* Returns the group IDs found in auditRefs.
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
* @return {Set<string>}
*/
_getGroupIds(auditRefs) {
const groupIds = auditRefs.map(ref => ref.group).filter(/** @return {g is string} */ g => !!g);
return new Set(groupIds);
}
/**
* Returns the group IDs whose audits are all considered passing.
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
* @return {Set<string>}
*/
_getPassingGroupIds(auditRefs) {
const uniqueGroupIds = this._getGroupIds(auditRefs);
// Remove any that have a failing audit.
for (const auditRef of auditRefs) {
if (!Util.showAsPassed(auditRef.result) && auditRef.group) {
uniqueGroupIds.delete(auditRef.group);
}
}
return uniqueGroupIds;
}
/**
* Render non-manual audits in groups, giving a badge to any group that has
* all passing audits.
* @param {Array<LH.ReportResult.AuditRef>} auditRefs
* @param {Object<string, LH.Result.ReportGroup>} groupDefinitions
* @return {Element}
*/
_renderAudits(auditRefs, groupDefinitions) {
const auditsElem = this.renderUnexpandableClump(auditRefs, groupDefinitions);
// Add a 'badged' class to group if all audits in that group pass.
const passsingGroupIds = this._getPassingGroupIds(auditRefs);
for (const groupId of passsingGroupIds) {
const groupElem = this.dom.find(`.lh-audit-group--${groupId}`, auditsElem);
groupElem.classList.add('lh-badged');
}
return auditsElem;
}
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = PwaCategoryRenderer;
} else {
self.PwaCategoryRenderer = PwaCategoryRenderer;
}
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
/** @typedef {import('./dom.js')} DOM */ /** @typedef {import('./dom.js')} DOM */
/** @typedef {import('./details-renderer.js').DetailsJSON} DetailsJSON */ /** @typedef {import('./details-renderer.js').DetailsJSON} DetailsJSON */
/* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer */ /* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer, PwaCategoryRenderer */
class ReportRenderer { class ReportRenderer {
/** /**
...@@ -42,6 +42,7 @@ class ReportRenderer { ...@@ -42,6 +42,7 @@ class ReportRenderer {
/** /**
* @param {LH.Result} result * @param {LH.Result} result
* @param {Element} container Parent element to render the report into. * @param {Element} container Parent element to render the report into.
* @return {Element}
*/ */
renderReport(result, container) { renderReport(result, container) {
// Mutate the UIStrings if necessary (while saving originals) // Mutate the UIStrings if necessary (while saving originals)
...@@ -55,7 +56,7 @@ class ReportRenderer { ...@@ -55,7 +56,7 @@ class ReportRenderer {
// put the UIStrings back into original state // put the UIStrings back into original state
Util.updateAllUIStrings(originalUIStrings); Util.updateAllUIStrings(originalUIStrings);
return /** @type {Element} **/ (container); return container;
} }
/** /**
...@@ -188,24 +189,39 @@ class ReportRenderer { ...@@ -188,24 +189,39 @@ class ReportRenderer {
const detailsRenderer = new DetailsRenderer(this._dom); const detailsRenderer = new DetailsRenderer(this._dom);
const categoryRenderer = new CategoryRenderer(this._dom, detailsRenderer); const categoryRenderer = new CategoryRenderer(this._dom, detailsRenderer);
categoryRenderer.setTemplateContext(this._templateContext); categoryRenderer.setTemplateContext(this._templateContext);
const perfCategoryRenderer = new PerformanceCategoryRenderer(this._dom, detailsRenderer);
perfCategoryRenderer.setTemplateContext(this._templateContext); /** @type {Record<string, CategoryRenderer>} */
const specificCategoryRenderers = {
performance: new PerformanceCategoryRenderer(this._dom, detailsRenderer),
pwa: new PwaCategoryRenderer(this._dom, detailsRenderer),
};
Object.values(specificCategoryRenderers).forEach(renderer => {
renderer.setTemplateContext(this._templateContext);
});
const categories = reportSection.appendChild(this._dom.createElement('div', 'lh-categories')); const categories = reportSection.appendChild(this._dom.createElement('div', 'lh-categories'));
for (const category of report.reportCategories) { for (const category of report.reportCategories) {
if (scoreHeader) { const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
scoreHeader.appendChild(categoryRenderer.renderScoreGauge(category)); categories.appendChild(renderer.render(category, report.categoryGroups));
} }
let renderer = categoryRenderer; if (scoreHeader) {
if (category.id === 'performance') { const defaultGauges = [];
renderer = perfCategoryRenderer; const customGauges = [];
for (const category of report.reportCategories) {
const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
const categoryGauge = renderer.renderScoreGauge(category);
// Group gauges that aren't default at the end of the header
if (renderer.renderScoreGauge === categoryRenderer.renderScoreGauge) {
defaultGauges.push(categoryGauge);
} else {
customGauges.push(categoryGauge);
} }
categories.appendChild(renderer.render(category, report.categoryGroups));
} }
scoreHeader.append(...defaultGauges, ...customGauges);
if (scoreHeader) {
const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext); const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext);
this._dom.find('.lh-scorescale-label', scoreScale).textContent = this._dom.find('.lh-scorescale-label', scoreScale).textContent =
Util.UIStrings.scorescaleLabel; Util.UIStrings.scorescaleLabel;
......
...@@ -62,6 +62,15 @@ class Util { ...@@ -62,6 +62,15 @@ class Util {
if (typeof clone.categories !== 'object') throw new Error('No categories provided.'); if (typeof clone.categories !== 'object') throw new Error('No categories provided.');
clone.reportCategories = Object.values(clone.categories); clone.reportCategories = Object.values(clone.categories);
// The proto process turns 'not-applicable' into 'not_applicable'. Correct this to support both.
// TODO: remove when underscore/hyphen proto issue is resolved. See #6371, #6201.
for (const audit of Object.values(clone.audits)) {
// @ts-ignore tsc rightly flags that this value shouldn't occur.
if (audit.scoreDisplayMode === 'not_applicable') {
audit.scoreDisplayMode = 'not-applicable';
}
}
// For convenience, smoosh all AuditResults into their auditDfn (which has just weight & group) // For convenience, smoosh all AuditResults into their auditDfn (which has just weight & group)
for (const category of clone.reportCategories) { for (const category of clone.reportCategories) {
category.auditRefs.forEach(auditMeta => { category.auditRefs.forEach(auditMeta => {
...@@ -398,7 +407,7 @@ class Util { ...@@ -398,7 +407,7 @@ class Util {
networkThrottling = `${Util.formatNumber(requestLatencyMs)}${NBSP}ms HTTP RTT, ` + networkThrottling = `${Util.formatNumber(requestLatencyMs)}${NBSP}ms HTTP RTT, ` +
`${Util.formatNumber(throttling.downloadThroughputKbps)}${NBSP}Kbps down, ` + `${Util.formatNumber(throttling.downloadThroughputKbps)}${NBSP}Kbps down, ` +
`${Util.formatNumber(throttling.uploadThroughputKbps)}${NBSP}Kbps up (DevTools)`; `${Util.formatNumber(throttling.uploadThroughputKbps)}${NBSP}Kbps up (DevTools)`;
summary = 'Throttled Fast 3G network'; summary = 'Throttled Slow 4G network';
break; break;
} }
case 'simulate': { case 'simulate': {
...@@ -406,7 +415,7 @@ class Util { ...@@ -406,7 +415,7 @@ class Util {
cpuThrottling = `${Util.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; cpuThrottling = `${Util.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
networkThrottling = `${Util.formatNumber(rttMs)}${NBSP}ms TCP RTT, ` + networkThrottling = `${Util.formatNumber(rttMs)}${NBSP}ms TCP RTT, ` +
`${Util.formatNumber(throughputKbps)}${NBSP}Kbps throughput (Simulated)`; `${Util.formatNumber(throughputKbps)}${NBSP}Kbps throughput (Simulated)`;
summary = 'Simulated Fast 3G network'; summary = 'Simulated Slow 4G network';
break; break;
} }
default: default:
...@@ -484,8 +493,8 @@ Util.UIStrings = { ...@@ -484,8 +493,8 @@ Util.UIStrings = {
/** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */
crcLongestDurationLabel: 'Maximum critical path latency:', crcLongestDurationLabel: 'Maximum critical path latency:',
/** Explanation shown to users below performance results to inform them that the test was done with a 3G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */
lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on emulated 3G. Values are estimated and may vary.', lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.',
/** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */
labDataTitle: 'Lab Data', labDataTitle: 'Lab Data',
}; };
......
...@@ -54,6 +54,8 @@ ...@@ -54,6 +54,8 @@
--metric-timeline-rule-color: #b3b3b3; --metric-timeline-rule-color: #b3b3b3;
--display-value-gray: hsl(216, 5%, 39%); --display-value-gray: hsl(216, 5%, 39%);
--report-width: calc(60 * var(--body-font-size)); --report-width: calc(60 * var(--body-font-size));
/* Edge doesn't support calc(var(calc())) */
--report-width-half: calc(30 * var(--body-font-size));
--report-header-height: 161px; --report-header-height: 161px;
--report-header-color: #202124; --report-header-color: #202124;
--navitem-font-size: var(--body-font-size); --navitem-font-size: var(--body-font-size);
...@@ -62,6 +64,7 @@ ...@@ -62,6 +64,7 @@
--navitem-vpadding: calc(var(--navitem-line-height) / 2); --navitem-vpadding: calc(var(--navitem-line-height) / 2);
--lh-score-highlight-bg: hsla(0, 0%, 90%, 0.2); --lh-score-highlight-bg: hsla(0, 0%, 90%, 0.2);
--lh-score-icon-background-size: 24px; --lh-score-icon-background-size: 24px;
--lh-group-icon-background-size: var(--lh-score-icon-background-size);
--lh-score-margin: 12px; --lh-score-margin: 12px;
--lh-table-header-bg: #f8f9fa; --lh-table-header-bg: #f8f9fa;
--lh-table-higlight-bg: hsla(0, 0%, 75%, 0.1); --lh-table-higlight-bg: hsla(0, 0%, 75%, 0.1);
...@@ -76,22 +79,29 @@ ...@@ -76,22 +79,29 @@
--lh-audit-group-vpadding: 8px; --lh-audit-group-vpadding: 8px;
--lh-section-vpadding: 12px; --lh-section-vpadding: 12px;
--chevron-size: 12px; --chevron-size: 12px;
--circle-size: calc(3 * var(--header-font-size));
/* Voodoo magic here to get narrow columns. 0 doesn't size the column like our friend 1px does */ --pass-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>check</title><path fill="%23178239" d="M24 4C12.95 4 4 12.95 4 24c0 11.04 8.95 20 20 20 11.04 0 20-8.96 20-20 0-11.05-8.96-20-20-20zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z"/></svg>');
--bytes-col-width: 1px; --average-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>info</title><path fill="%23E67700" d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z"/></svg>');
--pass-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>check</title><path fill="hsl(139, 70%, 30%)" d="M24 4C12.95 4 4 12.95 4 24c0 11.04 8.95 20 20 20 11.04 0 20-8.96 20-20 0-11.05-8.96-20-20-20zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z"/></svg>'); --fail-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>warn</title><path fill="%23C7221F" d="M2 42h44L24 4 2 42zm24-6h-4v-4h4v4zm0-8h-4v-8h4v8z"/></svg>');
--average-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>info</title><path fill="hsl(31, 100%, 45%)" d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z"/></svg>');
--fail-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>warn</title><path fill="hsl(1, 73%, 45%)" d="M2 42h44L24 4 2 42zm24-6h-4v-4h4v4zm0-8h-4v-8h4v8z"/></svg>'); --content-paste-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="%235E6268" d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"/><path d="M0 0h24v24H0z" fill="none"/></svg>');
--av-timer-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="%235E6268"><path d="M0 0h24v24H0z" fill="none"/><path d="M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.03-6.61l1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42A8.962 8.962 0 0 0 12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9a8.994 8.994 0 0 0 7.03-14.61zM12 20c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/></svg>');
--content-paste-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="hsl(216, 5%, 39%)" d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'); --photo-filter-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path fill="none" d="M0 0h48v48H0V0z"/><path d="M38.04 20v18H10V10h18V6H10.04c-2.2 0-4 1.8-4 4v28c0 2.2 1.8 4 4 4h28c2.2 0 4-1.8 4-4V20h-4zM34 20l1.88-4.12L40 14l-4.12-1.88L34 8l-1.88 4.12L28 14l4.12 1.88zm-7.5 1.5L24 16l-2.5 5.5L16 24l5.5 2.5L24 32l2.5-5.5L32 24z" fill="%235E6268"/></svg>');
--av-timer-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="hsl(216, 5%, 39%)"><path d="M0 0h24v24H0z" fill="none"/><path d="M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.03-6.61l1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42A8.962 8.962 0 0 0 12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9a8.994 8.994 0 0 0 7.03-14.61zM12 20c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/></svg>'); --visibility-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M0 0h48v48H0z" fill="none"/><path d="M24 9C14 9 5.46 15.22 2 24c3.46 8.78 12 15 22 15 10.01 0 18.54-6.22 22-15-3.46-8.78-11.99-15-22-15zm0 25c-5.52 0-10-4.48-10-10s4.48-10 10-10 10 4.48 10 10-4.48 10-10 10zm0-16c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6z" fill="%235E6268"/></svg>');
--photo-filter-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path fill="none" d="M0 0h48v48H0V0z"/><path d="M38.04 20v18H10V10h18V6H10.04c-2.2 0-4 1.8-4 4v28c0 2.2 1.8 4 4 4h28c2.2 0 4-1.8 4-4V20h-4zM34 20l1.88-4.12L40 14l-4.12-1.88L34 8l-1.88 4.12L28 14l4.12 1.88zm-7.5 1.5L24 16l-2.5 5.5L16 24l5.5 2.5L24 32l2.5-5.5L32 24z" fill="hsl(216, 5%, 39%)"/></svg>'); --check-circle-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M0 0h48v48H0z" fill="none"/><path d="M24 4C12.95 4 4 12.95 4 24c0 11.04 8.95 20 20 20 11.04 0 20-8.96 20-20 0-11.05-8.96-20-20-20zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z" fill="%235E6268"/></svg>');
--visibility-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M0 0h48v48H0z" fill="none"/><path d="M24 9C14 9 5.46 15.22 2 24c3.46 8.78 12 15 22 15 10.01 0 18.54-6.22 22-15-3.46-8.78-11.99-15-22-15zm0 25c-5.52 0-10-4.48-10-10s4.48-10 10-10 10 4.48 10 10-4.48 10-10 10zm0-16c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6z" fill="hsl(216, 5%, 39%)"/></svg>');
--check-circle-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M0 0h48v48H0z" fill="none"/><path d="M24 4C12.95 4 4 12.95 4 24c0 11.04 8.95 20 20 20 11.04 0 20-8.96 20-20 0-11.05-8.96-20-20-20zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z" fill="hsl(216, 5%, 39%)"/></svg>');
--check-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M0 0h48v48H0z" fill="none"/><path d="M18 32.34L9.66 24l-2.83 2.83L18 38l24-24-2.83-2.83z"/></svg>'); --check-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M0 0h48v48H0z" fill="none"/><path d="M18 32.34L9.66 24l-2.83 2.83L18 38l24-24-2.83-2.83z"/></svg>');
--search-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M31 28h-1.59l-.55-.55C30.82 25.18 32 22.23 32 19c0-7.18-5.82-13-13-13S6 11.82 6 19s5.82 13 13 13c3.23 0 6.18-1.18 8.45-3.13l.55.55V31l10 9.98L40.98 38 31 28zm-12 0a9 9 0 1 1 .001-18.001A9 9 0 0 1 19 28z" fill="hsl(216, 5%, 39%)"/><path d="M0 0h48v48H0z" fill="none" /></svg>'); --search-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M31 28h-1.59l-.55-.55C30.82 25.18 32 22.23 32 19c0-7.18-5.82-13-13-13S6 11.82 6 19s5.82 13 13 13c3.23 0 6.18-1.18 8.45-3.13l.55.55V31l10 9.98L40.98 38 31 28zm-12 0a9 9 0 1 1 .001-18.001A9 9 0 0 1 19 28z" fill="%235E6268"/><path d="M0 0h48v48H0z" fill="none" /></svg>');
--remove-circle-icon-url: url('data:image/svg+xml;utf8,<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" fill="hsl(216, 5%, 39%)"/></svg>'); --remove-circle-icon-url: url('data:image/svg+xml;utf8,<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" fill="%235E6268"/></svg>');
--pwa-fast-reliable-gray-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="nonzero"><circle fill="%23DAE0E3" cx="12" cy="12" r="12"/><path d="M12.3 4l6.3 2.8V11c0 3.88-2.69 7.52-6.3 8.4C8.69 18.52 6 14.89 6 11V6.8L12.3 4zm-.56 12.88l3.3-5.79.04-.08c.05-.1.01-.29-.26-.29h-1.96l.56-3.92h-.56L9.6 12.52c0 .03.07-.12-.03.07-.11.2-.12.37.2.37h1.97l-.56 3.92h.56z" fill="%23FFF"/></g></svg>');
--pwa-installable-gray-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="nonzero"><circle fill="%23DAE0E3" cx="12" cy="12" r="12"/><path d="M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z" fill="%23FFF"/></g></svg>');
--pwa-optimized-gray-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect fill="%23DAE0E3" width="24" height="24" rx="12"/><path fill="%23FFF" d="M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z"/><path d="M5 5h14v14H5z"/></g></svg>');
--pwa-fast-reliable-color-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><circle fill="%23CCE3F6" cx="12" cy="12" r="12"/><path d="M12 4.3l6.3 2.8v4.2c0 3.88-2.69 7.52-6.3 8.4-3.61-.88-6.3-4.51-6.3-8.4V7.1L12 4.3zm-.56 12.88l3.3-5.79.04-.08c.05-.1.01-.29-.26-.29h-1.96l.56-3.92h-.56L9.3 12.82c0 .03.07-.12-.03.07-.11.2-.12.37.2.37h1.97l-.56 3.92h.56z" fill="%23304FFE"/></g></svg>');
--pwa-installable-color-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><circle fill="%23D4ECD5" cx="12" cy="12" r="12"/><path d="M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z" fill="%23009688"/></g></svg>');
--pwa-optimized-color-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect fill="%23FCE4EC" width="24" height="24" rx="12"/><path d="M5 5h14v14H5z"/><path fill="%23EC3F7A" d="M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z"/></g></svg>');
} }
.lh-vars.lh-devtools { .lh-vars.lh-devtools {
...@@ -312,8 +322,18 @@ ...@@ -312,8 +322,18 @@
background-color: #F8F9FA; background-color: #F8F9FA;
} }
.lh-audit-group__summary::-webkit-details-marker, /* Hide the expandable arrow icon, three ways: via the CSS Counter Styles spec, for webkit/blink browsers, hiding the polyfilled icon */
.lh-expandable-details__summary::-webkit-details-marker { /* https://github.com/javan/details-element-polyfill/blob/master/src/details-element-polyfill/polyfill.sass */
.lh-audit-group > summary,
.lh-expandable-details > summary {
list-style-type: none;
}
.lh-audit-group > summary::-webkit-details-marker,
.lh-expandable-details > summary::-webkit-details-marker {
display: none;
}
.lh-audit-group > summary:before,
.lh-expandable-details > summary:before {
display: none; display: none;
} }
...@@ -360,7 +380,7 @@ ...@@ -360,7 +380,7 @@
.lh-metric__innerwrap { .lh-metric__innerwrap {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 11px var(--text-indent); padding: 8px var(--text-indent);
} }
.lh-metric__details { .lh-metric__details {
...@@ -508,7 +528,7 @@ ...@@ -508,7 +528,7 @@
.lh-filmstrip-container { .lh-filmstrip-container {
padding: 0 var(--expandable-indent); padding: 0 var(--expandable-indent);
/* smaller gap between metrics and filmstrip */ /* smaller gap between metrics and filmstrip */
margin: -16px auto 0 auto; margin: -8px auto 0 auto;
} }
.lh-filmstrip { .lh-filmstrip {
...@@ -564,7 +584,8 @@ ...@@ -564,7 +584,8 @@
} }
.lh-audit-group__header::before { .lh-audit-group__header::before {
content: ''; /* By default, groups don't get an icon */
content: none;
width: calc(var(--subheader-font-size) / 14 * 24); width: calc(var(--subheader-font-size) / 14 * 24);
height: calc(var(--subheader-font-size) / 14 * 24); height: calc(var(--subheader-font-size) / 14 * 24);
margin-right: calc(var(--subheader-font-size) / 2); margin-right: calc(var(--subheader-font-size) / 2);
...@@ -574,29 +595,55 @@ ...@@ -574,29 +595,55 @@
vertical-align: middle; vertical-align: middle;
} }
/* A11y/Seo groups within Passed don't get an icon */ .lh-clump--manual > summary .lh-audit-group__header::before {
.lh-audit-group--unadorned .lh-audit-group__header::before { content: '';
content: none;
}
.lh-audit-group--manual .lh-audit-group__header::before {
background-image: var(--search-icon-url); background-image: var(--search-icon-url);
} }
.lh-passed-audits .lh-audit-group__header::before { .lh-clump--passed > summary .lh-audit-group__header::before {
content: '';
background-image: var(--check-icon-url); background-image: var(--check-icon-url);
} }
.lh-clump--not-applicable > summary .lh-audit-group__header::before {
content: '';
background-image: var(--remove-circle-icon-url);
}
.lh-audit-group--diagnostics .lh-audit-group__header::before { .lh-audit-group--diagnostics .lh-audit-group__header::before {
content: '';
background-image: var(--content-paste-icon-url); background-image: var(--content-paste-icon-url);
} }
.lh-audit-group--opportunities .lh-audit-group__header::before { .lh-audit-group--load-opportunities .lh-audit-group__header::before {
content: '';
background-image: var(--photo-filter-icon-url); background-image: var(--photo-filter-icon-url);
} }
.lh-audit-group--metrics .lh-audit-group__header::before { .lh-audit-group--metrics .lh-audit-group__header::before {
content: '';
background-image: var(--av-timer-icon-url); background-image: var(--av-timer-icon-url);
} }
.lh-audit-group--not-applicable .lh-audit-group__header::before {
background-image: var(--remove-circle-icon-url); .lh-audit-group--pwa-fast-reliable .lh-audit-group__header::before {
content: '';
background-image: var(--pwa-fast-reliable-gray-url);
background-size: var(--lh-group-icon-background-size);
}
.lh-audit-group--pwa-installable .lh-audit-group__header::before {
content: '';
background-image: var(--pwa-installable-gray-url);
background-size: var(--lh-group-icon-background-size);
}
.lh-audit-group--pwa-optimized .lh-audit-group__header::before {
content: '';
background-image: var(--pwa-optimized-gray-url);
background-size: var(--lh-group-icon-background-size);
}
.lh-audit-group--pwa-fast-reliable.lh-badged .lh-audit-group__header::before {
background-image: var(--pwa-fast-reliable-color-url);
}
.lh-audit-group--pwa-installable.lh-badged .lh-audit-group__header::before {
background-image: var(--pwa-installable-color-url);
}
.lh-audit-group--pwa-optimized.lh-badged .lh-audit-group__header::before {
background-image: var(--pwa-optimized-color-url);
} }
/* Removing too much whitespace */ /* Removing too much whitespace */
...@@ -604,11 +651,15 @@ ...@@ -604,11 +651,15 @@
margin-top: calc(var(--circle-size)/2 * -1); margin-top: calc(var(--circle-size)/2 * -1);
border-bottom: none; border-bottom: none;
} }
.lh-audit-group--metrics .lh-audit-group__summary {
margin-bottom: 0;
}
.lh-audit-group__summary { .lh-audit-group__summary {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding-right: var(--text-indent); padding-right: var(--text-indent);
margin-bottom: var(--lh-audit-vpadding);
} }
.lh-audit-group__itemcount { .lh-audit-group__itemcount {
...@@ -622,11 +673,17 @@ ...@@ -622,11 +673,17 @@
.lh-audit-group__description { .lh-audit-group__description {
font-size: var(--body-font-size); font-size: var(--body-font-size);
color: var(--medium-75-gray); color: var(--medium-75-gray);
margin: var(--lh-audit-group-vpadding) 0; margin: 0 0 var(--lh-audit-group-vpadding);
} }
.lh-audit-group--unadorned .lh-audit-group__description { .lh-clump > .lh-audit-group__description,
margin-top: 0; .lh-audit-group--diagnostics .lh-audit-group__description,
.lh-audit-group--load-opportunities .lh-audit-group__description,
.lh-audit-group--metrics .lh-audit-group__description,
.lh-audit-group--pwa-fast-reliable .lh-audit-group__description,
.lh-audit-group--pwa-installable .lh-audit-group__description,
.lh-audit-group--pwa-optimized .lh-audit-group__description {
margin-top: var(--lh-audit-group-vpadding);
} }
.lh-audit-explanation { .lh-audit-explanation {
...@@ -648,6 +705,7 @@ ...@@ -648,6 +705,7 @@
} }
.lh-header-sticky { .lh-header-sticky {
position: -webkit-sticky;
position: sticky; position: sticky;
top: 0; top: 0;
width: 100%; width: 100%;
...@@ -723,10 +781,15 @@ ...@@ -723,10 +781,15 @@
border: 0; border: 0;
} }
.lh-scores-header .lh-gauge__wrapper { .lh-scores-header .lh-gauge__wrapper,
.lh-scores-header .lh-gauge--pwa__wrapper, {
margin: 0 4px; margin: 0 4px;
} }
.lh-scores-header .lh-gauge--pwa__wrapper {
border-left: 1px solid var(--report-secondary-border-color)
}
.lh-scorescale { .lh-scorescale {
color: var(--medium-75-gray); color: var(--medium-75-gray);
padding: 0 calc(var(--section-indent) * 1.5) 0; padding: 0 calc(var(--section-indent) * 1.5) 0;
...@@ -773,7 +836,6 @@ ...@@ -773,7 +836,6 @@
} }
.lh-category { .lh-category {
--circle-size: calc(2.5 * var(--header-font-size));
padding: var(--section-padding); padding: var(--section-padding);
} }
...@@ -820,19 +882,6 @@ ...@@ -820,19 +882,6 @@
line-height: var(--header-line-height); line-height: var(--header-line-height);
} }
.lh-passed-audits[open] summary.lh-passed-audits-summary {
margin-bottom: calc(var(--default-padding) * 2);
}
summary.lh-passed-audits-summary {
margin: calc(var(--default-padding) * 2) var(--default-padding);
margin-left: var(--default-padding);
margin-bottom: 0;
font-size: 15px;
display: flex;
align-items: center;
}
#lh-log { #lh-log {
position: fixed; position: fixed;
background-color: #323232; background-color: #323232;
...@@ -895,6 +944,7 @@ summary.lh-passed-audits-summary { ...@@ -895,6 +944,7 @@ summary.lh-passed-audits-summary {
.lh-table thead th { .lh-table thead th {
color: var(--medium-75-gray); color: var(--medium-75-gray);
font-weight: normal; font-weight: normal;
word-wrap: normal;
} }
.lh-table tbody tr:nth-child(even) { .lh-table tbody tr:nth-child(even) {
...@@ -918,7 +968,8 @@ summary.lh-passed-audits-summary { ...@@ -918,7 +968,8 @@ summary.lh-passed-audits-summary {
.lh-table-column--bytes, .lh-table-column--bytes,
.lh-table-column--timespanMs, .lh-table-column--timespanMs,
.lh-table-column--ms { .lh-table-column--ms,
.lh-table-column--numeric {
text-align: right; text-align: right;
} }
...@@ -930,18 +981,16 @@ summary.lh-passed-audits-summary { ...@@ -930,18 +981,16 @@ summary.lh-passed-audits-summary {
.lh-table-column--url { .lh-table-column--url {
min-width: 250px; min-width: 250px;
white-space: nowrap; white-space: nowrap;
max-width: 0;
} }
/* Keep bytes columns narrow if they follow the URL column */ /* Keep columns narrow if they follow the URL column */
/* 12% was determined to be a decent narrow width, but wide enough for column headings */
.lh-table-column--url + th.lh-table-column--bytes, .lh-table-column--url + th.lh-table-column--bytes,
.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--bytes, .lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--bytes,
.lh-table-column--url + .lh-table-column--ms,
.lh-table-column--url + .lh-table-column--ms + th.lh-table-column--bytes,
.lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--timespanMs { .lh-table-column--url + .lh-table-column--bytes + th.lh-table-column--timespanMs {
max-width: var(--bytes-col-width); width: 12%;
}
.lh-table-column--code {
max-width: var(--url-col-max-width);
} }
.lh-text__url { .lh-text__url {
...@@ -975,6 +1024,8 @@ summary.lh-passed-audits-summary { ...@@ -975,6 +1024,8 @@ summary.lh-passed-audits-summary {
*/ */
.lh-chevron { .lh-chevron {
--chevron-angle: 42deg; --chevron-angle: 42deg;
/* Edge doesn't support transform: rotate(calc(...)), so we define it here */
--chevron-angle-right: -42deg;
width: var(--chevron-size); width: var(--chevron-size);
height: var(--chevron-size); height: var(--chevron-size);
margin-top: calc((var(--body-line-height) - 12px) / 2); margin-top: calc((var(--body-line-height) - 12px) / 2);
...@@ -993,19 +1044,19 @@ summary.lh-passed-audits-summary { ...@@ -993,19 +1044,19 @@ summary.lh-passed-audits-summary {
transition: transform 300ms, stroke 300ms; transition: transform 300ms, stroke 300ms;
} }
.lh-audit-group > .lh-audit-group__summary > .lh-chevron .lh-chevron__line-right, .lh-audit-group > summary > .lh-audit-group__summary > .lh-chevron .lh-chevron__line-right,
.lh-audit-group[open] > .lh-audit-group__summary > .lh-chevron .lh-chevron__line-left, .lh-audit-group[open] > summary > .lh-audit-group__summary > .lh-chevron .lh-chevron__line-left,
.lh-audit > .lh-expandable-details .lh-chevron__line-right, .lh-audit > .lh-expandable-details .lh-chevron__line-right,
.lh-audit > .lh-expandable-details[open] .lh-chevron__line-left { .lh-audit > .lh-expandable-details[open] .lh-chevron__line-left {
transform: rotate(calc(var(--chevron-angle) * -1)); transform: rotate(var(--chevron-angle-right));
} }
.lh-audit-group[open] > .lh-audit-group__summary > .lh-chevron .lh-chevron__line-right, .lh-audit-group[open] > summary > .lh-audit-group__summary > .lh-chevron .lh-chevron__line-right,
.lh-audit > .lh-expandable-details[open] .lh-chevron__line-right { .lh-audit > .lh-expandable-details[open] .lh-chevron__line-right {
transform: rotate(var(--chevron-angle)); transform: rotate(var(--chevron-angle));
} }
.lh-audit-group[open] > .lh-audit-group__summary > .lh-chevron .lh-chevron__lines, .lh-audit-group[open] > summary > .lh-audit-group__summary > .lh-chevron .lh-chevron__lines,
.lh-audit > .lh-expandable-details[open] .lh-chevron__lines { .lh-audit > .lh-expandable-details[open] .lh-chevron__lines {
transform: translateY(calc(var(--chevron-size) * -1)); transform: translateY(calc(var(--chevron-size) * -1));
} }
...@@ -1051,6 +1102,7 @@ summary.lh-passed-audits-summary { ...@@ -1051,6 +1102,7 @@ summary.lh-passed-audits-summary {
z-index: 1; z-index: 1;
will-change: opacity; will-change: opacity;
right: 0; right: 0;
pointer-events: none;
} }
.tooltip::before { .tooltip::before {
...@@ -1068,5 +1120,5 @@ summary.lh-passed-audits-summary { ...@@ -1068,5 +1120,5 @@ summary.lh-passed-audits-summary {
@keyframes fadeInTooltip { @keyframes fadeInTooltip {
0% { opacity: 0; } 0% { opacity: 0; }
75% { opacity: 1; } 75% { opacity: 1; }
100% { opacity: 1; filter: drop-shadow(1px 0px 1px #aaa) drop-shadow(0px 2px 4px hsla(206, 6%, 25%, 0.15)); } 100% { opacity: 1; filter: drop-shadow(1px 0px 1px #aaa) drop-shadow(0px 2px 4px hsla(206, 6%, 25%, 0.15)); pointer-events: auto; }
} }
...@@ -27,9 +27,9 @@ limitations under the License. ...@@ -27,9 +27,9 @@ limitations under the License.
<template id="tmpl-lh-scorescale"> <template id="tmpl-lh-scorescale">
<div class="lh-scorescale"> <div class="lh-scorescale">
<span class="lh-scorescale-label"></span> <span class="lh-scorescale-label"></span>
<span class="lh-scorescale-range lh-scorescale-range--fail">0-49</span>
<span class="lh-scorescale-range lh-scorescale-range--average">50-89</span>
<span class="lh-scorescale-range lh-scorescale-range--pass">90-100</span> <span class="lh-scorescale-range lh-scorescale-range--pass">90-100</span>
<span class="lh-scorescale-range lh-scorescale-range--average">50-89</span>
<span class="lh-scorescale-range lh-scorescale-range--fail">0-49</span>
</div> </div>
</template> </template>
...@@ -37,8 +37,8 @@ limitations under the License. ...@@ -37,8 +37,8 @@ limitations under the License.
<template id="tmpl-lh-chevron"> <template id="tmpl-lh-chevron">
<svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100">
<g class="lh-chevron__lines"> <g class="lh-chevron__lines">
<path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"/> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path>
<path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"/> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path>
</g> </g>
</svg> </svg>
</template> </template>
...@@ -56,12 +56,14 @@ limitations under the License. ...@@ -56,12 +56,14 @@ limitations under the License.
<template id="tmpl-lh-audit"> <template id="tmpl-lh-audit">
<div class="lh-audit"> <div class="lh-audit">
<details class="lh-expandable-details"> <details class="lh-expandable-details">
<summary class="lh-audit__header lh-expandable-details__summary"> <summary>
<div class="lh-audit__header lh-expandable-details__summary">
<span class="lh-audit__index"></span> <span class="lh-audit__index"></span>
<span class="lh-audit__title"></span> <span class="lh-audit__title"></span>
<span class="lh-audit__display-text"></span> <span class="lh-audit__display-text"></span>
<div class="lh-audit__score-icon"></div> <div class="lh-audit__score-icon"></div>
<div class="lh-chevron-container"></div> <div class="lh-chevron-container"></div>
</div>
</summary> </summary>
<div class="lh-audit__description"></div> <div class="lh-audit__description"></div>
</details> </details>
...@@ -83,7 +85,8 @@ limitations under the License. ...@@ -83,7 +85,8 @@ limitations under the License.
<template id="tmpl-lh-opportunity"> <template id="tmpl-lh-opportunity">
<div class="lh-audit lh-audit--load-opportunity"> <div class="lh-audit lh-audit--load-opportunity">
<details class="lh-expandable-details"> <details class="lh-expandable-details">
<summary class="lh-audit__header lh-expandable-details__summary"> <summary>
<div class="lh-audit__header lh-expandable-details__summary">
<div class="lh-load-opportunity__cols"> <div class="lh-load-opportunity__cols">
<div class="lh-load-opportunity__col lh-load-opportunity__col--one"> <div class="lh-load-opportunity__col lh-load-opportunity__col--one">
<span class="lh-audit__index"></span> <span class="lh-audit__index"></span>
...@@ -97,6 +100,7 @@ limitations under the License. ...@@ -97,6 +100,7 @@ limitations under the License.
<div class="lh-chevron-container" title="See resources"></div> <div class="lh-chevron-container" title="See resources"></div>
</div> </div>
</div> </div>
</div>
</summary> </summary>
<div class="lh-audit__description"></div> <div class="lh-audit__description"></div>
</details> </details>
...@@ -164,7 +168,7 @@ limitations under the License. ...@@ -164,7 +168,7 @@ limitations under the License.
position: absolute; position: absolute;
top: var(--report-header-height); top: var(--report-header-height);
right: 50%; right: 50%;
transform: translate3d(calc(var(--report-width) / 2), -100%, 0); transform: translate3d(var(--report-width-half), -100%, 0);
opacity: 1; opacity: 1;
transform-origin: bottom right; transform-origin: bottom right;
will-change: transform, opacity; will-change: transform, opacity;
...@@ -193,7 +197,6 @@ limitations under the License. ...@@ -193,7 +197,6 @@ limitations under the License.
} }
.lh-scores-wrapper { .lh-scores-wrapper {
margin-top: -30px; margin-top: -30px;
transform: translateZ(1px);
} }
.lh-scores-wrapper__shadow { .lh-scores-wrapper__shadow {
opacity: 0; opacity: 0;
...@@ -355,7 +358,7 @@ limitations under the License. ...@@ -355,7 +358,7 @@ limitations under the License.
<svg width="217" height="148" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="217" height="148" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<mask id="a" x="-56" y="-54" width="284" height="202" maskUnits="userSpaceOnUse"> <mask id="a" x="-56" y="-54" width="284" height="202" maskUnits="userSpaceOnUse">
<path d="M-56-54h284v202H-56z" fill="#fff"/> <path d="M-56-54h284v202H-56z" fill="#fff"></path>
</mask> </mask>
<linearGradient id="b" x1="-525.16" y1="560.08" x2="-524.23" y2="560.08" gradientTransform="matrix(91 0 0 -77 47797 43181)" gradientUnits="userSpaceOnUse"> <linearGradient id="b" x1="-525.16" y1="560.08" x2="-524.23" y2="560.08" gradientTransform="matrix(91 0 0 -77 47797 43181)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#f1f3f4"/> <stop offset="0" stop-color="#f1f3f4"/>
...@@ -474,7 +477,6 @@ limitations under the License. ...@@ -474,7 +477,6 @@ limitations under the License.
<template id="tmpl-lh-gauge"> <template id="tmpl-lh-gauge">
<style> <style>
.lh-gauge__wrapper { .lh-gauge__wrapper {
--circle-size: calc(3 * var(--header-font-size));
--circle-size-half: calc(var(--circle-size) / 2); --circle-size-half: calc(var(--circle-size) / 2);
--circle-background: hsl(216, 12%, 92%); --circle-background: hsl(216, 12%, 92%);
--circle-border-width: 9; --circle-border-width: 9;
...@@ -570,6 +572,147 @@ limitations under the License. ...@@ -570,6 +572,147 @@ limitations under the License.
</a> </a>
</template> </template>
<!-- Lighthouse PWA badge gauge -->
<template id="tmpl-lh-gauge--pwa">
<style>
.lh-gauge--pwa__wrapper {
display: inline-flex;
align-items: center;
flex-direction: column;
text-decoration: none;
flex: 1;
min-width: auto;
position: relative;
}
.lh-gauge--pwa {
width: var(--circle-size);
height: var(--circle-size);
}
.lh-gauge--pwa .lh-gauge--pwa__component {
display: none;
}
.lh-gauge--pwa__wrapper:not(.lh-badged--all) .lh-gauge--pwa__logo > path {
/* Gray logo unless everything is passing. */
fill: #B0B0B0;
}
/* No passing groups. */
.lh-gauge--pwa__wrapper:not([class*='lh-badged--']) .lh-gauge--pwa__na-line {
display: inline;
}
/* Just optimized. Same n/a line as no passing groups. */
.lh-gauge--pwa__wrapper.lh-badged--pwa-optimized .lh-gauge--pwa__na-line {
display: inline;
}
/* Just fast and reliable. */
.lh-gauge--pwa__wrapper.lh-badged--pwa-fast-reliable:not(.lh-badged--pwa-installable) .lh-gauge--pwa__fast-reliable-badge {
display: inline;
}
/* Just installable. */
.lh-gauge--pwa__wrapper.lh-badged--pwa-installable:not(.lh-badged--pwa-fast-reliable) .lh-gauge--pwa__installable-badge {
display: inline;
}
/* Fast and reliable and installable. */
.lh-gauge--pwa__wrapper.lh-badged--pwa-fast-reliable.lh-badged--pwa-installable .lh-gauge--pwa__fast-reliable-installable-badges {
display: inline;
}
/* All passing groups. */
.lh-gauge--pwa__wrapper.lh-badged--all .lh-gauge--pwa__check-circle {
display: inline;
}
.lh-gauge__label {
font-size: var(--body-font-size);
line-height: var(--body-line-height);
margin-top: calc(0.5 * var(--body-line-height));
text-align: center;
color: black;
}
</style>
<a href="#" class="lh-gauge--pwa__wrapper">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" class="lh-gauge--pwa">
<defs>
<linearGradient id="lh-gauge--pwa__bg-disk__gradient" x1="50%" y1="2.15%" x2="50%" y2="97.645%">
<stop stop-color="#F1F3F4" offset="0%"></stop>
<stop stop-color="#DEE6EA" offset="100%"></stop>
</linearGradient>
<linearGradient id="lh-gauge--pwa__check-circle__gradient" x1="50%" y1="0%" x2="50%" y2="100%">
<stop stop-color="#00C852" offset="0%"></stop>
<stop stop-color="#009688" offset="100%"></stop>
</linearGradient>
<linearGradient id="lh-gauge--pwa__installable__shadow-gradient" x1="76.056%" x2="24.111%" y1="82.995%" y2="24.735%">
<stop stop-color="#A5D6A7" offset="0%"></stop>
<stop stop-color="#80CBC4" offset="100%"></stop>
</linearGradient>
<linearGradient id="lh-gauge--pwa__fast-reliable__shadow-gradient" x1="76.056%" y1="82.995%" x2="25.678%" y2="26.493%">
<stop stop-color="#64B5F6" offset="0%"></stop>
<stop stop-color="#2979FF" offset="100%"></stop>
</linearGradient>
<g id="lh-gauge--pwa__fast-reliable-badge">
<circle fill="#FFFFFF" cx="10" cy="10" r="10"></circle>
<path fill="#304FFE" d="M10 3.58l5.25 2.34v3.5c0 3.23-2.24 6.26-5.25 7-3.01-.74-5.25-3.77-5.25-7v-3.5L10 3.58zm-.47 10.74l2.76-4.83.03-.07c.04-.08 0-.24-.22-.24h-1.64l.47-3.26h-.47l-2.7 4.77c-.02.01.05-.1-.04.05-.09.16-.1.31.18.31h1.63l-.47 3.27h.47z"/>
</g>
<g id="lh-gauge--pwa__installable-badge">
<circle fill="#FFFFFF" cx="10" cy="10" r="10"></circle>
<path fill="#009688" d="M10 4.167A5.835 5.835 0 0 0 4.167 10 5.835 5.835 0 0 0 10 15.833 5.835 5.835 0 0 0 15.833 10 5.835 5.835 0 0 0 10 4.167zm2.917 6.416h-2.334v2.334H9.417v-2.334H7.083V9.417h2.334V7.083h1.166v2.334h2.334v1.166z"/>
</g>
</defs>
<g stroke="none" fill-rule="nonzero">
<!-- Background and PWA logo (color by default) -->
<circle fill="url(#lh-gauge--pwa__bg-disk__gradient)" cx="30" cy="30" r="30"></circle>
<g class="lh-gauge--pwa__logo">
<path fill="#3D3D3D" d="M35.66 19.39l.7-1.75h2L37.4 15 38.6 12l3.4 9h-2.51l-.58-1.61z"/>
<path fill="#304FFE" d="M33.52 21l3.65-9h-2.42l-2.5 5.82L30.5 12h-1.86l-1.9 5.82-1.35-2.65-1.21 3.72L25.4 21h2.38l1.72-5.2 1.64 5.2z"/>
<path fill="#3D3D3D" fill-rule="nonzero" d="M20.3 17.91h1.48c.45 0 .85-.05 1.2-.15l.39-1.18 1.07-3.3a2.64 2.64 0 0 0-.28-.37c-.55-.6-1.36-.91-2.42-.91H18v9h2.3V17.9zm1.96-3.84c.22.22.33.5.33.87 0 .36-.1.65-.29.87-.2.23-.59.35-1.15.35h-.86v-2.41h.87c.52 0 .89.1 1.1.32z"/>
</g>
<!-- No badges. -->
<rect class="lh-gauge--pwa__component lh-gauge--pwa__na-line" fill="#FFFFFF" x="20" y="32" width="20" height="4" rx="2"></rect>
<!-- Just fast and reliable. -->
<g class="lh-gauge--pwa__component lh-gauge--pwa__fast-reliable-badge" transform="translate(20, 29)">
<path fill="url(#lh-gauge--pwa__fast-reliable__shadow-gradient)" d="M33.63 19.49A30 30 0 0 1 16.2 30.36L3 17.14 17.14 3l16.49 16.49z"/>
<use xlink:href="#lh-gauge--pwa__fast-reliable-badge" />
</g>
<!-- Just installable. -->
<g class="lh-gauge--pwa__component lh-gauge--pwa__installable-badge" transform="translate(20, 29)">
<path fill="url(#lh-gauge--pwa__installable__shadow-gradient)" d="M33.629 19.487c-4.272 5.453-10.391 9.39-17.415 10.869L3 17.142 17.142 3 33.63 19.487z"/>
<use xlink:href="#lh-gauge--pwa__installable-badge" />
</g>
<!-- Fast and reliable and installable. -->
<g class="lh-gauge--pwa__component lh-gauge--pwa__fast-reliable-installable-badges">
<g transform="translate(8, 29)"> <!-- fast and reliable -->
<path fill="url(#lh-gauge--pwa__fast-reliable__shadow-gradient)" d="M16.321 30.463L3 17.143 17.142 3l22.365 22.365A29.864 29.864 0 0 1 22 31c-1.942 0-3.84-.184-5.679-.537z"/>
<use xlink:href="#lh-gauge--pwa__fast-reliable-badge" />
</g>
<g transform="translate(32, 29)"> <!-- installable -->
<path fill="url(#lh-gauge--pwa__installable__shadow-gradient)" d="M25.982 11.84a30.107 30.107 0 0 1-13.08 15.203L3 17.143 17.142 3l8.84 8.84z"/>
<use xlink:href="#lh-gauge--pwa__installable-badge" />
</g>
</g>
<!-- Full PWA. -->
<g class="lh-gauge--pwa__component lh-gauge--pwa__check-circle" transform="translate(18, 28)">
<circle fill="#FFFFFF" cx="12" cy="12" r="12"></circle>
<path fill="url(#lh-gauge--pwa__check-circle__gradient)" d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path>
</g>
</g>
</svg>
<div class="lh-gauge__label"></div>
</a>
</template>
<!-- Lighthouse crtiical request chains component --> <!-- Lighthouse crtiical request chains component -->
<template id="tmpl-lh-crc"> <template id="tmpl-lh-crc">
<div class="lh-crc-container"> <div class="lh-crc-container">
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
"lighthouse/renderer/dom.js", "lighthouse/renderer/dom.js",
"lighthouse/renderer/category-renderer.js", "lighthouse/renderer/category-renderer.js",
"lighthouse/renderer/performance-category-renderer.js", "lighthouse/renderer/performance-category-renderer.js",
"lighthouse/renderer/pwa-category-renderer.js",
"lighthouse/renderer/details-renderer.js", "lighthouse/renderer/details-renderer.js",
"lighthouse/renderer/crc-details-renderer.js", "lighthouse/renderer/crc-details-renderer.js",
"lighthouse/renderer/report-renderer.js", "lighthouse/renderer/report-renderer.js",
...@@ -48,6 +49,7 @@ ...@@ -48,6 +49,7 @@
"lighthouse/renderer/dom.js", "lighthouse/renderer/dom.js",
"lighthouse/renderer/category-renderer.js", "lighthouse/renderer/category-renderer.js",
"lighthouse/renderer/performance-category-renderer.js", "lighthouse/renderer/performance-category-renderer.js",
"lighthouse/renderer/pwa-category-renderer.js",
"lighthouse/renderer/details-renderer.js", "lighthouse/renderer/details-renderer.js",
"lighthouse/renderer/crc-details-renderer.js", "lighthouse/renderer/crc-details-renderer.js",
"lighthouse/renderer/report-renderer.js" "lighthouse/renderer/report-renderer.js"
......
...@@ -10,10 +10,10 @@ ...@@ -10,10 +10,10 @@
} }
], ],
"scripts": [ "scripts": [
"lighthouse/lighthouse-background.js", "lighthouse/lighthouse-dt-bundle.js",
"Audits2Service.js" "Audits2Service.js"
], ],
"skip_compilation": [ "skip_compilation": [
"lighthouse/lighthouse-background.js" "lighthouse/lighthouse-dt-bundle.js"
] ]
} }
...@@ -399,7 +399,7 @@ function renameIdentifiers(identifierMap) { ...@@ -399,7 +399,7 @@ function renameIdentifiers(identifierMap) {
return; return;
if (filePath.includes('externs.js')) if (filePath.includes('externs.js'))
return; return;
if (filePath.includes('eslint') || filePath.includes('lighthouse-background.js') || filePath.includes('/cm/') || if (filePath.includes('eslint') || filePath.includes('lighthouse-dt-bundle.js') || filePath.includes('/cm/') ||
filePath.includes('/xterm.js/') || filePath.includes('/acorn/')) filePath.includes('/xterm.js/') || filePath.includes('/acorn/'))
return; return;
if (filePath.includes('/cm_modes/') && !filePath.includes('DefaultCodeMirror') && if (filePath.includes('/cm_modes/') && !filePath.includes('DefaultCodeMirror') &&
......
...@@ -12,10 +12,76 @@ Run audits: enabled visible ...@@ -12,10 +12,76 @@ Run audits: enabled visible
=============== Lighthouse Status Updates =============== =============== Lighthouse Status Updates ===============
Loading… Loading…
Create config
Requiring gatherers
Requiring audits
Runner setup
Connecting to browser
Getting browser version
Resetting state with about:blank
Benchmarking machine
Initializing… Initializing…
Running beforePass methods
Retrieving setup: Scripts
Retrieving setup: CSSUsage
Retrieving setup: Viewport
Retrieving setup: ViewportDimensions
Retrieving setup: ThemeColor
Retrieving setup: Manifest
Retrieving setup: RuntimeExceptions
Retrieving setup: ChromeConsoleMessages
Retrieving setup: ImageUsage
Retrieving setup: Accessibility
Retrieving setup: AnchorsWithNoRelNoopener
Retrieving setup: AppCacheManifest
Retrieving setup: Doctype
Retrieving setup: DOMStats
Retrieving setup: JSLibraries
Retrieving setup: OptimizedImages
Retrieving setup: PasswordInputsWithPreventedPaste
Retrieving setup: ResponseCompression
Retrieving setup: TagsBlockingFirstPaint
Retrieving setup: MetaDescription
Retrieving setup: FontSize
Retrieving setup: CrawlableLinks
Retrieving setup: MetaRobots
Retrieving setup: Hreflang
Retrieving setup: EmbeddedContent
Retrieving setup: Canonical
Retrieving setup: RobotsTxt
Loading page & waiting for onload Loading page & waiting for onload
Getting browser version
Running pass methods
Retrieving in-page: Scripts
Retrieving in-page: CSSUsage
Retrieving in-page: Viewport
Retrieving in-page: ViewportDimensions
Retrieving in-page: ThemeColor
Retrieving in-page: Manifest
Retrieving in-page: RuntimeExceptions
Retrieving in-page: ChromeConsoleMessages
Retrieving in-page: ImageUsage
Retrieving in-page: Accessibility
Retrieving in-page: AnchorsWithNoRelNoopener
Retrieving in-page: AppCacheManifest
Retrieving in-page: Doctype
Retrieving in-page: DOMStats
Retrieving in-page: JSLibraries
Retrieving in-page: OptimizedImages
Retrieving in-page: PasswordInputsWithPreventedPaste
Retrieving in-page: ResponseCompression
Retrieving in-page: TagsBlockingFirstPaint
Retrieving in-page: MetaDescription
Retrieving in-page: FontSize
Retrieving in-page: CrawlableLinks
Retrieving in-page: MetaRobots
Retrieving in-page: Hreflang
Retrieving in-page: EmbeddedContent
Retrieving in-page: Canonical
Retrieving in-page: RobotsTxt
Retrieving trace Retrieving trace
Retrieving devtoolsLog and network records Retrieving devtoolsLog & network records
Running afterPass methods
Retrieving: Scripts Retrieving: Scripts
Retrieving: CSSUsage Retrieving: CSSUsage
Retrieving: Viewport Retrieving: Viewport
...@@ -35,7 +101,6 @@ Retrieving: OptimizedImages ...@@ -35,7 +101,6 @@ Retrieving: OptimizedImages
Retrieving: PasswordInputsWithPreventedPaste Retrieving: PasswordInputsWithPreventedPaste
Retrieving: ResponseCompression Retrieving: ResponseCompression
Retrieving: TagsBlockingFirstPaint Retrieving: TagsBlockingFirstPaint
Retrieving: WebSQL
Retrieving: MetaDescription Retrieving: MetaDescription
Retrieving: FontSize Retrieving: FontSize
Retrieving: CrawlableLinks Retrieving: CrawlableLinks
...@@ -44,52 +109,87 @@ Retrieving: Hreflang ...@@ -44,52 +109,87 @@ Retrieving: Hreflang
Retrieving: EmbeddedContent Retrieving: EmbeddedContent
Retrieving: Canonical Retrieving: Canonical
Retrieving: RobotsTxt Retrieving: RobotsTxt
Retrieving: Fonts Resetting state with about:blank
Running beforePass methods
Retrieving setup: ServiceWorker
Retrieving setup: Offline
Retrieving setup: StartUrl
Loading page & waiting for onload Loading page & waiting for onload
Retrieving devtoolsLog and network records Running pass methods
Retrieving in-page: ServiceWorker
Retrieving in-page: Offline
Retrieving in-page: StartUrl
Retrieving devtoolsLog & network records
Running afterPass methods
Retrieving: ServiceWorker Retrieving: ServiceWorker
Retrieving: Offline Retrieving: Offline
Retrieving: StartUrl Retrieving: StartUrl
Resetting state with about:blank
Running beforePass methods
Retrieving setup: HTTPRedirect
Retrieving setup: HTMLWithoutJavaScript
Loading page & waiting for onload Loading page & waiting for onload
Retrieving devtoolsLog and network records Running pass methods
Retrieving in-page: HTTPRedirect
Retrieving in-page: HTMLWithoutJavaScript
Retrieving devtoolsLog & network records
Running afterPass methods
Retrieving: HTTPRedirect Retrieving: HTTPRedirect
Retrieving: HTMLWithoutJavaScript Retrieving: HTMLWithoutJavaScript
Disconnecting from browser... Disconnecting from browser...
Analyzing and running audits... Analyzing and running audits...
Evaluating: Uses HTTPS Evaluating: Uses HTTPS
Computing artifact: NetworkRecords
Evaluating: Redirects HTTP traffic to HTTPS Evaluating: Redirects HTTP traffic to HTTPS
Evaluating: Registers a service worker Evaluating: Registers a service worker
Evaluating: Responds with a 200 when offline Evaluating: Current page responds with a 200 when offline
Evaluating: Has a `<meta name="viewport">` tag with `width` or `initial-scale` Evaluating: Has a `<meta name="viewport">` tag with `width` or `initial-scale`
Evaluating: Contains some content when JavaScript is not available Evaluating: Contains some content when JavaScript is not available
Evaluating: First Contentful Paint Evaluating: First Contentful Paint
Computing artifact: FirstContentfulPaint
Computing artifact: TraceOfTab
Evaluating: First Meaningful Paint Evaluating: First Meaningful Paint
Evaluating: Page load is fast enough on 3G Computing artifact: FirstMeaningfulPaint
Evaluating: Page load is fast enough on mobile networks
Computing artifact: Interactive
Evaluating: Speed Index Evaluating: Speed Index
Computing artifact: SpeedIndex
Evaluating: Screenshot Thumbnails Evaluating: Screenshot Thumbnails
Computing artifact: Speedline
Evaluating: Final Screenshot Evaluating: Final Screenshot
Computing artifact: Screenshots
Evaluating: Estimated Input Latency Evaluating: Estimated Input Latency
Computing artifact: EstimatedInputLatency
Evaluating: No browser errors logged to the console Evaluating: No browser errors logged to the console
Evaluating: Server response times are low (TTFB) Evaluating: Server response times are low (TTFB)
Computing artifact: MainResource
Evaluating: First CPU Idle Evaluating: First CPU Idle
Computing artifact: FirstCPUIdle
Evaluating: Time to Interactive Evaluating: Time to Interactive
Evaluating: User Timing marks and measures Evaluating: User Timing marks and measures
Evaluating: Minimize Critical Requests Depth Evaluating: Minimize Critical Requests Depth
Computing artifact: CriticalRequestChains
Evaluating: Avoid multiple page redirects Evaluating: Avoid multiple page redirects
Evaluating: User can be prompted to Install the Web App Evaluating: Web app manifest meets the installability requirements
Computing artifact: ManifestValues
Evaluating: Configured for a custom splash screen Evaluating: Configured for a custom splash screen
Evaluating: Address bar matches brand colors Evaluating: Sets an address-bar theme color
Evaluating: The `short_name` won't be truncated on the homescreen
Evaluating: Content is sized correctly for the viewport Evaluating: Content is sized correctly for the viewport
Evaluating: Displays images with correct aspect ratio Evaluating: Displays images with correct aspect ratio
Evaluating: Avoids deprecated APIs Evaluating: Avoids deprecated APIs
Evaluating: Minimizes main-thread work Evaluating: Minimizes main-thread work
Computing artifact: MainThreadTasks
Evaluating: JavaScript execution time Evaluating: JavaScript execution time
Evaluating: Preload key requests Evaluating: Preload key requests
Computing artifact: PageDependencyGraph
Computing artifact: LoadSimulator
Computing artifact: NetworkAnalysis
Evaluating: Preconnect to required origins Evaluating: Preconnect to required origins
Computing artifact: LoadSimulator
Evaluating: All text remains visible during webfont loads Evaluating: All text remains visible during webfont loads
Evaluating: Network Requests Evaluating: Network Requests
Evaluating: Metrics Evaluating: Metrics
Evaluating: start_url responds with a 200 when offline
Evaluating: Site works cross-browser Evaluating: Site works cross-browser
Evaluating: Page transitions don't feel like they block on the network Evaluating: Page transitions don't feel like they block on the network
Evaluating: Each page has a URL Evaluating: Each page has a URL
...@@ -159,7 +259,6 @@ Evaluating: Avoids requesting the geolocation permission on page load ...@@ -159,7 +259,6 @@ Evaluating: Avoids requesting the geolocation permission on page load
Evaluating: Avoids `document.write()` Evaluating: Avoids `document.write()`
Evaluating: Avoids front-end JavaScript libraries with known security vulnerabilities Evaluating: Avoids front-end JavaScript libraries with known security vulnerabilities
Evaluating: Detected JavaScript libraries Evaluating: Detected JavaScript libraries
Evaluating: Avoids WebSQL DB
Evaluating: Avoids requesting the notification permission on page load Evaluating: Avoids requesting the notification permission on page load
Evaluating: Allows users to paste into password fields Evaluating: Allows users to paste into password fields
Evaluating: Uses HTTP/2 for its own resources Evaluating: Uses HTTP/2 for its own resources
...@@ -179,7 +278,7 @@ Generating results... ...@@ -179,7 +278,7 @@ Generating results...
=============== Lighthouse Results =============== =============== Lighthouse Results ===============
URL: http://127.0.0.1:8000/devtools/resources/inspected-page.html URL: http://127.0.0.1:8000/devtools/resources/inspected-page.html
Version: 3.2.0 Version: 4.0.0-beta
accesskeys: not-applicable accesskeys: not-applicable
...@@ -230,6 +329,7 @@ http-status-code: pass ...@@ -230,6 +329,7 @@ http-status-code: pass
image-alt: not-applicable image-alt: not-applicable
image-aspect-ratio: pass image-aspect-ratio: pass
input-image-alt: not-applicable input-image-alt: not-applicable
installable-manifest: fail
interactive: flaky interactive: flaky
interactive-element-affordance: manual interactive-element-affordance: manual
is-crawlable: pass is-crawlable: pass
...@@ -245,7 +345,6 @@ load-fast-enough-for-pwa: flaky ...@@ -245,7 +345,6 @@ load-fast-enough-for-pwa: flaky
logical-tab-order: manual logical-tab-order: manual
mainthread-work-breakdown: ERROR Something went wrong with recording the trace over your page load. Please run Lighthouse again. (NO_FCP) mainthread-work-breakdown: ERROR Something went wrong with recording the trace over your page load. Please run Lighthouse again. (NO_FCP)
managed-focus: manual managed-focus: manual
manifest-short-name-length: not-applicable
meta-description: fail meta-description: fail
meta-refresh: not-applicable meta-refresh: not-applicable
meta-viewport: not-applicable meta-viewport: not-applicable
...@@ -254,9 +353,9 @@ mobile-friendly: manual ...@@ -254,9 +353,9 @@ mobile-friendly: manual
network-requests: informative network-requests: informative
no-document-write: pass no-document-write: pass
no-vulnerable-libraries: pass no-vulnerable-libraries: pass
no-websql: pass
notification-on-start: pass notification-on-start: pass
object-alt: not-applicable object-alt: not-applicable
offline-start-url: fail
offscreen-content-hidden: manual offscreen-content-hidden: manual
offscreen-images: flaky offscreen-images: flaky
password-inputs-can-be-pasted-into: pass password-inputs-can-be-pasted-into: pass
...@@ -298,9 +397,8 @@ video-caption: not-applicable ...@@ -298,9 +397,8 @@ video-caption: not-applicable
video-description: not-applicable video-description: not-applicable
viewport: fail viewport: fail
visual-order-follows-dom: manual visual-order-follows-dom: manual
webapp-install-banner: fail
without-javascript: fail without-javascript: fail
works-offline: fail works-offline: fail
# of .lh-audit divs: 112 # of .lh-audit divs: 111
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment