synthesizeTraceEvents(knownTaskNodes = []) {
    const naiveEvents = this._synthesizeNaiveTraceEvents();
    if (!naiveEvents.length) return [];

    let finalEvents = naiveEvents;
    if (knownTaskNodes.length) {
      // If we have task information, put the times back into raw trace event ts scale.
      /** @type {(baseTs: number) => (node: LH.Artifacts.TaskNode) => LH.Artifacts.TaskNode} */
      const rebaseTaskTime = baseTs => node => ({
        ...node,
        startTime: baseTs + node.startTime * 1000,
        endTime: baseTs + node.endTime * 1000,
        duration: node.duration * 1000,
      });

      // The first task node might not be time 0, so recompute the baseTs.
      const baseTs = knownTaskNodes[0].event.ts - knownTaskNodes[0].startTime * 1000;
      const knownTasks = knownTaskNodes.map(rebaseTaskTime(baseTs));

      // We'll also create tasks for our naive events so we have the B/E pairs readily available.
      const naiveProfilerTasks = MainThreadTasks.getMainThreadTasks(naiveEvents, [], Infinity)
        .map(rebaseTaskTime(naiveEvents[0].ts))
        .filter(CpuProfileModel.isSyntheticTask);
      if (!naiveProfilerTasks.length) throw new Error('Failed to create naive profiler tasks');

      finalEvents = this._refineTraceEventsWithTasks(knownTasks, naiveProfilerTasks, naiveEvents);
    }

    return finalEvents;
  }
Creates B/E-style trace events from a CpuProfile object created by `collectProfileEvents()`.
An optional set of tasks can be passed in to refine the start/end times.

@param {Array<LH.Artifacts.TaskNode>} [knownTaskNodes]
@return {Array<LH.TraceEvent>}