| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/semantics.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| /// Provides an iterable that efficiently returns all the [Element]s |
| /// rooted at the given [Element]. See [CachingIterable] for details. |
| /// |
| /// This function must be called again if the tree changes. You cannot |
| /// call this function once, then reuse the iterable after having |
| /// changed the state of the tree, because the iterable returned by |
| /// this function caches the results and only walks the tree once. |
| /// |
| /// The same applies to any iterable obtained indirectly through this |
| /// one, for example the results of calling `where` on this iterable |
| /// are also cached. |
| Iterable<Element> collectAllElementsFrom( |
| Element rootElement, { |
| required bool skipOffstage, |
| }) { |
| return CachingIterable<Element>(_DepthFirstElementTreeIterator(rootElement, !skipOffstage)); |
| } |
| |
| /// Provides an iterable that efficiently returns all the [SemanticsNode]s |
| /// rooted at the given [SemanticsNode]. See [CachingIterable] for details. |
| /// |
| /// By default, this will traverse the semantics tree in semantic traversal |
| /// order, but the traversal order can be changed by passing in a different |
| /// value to `order`. |
| /// |
| /// This function must be called again if the semantics change. You cannot call |
| /// this function once, then reuse the iterable after having changed the state |
| /// of the tree, because the iterable returned by this function caches the |
| /// results and only walks the tree once. |
| /// |
| /// The same applies to any iterable obtained indirectly through this |
| /// one, for example the results of calling `where` on this iterable |
| /// are also cached. |
| Iterable<SemanticsNode> collectAllSemanticsNodesFrom( |
| SemanticsNode root, { |
| DebugSemanticsDumpOrder order = DebugSemanticsDumpOrder.traversalOrder, |
| }) { |
| return CachingIterable<SemanticsNode>(_DepthFirstSemanticsTreeIterator(root, order)); |
| } |
| |
| /// Provides a recursive, efficient, depth first search of a tree. |
| /// |
| /// This iterator executes a depth first search as an iterable, and iterates in |
| /// a left to right order: |
| /// |
| /// 1 |
| /// / \ |
| /// 2 3 |
| /// / \ / \ |
| /// 4 5 6 7 |
| /// |
| /// Will iterate in order 2, 4, 5, 3, 6, 7. The given root element is not |
| /// included in the traversal. |
| abstract class _DepthFirstTreeIterator<ItemType> implements Iterator<ItemType> { |
| _DepthFirstTreeIterator(ItemType root) { |
| _fillStack(_collectChildren(root)); |
| } |
| |
| @override |
| ItemType get current => _current!; |
| late ItemType _current; |
| |
| final List<ItemType> _stack = <ItemType>[]; |
| |
| @override |
| bool moveNext() { |
| if (_stack.isEmpty) { |
| return false; |
| } |
| |
| _current = _stack.removeLast(); |
| _fillStack(_collectChildren(_current)); |
| return true; |
| } |
| |
| /// Fills the stack in such a way that the next element of a depth first |
| /// traversal is easily and efficiently accessible when calling `moveNext`. |
| void _fillStack(List<ItemType> children) { |
| // We reverse the list of children so we don't have to do use expensive |
| // `insert` or `remove` operations, and so the order of the traversal |
| // is depth first when built lazily through the iterator. |
| // |
| // This is faster than `_stack.addAll(children.reversed)`, presumably since |
| // we don't actually care about maintaining an iteration pointer. |
| while (children.isNotEmpty) { |
| _stack.add(children.removeLast()); |
| } |
| } |
| |
| /// Collect the children from [root] in the order they are expected to traverse. |
| List<ItemType> _collectChildren(ItemType root); |
| } |
| |
| /// [Element.visitChildren] does not guarantee order, but does guarantee stable |
| /// order. This iterator also guarantees stable order, and iterates in a left |
| /// to right order: |
| /// |
| /// 1 |
| /// / \ |
| /// 2 3 |
| /// / \ / \ |
| /// 4 5 6 7 |
| /// |
| /// Will iterate in order 2, 4, 5, 3, 6, 7. |
| /// |
| /// Performance is important here because this class is on the critical path |
| /// for flutter_driver and package:integration_test performance tests. |
| /// Performance is measured in the all_elements_bench microbenchmark. |
| /// Any changes to this implementation should check the before and after numbers |
| /// on that benchmark to avoid regressions in general performance test overhead. |
| /// |
| /// If we could use RTL order, we could save on performance, but numerous tests |
| /// have been written (and developers clearly expect) that LTR order will be |
| /// respected. |
| class _DepthFirstElementTreeIterator extends _DepthFirstTreeIterator<Element> { |
| _DepthFirstElementTreeIterator(super.root, this.includeOffstage); |
| |
| final bool includeOffstage; |
| |
| @override |
| List<Element> _collectChildren(Element root) { |
| final List<Element> children = <Element>[]; |
| if (includeOffstage) { |
| root.visitChildren(children.add); |
| } else { |
| root.debugVisitOnstageChildren(children.add); |
| } |
| |
| return children; |
| } |
| } |
| |
| /// Iterates the semantics tree starting at the given `root`. |
| /// |
| /// This will iterate in the same order expected from accessibility services, |
| /// so the results can be used to simulate the same traversal the engine will |
| /// make. The results are not filtered based on flags or visibility, so they |
| /// will need to be further filtered to fully simulate an accessibility service. |
| class _DepthFirstSemanticsTreeIterator extends _DepthFirstTreeIterator<SemanticsNode> { |
| _DepthFirstSemanticsTreeIterator(super.root, this.order); |
| |
| final DebugSemanticsDumpOrder order; |
| |
| @override |
| List<SemanticsNode> _collectChildren(SemanticsNode root) { |
| return root.debugListChildrenInOrder(order); |
| } |
| } |