static async getTraceEngineElements(traceEngineResult, navigationId) {
    // Can only resolve elements for the latest insight set, which should correspond
    // to the current navigation id (if present). Can't resolve elements for pages
    // that are gone.
    const insightSet = [...traceEngineResult.insights.values()].at(-1);
    if (!insightSet) {
      return [];
    }

    if (navigationId) {
      if (insightSet.navigation?.args.data?.navigationId !== navigationId) {
        return [];
      }
    } else {
      if (insightSet.navigation) {
        return [];
      }
    }

    /**
     * Execute `cb(obj, key)` on every object property (non-objects only), recursively.
     * @param {any} obj
     * @param {(obj: Record<string, unknown>, key: string) => void} cb
     * @param {Set<object>} seen
     */
    function recursiveObjectEnumerate(obj, cb, seen) {
      if (seen.has(seen)) {
        return;
      }

      seen.add(obj);

      if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
        if (obj instanceof Map) {
          for (const [key, val] of obj) {
            if (typeof val === 'object') {
              recursiveObjectEnumerate(val, cb, seen);
            } else {
              cb(val, key);
            }
          }
        } else {
          Object.keys(obj).forEach(key => {
            if (typeof obj[key] === 'object') {
              recursiveObjectEnumerate(obj[key], cb, seen);
            } else {
              cb(obj[key], key);
            }
          });
        }
      } else if (Array.isArray(obj)) {
        obj.forEach(item => {
          if (typeof item === 'object' || Array.isArray(item)) {
            recursiveObjectEnumerate(item, cb, seen);
          }
        });
      }
    }

    /** @type {number[]} */
    const nodeIds = [];
    recursiveObjectEnumerate(insightSet.model, (val, key) => {
      const keys = ['nodeId', 'node_id', 'backendNodeId'];
      if (typeof val === 'number' && keys.includes(key)) {
        nodeIds.push(val);
      }
    }, new Set());

    // TODO: handle digging into Map in recursiveObjectEnumerate.
    for (const shift of insightSet.model.CLSCulprits.shifts.values()) {
      nodeIds.push(...shift.unsizedImages.map(s => s.backendNodeId));
    }

    return [...new Set(nodeIds)].map(id => ({nodeId: id}));
  }
@param {LH.Artifacts.TraceEngineResult} traceEngineResult
@param {string|undefined} navigationId
@return {Promise<Array<{nodeId: number}>>}