Skip to content

Commit

Permalink
core: add largest contentful paint to lantern and default config (#9905)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark authored Nov 10, 2019
1 parent 93560f4 commit 7c5d86d
Show file tree
Hide file tree
Showing 22 changed files with 414 additions and 3,798 deletions.
8 changes: 8 additions & 0 deletions lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Object {
Object {
"path": "metrics/first-meaningful-paint",
},
Object {
"path": "metrics/largest-contentful-paint",
},
Object {
"path": "load-fast-enough-for-pwa",
},
Expand Down Expand Up @@ -744,6 +747,11 @@ Object {
"id": "first-meaningful-paint",
"weight": 1,
},
Object {
"group": "metrics",
"id": "largest-contentful-paint",
"weight": 0,
},
Object {
"group": "metrics",
"id": "speed-index",
Expand Down
71 changes: 71 additions & 0 deletions lighthouse-core/audits/metrics/largest-contentful-paint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* @license Copyright 2019 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';

const Audit = require('../audit.js');
const i18n = require('../../lib/i18n/i18n.js');
const ComputedLcp = require('../../computed/metrics/largest-contentful-paint.js');

const UIStrings = {
/** The name of the metric that marks the time at which the largest text or image is painted by the browser. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit. */
title: 'Largest Contentful Paint',
/** Description of the Largest Contentful Paint (LCP) metric, which marks the time at which the largest text or image is painted by the browser. This is displayed within a tooltip when the user hovers on the metric name to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
description: 'Largest Contentful Paint marks the time at which the largest text or image is ' +
`painted. [Learn More](https://web.dev/largest-contentful-paint)`, // TODO: waiting on LH specific doc.
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

class LargestContentfulPaint extends Audit {
/**
* @return {LH.Audit.Meta}
*/
static get meta() {
return {
id: 'largest-contentful-paint',
title: str_(UIStrings.title),
description: str_(UIStrings.description),
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
requiredArtifacts: ['traces', 'devtoolsLogs'],
};
}

/**
* @return {LH.Audit.ScoreOptions}
*/
static get defaultOptions() {
return {
// TODO: Reusing FCP's scoring curve. Set correctly once distribution of results is available.
scorePODR: 2000,
scoreMedian: 4000,
};
}

/**
* @param {LH.Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const metricComputationData = {trace, devtoolsLog, settings: context.settings};
const metricResult = await ComputedLcp.request(metricComputationData, context);

return {
score: Audit.computeLogNormalScore(
metricResult.timing,
context.options.scorePODR,
context.options.scoreMedian
),
numericValue: metricResult.timing,
displayValue: str_(i18n.UIStrings.seconds, {timeInMs: metricResult.timing}),
};
}
}

module.exports = LargestContentfulPaint;
module.exports.UIStrings = UIStrings;
7 changes: 7 additions & 0 deletions lighthouse-core/audits/predictive-perf.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const LanternInteractive = require('../computed/metrics/lantern-interactive.js')
const LanternFirstCPUIdle = require('../computed/metrics/lantern-first-cpu-idle.js');
const LanternSpeedIndex = require('../computed/metrics/lantern-speed-index.js');
const LanternEil = require('../computed/metrics/lantern-estimated-input-latency.js');
// TODO: we don't have LCP in the lantern test yet. https://github.com/GoogleChrome/lighthouse/issues/9953
// const LanternLcp = require('../computed/metrics/lantern-largest-contentful-paint.js');

// Parameters (in ms) for log-normal CDF scoring. To see the curve:
// https://www.desmos.com/calculator/rjp0lbit8y
Expand Down Expand Up @@ -53,6 +55,7 @@ class PredictivePerf extends Audit {
const ttfcpui = await LanternFirstCPUIdle.request({trace, devtoolsLog, settings}, context);
const si = await LanternSpeedIndex.request({trace, devtoolsLog, settings}, context);
const eil = await LanternEil.request({trace, devtoolsLog, settings}, context);
// const lcp = await LanternLcp.request({trace, devtoolsLog, settings}, context);

const values = {
roughEstimateOfFCP: fcp.timing,
Expand All @@ -78,6 +81,10 @@ class PredictivePerf extends Audit {
roughEstimateOfEIL: eil.timing,
optimisticEIL: eil.optimisticEstimate.timeInMs,
pessimisticEIL: eil.pessimisticEstimate.timeInMs,

// roughEstimateOfLCP: lcp.timing,
// optimisticLCP: lcp.optimisticEstimate.timeInMs,
// pessimisticLCP: lcp.pessimisticEstimate.timeInMs,
};

const score = Audit.computeLogNormalScore(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* @license Copyright 2019 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';

const makeComputedArtifact = require('../computed-artifact.js');
const LanternMetric = require('./lantern-metric.js');
const LHError = require('../../lib/lh-error.js');
const LanternFirstContentfulPaint = require('./lantern-first-contentful-paint.js');

/** @typedef {import('../../lib/dependency-graph/base-node.js').Node} Node */

class LanternLargestContentfulPaint extends LanternMetric {
/**
* @return {LH.Gatherer.Simulation.MetricCoefficients}
*/
static get COEFFICIENTS() {
// TODO: Calibrate
return {
intercept: 0,
optimistic: 0.5,
pessimistic: 0.5,
};
}

/**
* TODO: Validate.
* @param {Node} dependencyGraph
* @param {LH.Artifacts.TraceOfTab} traceOfTab
* @return {Node}
*/
static getOptimisticGraph(dependencyGraph, traceOfTab) {
const lcp = traceOfTab.timestamps.largestContentfulPaint;
if (!lcp) {
throw new LHError(LHError.errors.NO_LCP);
}

return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
dependencyGraph,
lcp,
_ => true
);
}

/**
* TODO: Validate.
* @param {Node} dependencyGraph
* @param {LH.Artifacts.TraceOfTab} traceOfTab
* @return {Node}
*/
static getPessimisticGraph(dependencyGraph, traceOfTab) {
const lcp = traceOfTab.timestamps.largestContentfulPaint;
if (!lcp) {
throw new LHError(LHError.errors.NO_LCP);
}

return LanternFirstContentfulPaint.getFirstPaintBasedGraph(
dependencyGraph,
lcp,
_ => true,
// For pessimistic LCP we'll include *all* layout nodes
node => node.didPerformLayout()
);
}

/**
* @param {LH.Artifacts.MetricComputationDataInput} data
* @param {LH.Audit.Context} context
* @return {Promise<LH.Artifacts.LanternMetric>}
*/
static async compute_(data, context) {
const fcpResult = await LanternFirstContentfulPaint.request(data, context);
const metricResult = await this.computeMetricWithGraphs(data, context);
metricResult.timing = Math.max(metricResult.timing, fcpResult.timing);
return metricResult;
}
}

module.exports = makeComputedArtifact(LanternLargestContentfulPaint);
13 changes: 7 additions & 6 deletions lighthouse-core/computed/metrics/largest-contentful-paint.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@
*/
'use strict';

const makeComputedArtifact = require('../computed-artifact.js');
const ComputedMetric = require('./metric.js');
const LHError = require('../../lib/lh-error.js');

/**
* @fileoverview Computed Largest Contentful Paint (LCP), the paint time of the largest in-viewport contentful element
* COMPAT: LCP's trace event was first introduced in m78. We can't surface an LCP for older Chrome versions
* @see https://github.com/WICG/largest-contentful-paint
* @see https://wicg.github.io/largest-contentful-paint/
* @see https://web.dev/largest-contentful-paint
*/

const makeComputedArtifact = require('../computed-artifact.js');
const ComputedMetric = require('./metric.js');
const LHError = require('../../lib/lh-error.js');
const LanternLargestContentfulPaint = require('./lantern-largest-contentful-paint.js');

class LargestContentfulPaint extends ComputedMetric {
/**
* @param {LH.Artifacts.MetricComputationData} data
* @param {LH.Audit.Context} context
* @return {Promise<LH.Artifacts.LanternMetric>}
*/
// eslint-disable-next-line no-unused-vars
static computeSimulatedMetric(data, context) {
throw new Error('Unimplemented');
return LanternLargestContentfulPaint.request(data, context);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ const defaultConfig = {
'without-javascript',
'metrics/first-contentful-paint',
'metrics/first-meaningful-paint',
'metrics/largest-contentful-paint',
'load-fast-enough-for-pwa',
'metrics/speed-index',
'screenshot-thumbnails',
Expand Down Expand Up @@ -383,6 +384,7 @@ const defaultConfig = {
auditRefs: [
{id: 'first-contentful-paint', weight: 3, group: 'metrics'},
{id: 'first-meaningful-paint', weight: 1, group: 'metrics'},
{id: 'largest-contentful-paint', weight: 0, group: 'metrics'},
{id: 'speed-index', weight: 4, group: 'metrics'},
{id: 'interactive', weight: 5, group: 'metrics'},
{id: 'first-cpu-idle', weight: 2, group: 'metrics'},
Expand Down
6 changes: 6 additions & 0 deletions lighthouse-core/lib/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,12 @@
"lighthouse-core/audits/metrics/interactive.js | description": {
"message": "Time to interactive is the amount of time it takes for the page to become fully interactive. [Learn more](https://web.dev/interactive)."
},
"lighthouse-core/audits/metrics/largest-contentful-paint.js | description": {
"message": "Largest Contentful Paint marks the time at which the largest text or image is painted. [Learn More](https://web.dev/largest-contentful-paint)"
},
"lighthouse-core/audits/metrics/largest-contentful-paint.js | title": {
"message": "Largest Contentful Paint"
},
"lighthouse-core/audits/metrics/max-potential-fid.js | description": {
"message": "The maximum potential First Input Delay that your users could experience is the duration, in milliseconds, of the longest task. [Learn more](https://web.dev/lighthouse-max-potential-fid)."
},
Expand Down
6 changes: 6 additions & 0 deletions lighthouse-core/lib/i18n/locales/en-XL.json
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,12 @@
"lighthouse-core/audits/metrics/interactive.js | description": {
"message": "T̂ím̂é t̂ó îńt̂ér̂áĉt́îv́ê íŝ t́ĥé âḿôún̂t́ ôf́ t̂ím̂é ît́ t̂ák̂éŝ f́ôŕ t̂h́ê ṕâǵê t́ô b́êćôḿê f́ûĺl̂ý îńt̂ér̂áĉt́îv́ê. [Ĺêár̂ń m̂ór̂é](https://web.dev/interactive)."
},
"lighthouse-core/audits/metrics/largest-contentful-paint.js | description": {
"message": "L̂ár̂ǵêśt̂ Ćôńt̂én̂t́f̂úl̂ Ṕâín̂t́ m̂ár̂ḱŝ t́ĥé t̂ím̂é ât́ ŵh́îćĥ t́ĥé l̂ár̂ǵêśt̂ t́êx́t̂ ór̂ ím̂áĝé îś p̂áîńt̂éd̂. [Ĺêár̂ń M̂ór̂é](https://web.dev/largest-contentful-paint)"
},
"lighthouse-core/audits/metrics/largest-contentful-paint.js | title": {
"message": "L̂ár̂ǵêśt̂ Ćôńt̂én̂t́f̂úl̂ Ṕâín̂t́"
},
"lighthouse-core/audits/metrics/max-potential-fid.js | description": {
"message": "T̂h́ê ḿâx́îḿûḿ p̂ót̂én̂t́îál̂ F́îŕŝt́ Îńp̂út̂ D́êĺâý t̂h́ât́ ŷóûŕ ûśêŕŝ ćôúl̂d́ êx́p̂ér̂íêńĉé îś t̂h́ê d́ûŕât́îón̂, ín̂ ḿîĺl̂íŝéĉón̂d́ŝ, óf̂ t́ĥé l̂ón̂ǵêśt̂ t́âśk̂. [Ĺêár̂ń m̂ór̂é](https://web.dev/lighthouse-max-potential-fid)."
},
Expand Down
13 changes: 13 additions & 0 deletions lighthouse-core/scripts/lantern/print-correlations.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* @property {number} [timeToFirstInteractive]
* @property {number} [timeToConsistentlyInteractive]
* @property {number} [speedIndex]
* @property {number} [largestContentfulPaint]
*/

/**
Expand All @@ -51,16 +52,19 @@
* @property {number} optimisticSI
* @property {number} optimisticTTFCPUI
* @property {number} optimisticTTI
* @property {number} optimisticLCP
* @property {number} pessimisticFCP
* @property {number} pessimisticFMP
* @property {number} pessimisticSI
* @property {number} pessimisticTTFCPUI
* @property {number} pessimisticTTI
* @property {number} pessimisticLCP
* @property {number} roughEstimateOfFCP
* @property {number} roughEstimateOfFMP
* @property {number} roughEstimateOfSI
* @property {number} roughEstimateOfTTFCPUI
* @property {number} roughEstimateOfTTI
* @property {number} roughEstimateOfLCP
*/

const fs = require('fs');
Expand Down Expand Up @@ -351,6 +355,10 @@ evaluateAndPrintAccuracy('speedIndex', 'optimisticSI');
evaluateAndPrintAccuracy('speedIndex', 'pessimisticSI');
evaluateAndPrintAccuracy('speedIndex', 'roughEstimateOfSI');

evaluateAndPrintAccuracy('largestContentfulPaint', 'optimisticLCP');
evaluateAndPrintAccuracy('largestContentfulPaint', 'pessimisticLCP');
evaluateAndPrintAccuracy('largestContentfulPaint', 'roughEstimateOfLCP');

const estimates = allEvaluations.filter(entry => entry.lanternMetric.includes('roughEstimate'));
const baselineEstimates = baselineEvaluations.filter(entry =>
entry.lanternMetric.includes('roughEstimate')
Expand All @@ -377,6 +385,11 @@ findAndPrintWorst10Sites('timeToConsistentlyInteractive', [
'roughEstimateOfTTI',
]);
findAndPrintWorst10Sites('speedIndex', ['optimisticSI', 'pessimisticSI', 'roughEstimateOfSI']);
findAndPrintWorst10Sites('largestContentfulPaint', [
'optimisticLCP',
'pessimisticLCP',
'roughEstimateOfLCP',
]);

findAndPrintFixesRegressions();

Expand Down
17 changes: 10 additions & 7 deletions lighthouse-core/scripts/lantern/run-on-all-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ for (const site of expectations.sites) {
const log = path.join(SITE_INDEX_DIR, site.unthrottled.devtoolsLogPath);

console.log('Running', site.url, '...');
const rawOutput = execFileSync(RUN_ONCE_PATH, [trace, log])
.toString()
.trim();
if (!rawOutput) console.log('ERROR EMPTY OUTPUT!');
const lantern = JSON.parse(rawOutput);

Object.assign(site, {lantern});
try {
const rawOutput = execFileSync(RUN_ONCE_PATH, [trace, log])
.toString()
.trim();
if (!rawOutput) console.log('ERROR EMPTY OUTPUT!');
const lantern = JSON.parse(rawOutput);
Object.assign(site, {lantern});
} catch (e) {
console.error(e);
}
}

// eslint-disable-next-line max-len
Expand Down
Loading

0 comments on commit 7c5d86d

Please sign in to comment.