static computeNavigationTimingsForFrame()

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,
    };
  }