static async audit(artifacts, context) {
    const settings = context.settings || {};
    const trace = artifacts.Trace;
    const devtoolsLog = artifacts.DevtoolsLog;
    const networkRecords = await NetworkRecords.request(devtoolsLog, context);
    const tasks = await MainThreadTasks.request(trace, context);
    const multiplier = settings.throttlingMethod === 'simulate' ?
      settings.throttling.cpuSlowdownMultiplier : 1;

    const executionTimings = getExecutionTimingsByURL(tasks, networkRecords);
    // Exclude our own tasks.
    executionTimings.delete('_lighthouse-eval.js');

    const tbtImpact = await this.getTbtImpact(artifacts, context);

    let hadExcessiveChromeExtension = false;
    let totalBootupTime = 0;
    const results = Array.from(executionTimings)
      .map(([url, timingByGroupId]) => {
        // Add up the totalExecutionTime for all the taskGroups
        let totalExecutionTimeForURL = 0;
        for (const [groupId, timespanMs] of Object.entries(timingByGroupId)) {
          timingByGroupId[groupId] = timespanMs * multiplier;
          totalExecutionTimeForURL += timespanMs * multiplier;
        }

        const scriptingTotal = timingByGroupId[taskGroups.scriptEvaluation.id] || 0;
        const parseCompileTotal = timingByGroupId[taskGroups.scriptParseCompile.id] || 0;

        // Add up all the JavaScript time of shown URLs
        if (totalExecutionTimeForURL >= context.options.thresholdInMs) {
          totalBootupTime += scriptingTotal + parseCompileTotal;
        }

        hadExcessiveChromeExtension = hadExcessiveChromeExtension ||
          (url.startsWith('chrome-extension:') && scriptingTotal > 100);

        return {
          url: url,
          total: totalExecutionTimeForURL,
          // Highlight the JavaScript task costs
          scripting: scriptingTotal,
          scriptParseCompile: parseCompileTotal,
        };
      })
      .filter(result => result.total >= context.options.thresholdInMs)
      .sort((a, b) => b.total - a.total);


    // TODO: consider moving this to core gathering so you don't need to run the audit for warning
    let runWarnings;
    if (hadExcessiveChromeExtension) {
      runWarnings = [str_(UIStrings.chromeExtensionsWarning)];
    }

    /** @type {LH.Audit.Details.Table['headings']} */
    const headings = [
      {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
      {key: 'total', granularity: 1, valueType: 'ms', label: str_(UIStrings.columnTotal)},
      {key: 'scripting', granularity: 1, valueType: 'ms', label: str_(UIStrings.columnScriptEval)},
      {key: 'scriptParseCompile', granularity: 1, valueType: 'ms',
        label: str_(UIStrings.columnScriptParse)},
    ];

    const details = BootupTime.makeTableDetails(headings, results,
      {wastedMs: totalBootupTime, sortedBy: ['total']});

    const score = Audit.computeLogNormalScore(
      {p10: context.options.p10, median: context.options.median},
      totalBootupTime
    );

    return {
      score,
      notApplicable: !results.length,
      numericValue: totalBootupTime,
      numericUnit: 'millisecond',
      displayValue: totalBootupTime > 0 ?
        str_(i18n.UIStrings.seconds, {timeInMs: totalBootupTime}) : '',
      details,
      runWarnings,
      metricSavings: {
        TBT: tbtImpact,
      },
    };
  }
@param {LH.Artifacts} artifacts
@param {LH.Audit.Context} context
@return {Promise<LH.Audit.Product>}