static computeValidLCPAllFrames(events, timeOriginEvent) {
    const lcpEvents = events.filter(this.isLCPEvent).reverse();

    /** @type {Map<string, LCPEvent>} */
    const finalLcpEventsByFrame = new Map();
    for (const e of lcpEvents) {
      if (e.ts <= timeOriginEvent.ts) break;

      // Already found final LCP state of this frame.
      const frame = e.args.frame;
      if (finalLcpEventsByFrame.has(frame)) continue;

      finalLcpEventsByFrame.set(frame, e);
    }

    /** @type {LCPCandidateEvent | undefined} */
    let maxLcpAcrossFrames;
    for (const lcp of finalLcpEventsByFrame.values()) {
      if (!this.isLCPCandidateEvent(lcp)) continue;
      if (!maxLcpAcrossFrames || lcp.args.data.size > maxLcpAcrossFrames.args.data.size) {
        maxLcpAcrossFrames = lcp;
      }
    }

    return {
      lcp: maxLcpAcrossFrames,
      // LCP events were found, but final LCP event of every frame was an invalidate event.
      invalidated: Boolean(!maxLcpAcrossFrames && finalLcpEventsByFrame.size),
    };
  }
Returns the maximum LCP event across all frames in `events`.
Sets `invalidated` flag if LCP of every frame is invalidated.

LCP's trace event was first introduced in m78. We can't surface an LCP for older Chrome versions.
LCP comes from a frame's latest `largestContentfulPaint::Candidate`, but it can be invalidated by a `largestContentfulPaint::Invalidate` event.

@param {LH.TraceEvent[]} events
@param {LH.TraceEvent} timeOriginEvent
@return {{lcp: LCPEvent | undefined, invalidated: boolean}}