Add/remove overlay child RenderObject from the tree in attach/detach#186564
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors RenderObject mutation checks and OverlayPortal lifecycle management. It introduces _debugClosestMutationRoot to streamline mutation permission logic and replaces boolean flags with a counter in _RenderTheater to handle deferred child updates more robustly. Additionally, _RenderLayoutSurrogateProxyBox now manages its own attachment lifecycle. Feedback includes a potential regression where a field assignment was replaced by an assertion in _OverlayPortalElement.insertRenderObjectChild, along with minor typo corrections in documentation comments.
| assert(renderObject._deferredLayoutChild == child); | ||
| slot._addChild(child as _RenderDeferredLayoutBox); |
There was a problem hiding this comment.
The assignment to renderObject._deferredLayoutChild was removed and replaced with an assert. However, insertRenderObjectChild is the primary point where the parent element receives the child's RenderObject. If this field is not set before this call, the assert will fail. Furthermore, the logic in attach and detach in _RenderLayoutSurrogateProxyBox depends on this field being correctly populated. Unless the assignment was moved to a location not shown in this diff (like a new setter or a different part of the element lifecycle), this appears to be a regression.
renderObject._deferredLayoutChild = child as _RenderDeferredLayoutBox;
slot._addChild(child as _RenderDeferredLayoutBox);There was a problem hiding this comment.
Actually the suggested change is more robust if we change the lifecycle of this in the future. But I think doing it during the construction of the render object makes the lifecycle of the variable easier to understand.
There was a problem hiding this comment.
added a comment.
There was a problem hiding this comment.
maybe the comment could be a little more explicit for findability? e.g
// _deferredLayoutChild is assigned in _DeferredLayout.createRenderObject.
|
|
||
| bool _debugMutationsLocked = false; | ||
|
|
||
| // Returns the closest ancestor that determines whether this RenderObject is |
There was a problem hiding this comment.
This is the same exact logic as before just restructured it a little bit.
This seems like will be a problem even if this is first build, how does this not crash every time? |
|
| markNeedsCompositingBitsUpdate(); | ||
| markNeedsSemanticsUpdate(); | ||
| child._parent = this; | ||
| if (attached) { |
There was a problem hiding this comment.
This breaks abstracting of attached. If we later update the attached getter to take more thing into account, this may break.
I think we should revert this
There was a problem hiding this comment.
Ah you mean someone overrides attached so it becomes something like super.attached && someOtherCondition? That is most likely not legal because the framework assumes that every render object in the tree must be attached to the same owner and every node must be attached when they're in the tree. So basically attached is synonymous with owner != null.
There was a problem hiding this comment.
I guess we should mark attached @nonVirtual?
There was a problem hiding this comment.
Or do you mean at some point we may change the framework so we can mark the entire tree detached despite the nodes have an owner, or mark the entire tree attached when the nodes don't have an owner?
There was a problem hiding this comment.
both, but mainly for us.
we can mark the entire tree detached despite the nodes have an owner, or mark the entire tree attached when the nodes don't have an owner?
right
There was a problem hiding this comment.
Undid the change. I think still we should add @nonVirtual to attached to make sure it's interchangeable with owner != null, since currently it's illegal to change the definition of attached (the framework assumes attached means owner != null or _owner != null and checks _owner instead of attached most of the time).
There was a problem hiding this comment.
This is not safe though.
Part of a reason why semantics compiling is hard to refactor is that they made a bunch of assumption based on mix match different property state, and I have to try my best to create a bunch of getter to make it more manageable.
I can totally imagine we later introduce addition logic to the getter that can mess up the things if the code assume _ower != null
There was a problem hiding this comment.
attached literally means wether this RenderObject is attached to an owner. Today it has to be synonymous with owner != null or you'll likely get an assert when calling addChild / dropChild (I think it is an intended invariant that all RenderObjects in the tree must be attached). If someone really wants to override the behavior of attached, they should override owner instead (IOW owner is going to be the single source of truth and attached is a convenience getter).
I don't see how we would want to change attached to account for some other flags, but I think we should consider changing the owner getter instead if we ever want to so attached and owner != null still mean the same thing (or introduce new flags to represent the new possible states) to avoid breaking existing code.
There was a problem hiding this comment.
The case I imagine will be something like
bool get attached {
if (inSomeWeirdLimboState) {
return false;
}
return owner != null;
}It is not the first time we have cases where we have to keep render object dangling in certain lifecycle phase without moving it to another lifecycle phase
I think we should consider changing the owner getter
you meant even through renderobject has _owner, but we still return null base on condition? I know we do that in some places, but it always feels a bit weird to me
There was a problem hiding this comment.
Ideally we don't have to touch neither attached or owner if we have to introduce a different lifecycle state (like offscreen or something if that's what you meant). The semantics of these two are already broadly accepted and changing them will probably break existing code. We can introduce a new source of truth (e.g., lifecycleState) and have owner and attached both reflect that. I find it confusing if attached needs to account for something that's not owner since the documentation literally says this means if this RO is attached to an owner (unless there is an example of inSomeWeirdLimboState?).
| // Detaches the deferred child if this node is being detached, but only if the theater isn't | ||
| // already detached (so the deferred child will be detached by the theater). | ||
| if (_deferredLayoutChild case final deferredChild? when deferredChild.theater.attached) { | ||
| overlayLocation!._deactivate(deferredChild); |
There was a problem hiding this comment.
_deactivate and _active should probably be named to _attach and _detach
There was a problem hiding this comment.
I couldn't come up with better names so I didn't change them. The method calls dropChild so its not regular detach. Added documentation to _deactivate to explain why it is so named.
There was a problem hiding this comment.
hmm _detachFromLayoutParent sounds more descriptive than _detach or _deactivate, WDYT?
There was a problem hiding this comment.
went with _detachFromLayoutSurrogate to match the class name.
| }()); | ||
| } | ||
|
|
||
| bool _didDeactivate = false; |
There was a problem hiding this comment.
this also need a better name
you are right, I forgot this part, so basically global key reparent a opened overlayportal will just crash 100% of the time? |
Not necessarily. The OverlayPortal needs to be in a LayoutBuilder because the mutation checks are only performed during layout. Also the overlay child needs to be marked as dirty after it is detached from the tree (See the test). |
| assert(renderObject._deferredLayoutChild == child); | ||
| slot._addChild(child as _RenderDeferredLayoutBox); |
There was a problem hiding this comment.
maybe the comment could be a little more explicit for findability? e.g
// _deferredLayoutChild is assigned in _DeferredLayout.createRenderObject.
chunhtai
left a comment
There was a problem hiding this comment.
LGTM, just some question about the code comments
| /// Undoes _detachFromLayoutSurrogate by adding the given `child` back to the | ||
| /// `_theater`. | ||
| /// | ||
| /// This is called when the OverlayPortal is activated. |
There was a problem hiding this comment.
should the is activated => is attached?
There was a problem hiding this comment.
The only case this can happen is when the element reactivates I think (and this method is not always called on attach, unless _didDetachDeferredChild is true meaning the OverlayPortalElement has been deactivated but the Overlay is not).
There was a problem hiding this comment.
Same for _detachFromLayoutSurrogate: it's only called if OverlayPortalElement is deactivating but the Overlay is still in the tree.
There was a problem hiding this comment.
I have to admit that I did not consider the case where the Overlay and OverlayPortal are both reparenting (but separately), in which case the detaching/reattaching won't happen. But that should work too because the detach/attach shenanigans are just for maintaining the "every RO in the tree must be attached" invariant and does not have to guarantee the target Overlay is still the overlay we're targeting. When OverlayPortal rebuilds after activation it will move the overlay child to the correct overlay.
| /// Removes the given `child` from the `_theater` but keeps it in the child list | ||
| /// (unlike `_removeChild`). | ||
| /// | ||
| /// This is typically called when the [OverlayPortal] deactivates. Since every |
There was a problem hiding this comment.
same deactivates => right before detached
There was a problem hiding this comment.
I think deactivate is more accurate (for reasons stated in the comment above).
|
Should #177693 also be added to this PR description so it is tracked here? Not sure if merging this PR will close that issue. |
|
autosubmit label was removed for flutter/flutter/186564, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR. |
|
auto label is removed for flutter/flutter/186564, Failed to enqueue flutter/flutter/186564 with HTTP 400: Pull request Required status check "Merge Queue Guard" is expected.. |
|
Should #187162 also be added to the PR description? |
flutter/flutter@e70534d...b05a9d7 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from 47155534833e to d9d6b440c4e7 (1 revision) (flutter/flutter#187301) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from f93ed13d77fb to 47155534833e (4 revisions) (flutter/flutter#187291) 2026-05-29 kevmoo@users.noreply.github.com [web_ui] Optimize skwasm text layout and path decoding to eliminate dynamic boxing churn under Wasm (flutter/flutter#186978) 2026-05-29 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from SBpmmPxqx3lAvGojE... to jMR_VXQi07kAk8vbR... (flutter/flutter#187279) 2026-05-29 burak.karahan@mail.ru Remove Material import from sliver tree rendering test (flutter/flutter#187000) 2026-05-29 bdero@google.com [Impeller] Remove the y_coord_scale Y-flip plumbing (flutter/flutter#187224) 2026-05-29 31859944+LongCatIsLooong@users.noreply.github.com Add/remove overlay child RenderObject from the tree in `attach`/`detach` (flutter/flutter#186564) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from fcfe5975c945 to f93ed13d77fb (4 revisions) (flutter/flutter#187273) 2026-05-28 evanwall@buffalo.edu Handle complex RSE rendering in the uber SDF pipeline (flutter/flutter#186434) 2026-05-28 engine-flutter-autoroll@skia.org Roll Dart SDK from 082191101fcc to 683322426411 (2 revisions) (flutter/flutter#187270) 2026-05-28 mvincentong@gmail.com Clarify route transition animations (flutter/flutter#186552) 2026-05-28 116356835+AbdeMohlbi@users.noreply.github.com document that the default Key is null and explain proper usage in list diffing (flutter/flutter#185197) 2026-05-28 srawlins@google.com [flutter_tools] Use super parameters in missed spots (flutter/flutter#186197) 2026-05-28 mr_nadeem_iqbal@yahoo.com docs: Document MediaQueryData.alwaysUse24HourFormat on macOS, Windows, Linux, web (#160664) (flutter/flutter#186642) 2026-05-28 engine-flutter-autoroll@skia.org Roll Skia from 5493e4c144cd to fcfe5975c945 (3 revisions) (flutter/flutter#187256) 2026-05-28 30870216+gaaclarke@users.noreply.github.com Shares opengles golden context (flutter/flutter#187243) 2026-05-28 jason-simmons@users.noreply.github.com Update the Curl CIPD package in .ci.yaml to version 8.20.0 (flutter/flutter#187133) 2026-05-28 737941+loic-sharma@users.noreply.github.com Improve SizedBox's docs (flutter/flutter#187208) 2026-05-28 bdero@google.com [Impeller] Support instanced rendering across all backends (flutter/flutter#186653) 2026-05-28 43054281+camsim99@users.noreply.github.com [Android] Reset system UI visibility flags when setting edge-to-edge mode (flutter/flutter#187207) 2026-05-28 engine-flutter-autoroll@skia.org Roll Skia from a38708fb7926 to 5493e4c144cd (7 revisions) (flutter/flutter#187241) 2026-05-28 30870216+gaaclarke@users.noreply.github.com Turned on impeller by default on macos (flutter/flutter#186546) 2026-05-28 mvincentong@gmail.com Clarify lazy scroll extent docs (flutter/flutter#186864) 2026-05-28 mdebbar@google.com [web] Fix WebParagraph locales test (flutter/flutter#186813) 2026-05-28 engine-flutter-autoroll@skia.org Roll Packages from 4b424d7 to 10cbdc5 (3 revisions) (flutter/flutter#187238) 2026-05-28 engine-flutter-autoroll@skia.org Roll Dart SDK from f3db7b7d9801 to 082191101fcc (8 revisions) (flutter/flutter#187235) 2026-05-28 engine-flutter-autoroll@skia.org Roll Skia from 32acea791248 to a38708fb7926 (1 revision) (flutter/flutter#187221) 2026-05-28 bdero@google.com [Flutter GPU] Add r32Float and remove Apple-only XR pixel formats (flutter/flutter#187069) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC boetger@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Fixes #180569
Fixes #177693
_RenderDeferredLayoutBoxfromElement.activate/Element.deactivatetoRenderObject.attach/RenderObject.detach._addDeferredChild/_removeDeferredChildreentrant.Before this
OverlayPortalElement.deactivateremoves the overlay child render object fromOverlay. This is fine but inOverlayPortalElement.activatetheOverlayPortal's render object has not reattach yet so the RO mutation check fails because it couldn't find the ancestor actively doing layout for the overlay child render object, when it tries to re-attach.Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.