static getTimingBreakdown(task, taskTimingsByEvent, timeByTaskGroup = new Map()) {
    const taskTiming = LongTasks.getTiming(task, taskTimingsByEvent);

    // Add up child time, while recursively stepping in to accumulate group times.
    let childrenTime = 0;
    if (taskTiming.duration > 0) {
      for (const child of task.children) {
        const {duration} = LongTasks.getTimingBreakdown(child, taskTimingsByEvent, timeByTaskGroup);
        childrenTime += duration;
      }
    }

    // Add this task's selfTime to its group's total time.
    const selfTime = taskTiming.duration - childrenTime;
    const taskGroupTime = timeByTaskGroup.get(task.group.id) || 0;
    timeByTaskGroup.set(task.group.id, taskGroupTime + selfTime);

    return {
      startTime: taskTiming.startTime,
      duration: taskTiming.duration,
      timeByTaskGroup,
    };
  }
Returns the timing information for the given task, recursively walking the
task's children and adding up time spent in each type of task activity.
If `taskTimingsByEvent` is present, it will be used for task timing instead
of the timings on the tasks themselves.
If `timeByTaskGroup` is not provided, a new Map will be populated with
timing breakdown; if one is provided, timing breakdown will be added to the
existing breakdown.

TODO: when simulated, a significant number of child tasks are dropped, so
most time will be attributed to 'other' (the category of the top-level
RunTask). See pruning in `PageDependencyGraph.linkCPUNodes`.
@param {LH.Artifacts.TaskNode} task
@param {Map<Lantern.Types.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} taskTimingsByEvent
@param {Map<TaskGroupIds, number>} [timeByTaskGroup]
@return {{startTime: number, duration: number, timeByTaskGroup: Map<TaskGroupIds, number>}}