summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/devtools/front_end/audits2
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2019-02-13 16:23:34 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-02-14 10:37:21 +0000
commit38a9a29f4f9436cace7f0e7abf9c586057df8a4e (patch)
treec4e8c458dc595bc0ddb435708fa2229edfd00bd4 /chromium/third_party/blink/renderer/devtools/front_end/audits2
parente684a3455bcc29a6e3e66a004e352dea4e1141e7 (diff)
downloadqtwebengine-chromium-38a9a29f4f9436cace7f0e7abf9c586057df8a4e.tar.gz
BASELINE: Update Chromium to 73.0.3683.37
Change-Id: I08c9af2948b645f671e5d933aca1f7a90ea372f2 Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/devtools/front_end/audits2')
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js352
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js13
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js25
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/dom.js17
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/logger.js81
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js25
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js84
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/pwa-category-renderer.js156
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js49
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js492
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js24
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css185
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html353
-rw-r--r--chromium/third_party/blink/renderer/devtools/front_end/audits2/module.json2
14 files changed, 1529 insertions, 329 deletions
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js
index 973526c5ef4..c20523bcf26 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js
@@ -22,6 +22,7 @@
/** @typedef {import('./report-renderer.js')} ReportRenderer */
/** @typedef {import('./details-renderer.js')} DetailsRenderer */
/** @typedef {import('./util.js')} Util */
+/** @typedef {'failed'|'warning'|'manual'|'passed'|'notApplicable'} TopLevelClumpId */
class CategoryRenderer {
/**
@@ -40,6 +41,18 @@ class CategoryRenderer {
}
/**
+ * Display info per top-level clump. Define on class to avoid race with Util init.
+ */
+ get _clumpTitles() {
+ return {
+ warning: Util.UIStrings.warningAuditsGroupTitle,
+ manual: Util.UIStrings.manualAuditsGroupTitle,
+ passed: Util.UIStrings.passedAuditsGroupTitle,
+ notApplicable: Util.UIStrings.notApplicableAuditsGroupTitle,
+ };
+ }
+
+ /**
* @param {LH.ReportResult.AuditRef} audit
* @param {number} index
* @return {Element}
@@ -129,19 +142,20 @@ class CategoryRenderer {
*/
_setRatingClass(element, score, scoreDisplayMode) {
const rating = Util.calculateRating(score, scoreDisplayMode);
- element.classList.add(`lh-audit--${rating}`, `lh-audit--${scoreDisplayMode}`);
+ element.classList.add(`lh-audit--${rating}`, `lh-audit--${scoreDisplayMode.toLowerCase()}`);
return element;
}
/**
* @param {LH.ReportResult.Category} category
+ * @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
* @return {Element}
*/
- renderCategoryHeader(category) {
+ renderCategoryHeader(category, groupDefinitions) {
const tmpl = this.dom.cloneTemplate('#tmpl-lh-category-header', this.templateContext);
const gaugeContainerEl = this.dom.find('.lh-score__gauge', tmpl);
- const gaugeEl = this.renderScoreGauge(category);
+ const gaugeEl = this.renderScoreGauge(category, groupDefinitions);
gaugeContainerEl.appendChild(gaugeEl);
this.dom.find('.lh-category-header__title', tmpl).appendChild(
@@ -158,19 +172,13 @@ class CategoryRenderer {
* Renders the group container for a group of audits. Individual audit elements can be added
* directly to the returned element.
* @param {LH.Result.ReportGroup} group
- * @param {{expandable: boolean, itemCount?: number}} opts
* @return {Element}
*/
- renderAuditGroup(group, opts) {
- const expandable = opts.expandable;
- 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');
- if (expandable) {
- const chevronEl = summmaryEl.appendChild(this._createChevron());
- chevronEl.title = Util.UIStrings.auditGroupExpandTooltip;
- }
+ renderAuditGroup(group) {
+ const groupEl = this.dom.createElement('div', 'lh-audit-group');
+ const summaryEl = this.dom.createChildOf(groupEl, 'div');
+ const summaryInnerEl = this.dom.createChildOf(summaryEl, 'div', 'lh-audit-group__summary');
+ const headerEl = this.dom.createChildOf(summaryInnerEl, 'div', 'lh-audit-group__header');
if (group.description) {
const auditGroupDescription = this.dom.createElement('div', 'lh-audit-group__description');
@@ -179,84 +187,112 @@ class CategoryRenderer {
}
headerEl.textContent = group.title;
- if (opts.itemCount) {
- // TODO(i18n): support multiple locales here
- itemCountEl.textContent = `${opts.itemCount} audits`;
- }
return groupEl;
}
/**
- * Find the total number of audits contained within a section.
- * Accounts for nested subsections like Accessibility.
- * @param {Array<Element>} elements
- * @return {number}
+ * Takes an array of auditRefs, groups them if requested, then returns an
+ * array of audit and audit-group elements.
+ * @param {Array<LH.ReportResult.AuditRef>} auditRefs
+ * @param {Object<string, LH.Result.ReportGroup>} groupDefinitions
+ * @return {Array<Element>}
*/
- _getTotalAuditsLength(elements) {
- // Create a scratch element to append sections to so we can reuse querySelectorAll().
- const scratch = this.dom.createElement('div');
- elements.forEach(function(element) {
- scratch.appendChild(element);
- });
- const subAudits = scratch.querySelectorAll('.lh-audit');
- if (subAudits.length) {
- return subAudits.length;
- } else {
- return elements.length;
+ _renderGroupedAudits(auditRefs, groupDefinitions) {
+ // Audits grouped by their group (or under notAGroup).
+ /** @type {Map<string, Array<LH.ReportResult.AuditRef>>} */
+ const grouped = new Map();
+
+ // Add audits without a group first so they will appear first.
+ const notAGroup = 'NotAGroup';
+ grouped.set(notAGroup, []);
+
+ for (const auditRef of auditRefs) {
+ const groupId = auditRef.group || notAGroup;
+ const groupAuditRefs = grouped.get(groupId) || [];
+ groupAuditRefs.push(auditRef);
+ grouped.set(groupId, groupAuditRefs);
}
- }
- /**
- * @param {Array<Element>} elements
- * @return {Element}
- */
- _renderFailedAuditsSection(elements) {
- const failedElem = this.dom.createElement('div');
- failedElem.classList.add('lh-failed-audits');
- elements.forEach(elem => failedElem.appendChild(elem));
- return failedElem;
- }
+ /** @type {Array<Element>} */
+ const auditElements = [];
+ // Continuous numbering across all groups.
+ let index = 0;
- /**
- * @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;
+ 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.
+ const groupDef = groupDefinitions[groupId];
+ const auditGroupElem = this.renderAuditGroup(groupDef);
+ for (const auditRef of groupAuditRefs) {
+ auditGroupElem.appendChild(this.renderAudit(auditRef, index++));
+ }
+ auditGroupElem.classList.add(`lh-audit-group--${groupId}`);
+ auditElements.push(auditGroupElem);
+ }
+
+ return auditElements;
}
/**
- * @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}
*/
- _renderNotApplicableAuditsSection(elements) {
- const notApplicableElem = this.renderAuditGroup({
- title: Util.UIStrings.notApplicableAuditsGroupTitle,
- }, {expandable: true, itemCount: this._getTotalAuditsLength(elements)});
- notApplicableElem.classList.add('lh-audit-group--not-applicable');
- elements.forEach(elem => notApplicableElem.appendChild(elem));
- return notApplicableElem;
+ renderUnexpandableClump(auditRefs, groupDefinitions) {
+ const clumpElement = this.dom.createElement('div');
+ const elements = this._renderGroupedAudits(auditRefs, groupDefinitions);
+ elements.forEach(elem => clumpElement.appendChild(elem));
+ return clumpElement;
}
/**
- * @param {Array<LH.ReportResult.AuditRef>} manualAudits
- * @param {string} [manualDescription]
+ * Take a set of audits and render in a top-level, expandable clump that starts
+ * in a collapsed state.
+ * @param {Exclude<TopLevelClumpId, 'failed'>} clumpId
+ * @param {{auditRefs: Array<LH.ReportResult.AuditRef>, description?: string}} clumpOpts
* @return {Element}
*/
- _renderManualAudits(manualAudits, manualDescription) {
- const group = {title: Util.UIStrings.manualAuditsGroupTitle, description: manualDescription};
- const auditGroupElem = this.renderAuditGroup(group,
- {expandable: true, itemCount: manualAudits.length});
- auditGroupElem.classList.add('lh-audit-group--manual');
- manualAudits.forEach((audit, i) => {
- auditGroupElem.appendChild(this.renderAudit(audit, i));
- });
- return auditGroupElem;
+ renderClump(clumpId, {auditRefs, description}) {
+ const clumpTmpl = this.dom.cloneTemplate('#tmpl-lh-clump', this.templateContext);
+ const clumpElement = this.dom.find('.lh-clump', clumpTmpl);
+
+ if (clumpId === 'warning') {
+ clumpElement.setAttribute('open', '');
+ }
+
+ const summaryInnerEl = this.dom.find('.lh-audit-group__summary', clumpElement);
+ const chevronEl = summaryInnerEl.appendChild(this._createChevron());
+ chevronEl.title = Util.UIStrings.auditGroupExpandTooltip;
+
+ const headerEl = this.dom.find('.lh-audit-group__header', clumpElement);
+ const title = this._clumpTitles[clumpId];
+ headerEl.textContent = title;
+ if (description) {
+ const markdownDescriptionEl = this.dom.convertMarkdownLinkSnippets(description);
+ const auditGroupDescription = this.dom.createElement('div', 'lh-audit-group__description');
+ auditGroupDescription.appendChild(markdownDescriptionEl);
+ clumpElement.appendChild(auditGroupDescription);
+ }
+
+ const itemCountEl = this.dom.find('.lh-audit-group__itemcount', clumpElement);
+ // TODO(i18n): support multiple locales here
+ itemCountEl.textContent = `${auditRefs.length} audits`;
+
+ // Add all audit results to the clump.
+ const auditElements = auditRefs.map(this.renderAudit.bind(this));
+ clumpElement.append(...auditElements);
+
+ clumpElement.classList.add(`lh-clump--${clumpId.toLowerCase()}`);
+ return clumpElement;
}
/**
@@ -269,9 +305,10 @@ class CategoryRenderer {
/**
* @param {LH.ReportResult.Category} category
+ * @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
* @return {DocumentFragment}
*/
- renderScoreGauge(category) {
+ renderScoreGauge(category, groupDefinitions) { // eslint-disable-line no-unused-vars
const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge', this.templateContext);
const wrapper = /** @type {HTMLAnchorElement} */ (this.dom.find('.lh-gauge__wrapper', tmpl));
wrapper.href = `#${category.id}`;
@@ -302,104 +339,93 @@ class CategoryRenderer {
}
/**
- * @param {LH.ReportResult.Category} category
- * @param {Object<string, LH.Result.ReportGroup>} [groupDefinitions]
- * @return {Element}
+ * @param {LH.ReportResult.AuditRef} audit
+ * @return {boolean}
*/
- render(category, groupDefinitions) {
- const element = this.dom.createElement('div', 'lh-category');
- this.createPermalinkSpan(element, category.id);
- element.appendChild(this.renderCategoryHeader(category));
-
- 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;
+ _auditHasWarning(audit) {
+ return Boolean(audit.result.warnings && audit.result.warnings.length);
+ }
- if (auditsGroupedByGroup[groupId]) {
- group = auditsGroupedByGroup[groupId];
- } else {
- group = {passed: [], failed: [], notApplicable: []};
- auditsGroupedByGroup[groupId] = group;
- }
- } else {
- group = auditsUngrouped;
- }
+ /**
+ * Returns the id of the top-level clump to put this audit in.
+ * @param {LH.ReportResult.AuditRef} auditRef
+ * @return {TopLevelClumpId}
+ */
+ _getClumpIdForAuditRef(auditRef) {
+ const scoreDisplayMode = auditRef.result.scoreDisplayMode;
+ if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'notApplicable') {
+ return scoreDisplayMode;
+ }
- if (auditRef.result.scoreDisplayMode === 'not-applicable') {
- group.notApplicable.push(auditRef);
- } else if (Util.showAsPassed(auditRef.result)) {
- group.passed.push(auditRef);
+ if (Util.showAsPassed(auditRef.result)) {
+ if (this._auditHasWarning(auditRef)) {
+ return 'warning';
} else {
- group.failed.push(auditRef);
- }
- });
-
- 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);
+ return 'passed';
}
-
- 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);
+ } else {
+ return 'failed';
}
+ }
- if (manualAudits.length) {
- const manualEl = this._renderManualAudits(manualAudits, category.manualDescription);
- element.appendChild(manualEl);
+ /**
+ * Renders a set of top level sections (clumps), under a status of failed, warning,
+ * manual, passed, or notApplicable. The result ends up something like:
+ *
+ * failed clump
+ * ├── audit 1 (w/o group)
+ * ├── audit 2 (w/o group)
+ * ├── audit group
+ * | ├── audit 3
+ * | └── audit 4
+ * └── audit group
+ * ├── audit 5
+ * └── audit 6
+ * other clump (e.g. 'manual')
+ * ├── audit 1
+ * ├── audit 2
+ * ├── …
+ * ⋮
+ * @param {LH.ReportResult.Category} category
+ * @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, groupDefinitions));
+
+ // Top level clumps for audits, in order they will appear in the report.
+ /** @type {Map<TopLevelClumpId, Array<LH.ReportResult.AuditRef>>} */
+ const clumps = new Map();
+ clumps.set('failed', []);
+ clumps.set('warning', []);
+ clumps.set('manual', []);
+ clumps.set('passed', []);
+ clumps.set('notApplicable', []);
+
+ // 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);
}
- if (passedElements.length) {
- const passedElem = this.renderPassedAuditsSection(passedElements);
- element.appendChild(passedElem);
- }
+ // Render each clump.
+ for (const [clumpId, auditRefs] of clumps) {
+ if (auditRefs.length === 0) continue;
+
+ if (clumpId === 'failed') {
+ const clumpElem = this.renderUnexpandableClump(auditRefs, groupDefinitions);
+ clumpElem.classList.add(`lh-clump--failed`);
+ element.appendChild(clumpElem);
+ continue;
+ }
- if (notApplicableElements.length) {
- const notApplicableElem = this._renderNotApplicableAuditsSection(notApplicableElements);
- element.appendChild(notApplicableElem);
+ const description = clumpId === 'manual' ? category.manualDescription : undefined;
+ const clumpElem = this.renderClump(clumpId, {auditRefs, description});
+ element.appendChild(clumpElem);
}
return element;
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js
index 0ab374132e3..9ffde229528 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js
@@ -59,7 +59,7 @@ class CriticalRequestChainRenderer {
const node = parent[id];
const siblings = Object.keys(parent);
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.
const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : [];
@@ -149,11 +149,12 @@ class CriticalRequestChainRenderer {
*/
static buildTree(dom, tmpl, segment, elem, details) {
elem.appendChild(CriticalRequestChainRenderer.createChainNode(dom, tmpl, segment));
-
- for (const key of Object.keys(segment.node.children)) {
- const childSegment = CriticalRequestChainRenderer.createSegment(segment.node.children, key,
- segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild);
- CriticalRequestChainRenderer.buildTree(dom, tmpl, childSegment, elem, details);
+ if (segment.node.children) {
+ for (const key of Object.keys(segment.node.children)) {
+ const childSegment = CriticalRequestChainRenderer.createSegment(segment.node.children, key,
+ segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild);
+ CriticalRequestChainRenderer.buildTree(dom, tmpl, childSegment, elem, details);
+ }
}
}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
index a5cf44b33b7..b440662f3b8 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
@@ -80,6 +80,8 @@ class DetailsRenderer {
case 'opportunity':
// @ts-ignore - TODO(bckenny): Fix type hierarchy
return this._renderOpportunityTable(details);
+ case 'numeric':
+ return this._renderNumeric(/** @type {StringDetailsJSON} */ (details));
default: {
throw new Error(`Unknown type: ${details.type}`);
}
@@ -124,14 +126,11 @@ class DetailsRenderer {
displayedPath = parsed.file === '/' ? parsed.origin : parsed.file;
displayedHost = parsed.file === '/' ? '' : `(${parsed.hostname})`;
title = url;
- } catch (/** @type {!Error} */ e) {
- if (!e.name.startsWith('TypeError')) {
- throw e;
- }
+ } catch (e) {
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({
value: displayedPath,
}));
@@ -162,7 +161,7 @@ class DetailsRenderer {
});
}
- const a = /** @type {HTMLAnchorElement} */ (this._dom.createElement('a'));
+ const a = this._dom.createElement('a');
a.rel = 'noopener';
a.target = '_blank';
a.textContent = details.text;
@@ -182,13 +181,23 @@ class DetailsRenderer {
}
/**
+ * @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.
* If the supplied details doesn't have an image/* mimeType, then an empty span is returned.
* @param {{value: string}} details
* @return {Element}
*/
_renderThumbnail(details) {
- const element = /** @type {HTMLImageElement}*/ (this._dom.createElement('img', 'lh-thumbnail'));
+ const element = this._dom.createElement('img', 'lh-thumbnail');
const strValue = details.value;
element.src = strValue;
element.title = strValue;
@@ -337,7 +346,7 @@ class DetailsRenderer {
* @protected
*/
renderNode(item) {
- const element = /** @type {HTMLSpanElement} */ (this._dom.createElement('span', 'lh-node'));
+ const element = this._dom.createElement('span', 'lh-node');
if (item.snippet) {
element.textContent = item.snippet;
}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/dom.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/dom.js
index fa1d564720d..3c10ca6476d 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/dom.js
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/dom.js
@@ -18,6 +18,8 @@
/* globals URL self */
+/** @typedef {HTMLElementTagNameMap & {[id: string]: HTMLElement}} HTMLElmentByTagName */
+
class DOM {
/**
* @param {Document} document
@@ -27,14 +29,14 @@ class DOM {
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 {Object<string, (string|undefined)>=} attrs Attribute key/val pairs.
* Note: if an attribute key has an undefined value, this method does not
* set the attribute on the node.
- * @return {Element}
+ * @return {HTMLElmentByTagName[T]}
*/
createElement(name, className, attrs = {}) {
const element = this._document.createElement(name);
@@ -58,13 +60,14 @@ class DOM {
}
/**
+ * @template {string} T
* @param {Element} parentElem
- * @param {string} elementName
+ * @param {T} elementName
* @param {string=} className
* @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs.
* Note: if an attribute key has an undefined value, this method does not
* set the attribute on the node.
- * @return {Element}
+ * @return {HTMLElmentByTagName[T]}
*/
createChildOf(parentElem, elementName, className, attrs) {
const element = this.createElement(elementName, className, attrs);
@@ -122,7 +125,7 @@ class DOM {
// Append link if there are any.
if (linkText && linkHref) {
- const a = /** @type {HTMLAnchorElement} */ (this.createElement('a'));
+ const a = this.createElement('a');
a.rel = 'noopener';
a.target = '_blank';
a.textContent = linkText;
@@ -147,7 +150,7 @@ class DOM {
const [preambleText, codeText] = parts.splice(0, 2);
element.appendChild(this._document.createTextNode(preambleText));
if (codeText) {
- const pre = /** @type {HTMLPreElement} */ (this.createElement('code'));
+ const pre = this.createElement('code');
pre.textContent = codeText;
element.appendChild(pre);
}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/logger.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/logger.js
new file mode 100644
index 00000000000..5fea0543e4c
--- /dev/null
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/logger.js
@@ -0,0 +1,81 @@
+/**
+ * @license
+ * Copyright 2017 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';
+
+/**
+ * Logs messages via a UI butter.
+ */
+class Logger {
+ /**
+ * @param {Element} element
+ */
+ constructor(element) {
+ /** @type {Element} */
+ this.el = element;
+ this._id = undefined;
+ }
+
+ /**
+ * Shows a butter bar.
+ * @param {string} msg The message to show.
+ * @param {boolean=} autoHide True to hide the message after a duration.
+ * Default is true.
+ */
+ log(msg, autoHide = true) {
+ this._id && clearTimeout(this._id);
+
+ this.el.textContent = msg;
+ this.el.classList.add('show');
+ if (autoHide) {
+ this._id = setTimeout(_ => {
+ this.el.classList.remove('show');
+ }, 7000);
+ }
+ }
+
+ /**
+ * @param {string} msg
+ */
+ warn(msg) {
+ this.log('Warning: ' + msg);
+ }
+
+ /**
+ * @param {string} msg
+ */
+ error(msg) {
+ this.log(msg);
+
+ // Rethrow to make sure it's auditable as an error, but in a setTimeout so page
+ // recovers gracefully and user can try loading a report again.
+ setTimeout(_ => {
+ throw new Error(msg);
+ }, 0);
+ }
+
+ /**
+ * Explicitly hides the butter bar.
+ */
+ hide() {
+ this._id && clearTimeout(this._id);
+ this.el.classList.remove('show');
+ }
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = Logger;
+}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js
index 873c0b765c5..870bdfc4e79 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js
@@ -120,16 +120,16 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
const element = this.dom.createElement('div', 'lh-category');
if (environment === 'PSI') {
const gaugeEl = this.dom.createElement('div', 'lh-score__gauge');
- gaugeEl.appendChild(this.renderScoreGauge(category));
+ gaugeEl.appendChild(this.renderScoreGauge(category, groups));
element.appendChild(gaugeEl);
} else {
this.createPermalinkSpan(element, category.id);
- element.appendChild(this.renderCategoryHeader(category));
+ element.appendChild(this.renderCategoryHeader(category, groups));
}
// Metrics
const metricAudits = category.auditRefs.filter(audit => audit.group === 'metrics');
- const metricAuditsEl = this.renderAuditGroup(groups.metrics, {expandable: false});
+ const metricAuditsEl = this.renderAuditGroup(groups.metrics);
const keyMetrics = metricAudits.filter(a => a.weight >= 3);
const otherMetrics = metricAudits.filter(a => a.weight < 3);
@@ -176,7 +176,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
const wastedMsValues = opportunityAudits.map(audit => this._getWastedMs(audit));
const maxWaste = Math.max(...wastedMsValues);
const scale = Math.max(Math.ceil(maxWaste / 1000) * 1000, minimumScale);
- const groupEl = this.renderAuditGroup(groups['load-opportunities'], {expandable: false});
+ const groupEl = this.renderAuditGroup(groups['load-opportunities']);
const tmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity-header', this.templateContext);
this.dom.find('.lh-load-opportunity__col--one', tmpl).textContent =
@@ -188,7 +188,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
groupEl.appendChild(headerEl);
opportunityAudits.forEach((item, i) =>
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);
}
@@ -202,21 +202,24 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
});
if (diagnosticAudits.length) {
- const groupEl = this.renderAuditGroup(groups['diagnostics'], {expandable: false});
+ const groupEl = this.renderAuditGroup(groups['diagnostics']);
diagnosticAudits.forEach((item, i) => groupEl.appendChild(this.renderAudit(item, i)));
groupEl.classList.add('lh-audit-group--diagnostics');
element.appendChild(groupEl);
}
// Passed audits
- const passedElements = category.auditRefs
+ const passedAudits = category.auditRefs
.filter(audit => (audit.group === 'load-opportunities' || audit.group === 'diagnostics') &&
- Util.showAsPassed(audit.result))
- .map((audit, i) => this.renderAudit(audit, i));
+ Util.showAsPassed(audit.result));
- 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);
return element;
}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js
new file mode 100644
index 00000000000..a1b063cc729
--- /dev/null
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js
@@ -0,0 +1,84 @@
+/**
+ * @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 DOM PerformanceCategoryRenderer Util DetailsRenderer */
+
+
+/**
+ * Returns all the elements that PSI needs to render the report
+ * We expose this helper method to minimize the 'public' API surface of the renderer
+ * and allow us to refactor without two-sided patches.
+ *
+ * const {scoreGaugeEl, perfCategoryEl, finalScreenshotDataUri} = prepareLabData(
+ * LHResultJsonString,
+ * document
+ * );
+ *
+ * @param {LH.Result | string} LHResult The stringified version of {LH.Result}
+ * @param {Document} document The host page's window.document
+ * @return {{scoreGaugeEl: Element, perfCategoryEl: Element, finalScreenshotDataUri: string|null}}
+ */
+function prepareLabData(LHResult, document) {
+ const lhResult = (typeof LHResult === 'string') ?
+ /** @type {LH.Result} */ (JSON.parse(LHResult)) : LHResult;
+
+ const dom = new DOM(document);
+
+ // Assume fresh styles needed on every call, so mark all template styles as unused.
+ dom.resetTemplates();
+
+ const reportLHR = Util.prepareReportResult(lhResult);
+ const perfCategory = reportLHR.reportCategories.find(cat => cat.id === 'performance');
+ if (!perfCategory) throw new Error(`No performance category. Can't make lab data section`);
+ if (!reportLHR.categoryGroups) throw new Error(`No category groups found.`);
+
+ // Use custom title and description.
+ reportLHR.categoryGroups.metrics.title = lhResult.i18n.rendererFormattedStrings.labDataTitle;
+ reportLHR.categoryGroups.metrics.description =
+ lhResult.i18n.rendererFormattedStrings.lsPerformanceCategoryDescription;
+
+ const perfRenderer = new PerformanceCategoryRenderer(dom, new DetailsRenderer(dom));
+ // PSI environment string will ensure the categoryHeader and permalink elements are excluded
+ const perfCategoryEl = perfRenderer.render(perfCategory, reportLHR.categoryGroups, 'PSI');
+
+ const scoreGaugeEl = dom.find('.lh-score__gauge', perfCategoryEl);
+ scoreGaugeEl.remove();
+ const scoreGaugeWrapperEl = dom.find('.lh-gauge__wrapper', scoreGaugeEl);
+ scoreGaugeWrapperEl.classList.add('lh-gauge__wrapper--huge');
+ // Remove navigation link on gauge
+ scoreGaugeWrapperEl.removeAttribute('href');
+
+ const finalScreenshotDataUri = _getFinalScreenshot(perfCategory);
+ return {scoreGaugeEl, perfCategoryEl, finalScreenshotDataUri};
+}
+
+/**
+ * @param {LH.ReportResult.Category} perfCategory
+ * @return {null|string}
+ */
+function _getFinalScreenshot(perfCategory) {
+ const auditRef = perfCategory.auditRefs.find(audit => audit.id === 'final-screenshot');
+ if (!auditRef || !auditRef.result || auditRef.result.scoreDisplayMode === 'error') return null;
+ return auditRef.result.details.data;
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = prepareLabData;
+} else {
+ self.prepareLabData = prepareLabData;
+}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/pwa-category-renderer.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/pwa-category-renderer.js
new file mode 100644
index 00000000000..7e07206f3eb
--- /dev/null
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/pwa-category-renderer.js
@@ -0,0 +1,156 @@
+/**
+ * @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, groupDefinitions));
+
+ const auditRefs = category.auditRefs;
+
+ // Regular audits aren't split up into pass/fail/notApplicable 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, description: category.manualDescription});
+ categoryElem.appendChild(manualElem);
+
+ return categoryElem;
+ }
+
+ /**
+ * @param {LH.ReportResult.Category} category
+ * @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
+ * @return {DocumentFragment}
+ */
+ renderScoreGauge(category, groupDefinitions) {
+ // Defer to parent-gauge style if category error.
+ if (category.score === null) {
+ return super.renderScoreGauge(category, groupDefinitions);
+ }
+
+ 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;
+ wrapper.title = this._getGaugeTooltip(category.auditRefs, groupDefinitions);
+ 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;
+ }
+
+ /**
+ * Returns a tooltip string summarizing group pass rates.
+ * @param {Array<LH.ReportResult.AuditRef>} auditRefs
+ * @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
+ * @return {string}
+ */
+ _getGaugeTooltip(auditRefs, groupDefinitions) {
+ const groupIds = this._getGroupIds(auditRefs);
+
+ const tips = [];
+ for (const groupId of groupIds) {
+ const groupAuditRefs = auditRefs.filter(ref => ref.group === groupId);
+ const auditCount = groupAuditRefs.length;
+ const passedCount = groupAuditRefs.filter(ref => Util.showAsPassed(ref.result)).length;
+
+ const title = groupDefinitions[groupId].title;
+ tips.push(`${title}: ${passedCount}/${auditCount}`);
+ }
+
+ return tips.join(', ');
+ }
+
+ /**
+ * 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;
+}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
index 23e772fb3f5..e04dbcc33f9 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
@@ -26,7 +26,7 @@
/** @typedef {import('./dom.js')} DOM */
/** @typedef {import('./details-renderer.js').DetailsJSON} DetailsJSON */
-/* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer */
+/* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer, PwaCategoryRenderer */
class ReportRenderer {
/**
@@ -42,6 +42,7 @@ class ReportRenderer {
/**
* @param {LH.Result} result
* @param {Element} container Parent element to render the report into.
+ * @return {Element}
*/
renderReport(result, container) {
// Mutate the UIStrings if necessary (while saving originals)
@@ -55,7 +56,7 @@ class ReportRenderer {
// put the UIStrings back into original state
Util.updateAllUIStrings(originalUIStrings);
- return /** @type {Element} **/ (container);
+ return container;
}
/**
@@ -188,24 +189,48 @@ class ReportRenderer {
const detailsRenderer = new DetailsRenderer(this._dom);
const categoryRenderer = new CategoryRenderer(this._dom, detailsRenderer);
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'));
for (const category of report.reportCategories) {
- if (scoreHeader) {
- scoreHeader.appendChild(categoryRenderer.renderScoreGauge(category));
- }
-
- let renderer = categoryRenderer;
- if (category.id === 'performance') {
- renderer = perfCategoryRenderer;
- }
+ const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
categories.appendChild(renderer.render(category, report.categoryGroups));
}
+ // Fireworks
+ const scoresAll100 = report.reportCategories.every(cat => cat.score === 1);
+ if (scoresAll100 && !this._dom.isDevTools()) {
+ headerContainer.classList.add('score100');
+ this._dom.find('.lh-header', headerContainer).addEventListener('click', _ => {
+ headerContainer.classList.toggle('fireworks-paused');
+ });
+ }
+
if (scoreHeader) {
+ const defaultGauges = [];
+ const customGauges = [];
+ for (const category of report.reportCategories) {
+ const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
+ const categoryGauge = renderer.renderScoreGauge(category, report.categoryGroups || {});
+
+ // Group gauges that aren't default at the end of the header
+ if (renderer.renderScoreGauge === categoryRenderer.renderScoreGauge) {
+ defaultGauges.push(categoryGauge);
+ } else {
+ customGauges.push(categoryGauge);
+ }
+ }
+ scoreHeader.append(...defaultGauges, ...customGauges);
+
const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext);
this._dom.find('.lh-scorescale-label', scoreScale).textContent =
Util.UIStrings.scorescaleLabel;
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js
new file mode 100644
index 00000000000..bfc1b627c52
--- /dev/null
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js
@@ -0,0 +1,492 @@
+/**
+ * @license
+ * Copyright 2017 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';
+
+/**
+ * @fileoverview Adds export button, print, and other dynamic functionality to
+ * the report.
+ */
+
+/* globals self URL Blob CustomEvent getFilenamePrefix window */
+
+/** @typedef {import('./dom.js')} DOM */
+
+class ReportUIFeatures {
+ /**
+ * @param {DOM} dom
+ */
+ constructor(dom) {
+ /** @type {LH.ReportResult} */
+ this.json; // eslint-disable-line no-unused-expressions
+ /** @type {DOM} */
+ this._dom = dom;
+ /** @type {Document} */
+ this._document = this._dom.document();
+ /** @type {boolean} */
+ this._copyAttempt = false;
+ /** @type {HTMLElement} */
+ this.exportButton; // eslint-disable-line no-unused-expressions
+ /** @type {HTMLElement} */
+ this.headerSticky; // eslint-disable-line no-unused-expressions
+ /** @type {HTMLElement} */
+ this.headerBackground; // eslint-disable-line no-unused-expressions
+ /** @type {HTMLElement} */
+ this.headerContent; // eslint-disable-line no-unused-expressions
+ /** @type {HTMLElement} */
+ this.lighthouseIcon; // eslint-disable-line no-unused-expressions
+ /** @type {!HTMLElement} */
+ this.scoresWrapperBg; // eslint-disable-line no-unused-expressions
+ /** @type {!HTMLElement} */
+ this.productInfo; // eslint-disable-line no-unused-expressions
+ /** @type {HTMLElement} */
+ this.toolbar; // eslint-disable-line no-unused-expressions
+ /** @type {HTMLElement} */
+ this.toolbarMetadata; // eslint-disable-line no-unused-expressions
+ /** @type {HTMLElement} */
+ this.env; // eslint-disable-line no-unused-expressions
+ /** @type {number} */
+ this.headerOverlap = 0;
+ /** @type {number} */
+ this.headerHeight = 0;
+ /** @type {number} */
+ this.latestKnownScrollY = 0;
+ /** @type {boolean} */
+ this.isAnimatingHeader = false;
+
+ this.onMediaQueryChange = this.onMediaQueryChange.bind(this);
+ this.onCopy = this.onCopy.bind(this);
+ this.onExportButtonClick = this.onExportButtonClick.bind(this);
+ this.onExport = this.onExport.bind(this);
+ this.onKeyDown = this.onKeyDown.bind(this);
+ this.printShortCutDetect = this.printShortCutDetect.bind(this);
+ this.onScroll = this.onScroll.bind(this);
+ this.onChevronClick = this.onChevronClick.bind(this);
+ }
+
+ /**
+ * Adds export button, print, and other functionality to the report. The method
+ * should be called whenever the report needs to be re-rendered.
+ * @param {LH.ReportResult} report
+ */
+ initFeatures(report) {
+ if (this._dom.isDevTools()) return;
+
+ this.json = report;
+ this._setupMediaQueryListeners();
+ this._setupExportButton();
+ this._setUpCollapseDetailsAfterPrinting();
+ this._setupHeaderAnimation();
+ this._resetUIState();
+ this._document.addEventListener('keydown', this.printShortCutDetect);
+ this._document.addEventListener('copy', this.onCopy);
+ }
+
+ /**
+ * Fires a custom DOM event on target.
+ * @param {string} name Name of the event.
+ * @param {Node=} target DOM node to fire the event on.
+ * @param {*=} detail Custom data to include.
+ */
+ _fireEventOn(name, target = this._document, detail) {
+ const event = new CustomEvent(name, detail ? {detail} : undefined);
+ target.dispatchEvent(event);
+ }
+
+ _setupMediaQueryListeners() {
+ const mediaQuery = self.matchMedia('(max-width: 500px)');
+ mediaQuery.addListener(this.onMediaQueryChange);
+ // Ensure the handler is called on init
+ this.onMediaQueryChange(mediaQuery);
+ }
+
+ /**
+ * Handle media query change events.
+ * @param {MediaQueryList|MediaQueryListEvent} mql
+ */
+ onMediaQueryChange(mql) {
+ const root = this._dom.find('.lh-root', this._document);
+ root.classList.toggle('lh-narrow', mql.matches);
+ }
+
+ _setupExportButton() {
+ this.exportButton = this._dom.find('.lh-export__button', this._document);
+ this.exportButton.addEventListener('click', this.onExportButtonClick);
+
+ const dropdown = this._dom.find('.lh-export__dropdown', this._document);
+ dropdown.addEventListener('click', this.onExport);
+ }
+
+ _setupHeaderAnimation() {
+ const scoresWrapper = this._dom.find('.lh-scores-wrapper', this._document);
+ const computedMarginTop = window.getComputedStyle(scoresWrapper).marginTop;
+ this.headerOverlap = parseFloat(computedMarginTop || '0');
+ this.headerSticky = this._dom.find('.lh-header-sticky', this._document);
+ this.headerBackground = this._dom.find('.lh-header-bg', this._document);
+ this.headerContent = this._dom.find('.lh-header', this._document);
+ this.lighthouseIcon = this._dom.find('.lh-lighthouse', this._document);
+ this.scoresWrapperBg = this._dom.find('.lh-scores-wrapper__background', this._document);
+ this.productInfo = this._dom.find('.lh-product-info', this._document);
+ this.toolbar = this._dom.find('.lh-toolbar', this._document);
+ this.toolbarMetadata = this._dom.find('.lh-toolbar__metadata', this._document);
+ const computedHeight = window.getComputedStyle(this.headerBackground).height;
+ this.headerHeight = parseFloat(computedHeight || '0');
+
+ this._document.addEventListener('scroll', this.onScroll, {passive: true});
+
+ const toolbarChevron = this._dom.find('.lh-toggle-arrow', this.toolbar);
+ toolbarChevron.addEventListener('click', this.onChevronClick);
+ }
+
+ /**
+ * Handle copy events.
+ * @param {ClipboardEvent} e
+ */
+ onCopy(e) {
+ // Only handle copy button presses (e.g. ignore the user copying page text).
+ if (this._copyAttempt) {
+ // We want to write our own data to the clipboard, not the user's text selection.
+ e.preventDefault();
+ e.clipboardData.setData('text/plain', JSON.stringify(this.json, null, 2));
+
+ this._fireEventOn('lh-log', this._document, {
+ cmd: 'log', msg: 'Report JSON copied to clipboard',
+ });
+ }
+
+ this._copyAttempt = false;
+ }
+
+ /**
+ * Copies the report JSON to the clipboard (if supported by the browser).
+ */
+ onCopyButtonClick() {
+ this._fireEventOn('lh-analytics', this._document, {
+ cmd: 'send',
+ fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'},
+ });
+
+ try {
+ if (this._document.queryCommandSupported('copy')) {
+ this._copyAttempt = true;
+
+ // Note: In Safari 10.0.1, execCommand('copy') returns true if there's
+ // a valid text selection on the page. See http://caniuse.com/#feat=clipboard.
+ if (!this._document.execCommand('copy')) {
+ this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.
+
+ this._fireEventOn('lh-log', this._document, {
+ cmd: 'warn', msg: 'Your browser does not support copy to clipboard.',
+ });
+ }
+ }
+ } catch (/** @type {Error} */ e) {
+ this._copyAttempt = false;
+ this._fireEventOn('lh-log', this._document, {cmd: 'log', msg: e.message});
+ }
+ }
+
+ onScroll() {
+ this.latestKnownScrollY = window.scrollY;
+
+ if (!this.isAnimatingHeader) {
+ window.requestAnimationFrame(this.animateHeader.bind(this));
+ }
+ this.isAnimatingHeader = true;
+ }
+
+ onChevronClick() {
+ const toggle = this._dom.find('.lh-config__settings-toggle', this._document);
+
+ if (toggle.hasAttribute('open')) {
+ toggle.removeAttribute('open');
+ } else {
+ toggle.setAttribute('open', 'true');
+ }
+ }
+
+ animateHeader() {
+ const collapsedHeaderHeight = 50;
+ const heightDiff = this.headerHeight - collapsedHeaderHeight + this.headerOverlap;
+ const scrollPct = Math.max(0, Math.min(1,
+ this.latestKnownScrollY / (this.headerHeight - collapsedHeaderHeight)));
+
+ const scoresContainer = /** @type {HTMLElement} */ (this.scoresWrapperBg.parentElement);
+
+ this.headerSticky.style.transform = `translateY(${heightDiff * scrollPct * -1}px)`;
+ this.headerBackground.style.transform = `translateY(${scrollPct * this.headerOverlap}px)`;
+ this.lighthouseIcon.style.transform =
+ `translate3d(0,` +
+ `-${scrollPct * this.headerOverlap * -1}px, 0) scale(${1 - scrollPct})`;
+ this.headerContent.style.opacity = (1 - scrollPct).toString();
+
+ // Switch up the score background & shadows
+ this.scoresWrapperBg.style.opacity = (1 - scrollPct).toString();
+ this.scoresWrapperBg.style.transform = `scaleY(${1 - scrollPct * 0.2})`;
+ const scoreShadow = this._dom.find('.lh-scores-wrapper__shadow', scoresContainer);
+ scoreShadow.style.opacity = scrollPct.toString();
+ scoreShadow.style.transform = `scaleY(${1 - scrollPct * 0.2})`;
+
+ // Fade & move the scorescale
+ try {
+ const scoreScalePositionDelta = 32;
+ const scoreScale = this._dom.find('.lh-scorescale', scoresContainer);
+ scoreScale.style.opacity = `${1 - scrollPct}`;
+ scoreScale.style.transform = `translateY(${scrollPct * -scoreScalePositionDelta}px)`;
+ } catch (e) {}
+
+ // Move the toolbar & export
+ this.toolbar.style.transform = `translateY(${heightDiff * scrollPct}px)`;
+ const exportParent = this.exportButton.parentElement;
+ if (exportParent) {
+ exportParent.style.transform = `translateY(${heightDiff * scrollPct}px)`;
+ }
+ this.exportButton.style.transform = `scale(${1 - 0.2 * scrollPct})`;
+ // Start showing the productinfo when we are at the 50% mark of our animation
+ const opacity = scrollPct < 0.5 ? 0 : (scrollPct - 0.5) * 2;
+ this.productInfo.style.opacity = this.toolbarMetadata.style.opacity = opacity.toString();
+
+ this.isAnimatingHeader = false;
+ }
+
+ closeExportDropdown() {
+ this.exportButton.classList.remove('active');
+ }
+
+ /**
+ * Click handler for export button.
+ * @param {Event} e
+ */
+ onExportButtonClick(e) {
+ e.preventDefault();
+ const el = /** @type {Element} */ (e.target);
+ el.classList.toggle('active');
+ this._document.addEventListener('keydown', this.onKeyDown);
+ }
+
+ /**
+ * Resets the state of page before capturing the page for export.
+ * When the user opens the exported HTML page, certain UI elements should
+ * be in their closed state (not opened) and the templates should be unstamped.
+ */
+ _resetUIState() {
+ this.closeExportDropdown();
+ this._dom.resetTemplates();
+ }
+
+ /**
+ * Handler for "export as" button.
+ * @param {Event} e
+ */
+ onExport(e) {
+ e.preventDefault();
+
+ const el = /** @type {?Element} */ (e.target);
+
+ if (!el || !el.hasAttribute('data-action')) {
+ return;
+ }
+
+ switch (el.getAttribute('data-action')) {
+ case 'copy':
+ this.onCopyButtonClick();
+ break;
+ case 'print-summary':
+ this.collapseAllDetails();
+ this.closeExportDropdown();
+ self.print();
+ break;
+ case 'print-expanded':
+ this.expandAllDetails();
+ this.closeExportDropdown();
+ self.print();
+ break;
+ case 'save-json': {
+ const jsonStr = JSON.stringify(this.json, null, 2);
+ this._saveFile(new Blob([jsonStr], {type: 'application/json'}));
+ break;
+ }
+ case 'save-html': {
+ const htmlStr = this.getReportHtml();
+ try {
+ this._saveFile(new Blob([htmlStr], {type: 'text/html'}));
+ } catch (/** @type {Error} */ e) {
+ this._fireEventOn('lh-log', this._document, {
+ cmd: 'error', msg: 'Could not export as HTML. ' + e.message,
+ });
+ }
+ break;
+ }
+ case 'open-viewer': {
+ const viewerPath = '/lighthouse/viewer/';
+ ReportUIFeatures.openTabAndSendJsonReport(this.json, viewerPath);
+ break;
+ }
+ case 'save-gist': {
+ this.saveAsGist();
+ break;
+ }
+ }
+
+ this.closeExportDropdown();
+ this._document.removeEventListener('keydown', this.onKeyDown);
+ }
+
+ /**
+ * Keydown handler for the document.
+ * @param {KeyboardEvent} e
+ */
+ onKeyDown(e) {
+ if (e.keyCode === 27) { // ESC
+ this.closeExportDropdown();
+ }
+ }
+
+ /**
+ * Opens a new tab to the online viewer and sends the local page's JSON results
+ * to the online viewer using postMessage.
+ * @param {LH.ReportResult} reportJson
+ * @param {string} viewerPath
+ * @protected
+ */
+ static openTabAndSendJsonReport(reportJson, viewerPath) {
+ const VIEWER_ORIGIN = 'https://googlechrome.github.io';
+ // Chrome doesn't allow us to immediately postMessage to a popup right
+ // after it's created. Normally, we could also listen for the popup window's
+ // load event, however it is cross-domain and won't fire. Instead, listen
+ // for a message from the target app saying "I'm open".
+ const json = reportJson;
+ window.addEventListener('message', function msgHandler(messageEvent) {
+ if (messageEvent.origin !== VIEWER_ORIGIN) {
+ return;
+ }
+ if (popup && messageEvent.data.opened) {
+ popup.postMessage({lhresults: json}, VIEWER_ORIGIN);
+ window.removeEventListener('message', msgHandler);
+ }
+ });
+
+ // The popup's window.name is keyed by version+url+fetchTime, so we reuse/select tabs correctly
+ // @ts-ignore - If this is a v2 LHR, use old `generatedTime`.
+ const fallbackFetchTime = /** @type {string} */ (json.generatedTime);
+ const fetchTime = json.fetchTime || fallbackFetchTime;
+ const windowName = `${json.lighthouseVersion}-${json.requestedUrl}-${fetchTime}`;
+ const popup = window.open(`${VIEWER_ORIGIN}${viewerPath}`, windowName);
+ }
+
+ /**
+ * Expands audit details when user prints via keyboard shortcut.
+ * @param {KeyboardEvent} e
+ */
+ printShortCutDetect(e) {
+ if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) { // Ctrl+P
+ this.closeExportDropdown();
+ }
+ }
+
+ /**
+ * Expands all audit `<details>`.
+ * Ideally, a print stylesheet could take care of this, but CSS has no way to
+ * open a `<details>` element.
+ */
+ expandAllDetails() {
+ const details = /** @type {Array<HTMLDetailsElement>} */ (this._dom.findAll(
+ '.lh-categories details', this._document));
+ details.map(detail => detail.open = true);
+ }
+
+ /**
+ * Collapses all audit `<details>`.
+ * open a `<details>` element.
+ */
+ collapseAllDetails() {
+ const details = /** @type {Array<HTMLDetailsElement>} */ (this._dom.findAll(
+ '.lh-categories details', this._document));
+ details.map(detail => detail.open = false);
+ }
+
+ /**
+ * Sets up listeners to collapse audit `<details>` when the user closes the
+ * print dialog, all `<details>` are collapsed.
+ */
+ _setUpCollapseDetailsAfterPrinting() {
+ // FF and IE implement these old events.
+ if ('onbeforeprint' in self) {
+ self.addEventListener('afterprint', this.collapseAllDetails);
+ } else {
+ const win = /** @type {Window} */ (self);
+ // Note: FF implements both window.onbeforeprint and media listeners. However,
+ // it doesn't matchMedia doesn't fire when matching 'print'.
+ win.matchMedia('print').addListener(mql => {
+ if (mql.matches) {
+ this.expandAllDetails();
+ } else {
+ this.collapseAllDetails();
+ }
+ });
+ }
+ }
+
+ /**
+ * Returns the html that recreates this report.
+ * @return {string}
+ * @protected
+ */
+ getReportHtml() {
+ this._resetUIState();
+ // @ts-ignore - technically documentElement can be null, but that's dumb - https://dom.spec.whatwg.org/#document-element
+ return this._document.documentElement.outerHTML;
+ }
+
+ /**
+ * Save json as a gist. Unimplemented in base UI features.
+ * @protected
+ */
+ saveAsGist() {
+ throw new Error('Cannot save as gist from base report');
+ }
+
+ /**
+ * Downloads a file (blob) using a[download].
+ * @param {Blob|File} blob The file to save.
+ * @private
+ */
+ _saveFile(blob) {
+ const filename = getFilenamePrefix({
+ finalUrl: this.json.finalUrl,
+ fetchTime: this.json.fetchTime,
+ });
+
+ const ext = blob.type.match('json') ? '.json' : '.html';
+ const href = URL.createObjectURL(blob);
+
+ const a = this._dom.createElement('a');
+ a.download = `${filename}${ext}`;
+ a.href = href;
+ this._document.body.appendChild(a); // Firefox requires anchor to be in the DOM.
+ a.click();
+
+ // cleanup.
+ this._document.body.removeChild(a);
+ setTimeout(_ => URL.revokeObjectURL(href), 500);
+ }
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = ReportUIFeatures;
+} else {
+ self.ReportUIFeatures = ReportUIFeatures;
+}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js
index 2b1996a1da8..6a752d517f3 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js
@@ -62,6 +62,16 @@ class Util {
if (typeof clone.categories !== 'object') throw new Error('No categories provided.');
clone.reportCategories = Object.values(clone.categories);
+ // Turn 'not-applicable' and 'not_applicable' into 'notApplicable' to support old reports.
+ // TODO: remove when underscore/hyphen proto issue is resolved. See #6371, #6201, #6783.
+ for (const audit of Object.values(clone.audits)) {
+ // @ts-ignore tsc rightly flags that this value shouldn't occur.
+ // eslint-disable-next-line max-len
+ if (audit.scoreDisplayMode === 'not_applicable' || audit.scoreDisplayMode === 'not-applicable') {
+ audit.scoreDisplayMode = 'notApplicable';
+ }
+ }
+
// For convenience, smoosh all AuditResults into their auditDfn (which has just weight & group)
for (const category of clone.reportCategories) {
category.auditRefs.forEach(auditMeta => {
@@ -134,7 +144,7 @@ class Util {
static showAsPassed(audit) {
switch (audit.scoreDisplayMode) {
case 'manual':
- case 'not-applicable':
+ case 'notApplicable':
return true;
case 'error':
case 'informative':
@@ -154,7 +164,7 @@ class Util {
*/
static calculateRating(score, scoreDisplayMode) {
// Handle edge cases first, manual and not applicable receive 'pass', errored audits receive 'error'
- if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'not-applicable') {
+ if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'notApplicable') {
return RATINGS.PASS.label;
} else if (scoreDisplayMode === 'error') {
return RATINGS.ERROR.label;
@@ -398,7 +408,7 @@ class Util {
networkThrottling = `${Util.formatNumber(requestLatencyMs)}${NBSP}ms HTTP RTT, ` +
`${Util.formatNumber(throttling.downloadThroughputKbps)}${NBSP}Kbps down, ` +
`${Util.formatNumber(throttling.uploadThroughputKbps)}${NBSP}Kbps up (DevTools)`;
- summary = 'Throttled Fast 3G network';
+ summary = 'Throttled Slow 4G network';
break;
}
case 'simulate': {
@@ -406,7 +416,7 @@ class Util {
cpuThrottling = `${Util.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
networkThrottling = `${Util.formatNumber(rttMs)}${NBSP}ms TCP RTT, ` +
`${Util.formatNumber(throughputKbps)}${NBSP}Kbps throughput (Simulated)`;
- summary = 'Simulated Fast 3G network';
+ summary = 'Simulated Slow 4G network';
break;
}
default:
@@ -467,6 +477,8 @@ Util.UIStrings = {
warningHeader: 'Warnings: ',
/** The tooltip text on an expandable chevron icon. Clicking the icon expands a section to reveal a list of audit results that was hidden by default. */
auditGroupExpandTooltip: 'Show audits',
+ /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */
+ warningAuditsGroupTitle: 'Passed audits but with warnings',
/** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
passedAuditsGroupTitle: 'Passed audits',
/** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */
@@ -484,8 +496,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 */
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. */
- lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on emulated 3G. Values are estimated and may vary.',
+ /** 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 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. */
labDataTitle: 'Lab Data',
};
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css
index c65e8e5359d..8ece38dc6be 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css
@@ -54,6 +54,9 @@
--metric-timeline-rule-color: #b3b3b3;
--display-value-gray: hsl(216, 5%, 39%);
--report-width: calc(60 * var(--body-font-size));
+ --report-min-width: 400px;
+ /* Edge doesn't support calc(var(calc())) */
+ --report-width-half: calc(30 * var(--body-font-size));
--report-header-height: 161px;
--report-header-color: #202124;
--navitem-font-size: var(--body-font-size);
@@ -62,6 +65,7 @@
--navitem-vpadding: calc(var(--navitem-line-height) / 2);
--lh-score-highlight-bg: hsla(0, 0%, 90%, 0.2);
--lh-score-icon-background-size: 24px;
+ --lh-group-icon-background-size: var(--lh-score-icon-background-size);
--lh-score-margin: 12px;
--lh-table-header-bg: #f8f9fa;
--lh-table-higlight-bg: hsla(0, 0%, 75%, 0.1);
@@ -76,22 +80,30 @@
--lh-audit-group-vpadding: 8px;
--lh-section-vpadding: 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 */
- --bytes-col-width: 1px;
- --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>');
- --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="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>');
- --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>');
- --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>');
- --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>');
+ --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>');
+ --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>');
+ --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>');
+
+ --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>');
+ --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>');
+ --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>');
+ --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>');
--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>');
- --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>');
+ --warning-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="%235E6268" d="M2 42h44L24 4 2 42zm24-6h-4v-4h4v4zm0-8h-4v-8h4v8z"/></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="%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 {
@@ -312,8 +324,18 @@
background-color: #F8F9FA;
}
-.lh-audit-group__summary::-webkit-details-marker,
-.lh-expandable-details__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 */
+/* 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;
}
@@ -360,7 +382,7 @@
.lh-metric__innerwrap {
display: flex;
justify-content: space-between;
- padding: 11px var(--text-indent);
+ padding: 8px var(--text-indent);
}
.lh-metric__details {
@@ -508,7 +530,7 @@
.lh-filmstrip-container {
padding: 0 var(--expandable-indent);
/* smaller gap between metrics and filmstrip */
- margin: -16px auto 0 auto;
+ margin: -8px auto 0 auto;
}
.lh-filmstrip {
@@ -564,7 +586,8 @@
}
.lh-audit-group__header::before {
- content: '';
+ /* By default, groups don't get an icon */
+ content: none;
width: calc(var(--subheader-font-size) / 14 * 24);
height: calc(var(--subheader-font-size) / 14 * 24);
margin-right: calc(var(--subheader-font-size) / 2);
@@ -574,29 +597,59 @@
vertical-align: middle;
}
-/* A11y/Seo groups within Passed don't get an icon */
-.lh-audit-group--unadorned .lh-audit-group__header::before {
- content: none;
+.lh-clump--warning > summary .lh-audit-group__header::before {
+ content: '';
+ background-image: var(--warning-icon-url);
}
-
-
-.lh-audit-group--manual .lh-audit-group__header::before {
+.lh-clump--manual > summary .lh-audit-group__header::before {
+ content: '';
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);
}
+.lh-clump--notapplicable > summary .lh-audit-group__header::before {
+ content: '';
+ background-image: var(--remove-circle-icon-url);
+}
+
.lh-audit-group--diagnostics .lh-audit-group__header::before {
+ content: '';
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);
}
.lh-audit-group--metrics .lh-audit-group__header::before {
+ content: '';
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 */
@@ -604,11 +657,17 @@
margin-top: calc(var(--circle-size)/2 * -1);
border-bottom: none;
}
+.lh-audit-group--metrics .lh-audit-group__summary {
+ margin-top: 0;
+ margin-bottom: 0;
+}
.lh-audit-group__summary {
display: flex;
justify-content: space-between;
padding-right: var(--text-indent);
+ margin-top: calc(var(--section-padding) * 1.5);
+ margin-bottom: var(--section-padding);
}
.lh-audit-group__itemcount {
@@ -622,11 +681,17 @@
.lh-audit-group__description {
font-size: var(--body-font-size);
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 {
- margin-top: 0;
+.lh-clump > .lh-audit-group__description,
+.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 {
@@ -648,9 +713,11 @@
}
.lh-header-sticky {
+ position: -webkit-sticky;
position: sticky;
top: 0;
width: 100%;
+ min-width: var(--report-min-width);
z-index: 2;
will-change: transform;
}
@@ -668,6 +735,7 @@
.lh-report {
background-color: #fff;
+ min-width: var(--report-min-width);
}
@media screen {
.lh-report {
@@ -723,8 +791,13 @@
border: 0;
}
-.lh-scores-header .lh-gauge__wrapper {
- margin: 0 4px;
+.lh-scores-header .lh-gauge__wrapper,
+.lh-scores-header .lh-gauge--pwa__wrapper {
+ padding: 0 4px;
+}
+
+.lh-scores-header .lh-gauge--pwa__wrapper {
+ border-left: 1px solid var(--report-secondary-border-color)
}
.lh-scorescale {
@@ -773,7 +846,6 @@
}
.lh-category {
- --circle-size: calc(2.5 * var(--header-font-size));
padding: var(--section-padding);
}
@@ -820,19 +892,6 @@
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 {
position: fixed;
background-color: #323232;
@@ -864,7 +923,6 @@ summary.lh-passed-audits-summary {
.lh-report {
margin-left: 0;
width: 100%;
- min-width: 400px;
}
}
@@ -895,6 +953,7 @@ summary.lh-passed-audits-summary {
.lh-table thead th {
color: var(--medium-75-gray);
font-weight: normal;
+ word-wrap: normal;
}
.lh-table tbody tr:nth-child(even) {
@@ -918,7 +977,8 @@ summary.lh-passed-audits-summary {
.lh-table-column--bytes,
.lh-table-column--timespanMs,
-.lh-table-column--ms {
+.lh-table-column--ms,
+.lh-table-column--numeric {
text-align: right;
}
@@ -930,18 +990,16 @@ summary.lh-passed-audits-summary {
.lh-table-column--url {
min-width: 250px;
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 + .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 {
- max-width: var(--bytes-col-width);
-}
-
-.lh-table-column--code {
- max-width: var(--url-col-max-width);
+ width: 12%;
}
.lh-text__url {
@@ -975,6 +1033,8 @@ summary.lh-passed-audits-summary {
*/
.lh-chevron {
--chevron-angle: 42deg;
+ /* Edge doesn't support transform: rotate(calc(...)), so we define it here */
+ --chevron-angle-right: -42deg;
width: var(--chevron-size);
height: var(--chevron-size);
margin-top: calc((var(--body-line-height) - 12px) / 2);
@@ -993,19 +1053,19 @@ summary.lh-passed-audits-summary {
transition: transform 300ms, stroke 300ms;
}
-.lh-audit-group > .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 > summary > .lh-audit-group__summary > .lh-chevron .lh-chevron__line-right,
+.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[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 {
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 {
transform: translateY(calc(var(--chevron-size) * -1));
}
@@ -1051,6 +1111,7 @@ summary.lh-passed-audits-summary {
z-index: 1;
will-change: opacity;
right: 0;
+ pointer-events: none;
}
.tooltip::before {
@@ -1068,5 +1129,5 @@ summary.lh-passed-audits-summary {
@keyframes fadeInTooltip {
0% { opacity: 0; }
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; }
}
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html
index 29e93cb1131..1f90384b387 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html
@@ -27,9 +27,9 @@ limitations under the License.
<template id="tmpl-lh-scorescale">
<div class="lh-scorescale">
<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--average">50-89</span>
+ <span class="lh-scorescale-range lh-scorescale-range--fail">0-49</span>
</div>
</template>
@@ -37,8 +37,8 @@ limitations under the License.
<template id="tmpl-lh-chevron">
<svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100">
<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-right" d="M90 50H50" 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>
</g>
</svg>
</template>
@@ -52,16 +52,32 @@ limitations under the License.
</div>
</template>
+<!-- Lighthouse clump -->
+<template id="tmpl-lh-clump">
+ <!-- TODO: group classes shouldn't be reused for clumps. -->
+ <details class="lh-clump lh-audit-group">
+ <summary>
+ <div class="lh-audit-group__summary">
+ <div class="lh-audit-group__header"></div>
+ <div class="lh-audit-group__itemcount"></div>
+ <div class=""></div>
+ </div>
+ </summary>
+ </details>
+</template>
+
<!-- Lighthouse audit -->
<template id="tmpl-lh-audit">
<div class="lh-audit">
<details class="lh-expandable-details">
- <summary class="lh-audit__header lh-expandable-details__summary">
- <span class="lh-audit__index"></span>
- <span class="lh-audit__title"></span>
- <span class="lh-audit__display-text"></span>
- <div class="lh-audit__score-icon"></div>
- <div class="lh-chevron-container"></div>
+ <summary>
+ <div class="lh-audit__header lh-expandable-details__summary">
+ <span class="lh-audit__index"></span>
+ <span class="lh-audit__title"></span>
+ <span class="lh-audit__display-text"></span>
+ <div class="lh-audit__score-icon"></div>
+ <div class="lh-chevron-container"></div>
+ </div>
</summary>
<div class="lh-audit__description"></div>
</details>
@@ -83,21 +99,23 @@ limitations under the License.
<template id="tmpl-lh-opportunity">
<div class="lh-audit lh-audit--load-opportunity">
<details class="lh-expandable-details">
- <summary class="lh-audit__header lh-expandable-details__summary">
- <div class="lh-load-opportunity__cols">
- <div class="lh-load-opportunity__col lh-load-opportunity__col--one">
- <span class="lh-audit__index"></span>
- <div class="lh-audit__title"></div>
- </div>
- <div class="lh-load-opportunity__col lh-load-opportunity__col--two">
- <div class="lh-load-opportunity__sparkline">
- <div class="lh-sparkline"><div class="lh-sparkline__bar"></div></div>
+ <summary>
+ <div class="lh-audit__header lh-expandable-details__summary">
+ <div class="lh-load-opportunity__cols">
+ <div class="lh-load-opportunity__col lh-load-opportunity__col--one">
+ <span class="lh-audit__index"></span>
+ <div class="lh-audit__title"></div>
+ </div>
+ <div class="lh-load-opportunity__col lh-load-opportunity__col--two">
+ <div class="lh-load-opportunity__sparkline">
+ <div class="lh-sparkline"><div class="lh-sparkline__bar"></div></div>
+ </div>
+ <div class="lh-audit__display-text"></div>
+ <div class="lh-chevron-container" title="See resources"></div>
+ </div>
</div>
- <div class="lh-audit__display-text"></div>
- <div class="lh-chevron-container" title="See resources"></div>
</div>
- </div>
- </summary>
+ </summary>
<div class="lh-audit__description"></div>
</details>
</div>
@@ -159,12 +177,12 @@ limitations under the License.
top: 0;
width: 100%;
will-change: transform;
+ contain: strict;
}
.lh-lighthouse {
position: absolute;
- top: var(--report-header-height);
- right: 50%;
- transform: translate3d(calc(var(--report-width) / 2), -100%, 0);
+ bottom: -4px;
+ right: 0;
opacity: 1;
transform-origin: bottom right;
will-change: transform, opacity;
@@ -183,6 +201,7 @@ limitations under the License.
color: var(--report-header-color);
z-index: 1;
position: relative;
+ max-width: 60%;
}
.lh-metadata__results {
text-overflow: ellipsis;
@@ -193,7 +212,6 @@ limitations under the License.
}
.lh-scores-wrapper {
margin-top: -30px;
- transform: translateZ(1px);
}
.lh-scores-wrapper__shadow {
opacity: 0;
@@ -349,37 +367,124 @@ limitations under the License.
77.0001% { transform: translateX(195px); }
100% { transform: translateX(0px); }
} */
+
+ .score100 .lh-header-bg {
+ background-color: hsl(234, 64%, 19%);
+ }
+ .score100 .lh-metadata, .score100 .lh-toolbar__metadata, .score100 .lh-product-info {
+ color: #fff;
+ }
+ .score100 .lh-config {
+ color: #eee;
+ }
+
+ /* CSS Fireworks. Originally by Eddie Lin
+ https://codepen.io/paulirish/pen/yEVMbP
+ */
+ .pyro {
+ display: none;
+ }
+ .score100 .pyro {
+ display: block;
+ }
+ .score100 .lh-lighthouse stop:first-child {
+ stop-color: hsla(200, 12%, 95%, 0);
+ }
+ .score100 .lh-lighthouse stop:last-child {
+ stop-color: hsla(65, 81%, 76%, 1);
+ }
+
+ .pyro > .before, .pyro > .after {
+ position: absolute;
+ width: 5px;
+ height: 5px;
+ border-radius: 2.5px;
+ box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff;
+ animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards;
+ animation-delay: 1s, 1s, 1s;
+ }
+
+ .pyro > .after {
+ animation-delay: 2.25s, 2.25s, 2.25s;
+ animation-duration: 1.25s, 1.25s, 6.25s;
+ }
+ .fireworks-paused .pyro > div {
+ animation-play-state: paused;
+ }
+
+ @keyframes bang {
+ to {
+ box-shadow: -70px -115.67px #47ebbc, -28px -99.67px #eb47a4, 58px -31.67px #7eeb47, 13px -141.67px #eb47c5, -19px 6.33px #7347eb, -2px -74.67px #ebd247, 24px -151.67px #eb47e0, 57px -138.67px #b4eb47, -51px -104.67px #479eeb, 62px 8.33px #ebcf47, -93px 0.33px #d547eb, -16px -118.67px #47bfeb, 53px -84.67px #47eb83, 66px -57.67px #eb47bf, -93px -65.67px #91eb47, 30px -13.67px #86eb47, -2px -59.67px #83eb47, -44px 1.33px #eb47eb, 61px -58.67px #47eb73, 5px -22.67px #47e8eb, -66px -28.67px #ebe247, 42px -123.67px #eb5547, -75px 26.33px #7beb47, 15px -52.67px #a147eb, 36px -51.67px #eb8347, -38px -12.67px #eb5547, -46px -59.67px #47eb81, 78px -114.67px #eb47ba, 15px -156.67px #eb47bf, -36px 1.33px #eb4783, -72px -86.67px #eba147, 31px -46.67px #ebe247, -68px 29.33px #47e2eb, -55px 19.33px #ebe047, -56px 27.33px #4776eb, -13px -91.67px #eb5547, -47px -138.67px #47ebc7, -18px -96.67px #eb47ac, 11px -88.67px #4783eb, -67px -28.67px #47baeb, 53px 10.33px #ba47eb, 11px 19.33px #5247eb, -5px -11.67px #eb4791, -68px -4.67px #47eba7, 95px -37.67px #eb478b, -67px -162.67px #eb5d47, -54px -120.67px #eb6847, 49px -12.67px #ebe047, 88px 8.33px #47ebda, 97px 33.33px #eb8147, 6px -71.67px #ebbc47;
+ }
+ }
+ @keyframes gravity {
+ to {
+ transform: translateY(80px);
+ opacity: 0;
+ }
+ }
+ @keyframes position {
+ 0%, 19.9% {
+ margin-top: 4%;
+ margin-left: 47%;
+ }
+ 20%, 39.9% {
+ margin-top: 7%;
+ margin-left: 30%;
+ }
+ 40%, 59.9% {
+ margin-top: 6%;
+ margin-left: 70%;
+ }
+ 60%, 79.9% {
+ margin-top: 3%;
+ margin-left: 20%;
+ }
+ 80%, 99.9% {
+ margin-top: 3%;
+ margin-left: 80%;
+ }
+ }
</style>
- <div class="lh-header-bg"></div>
- <div class="lh-lighthouse">
- <svg width="217" height="148" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <mask id="a" x="-56" y="-54" width="284" height="202" maskUnits="userSpaceOnUse">
- <path d="M-56-54h284v202H-56z" fill="#fff"/>
- </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">
- <stop offset="0" stop-color="#f1f3f4"/>
- <stop offset="1" stop-color="#fff"/>
- </linearGradient>
- </defs>
- <g mask="url(#a)">
- <path d="M95 47h24v2H95z" fill="#ec5548"/>
- <path d="M98 49h18v11H98z" fill="#fbc21b"/>
- <path d="M95 59h24v7H95z" fill="#ec5548"/>
- <path d="M97.63 66h19.74l2.63 47H95z" fill="#fff"/>
- <path d="M107 38a10 10 0 0 1 10 10v1H97v-1a10 10 0 0 1 10-10zM96.77 82.23l21-10.7.63 11.87-22.31 11.87zM95 110.8L119.1 98l.9 14H95z" fill="#ec5548"/>
- <path d="M0 148a177.58 177.58 0 0 1 217 0z" fill="#e8eaed"/>
- <path d="M103 49a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5z" fill="#fef0c8"/>
- <path d="M7 16l91 33.18v10L7 93z" fill="url(#b)"/>
- </g>
- <g mask="url(#a)" class="lh-lighthouse__clouds">
- <path d="M60 .19A9.77 9.77 0 0 1 61.93 0a9.44 9.44 0 0 1 9.24 7.83A7.24 7.24 0 0 1 79 14.45v.73A7.37 7.37 0 0 1 76.2 21h-31a7.44 7.44 0 0 1-1.2-4.09 7.31 7.31 0 0 1 7.26-7.36 6.84 6.84 0 0 1 1.28.1v-.11A9.51 9.51 0 0 1 60 .19m79.78 22.31h-17.9a4.37 4.37 0 0 1-.63-2.25 4.2 4.2 0 0 1 4.16-4.25 4.37 4.37 0 0 1 .72.06V16a5.35 5.35 0 0 1 10.64-1h.33a4.2 4.2 0 0 1 4.15 4.25 4.29 4.29 0 0 1-1.47 3.25zM163 62h-24.15a5.1 5.1 0 0 1-.85-2.81 5.65 5.65 0 0 1 6.59-5.19v-.08a7.07 7.07 0 0 1 7.24-6.92 7.15 7.15 0 0 1 7.17 5.64h.44a5.46 5.46 0 0 1 5.6 5.32A5.19 5.19 0 0 1 163 62z" fill="#fff"/>
- </g>
- </svg>
+ <div class="lh-header-bg">
+ <div class="pyro">
+ <div class="before"></div>
+ <div class="after"></div>
+ </div>
</div>
+
<div class="lh-header-container">
<div class="lh-header">
+ <div class="lh-lighthouse">
+ <svg width="217" height="148" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <mask id="a" x="-56" y="-54" width="284" height="202" maskUnits="userSpaceOnUse">
+ <path d="M-56-54h284v202H-56z" fill="#fff"></path>
+ </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">
+ <stop offset="0" stop-color="#FFFFFF00"/>
+ <stop offset="1" stop-color="#FFFFFF"/>
+ </linearGradient>
+ </defs>
+ <g mask="url(#a)">
+ <!-- lighthouse building elements -->
+ <path d="M95 47h24v2H95z" fill="#ec5548"/>
+ <path d="M95 59h24v7H95z" fill="#ec5548"/>
+ <path d="M97.63 66h19.74l2.63 47H95z" fill="#fff"/>
+ <path d="M107 38a10 10 0 0 1 10 10v1H97v-1a10 10 0 0 1 10-10zM96.77 82.23l21-10.7.63 11.87-22.31 11.87zM95 110.8L119.1 98l.9 14H95z" fill="#ec5548"/>
+ <path d="M0 148a177.58 177.58 0 0 1 217 0z" fill="#e8eaed"/>
+ <!-- lighthouse beam elements -->
+ <path d="M98 49h18v11H98z" fill="#fbc21b"/>
+ <path d="M103 49a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5z" fill="#fef0c8"/>
+ <path d="M7 16l91 33.18v10L7 93z" fill="url(#b)"/>
+ </g>
+ <g mask="url(#a)" class="lh-lighthouse__clouds">
+ <path d="M60 .19A9.77 9.77 0 0 1 61.93 0a9.44 9.44 0 0 1 9.24 7.83A7.24 7.24 0 0 1 79 14.45v.73A7.37 7.37 0 0 1 76.2 21h-31a7.44 7.44 0 0 1-1.2-4.09 7.31 7.31 0 0 1 7.26-7.36 6.84 6.84 0 0 1 1.28.1v-.11A9.51 9.51 0 0 1 60 .19m79.78 22.31h-17.9a4.37 4.37 0 0 1-.63-2.25 4.2 4.2 0 0 1 4.16-4.25 4.37 4.37 0 0 1 .72.06V16a5.35 5.35 0 0 1 10.64-1h.33a4.2 4.2 0 0 1 4.15 4.25 4.29 4.29 0 0 1-1.47 3.25zM163 62h-24.15a5.1 5.1 0 0 1-.85-2.81 5.65 5.65 0 0 1 6.59-5.19v-.08a7.07 7.07 0 0 1 7.24-6.92 7.15 7.15 0 0 1 7.17 5.64h.44a5.46 5.46 0 0 1 5.6 5.32A5.19 5.19 0 0 1 163 62z" fill="#fff"/>
+ </g>
+ </svg>
+ </div>
+
<div class="lh-metadata">
<div class="lh-metadata__results"><a href="" class="lh-metadata__url" target="_blank" rel="noopener"></a></div>
<div class="lh-config">
@@ -474,7 +579,6 @@ limitations under the License.
<template id="tmpl-lh-gauge">
<style>
.lh-gauge__wrapper {
- --circle-size: calc(3 * var(--header-font-size));
--circle-size-half: calc(var(--circle-size) / 2);
--circle-background: hsl(216, 12%, 92%);
--circle-border-width: 9;
@@ -570,6 +674,147 @@ limitations under the License.
</a>
</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 -->
<template id="tmpl-lh-crc">
<div class="lh-crc-container">
diff --git a/chromium/third_party/blink/renderer/devtools/front_end/audits2/module.json b/chromium/third_party/blink/renderer/devtools/front_end/audits2/module.json
index b1f07d06d19..61b44db117d 100644
--- a/chromium/third_party/blink/renderer/devtools/front_end/audits2/module.json
+++ b/chromium/third_party/blink/renderer/devtools/front_end/audits2/module.json
@@ -24,6 +24,7 @@
"lighthouse/renderer/dom.js",
"lighthouse/renderer/category-renderer.js",
"lighthouse/renderer/performance-category-renderer.js",
+ "lighthouse/renderer/pwa-category-renderer.js",
"lighthouse/renderer/details-renderer.js",
"lighthouse/renderer/crc-details-renderer.js",
"lighthouse/renderer/report-renderer.js",
@@ -48,6 +49,7 @@
"lighthouse/renderer/dom.js",
"lighthouse/renderer/category-renderer.js",
"lighthouse/renderer/performance-category-renderer.js",
+ "lighthouse/renderer/pwa-category-renderer.js",
"lighthouse/renderer/details-renderer.js",
"lighthouse/renderer/crc-details-renderer.js",
"lighthouse/renderer/report-renderer.js"