static computeImpactsFromLantern(tasks, tbtNodeTimings, startTimeMs, endTimeMs) {
    /** @type {Map<LH.Artifacts.TaskNode, number>} */
    const taskToImpact = new Map();

    /** @type {Map<LH.Artifacts.TaskNode, number>} */
    const taskToBlockingTime = new Map();

    /** @type {Map<LH.Artifacts.TaskNode, {start: number, end: number, duration: number}>} */
    const topLevelTaskToEvent = new Map();

    /** @type {Map<Lantern.Types.TraceEvent, LH.Artifacts.TaskNode>} */
    const traceEventToTask = new Map();
    for (const task of tasks) {
      traceEventToTask.set(task.event, task);
    }

    // Use lantern TBT timings to calculate the TBT impact of top level tasks.
    for (const [node, timing] of tbtNodeTimings) {
      if (node.type !== 'cpu') continue;

      const event = {
        start: timing.startTime,
        end: timing.endTime,
        duration: timing.duration,
      };

      const tbtImpact = calculateTbtImpactForEvent(event, startTimeMs, endTimeMs);
      const blockingTime = calculateTbtImpactForEvent(event, -Infinity, Infinity);

      const task = traceEventToTask.get(node.event);
      if (!task) continue;

      topLevelTaskToEvent.set(task, event);
      taskToImpact.set(task, tbtImpact);
      taskToBlockingTime.set(task, blockingTime);
    }

    // Interpolate the TBT impact of remaining tasks using the top level ancestor tasks.
    // We don't have any lantern estimates for tasks that are not top level, so we need to estimate
    // the lantern timing based on the task's observed timing relative to it's top level task's observed timing.
    for (const task of tasks) {
      if (taskToImpact.has(task) || taskToBlockingTime.has(task)) continue;

      const topLevelTask = this.getTopLevelTask(task);

      const topLevelEvent = topLevelTaskToEvent.get(topLevelTask);
      if (!topLevelEvent) continue;

      const startRatio = (task.startTime - topLevelTask.startTime) / topLevelTask.duration;
      const start = startRatio * topLevelEvent.duration + topLevelEvent.start;

      const endRatio = (topLevelTask.endTime - task.endTime) / topLevelTask.duration;
      const end = topLevelEvent.end - endRatio * topLevelEvent.duration;

      const event = {
        start,
        end,
        duration: end - start,
      };

      const tbtImpact = calculateTbtImpactForEvent(event, startTimeMs, endTimeMs, topLevelEvent);
      const blockingTime = calculateTbtImpactForEvent(event, -Infinity, Infinity, topLevelEvent);

      taskToImpact.set(task, tbtImpact);
      taskToBlockingTime.set(task, blockingTime);
    }

    return this.createImpactTasks(tasks, taskToImpact, taskToBlockingTime);
  }
@param {LH.Artifacts.TaskNode[]} tasks
@param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
@param {number} startTimeMs
@param {number} endTimeMs
@return {LH.Artifacts.TBTImpactTask[]}