walk method

Returns a flattened, depth-first snapshot of the semantics subtree below context — mirroring ElementTreeWalker.walk's scoping, so a BuildContext passed to one behaves the same way passed to the other (e.g. via A11yReport.generate's or expectNoA11yViolations's finder).

A SemanticsNode only exists for render objects that are semantics boundaries, not every Element — so this walks down the render tree from context until it finds one (there can be several, e.g. siblings that each start their own boundary), and reads each one's own SemanticsNode subtree from there.

Implementation

List<A11ySemanticsNodeInfo> walk(BuildContext context) {
  final renderObject = context.findRenderObject();
  if (renderObject == null) return const [];

  final owner = View.pipelineOwnerOf(context).semanticsOwner;
  if (owner == null) return const [];

  // View.of throws if context sits above the app's View (e.g. the true
  // root element callers reach for when they want "the whole app") —
  // View.pipelineOwnerOf above already falls back gracefully for that
  // same case, so mirror it here instead of crashing.
  final devicePixelRatio =
      View.maybeOf(context)?.devicePixelRatio ??
      RendererBinding
          .instance
          .platformDispatcher
          .views
          .first
          .devicePixelRatio;

  final roots = <SemanticsNode>[];
  void findRoots(RenderObject current) {
    final semantics = current.debugSemantics;
    if (semantics != null) {
      roots.add(semantics);
      return;
    }
    current.visitChildren(findRoots);
  }

  findRoots(renderObject);

  final flat = <A11ySemanticsNodeInfo>[];
  void flatten(A11ySemanticsNodeInfo info) {
    flat.add(info);
    info.childrenInTraversalOrder.forEach(flatten);
  }

  for (final root in roots) {
    flatten(_capture(root, 0, devicePixelRatio));
  }
  return flat;
}