in src/sdk/lh-trace-processor.ts [934:1023]
static computeNavigationTimingsForFrame(frameEvents, options) {
const { timeOriginEvt } = options;
// Find our first paint of this frame
const firstPaint = frameEvents.find(
e => e.name === 'firstPaint' && e.ts > timeOriginEvt.ts
);
// FCP will follow at/after the FP. Used in so many places we require it.
const firstContentfulPaint = frameEvents.find(
e => e.name === 'firstContentfulPaint' && e.ts > timeOriginEvt.ts
);
if (!firstContentfulPaint) {
throw this.createNoFirstContentfulPaintError();
}
// fMP will follow at/after the FP
let firstMeaningfulPaint = frameEvents.find(
e => e.name === 'firstMeaningfulPaint' && e.ts > timeOriginEvt.ts
);
let fmpFellBack = false;
// If there was no firstMeaningfulPaint event found in the trace, the network idle detection
// may have not been triggered before Lighthouse finished tracing.
// In this case, we'll use the last firstMeaningfulPaintCandidate we can find.
// However, if no candidates were found (a bogus trace, likely), we fail.
if (!firstMeaningfulPaint) {
const fmpCand = 'firstMeaningfulPaintCandidate';
fmpFellBack = true;
log(`No firstMeaningfulPaint found, falling back to last ${fmpCand}`);
const lastCandidate = frameEvents.filter(e => e.name === fmpCand).pop();
if (!lastCandidate) {
log('No `firstMeaningfulPaintCandidate` events found in trace');
}
firstMeaningfulPaint = lastCandidate;
}
// This function accepts events spanning multiple frames, but this usage will only provide events from the main frame.
const lcpResult = this.computeValidLCPAllFrames(frameEvents, timeOriginEvt);
const load = frameEvents.find(
e => e.name === 'loadEventEnd' && e.ts > timeOriginEvt.ts
);
const domContentLoaded = frameEvents.find(
e => e.name === 'domContentLoadedEventEnd' && e.ts > timeOriginEvt.ts
);
/** @param {{ts: number}=} event */
const getTimestamp = event => event && event.ts;
/** @type {TraceNavigationTimesForFrame} */
const timestamps = {
timeOrigin: timeOriginEvt.ts,
firstPaint: getTimestamp(firstPaint),
firstContentfulPaint: firstContentfulPaint.ts,
firstMeaningfulPaint: getTimestamp(firstMeaningfulPaint),
largestContentfulPaint: getTimestamp(lcpResult.lcp),
load: getTimestamp(load),
domContentLoaded: getTimestamp(domContentLoaded),
};
/** @param {number} ts */
const getTiming = ts => (ts - timeOriginEvt.ts) / 1000;
/** @param {number=} ts */
const maybeGetTiming = ts => (ts === undefined ? undefined : getTiming(ts));
/** @type {TraceNavigationTimesForFrame} */
const timings = {
timeOrigin: 0,
firstPaint: maybeGetTiming(timestamps.firstPaint),
firstContentfulPaint: getTiming(timestamps.firstContentfulPaint),
firstMeaningfulPaint: maybeGetTiming(timestamps.firstMeaningfulPaint),
largestContentfulPaint: maybeGetTiming(timestamps.largestContentfulPaint),
load: maybeGetTiming(timestamps.load),
domContentLoaded: maybeGetTiming(timestamps.domContentLoaded),
};
return {
timings,
timestamps,
timeOriginEvt: timeOriginEvt,
firstPaintEvt: firstPaint,
firstContentfulPaintEvt: firstContentfulPaint,
firstMeaningfulPaintEvt: firstMeaningfulPaint,
largestContentfulPaintEvt: lcpResult.lcp,
loadEvt: load,
domContentLoadedEvt: domContentLoaded,
fmpFellBack,
lcpInvalidated: lcpResult.invalidated,
};
}