flushSemantics method
Update the semantics for render objects marked as needing a semantics update.
Initially, only the root node, as scheduled by RenderObject.scheduleInitialSemantics, needs a semantics update.
This function is one of the core stages of the rendering pipeline. The semantics are compiled after painting and only after RenderObject.scheduleInitialSemantics has been called.
See RendererBinding for an example of how this function is used.
Implementation
// See [_RenderObjectSemantics]'s documentation for detailed explanations on
// what this method does.
void flushSemantics() {
if (_semanticsOwner == null) {
return;
}
if (!kReleaseMode) {
FlutterTimeline.startSync('SEMANTICS$_debugRootSuffixForTimelineEventNames');
}
assert(_semanticsOwner != null);
assert(() {
_debugDoingSemantics = true;
return true;
}());
try {
// This has to be a top-down order to be more performant. Otherwise, a parent
// change can invalidate the whole subtree.
final List<RenderObject> nodesToProcess =
_nodesNeedingSemanticsUpdate
.where((RenderObject object) => !object._needsLayout && object.owner == this)
.toList()
..sort((RenderObject a, RenderObject b) => a.depth - b.depth);
_nodesNeedingSemanticsUpdate.clear();
if (!kReleaseMode) {
FlutterTimeline.startSync('Semantics.updateChildren');
}
final RenderObject? rootNode = this.rootNode;
for (final node in nodesToProcess) {
if (node._semantics.parentDataDirty) {
// This node is either blocked by a sibling
// (via SemanticsConfiguration.isBlockingSemanticsOfPreviouslyPaintedNodes)
// or is hidden by parent through visitChildrenForSemantics. Otherwise,
// the parent node would have updated this node's parent data and it
// would not be dirty.
//
// We MUST NOT update the parent data now since it may create a gap of
// render objects with dirty parent data when this branch later rejoins
// the rendering tree.
continue;
}
node._semantics.updateChildren();
}
if (!kReleaseMode) {
FlutterTimeline.finishSync();
}
assert(() {
assert(nodesToProcess.isEmpty || rootNode != null);
if (rootNode != null) {
_RenderObjectSemantics.debugCheckForParentData(rootNode);
}
return true;
}());
if (!kReleaseMode) {
FlutterTimeline.startSync('Semantics.ensureGeometry');
}
// It is possible the updateChildren above caused some nodes to be added
// to _nodesNeedingSemanticsGeometryUpdate. Therefore, we need to
// process them here.
final List<RenderObject> nodesToProcessGeometry = _nodesNeedingSemanticsGeometryUpdate
.where(
(RenderObject object) =>
!object._needsLayout && object.owner == this && !object._semantics.parentDataDirty,
)
.toList();
_nodesNeedingSemanticsGeometryUpdate.clear();
// For every node in this list, needs to clear geometry immediate _RenderObjectSemantics
// that forms semantics node in the subtree.
//
// Case 1, this node forms semantics node, then clear the geometry of this node if it is
// a relayout boundary. Otherwise, the parent should handle clearing the geometry.
//
// Case 2, this node does not form semantics node, then clear immediate children's
// geometry if they form semantics node because this node's transform and clip may affect
// the geometry of the children.
for (final node in nodesToProcessGeometry) {
if (node._semantics.shouldFormSemanticsNode && node._semantics.geometryDirty) {
// This node is already dirty, skip it.
continue;
}
if (node._semantics.shouldFormSemanticsNode && (node._isRelayoutBoundary ?? false)) {
// If this node is a relayout boundary, it can change size without parent relayout.
// In this case, we still need to clear its geometry.
node._semantics.geometry = null;
continue;
}
if (!node._semantics.contributesToSemanticsTree) {
// This node merely presents its subtree in the mergeup, so we need to clear
// the geometry for all the semantics nodes in the mergeup.
for (final _RenderObjectSemantics child
in node._semantics.mergeUp.whereType<_RenderObjectSemantics>()) {
if (child.shouldFormSemanticsNode) {
child.geometry = null;
} else {
// Even though this node does not form a semantics node, it still
// represents a group of render objects in the subtree, where a node that
// forms a semantics node is in its _children.
for (final _RenderObjectSemantics nodeInSubtree in child._children) {
assert(nodeInSubtree.shouldFormSemanticsNode);
nodeInSubtree.geometry = null;
}
}
}
continue;
}
// If we reach here, this node either forms a node but not a relayout boundary,
// or it does not form a node but still contributes to the semantics tree.
// In both cases, we need to clear the geometry for all the semantics nodes in
// the subtree.
for (final _RenderObjectSemantics child in node._semantics._children) {
child.geometry = null;
}
}
// Used to invalidate the [_RenderObjectSemantics.firstAncestorNodeWithCleanGeometry] cache.
//
// Multiple items in [nodesToProcessGeometry] often share the same first
// ancestor, making redundant searches expensive. Passing this token allows
// nodes to reuse cached ancestor lookups.
//
// See [_RenderObjectSemantics.computeAncestorInfo] for details.
final treeShapeToken = Object();
final nodeToEnsureGeometry = <_RenderObjectSemantics>{};
for (final node in nodesToProcessGeometry) {
// We need a node that has clean geometry to start from.
node._semantics.computeAncestorInfo(treeShapeToken);
if (node._semantics.firstAncestorNodeWithCleanGeometry != null) {
nodeToEnsureGeometry.add(node._semantics.firstAncestorNodeWithCleanGeometry!);
}
}
for (final _RenderObjectSemantics node
in nodeToEnsureGeometry.toList()..sort(
(_RenderObjectSemantics a, _RenderObjectSemantics b) =>
a.renderObject.depth - b.renderObject.depth,
)) {
node.ensureGeometry();
}
if (!kReleaseMode) {
FlutterTimeline.finishSync();
}
if (!kReleaseMode) {
FlutterTimeline.startSync('Semantics.ensureSemanticsNode');
}
for (final RenderObject node in nodesToProcess.reversed) {
node._semantics.computeAncestorInfo(treeShapeToken);
final targets = <_RenderObjectSemantics>[];
if (node._semantics.geometryDirty) {
if (node._semantics.firstAncestorNodeWithCleanGeometry != null) {
targets.add(node._semantics.firstAncestorNodeWithCleanGeometry!);
}
} else {
// When this node is a semantics boundary and a layout boundary and its
// geometry becomes invisible after the ensureGeometry call above,
// the parent of this node will have to update its semantics subtree to remove
// this node from its children.
if (!node._semantics.geometry!.isVisible && !node._semantics.isRoot) {
final _RenderObjectSemantics? parentInSemanticsTree =
node._semantics.parentInSemanticsTree;
if (parentInSemanticsTree != null) {
if (!parentInSemanticsTree.geometryDirty) {
targets.add(parentInSemanticsTree);
} else {
final _RenderObjectSemantics? firstAncestorNodeWithCleanGeometry =
parentInSemanticsTree.firstAncestorNodeWithCleanGeometry;
// firstAncestorNodeWithCleanGeometry can be null if this is a blocked branch.
if (firstAncestorNodeWithCleanGeometry != null) {
targets.add(firstAncestorNodeWithCleanGeometry);
}
}
}
}
targets.add(node._semantics);
}
for (final target in targets) {
if (target.parentDataDirty) {
// This node can't be updated since it is not in the tree.
continue;
}
target.ensureSemanticsNode();
}
}
assert(() {
if (rootNode != null) {
_RenderObjectSemantics.debugCheckForBuilds(rootNode._semantics);
}
return true;
}());
if (!kReleaseMode) {
FlutterTimeline.finishSync();
}
_semanticsOwner!.sendSemanticsUpdate();
for (final PipelineOwner child in _children) {
child.flushSemantics();
}
assert(
_nodesNeedingSemanticsUpdate.isEmpty,
'Child PipelineOwners must not dirty nodes in their parent.',
);
assert(
_nodesNeedingSemanticsGeometryUpdate.isEmpty,
"Child PipelineOwners must not dirty nodes' geometry in their parent.",
);
} finally {
assert(() {
_debugDoingSemantics = false;
return true;
}());
if (!kReleaseMode) {
FlutterTimeline.finishSync();
}
}
}