diff --git a/lighthouse-cli/test/fixtures/perf/frame-metrics-inner.html b/lighthouse-cli/test/fixtures/perf/frame-metrics-inner.html
new file mode 100644
index 000000000000..6dd5f50f30b4
--- /dev/null
+++ b/lighthouse-cli/test/fixtures/perf/frame-metrics-inner.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ THIS IS THE INNER FRAME LCP AND FCP
+
+
+
diff --git a/lighthouse-cli/test/fixtures/perf/frame-metrics.html b/lighthouse-cli/test/fixtures/perf/frame-metrics.html
new file mode 100644
index 000000000000..1b4b17a2d8dd
--- /dev/null
+++ b/lighthouse-cli/test/fixtures/perf/frame-metrics.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lighthouse-cli/test/smokehouse/test-definitions/perf/expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/perf/expectations.js
index 83244dcb2a41..9ce9d9ba5397 100644
--- a/lighthouse-cli/test/smokehouse/test-definitions/perf/expectations.js
+++ b/lighthouse-cli/test/smokehouse/test-definitions/perf/expectations.js
@@ -323,4 +323,31 @@ module.exports = [
},
},
},
+ {
+ lhr: {
+ requestedUrl: 'http://localhost:10200/perf/frame-metrics.html',
+ finalUrl: 'http://localhost:10200/perf/frame-metrics.html',
+ audits: {
+ 'metrics': {
+ score: null,
+ details: {
+ type: 'debugdata',
+ items: [
+ {
+ firstContentfulPaint: '>5000',
+ firstContentfulPaintAllFrames: '<5000',
+ largestContentfulPaint: '>5000',
+ largestContentfulPaintAllFrames: '<5000',
+ cumulativeLayoutShift: '0.001 +/- 0.0005',
+ cumulativeLayoutShiftAllFrames: '0.068 +/- 0.0005',
+ },
+ {
+ lcpInvalidated: false,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
];
diff --git a/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js b/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js
new file mode 100644
index 000000000000..6abc243d811d
--- /dev/null
+++ b/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js
@@ -0,0 +1,34 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. 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 ComputedMetric = require('./metric.js');
+
+class FirstContentfulPaintAllFrames extends ComputedMetric {
+ /**
+ * @return {Promise}
+ */
+ static computeSimulatedMetric() {
+ // TODO: Add support for all frames in lantern.
+ throw new Error('FCP All Frames not implemented in lantern');
+ }
+
+ /**
+ * @param {LH.Artifacts.MetricComputationData} data
+ * @return {Promise}
+ */
+ static async computeObservedMetric(data) {
+ const {traceOfTab} = data;
+
+ return {
+ timing: traceOfTab.timings.firstContentfulPaintAllFrames,
+ timestamp: traceOfTab.timestamps.firstContentfulPaintAllFrames,
+ };
+ }
+}
+
+module.exports = makeComputedArtifact(FirstContentfulPaintAllFrames);
diff --git a/lighthouse-core/computed/metrics/timing-summary.js b/lighthouse-core/computed/metrics/timing-summary.js
index f796abd53816..85f0f1f8b0ef 100644
--- a/lighthouse-core/computed/metrics/timing-summary.js
+++ b/lighthouse-core/computed/metrics/timing-summary.js
@@ -8,6 +8,7 @@
const TraceOfTab = require('../trace-of-tab.js');
const Speedline = require('../speedline.js');
const FirstContentfulPaint = require('./first-contentful-paint.js');
+const FirstContentfulPaintAllFrames = require('./first-contentful-paint-all-frames.js');
const FirstMeaningfulPaint = require('./first-meaningful-paint.js');
const LargestContentfulPaint = require('./largest-contentful-paint.js');
const LargestContentfulPaintAllFrames = require('./largest-contentful-paint-all-frames.js');
@@ -44,6 +45,7 @@ class TimingSummary {
const traceOfTab = await TraceOfTab.request(trace, context);
const speedline = await Speedline.request(trace, context);
const firstContentfulPaint = await FirstContentfulPaint.request(metricComputationData, context);
+ const firstContentfulPaintAllFrames = await requestOrUndefined(FirstContentfulPaintAllFrames, metricComputationData); // eslint-disable-line max-len
const firstMeaningfulPaint = await FirstMeaningfulPaint.request(metricComputationData, context);
const largestContentfulPaint = await requestOrUndefined(LargestContentfulPaint, metricComputationData); // eslint-disable-line max-len
const largestContentfulPaintAllFrames = await requestOrUndefined(LargestContentfulPaintAllFrames, metricComputationData); // eslint-disable-line max-len
@@ -67,6 +69,8 @@ class TimingSummary {
// Include the simulated/observed performance metrics
firstContentfulPaint: firstContentfulPaint.timing,
firstContentfulPaintTs: firstContentfulPaint.timestamp,
+ firstContentfulPaintAllFrames: firstContentfulPaintAllFrames && firstContentfulPaintAllFrames.timing, // eslint-disable-line max-len
+ firstContentfulPaintAllFramesTs: firstContentfulPaintAllFrames && firstContentfulPaintAllFrames.timestamp, // eslint-disable-line max-len
firstMeaningfulPaint: firstMeaningfulPaint.timing,
firstMeaningfulPaintTs: firstMeaningfulPaint.timestamp,
largestContentfulPaint: largestContentfulPaint && largestContentfulPaint.timing,
@@ -97,6 +101,8 @@ class TimingSummary {
observedFirstPaintTs: traceOfTab.timestamps.firstPaint,
observedFirstContentfulPaint: traceOfTab.timings.firstContentfulPaint,
observedFirstContentfulPaintTs: traceOfTab.timestamps.firstContentfulPaint,
+ observedFirstContentfulPaintAllFrames: traceOfTab.timings.firstContentfulPaintAllFrames,
+ observedFirstContentfulPaintAllFramesTs: traceOfTab.timestamps.firstContentfulPaintAllFrames,
observedFirstMeaningfulPaint: traceOfTab.timings.firstMeaningfulPaint,
observedFirstMeaningfulPaintTs: traceOfTab.timestamps.firstMeaningfulPaint,
observedLargestContentfulPaint: traceOfTab.timings.largestContentfulPaint,
diff --git a/lighthouse-core/computed/trace-of-tab.js b/lighthouse-core/computed/trace-of-tab.js
index eb63b974292b..4f30851c1566 100644
--- a/lighthouse-core/computed/trace-of-tab.js
+++ b/lighthouse-core/computed/trace-of-tab.js
@@ -50,13 +50,30 @@ class TraceOfTab {
// We'll check that we got an FCP here and re-type accordingly so all of our consumers don't
// have to repeat this check.
const traceOfTab = await LHTraceProcessor.computeTraceOfTab(trace);
- const {timings, timestamps, firstContentfulPaintEvt} = traceOfTab;
- const {firstContentfulPaint: firstContentfulPaintTiming} = timings;
- const {firstContentfulPaint: firstContentfulPaintTs} = timestamps;
+ const {
+ timings,
+ timestamps,
+ firstContentfulPaintEvt,
+ firstContentfulPaintAllFramesEvt,
+ } = traceOfTab;
+ const {
+ firstContentfulPaint: firstContentfulPaintTiming,
+ firstContentfulPaintAllFrames: firstContentfulPaintAllFramesTiming,
+ } = timings;
+ const {
+ firstContentfulPaint: firstContentfulPaintTs,
+ firstContentfulPaintAllFrames: firstContentfulPaintAllFramesTs,
+ } = timestamps;
+
if (
!firstContentfulPaintEvt ||
firstContentfulPaintTiming === undefined ||
- firstContentfulPaintTs === undefined
+ firstContentfulPaintTs === undefined ||
+ // FCP-AF will only be undefined if FCP is also undefined.
+ // These conditions are for enforcing types and should never actually trigger.
+ !firstContentfulPaintAllFramesEvt ||
+ firstContentfulPaintAllFramesTiming === undefined ||
+ firstContentfulPaintAllFramesTs === undefined
) {
throw new LHError(LHError.errors.NO_FCP);
}
@@ -66,8 +83,17 @@ class TraceOfTab {
return {
...traceOfTab,
firstContentfulPaintEvt,
- timings: {...timings, firstContentfulPaint: firstContentfulPaintTiming},
- timestamps: {...timestamps, firstContentfulPaint: firstContentfulPaintTs},
+ firstContentfulPaintAllFramesEvt,
+ timings: {
+ ...timings,
+ firstContentfulPaint: firstContentfulPaintTiming,
+ firstContentfulPaintAllFrames: firstContentfulPaintAllFramesTiming,
+ },
+ timestamps: {
+ ...timestamps,
+ firstContentfulPaint: firstContentfulPaintTs,
+ firstContentfulPaintAllFrames: firstContentfulPaintAllFramesTs,
+ },
};
}
}
diff --git a/lighthouse-core/lib/tracehouse/trace-processor.js b/lighthouse-core/lib/tracehouse/trace-processor.js
index aeb4d153c2b3..393d651d696d 100644
--- a/lighthouse-core/lib/tracehouse/trace-processor.js
+++ b/lighthouse-core/lib/tracehouse/trace-processor.js
@@ -16,9 +16,9 @@
* 4. Return all those items in one handy bundle.
*/
-/** @typedef {Omit & {firstContentfulPaint?: number}} TraceTimesWithoutFCP */
+/** @typedef {Omit & {firstContentfulPaint?: number, firstContentfulPaintAllFrames?: number}} TraceTimesWithoutFCP */
/** @typedef {Omit} TraceTimesWithoutFCPAndTraceEnd */
-/** @typedef {Omit & {timings: TraceTimesWithoutFCP, timestamps: TraceTimesWithoutFCP, firstContentfulPaintEvt?: LH.Artifacts.TraceOfTab['firstContentfulPaintEvt']}} TraceOfTabWithoutFCP */
+/** @typedef {Omit & {timings: TraceTimesWithoutFCP, timestamps: TraceTimesWithoutFCP, firstContentfulPaintEvt?: LH.Artifacts.TraceOfTab['firstContentfulPaintEvt'], firstContentfulPaintAllFramesEvt?: LH.Artifacts.TraceOfTab['largestContentfulPaintAllFramesEvt']}} TraceOfTabWithoutFCP */
/** @typedef {'lastNavigationStart'|'firstResourceSendRequest'} TimeOriginDeterminationMethod */
/** @typedef {Omit & {name: 'FrameCommittedInBrowser', args: {data: {frame: string, url: string, parent?: string}}}} FrameCommittedEvent */
/** @typedef {Omit & {name: 'largestContentfulPaint::Invalidate'|'largestContentfulPaint::Candidate', args: {data?: {size?: number}, frame: string}}} LCPEvent */
@@ -611,15 +611,26 @@ class TraceProcessor {
});
const frameIdToRootFrameId = this.resolveRootFrames(frames);
- // Filter to just events matching the frame ID, just to make sure.
+ // Filter to just events matching the main frame ID, just to make sure.
const frameEvents = keyEvents.filter(e => e.args.frame === mainFrameIds.frameId);
// Filter to just events matching the main frame ID or any child frame IDs.
- const frameTreeEvents = keyEvents.filter(e => {
- return e.args &&
- e.args.frame &&
- frameIdToRootFrameId.get(e.args.frame) === mainFrameIds.frameId;
- });
+ // In practice, there should always be FrameCommittedInBrowser events to define the frame tree.
+ // Unfortunately, many test traces do not include FrameCommittedInBrowser events due to minification.
+ // This ensures there is always a minimal frame tree and events so those tests don't fail.
+ let frameTreeEvents = [];
+ if (frameIdToRootFrameId.has(mainFrameIds.frameId)) {
+ frameTreeEvents = keyEvents.filter(e => {
+ return e.args.frame && frameIdToRootFrameId.get(e.args.frame) === mainFrameIds.frameId;
+ });
+ } else {
+ log.warn(
+ 'trace-of-tab',
+ 'frameTreeEvents may be incomplete, make sure the trace has FrameCommittedInBrowser events'
+ );
+ frameIdToRootFrameId.set(mainFrameIds.frameId, mainFrameIds.frameId);
+ frameTreeEvents = frameEvents;
+ }
// Compute our time origin to use for all relative timings.
const timeOriginEvt = this.computeTimeOrigin(
@@ -630,6 +641,11 @@ class TraceProcessor {
// Compute the key frame timings for the main frame.
const frameTimings = this.computeKeyTimingsForFrame(frameEvents, {timeOriginEvt});
+ // Compute FCP for all frames.
+ const fcpAllFramesEvt = frameTreeEvents.find(
+ e => e.name === 'firstContentfulPaint' && e.ts > timeOriginEvt.ts
+ );
+
// Compute LCP for all frames.
const lcpAllFramesEvt = this.computeValidLCPAllFrames(frameTreeEvents, timeOriginEvt).lcp;
@@ -658,6 +674,7 @@ class TraceProcessor {
timeOrigin: frameTimings.timings.timeOrigin,
firstPaint: frameTimings.timings.firstPaint,
firstContentfulPaint: frameTimings.timings.firstContentfulPaint,
+ firstContentfulPaintAllFrames: maybeGetTiming(fcpAllFramesEvt && fcpAllFramesEvt.ts),
firstMeaningfulPaint: frameTimings.timings.firstMeaningfulPaint,
largestContentfulPaint: frameTimings.timings.largestContentfulPaint,
largestContentfulPaintAllFrames: maybeGetTiming(lcpAllFramesEvt && lcpAllFramesEvt.ts),
@@ -669,6 +686,7 @@ class TraceProcessor {
timeOrigin: frameTimings.timestamps.timeOrigin,
firstPaint: frameTimings.timestamps.firstPaint,
firstContentfulPaint: frameTimings.timestamps.firstContentfulPaint,
+ firstContentfulPaintAllFrames: fcpAllFramesEvt && fcpAllFramesEvt.ts,
firstMeaningfulPaint: frameTimings.timestamps.firstMeaningfulPaint,
largestContentfulPaint: frameTimings.timestamps.largestContentfulPaint,
largestContentfulPaintAllFrames: lcpAllFramesEvt && lcpAllFramesEvt.ts,
@@ -679,6 +697,7 @@ class TraceProcessor {
timeOriginEvt: frameTimings.timeOriginEvt,
firstPaintEvt: frameTimings.firstPaintEvt,
firstContentfulPaintEvt: frameTimings.firstContentfulPaintEvt,
+ firstContentfulPaintAllFramesEvt: fcpAllFramesEvt,
firstMeaningfulPaintEvt: frameTimings.firstMeaningfulPaintEvt,
largestContentfulPaintEvt: frameTimings.largestContentfulPaintEvt,
largestContentfulPaintAllFramesEvt: lcpAllFramesEvt,
diff --git a/lighthouse-core/test/audits/__snapshots__/metrics-test.js.snap b/lighthouse-core/test/audits/__snapshots__/metrics-test.js.snap
index 22dc7d64174a..47b8db35c659 100644
--- a/lighthouse-core/test/audits/__snapshots__/metrics-test.js.snap
+++ b/lighthouse-core/test/audits/__snapshots__/metrics-test.js.snap
@@ -9,6 +9,8 @@ Object {
"firstCPUIdle": 863,
"firstCPUIdleTs": 23466886143,
"firstContentfulPaint": 863,
+ "firstContentfulPaintAllFrames": 683,
+ "firstContentfulPaintAllFramesTs": 23466705983,
"firstContentfulPaintTs": 23466886143,
"firstMeaningfulPaint": 863,
"firstMeaningfulPaintTs": 23466886143,
@@ -24,6 +26,8 @@ Object {
"observedDomContentLoaded": 596,
"observedDomContentLoadedTs": 23466619325,
"observedFirstContentfulPaint": 863,
+ "observedFirstContentfulPaintAllFrames": 683,
+ "observedFirstContentfulPaintAllFramesTs": 23466705983,
"observedFirstContentfulPaintTs": 23466886143,
"observedFirstMeaningfulPaint": 863,
"observedFirstMeaningfulPaintTs": 23466886143,
@@ -62,6 +66,8 @@ Object {
"firstCPUIdle": 3790,
"firstCPUIdleTs": undefined,
"firstContentfulPaint": 2289,
+ "firstContentfulPaintAllFrames": undefined,
+ "firstContentfulPaintAllFramesTs": undefined,
"firstContentfulPaintTs": undefined,
"firstMeaningfulPaint": 2758,
"firstMeaningfulPaintTs": undefined,
@@ -77,6 +83,8 @@ Object {
"observedDomContentLoaded": 1513,
"observedDomContentLoadedTs": 713038536140,
"observedFirstContentfulPaint": 1122,
+ "observedFirstContentfulPaintAllFrames": 1122,
+ "observedFirstContentfulPaintAllFramesTs": 713038144775,
"observedFirstContentfulPaintTs": 713038144775,
"observedFirstMeaningfulPaint": 1122,
"observedFirstMeaningfulPaintTs": 713038144775,
@@ -85,8 +93,8 @@ Object {
"observedFirstVisualChange": 1105,
"observedFirstVisualChangeTs": 713038128064,
"observedLargestContentfulPaint": 1122,
- "observedLargestContentfulPaintAllFrames": undefined,
- "observedLargestContentfulPaintAllFramesTs": undefined,
+ "observedLargestContentfulPaintAllFrames": 1122,
+ "observedLargestContentfulPaintAllFramesTs": 713038144775,
"observedLargestContentfulPaintTs": 713038144775,
"observedLastVisualChange": 1722,
"observedLastVisualChangeTs": 713038745064,
@@ -115,6 +123,8 @@ Object {
"firstCPUIdle": 1582,
"firstCPUIdleTs": 225415754204,
"firstContentfulPaint": 499,
+ "firstContentfulPaintAllFrames": 499,
+ "firstContentfulPaintAllFramesTs": 225414670885,
"firstContentfulPaintTs": 225414670885,
"firstMeaningfulPaint": 783,
"firstMeaningfulPaintTs": 225414955343,
@@ -130,6 +140,8 @@ Object {
"observedDomContentLoaded": 560,
"observedDomContentLoadedTs": 225414732309,
"observedFirstContentfulPaint": 499,
+ "observedFirstContentfulPaintAllFrames": 499,
+ "observedFirstContentfulPaintAllFramesTs": 225414670885,
"observedFirstContentfulPaintTs": 225414670885,
"observedFirstMeaningfulPaint": 783,
"observedFirstMeaningfulPaintTs": 225414955343,
@@ -168,6 +180,8 @@ Object {
"firstCPUIdle": 4313,
"firstCPUIdleTs": undefined,
"firstContentfulPaint": 1337,
+ "firstContentfulPaintAllFrames": undefined,
+ "firstContentfulPaintAllFramesTs": undefined,
"firstContentfulPaintTs": undefined,
"firstMeaningfulPaint": 1553,
"firstMeaningfulPaintTs": undefined,
@@ -183,6 +197,8 @@ Object {
"observedDomContentLoaded": 560,
"observedDomContentLoadedTs": 225414732309,
"observedFirstContentfulPaint": 499,
+ "observedFirstContentfulPaintAllFrames": 499,
+ "observedFirstContentfulPaintAllFramesTs": 225414670885,
"observedFirstContentfulPaintTs": 225414670885,
"observedFirstMeaningfulPaint": 783,
"observedFirstMeaningfulPaintTs": 225414955343,
diff --git a/lighthouse-core/test/computed/metrics/first-contentful-paint-all-frames-test.js b/lighthouse-core/test/computed/metrics/first-contentful-paint-all-frames-test.js
new file mode 100644
index 000000000000..8293fc238d51
--- /dev/null
+++ b/lighthouse-core/test/computed/metrics/first-contentful-paint-all-frames-test.js
@@ -0,0 +1,54 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. 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 FirstContentfulPaintAllFrames = require('../../../computed/metrics/first-contentful-paint-all-frames.js'); // eslint-disable-line max-len
+const FirstContentfulPaint = require('../../../computed/metrics/first-contentful-paint.js'); // eslint-disable-line max-len
+const trace = require('../../fixtures/traces/frame-metrics-m89.json');
+const devtoolsLog = require('../../fixtures/traces/frame-metrics-m89.devtools.log.json');
+
+/* eslint-env jest */
+
+describe('Metrics: FCP all frames', () => {
+ it('should throw for simulated throttling', async () => {
+ const settings = {throttlingMethod: 'simulate'};
+ const context = {settings, computedCache: new Map()};
+ const resultPromise = FirstContentfulPaintAllFrames.request(
+ {trace, devtoolsLog, settings},
+ context
+ );
+
+ // TODO: Implement lantern solution for FCP all frames.
+ await expect(resultPromise).rejects.toThrow();
+ });
+
+ it('should compute FCP-AF separate from FCP', async () => {
+ const settings = {throttlingMethod: 'provided'};
+ const context = {settings, computedCache: new Map()};
+
+ const result = await FirstContentfulPaintAllFrames.request(
+ {trace, devtoolsLog, settings},
+ context
+ );
+ const mainFrameResult = await FirstContentfulPaint.request(
+ {trace, devtoolsLog, settings},
+ context
+ );
+
+ expect(result).toEqual(
+ {
+ timestamp: 23466705983,
+ timing: 682.853,
+ }
+ );
+ expect(mainFrameResult).toEqual(
+ {
+ timestamp: 23466886143,
+ timing: 863.013,
+ }
+ );
+ });
+});
diff --git a/lighthouse-core/test/computed/metrics/largest-contentful-paint-all-frames-test.js b/lighthouse-core/test/computed/metrics/largest-contentful-paint-all-frames-test.js
index 8e6e1b7def3c..e3f3674ddc95 100644
--- a/lighthouse-core/test/computed/metrics/largest-contentful-paint-all-frames-test.js
+++ b/lighthouse-core/test/computed/metrics/largest-contentful-paint-all-frames-test.js
@@ -46,13 +46,16 @@ describe('Metrics: LCP from all frames', () => {
await expect(resultPromise).rejects.toThrow('NO_LCP_ALL_FRAMES');
});
- it('should fail if even if main frame LCP is available', async () => {
+ it('should use main frame LCP if no other frames', async () => {
const settings = {throttlingMethod: 'provided'};
const context = {settings, computedCache: new Map()};
- const resultPromise = LargestContentfulPaintAllFrames.request(
+ const result = await LargestContentfulPaintAllFrames.request(
{trace: traceMainFrame, devtoolsLog: devtoolsLogMainFrame, settings},
context
);
- await expect(resultPromise).rejects.toThrow('NO_LCP_ALL_FRAMES');
+ await expect(result).toEqual({
+ timestamp: 713038144775,
+ timing: 1121.711,
+ });
});
});
diff --git a/lighthouse-core/test/computed/metrics/timing-summary-test.js b/lighthouse-core/test/computed/metrics/timing-summary-test.js
index e4e1e8a02994..c9190896fd37 100644
--- a/lighthouse-core/test/computed/metrics/timing-summary-test.js
+++ b/lighthouse-core/test/computed/metrics/timing-summary-test.js
@@ -25,6 +25,8 @@ describe('Timing summary', () => {
"firstCPUIdle": 863.013,
"firstCPUIdleTs": 23466886143,
"firstContentfulPaint": 863.013,
+ "firstContentfulPaintAllFrames": 682.853,
+ "firstContentfulPaintAllFramesTs": 23466705983,
"firstContentfulPaintTs": 23466886143,
"firstMeaningfulPaint": 863.013,
"firstMeaningfulPaintTs": 23466886143,
@@ -40,6 +42,8 @@ describe('Timing summary', () => {
"observedDomContentLoaded": 596.195,
"observedDomContentLoadedTs": 23466619325,
"observedFirstContentfulPaint": 863.013,
+ "observedFirstContentfulPaintAllFrames": 682.853,
+ "observedFirstContentfulPaintAllFramesTs": 23466705983,
"observedFirstContentfulPaintTs": 23466886143,
"observedFirstMeaningfulPaint": 863.013,
"observedFirstMeaningfulPaintTs": 23466886143,
diff --git a/lighthouse-core/test/computed/trace-of-tab-test.js b/lighthouse-core/test/computed/trace-of-tab-test.js
index be8b76544d8c..53c5dc7541a8 100644
--- a/lighthouse-core/test/computed/trace-of-tab-test.js
+++ b/lighthouse-core/test/computed/trace-of-tab-test.js
@@ -22,6 +22,7 @@ describe('TraceOfTabComputed', () => {
delete traceOfTab.processEvents;
delete traceOfTab.mainThreadEvents;
+ delete traceOfTab.frameTreeEvents;
expect(traceOfTab).toEqual({
domContentLoadedEvt: {
@@ -49,6 +50,19 @@ describe('TraceOfTabComputed', () => {
ts: 225414670885,
tts: 866570,
},
+ firstContentfulPaintAllFramesEvt: {
+ args: {
+ frame: '0x25a638821e30',
+ },
+ cat: 'loading,rail,devtools.timeline',
+ name: 'firstContentfulPaint',
+ ph: 'I',
+ pid: 44277,
+ s: 'p',
+ tid: 775,
+ ts: 225414670885,
+ tts: 866570,
+ },
firstMeaningfulPaintEvt: {
args: {
frame: '0x25a638821e30',
@@ -106,10 +120,10 @@ describe('TraceOfTabComputed', () => {
tts: 455539,
},
frames: [],
- frameTreeEvents: [],
timestamps: {
domContentLoaded: 225414732309,
firstContentfulPaint: 225414670885,
+ firstContentfulPaintAllFrames: 225414670885,
firstMeaningfulPaint: 225414955343,
firstPaint: 225414670868,
load: 225416370913,
@@ -119,6 +133,7 @@ describe('TraceOfTabComputed', () => {
timings: {
domContentLoaded: 560.294,
firstContentfulPaint: 498.87,
+ firstContentfulPaintAllFrames: 498.87,
firstMeaningfulPaint: 783.328,
firstPaint: 498.853,
load: 2198.898,
diff --git a/lighthouse-core/test/lib/tracehouse/trace-processor-test.js b/lighthouse-core/test/lib/tracehouse/trace-processor-test.js
index 6ae7f00d017e..f90b5b8acbd6 100644
--- a/lighthouse-core/test/lib/tracehouse/trace-processor-test.js
+++ b/lighthouse-core/test/lib/tracehouse/trace-processor-test.js
@@ -225,6 +225,29 @@ describe('TraceProcessor', () => {
'Event2',
]);
});
+
+ it('frameTreeEvents includes main frame events if no FrameCommittedInBrowser found', () => {
+ const testTrace = createTestTrace({timeOrigin: 0, traceEnd: 2000});
+ const mainFrame = testTrace.traceEvents[0].args.frame;
+ const childFrame = 'CHILDFRAME';
+ const otherMainFrame = 'ANOTHERTAB';
+ const cat = 'loading,rail,devtools.timeline';
+ testTrace.traceEvents.push(
+ /* eslint-disable max-len */
+ {name: 'Event1', cat, args: {frame: mainFrame}},
+ {name: 'Event2', cat, args: {frame: childFrame}},
+ {name: 'Event3', cat, args: {frame: otherMainFrame}}
+ /* eslint-enable max-len */
+ );
+ const trace = TraceProcessor.computeTraceOfTab(testTrace);
+ expect(trace.frameTreeEvents.map(e => e.name)).toEqual([
+ 'navigationStart',
+ 'domContentLoadedEventEnd',
+ 'firstContentfulPaint',
+ 'firstMeaningfulPaint',
+ 'Event1',
+ ]);
+ });
});
describe('getMainThreadTopLevelEvents', () => {
@@ -505,36 +528,67 @@ Object {
});
});
- describe('finds correct LCP from all frames', () => {
+ describe('finds correct metrics from all frames', () => {
it('in a trace', () => {
const trace = TraceProcessor.computeTraceOfTab(lcpAllFramesTrace);
expect({
+ // Main frame
+ 'mainFrameIds.frameId': trace.mainFrameIds.frameId,
'firstContentfulPaintEvt.ts': trace.firstContentfulPaintEvt.ts,
'largestContentfulPaintEvt.ts': trace.largestContentfulPaintEvt.ts,
- 'mainFrameIds.frameId': trace.mainFrameIds.frameId,
- 'timeOriginEvt.ts': trace.timeOriginEvt.ts,
'timestamps.firstContentfulPaint': trace.timestamps.firstContentfulPaint,
'timestamps.largestContentfulPaint': trace.timestamps.largestContentfulPaint,
- 'timestamps.largestContentfulPaintAllFrames': trace.timestamps.largestContentfulPaintAllFrames, // eslint-disable-line max-len
'timings.firstContentfulPaint': trace.timings.firstContentfulPaint,
'timings.largestContentfulPaint': trace.timings.largestContentfulPaint,
+ // All frames
+ 'firstContentfulPaintAllFramesEvt.ts': trace.firstContentfulPaintAllFramesEvt.ts,
+ 'largestContentfulPaintAllFramesEvt.ts': trace.largestContentfulPaintAllFramesEvt.ts,
+ 'timestamps.firstContentfulPaintAllFrames': trace.timestamps.firstContentfulPaintAllFrames, // eslint-disable-line max-len
+ 'timestamps.largestContentfulPaintAllFrames': trace.timestamps.largestContentfulPaintAllFrames, // eslint-disable-line max-len
+ 'timings.firstContentfulPaintAllFrames': trace.timings.firstContentfulPaintAllFrames,
'timings.largestContentfulPaintAllFrames': trace.timings.largestContentfulPaintAllFrames,
}).toMatchInlineSnapshot(`
Object {
+ "firstContentfulPaintAllFramesEvt.ts": 23466705983,
"firstContentfulPaintEvt.ts": 23466886143,
+ "largestContentfulPaintAllFramesEvt.ts": 23466705983,
"largestContentfulPaintEvt.ts": 23466886143,
"mainFrameIds.frameId": "207613A6AD77B492759226780A40F6F4",
- "timeOriginEvt.ts": 23466023130,
"timestamps.firstContentfulPaint": 23466886143,
+ "timestamps.firstContentfulPaintAllFrames": 23466705983,
"timestamps.largestContentfulPaint": 23466886143,
"timestamps.largestContentfulPaintAllFrames": 23466705983,
"timings.firstContentfulPaint": 863.013,
+ "timings.firstContentfulPaintAllFrames": 682.853,
"timings.largestContentfulPaint": 863.013,
"timings.largestContentfulPaintAllFrames": 682.853,
}
`);
});
+ it('finds FCP from all frames', () => {
+ const testTrace = createTestTrace({timeOrigin: 0, traceEnd: 2000});
+ const mainFrame = testTrace.traceEvents[0].args.frame;
+ const childFrame = 'CHILDFRAME';
+ const cat = 'loading,rail,devtools.timeline';
+
+ // Remove default FCP event because we will define them manually.
+ testTrace.traceEvents
+ = testTrace.traceEvents.filter(e => e.name !== 'firstContentfulPaint');
+
+ testTrace.traceEvents.push(
+ /* eslint-disable max-len */
+ {name: 'FrameCommittedInBrowser', cat, args: {data: {frame: mainFrame, url: 'https://example.com'}}, ts: 900, duration: 10},
+ {name: 'FrameCommittedInBrowser', cat, args: {data: {frame: childFrame, parent: mainFrame, url: 'https://frame.com'}}, ts: 910, duration: 10},
+ {name: 'firstContentfulPaint', cat, args: {frame: childFrame}, ts: 1000, duration: 10},
+ {name: 'firstContentfulPaint', cat, args: {frame: mainFrame}, ts: 1100, duration: 10}
+ /* eslint-enable max-len */
+ );
+ const trace = TraceProcessor.computeTraceOfTab(testTrace);
+ assert.equal(trace.timestamps.firstContentfulPaint, 1100);
+ assert.equal(trace.timestamps.firstContentfulPaintAllFrames, 1000);
+ });
+
it('finds LCP from all frames', () => {
const testTrace = createTestTrace({timeOrigin: 0, traceEnd: 2000});
const mainFrame = testTrace.traceEvents[0].args.frame;
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 3fecb987a3df..4b069144971b 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -1499,10 +1499,14 @@
{
"firstContentfulPaint": 3969,
"firstContentfulPaintTs": 185607289047,
+ "firstContentfulPaintAllFrames": 3969,
+ "firstContentfulPaintAllFramesTs": 185607289047,
"firstMeaningfulPaint": 3969,
"firstMeaningfulPaintTs": 185607289048,
"largestContentfulPaint": 4927,
"largestContentfulPaintTs": 185608247190,
+ "largestContentfulPaintAllFrames": 4927,
+ "largestContentfulPaintAllFramesTs": 185608247190,
"firstCPUIdle": 4927,
"firstCPUIdleTs": 185608247190,
"interactive": 4927,
@@ -1522,10 +1526,14 @@
"observedFirstPaintTs": 185607289043,
"observedFirstContentfulPaint": 3969,
"observedFirstContentfulPaintTs": 185607289047,
+ "observedFirstContentfulPaintAllFrames": 3969,
+ "observedFirstContentfulPaintAllFramesTs": 185607289047,
"observedFirstMeaningfulPaint": 3969,
"observedFirstMeaningfulPaintTs": 185607289048,
"observedLargestContentfulPaint": 4927,
"observedLargestContentfulPaintTs": 185608247190,
+ "observedLargestContentfulPaintAllFrames": 4927,
+ "observedLargestContentfulPaintAllFramesTs": 185608247190,
"observedTraceEnd": 10281,
"observedTraceEndTs": 185613601189,
"observedLoad": 4924,
@@ -6379,6 +6387,12 @@
"duration": 100,
"entryType": "measure"
},
+ {
+ "startTime": 0,
+ "name": "lh:computed:FirstContentfulPaintAllFrames",
+ "duration": 100,
+ "entryType": "measure"
+ },
{
"startTime": 0,
"name": "lh:computed:LargestContentfulPaintAllFrames",
diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts
index ec41369188c0..4bc7df0437b0 100644
--- a/types/artifacts.d.ts
+++ b/types/artifacts.d.ts
@@ -590,6 +590,7 @@ declare global {
timeOrigin: number;
firstPaint?: number;
firstContentfulPaint: number;
+ firstContentfulPaintAllFrames: number;
firstMeaningfulPaint?: number;
largestContentfulPaint?: number;
largestContentfulPaintAllFrames?: number;
@@ -619,6 +620,8 @@ declare global {
firstPaintEvt?: TraceEvent;
/** The trace event marking firstContentfulPaint, if it was found. */
firstContentfulPaintEvt: TraceEvent;
+ /** The trace event marking firstContentfulPaint from all frames, if it was found. */
+ firstContentfulPaintAllFramesEvt: TraceEvent;
/** The trace event marking firstMeaningfulPaint, if it was found. */
firstMeaningfulPaintEvt?: TraceEvent;
/** The trace event marking largestContentfulPaint, if it was found. */
@@ -665,6 +668,8 @@ declare global {
export interface TimingSummary {
firstContentfulPaint: number;
firstContentfulPaintTs: number | undefined;
+ firstContentfulPaintAllFrames: number | undefined;
+ firstContentfulPaintAllFramesTs: number | undefined;
firstMeaningfulPaint: number;
firstMeaningfulPaintTs: number | undefined;
largestContentfulPaint: number | undefined;
@@ -693,6 +698,8 @@ declare global {
observedFirstPaintTs: number | undefined;
observedFirstContentfulPaint: number;
observedFirstContentfulPaintTs: number;
+ observedFirstContentfulPaintAllFrames: number;
+ observedFirstContentfulPaintAllFramesTs: number;
observedFirstMeaningfulPaint: number | undefined;
observedFirstMeaningfulPaintTs: number | undefined;
observedLargestContentfulPaint: number | undefined;