Future buildVariablesTree()

in packages/devtools_app/lib/src/debugger/debugger_model.dart [339:499]


Future<void> buildVariablesTree(
  DartObjectNode variable, {
  bool expandAll = false,
}) async {
  final ref = variable.ref;
  if (!variable.isExpandable || variable.treeInitializeStarted || ref == null)
    return;
  variable.treeInitializeStarted = true;

  final isolateRef = ref.isolateRef;
  final instanceRef = ref.instanceRef;
  final diagnostic = ref.diagnostic;
  if (diagnostic != null && includeDiagnosticPropertiesInDebugger) {
    final service = diagnostic.inspectorService;
    Future<void> _addPropertiesHelper(
        List<RemoteDiagnosticsNode> properties) async {
      if (properties == null) return;
      await addExpandableChildren(
        variable,
        await _createVariablesForDiagnostics(
          service,
          properties,
          isolateRef,
        ),
        expandAll: true,
      );
    }

    if (diagnostic.inlineProperties?.isNotEmpty ?? false) {
      await _addPropertiesHelper(diagnostic.inlineProperties);
    } else {
      assert(!service.disposed);
      if (!service.disposed) {
        await _addPropertiesHelper(await diagnostic.getProperties(service));
      }
    }
  }
  final existingNames = <String>{};
  for (var child in variable.children) {
    final name = child?.name;
    if (name != null && name.isNotEmpty) {
      existingNames.add(name);
      if (!isPrivate(name)) {
        // Assume private and public names with the same name reference the same
        // data so showing both is not useful.
        existingNames.add('_$name');
      }
    }
  }

  if (variable.childCount > DartObjectNode.MAX_CHILDREN_IN_GROUPING) {
    final numChildrenInGrouping =
        variable.childCount >= pow(DartObjectNode.MAX_CHILDREN_IN_GROUPING, 2)
            ? (roundToNearestPow10(variable.childCount) /
                    DartObjectNode.MAX_CHILDREN_IN_GROUPING)
                .floor()
            : DartObjectNode.MAX_CHILDREN_IN_GROUPING;

    var start = variable.offset ?? 0;
    final end = start + variable.childCount;
    while (start < end) {
      final count = min(end - start, numChildrenInGrouping);
      variable.addChild(
        DartObjectNode.grouping(variable.ref, offset: start, count: count),
      );
      start += count;
    }
  } else if (instanceRef != null && serviceManager.service != null) {
    try {
      final dynamic result =
          await _getObjectWithRetry(instanceRef.id, variable);
      if (result is Instance) {
        if (result.associations != null) {
          variable.addAllChildren(
              _createVariablesForAssociations(result, isolateRef));
        } else if (result.elements != null) {
          variable
              .addAllChildren(_createVariablesForElements(result, isolateRef));
        } else if (result.bytes != null) {
          variable.addAllChildren(_createVariablesForBytes(result, isolateRef));
          // Check fields last, as all instanceRefs may have a non-null fields
          // with no entries.
        } else if (result.fields != null) {
          variable.addAllChildren(_createVariablesForFields(result, isolateRef,
              existingNames: existingNames));
        }
      }
    } on SentinelException {
      // Fail gracefully if calling `getObject` throws a SentinelException.
    }
  }
  if (diagnostic != null && includeDiagnosticChildren) {
    // Always add children last after properties to avoid confusion.
    final ObjectGroupBase service = diagnostic.inspectorService;
    final diagnosticChildren = await diagnostic.children;
    if (diagnosticChildren?.isNotEmpty ?? false) {
      final childrenNode = DartObjectNode.text(
        pluralize('child', diagnosticChildren.length, plural: 'children'),
      );
      variable.addChild(childrenNode);

      await addExpandableChildren(
        childrenNode,
        await _createVariablesForDiagnostics(
          service,
          diagnosticChildren,
          isolateRef,
        ),
        expandAll: expandAll,
      );
    }
  }
  final inspectorService = serviceManager.inspectorService;
  if (inspectorService != null) {
    final tasks = <Future>[];
    ObjectGroupBase group;
    Future<void> _maybeUpdateRef(DartObjectNode child) async {
      if (child.ref == null) return;
      if (child.ref.diagnostic == null) {
        // TODO(jacobr): also check whether the InstanceRef is an instance of
        // Diagnosticable and show the Diagnosticable properties in that case.
        final instanceRef = child.ref.instanceRef;
        // This is an approximation of eval('instanceRef is DiagnosticsNode')
        // TODO(jacobr): cache the full class hierarchy so we can cheaply check
        // instanceRef is DiagnosticsNode without having to do an eval.
        if (instanceRef != null &&
            (instanceRef.classRef?.name == 'DiagnosticableTreeNode' ||
                instanceRef.classRef?.name == 'DiagnosticsProperty')) {
          // The user is expecting to see the object the DiagnosticsNode is
          // describing not the DiagnosticsNode itself.
          try {
            group ??= inspectorService.createObjectGroup('temp');
            final valueInstanceRef = await group.evalOnRef(
              'object.value',
              child.ref,
            );
            // TODO(jacobr): add the Diagnostics properties as well?
            child._ref = GenericInstanceRef(
              isolateRef: isolateRef,
              value: valueInstanceRef,
            );
          } catch (e) {
            if (e is! SentinelException) {
              log('Caught $e accessing the value of an object',
                  LogLevel.warning);
            }
          }
        }
      }
    }

    for (var child in variable.children) {
      tasks.add(_maybeUpdateRef(child));
    }
    if (tasks.isNotEmpty) {
      await Future.wait(tasks);
      unawaited(group?.dispose());
    }
  }
  variable.treeInitializeComplete = true;
}