fix: per-instance tooltip render-object registry to stop two-tooltip hit-test crash - #660
Open
mtallenca wants to merge 1 commit into
Conversation
…t crash
Tooltip child layout was coordinated through a process-wide static map,
`RenderObjectManager.renderObjects`, keyed only by TooltipLayoutSlot
(tooltipBox / actionBox / arrow). `_RenderPositionDelegate.performLayout`
registers its children into that map, lays them out through it, then clears
it. When two tooltips are alive at the same time — e.g. one dismissing while
the next starts, or a lingering overlay — their layouts share the one map and
clobber each other's slots. With asserts disabled (release), a clobbered
delegate skips laying out its own children yet still sizes itself to
`constraints.biggest` (the full screen), so the next pointer event hit-tests a
never-laid-out child and crashes in RenderBox.hitTest ("Cannot hit test a
render box that has never been laid out", `_size!`). Two nested
`_RenderPositionDelegate.hitTestChildren` frames in the crash stack are the
signature of two simultaneously-live tooltips.
Fixes:
- Move the slot->RenderObjectManager map onto each `_RenderPositionDelegate`
instance (`_renderObjects` + `_managerFor`), so two delegates can never see
or overwrite each other's children. Drops the `static renderObjects` map,
`static clear()`, and the `TooltipLayoutSlot.getObjectManager` extension.
- Harden `_RenderPositionDelegate.hitTestChildren` to skip any child that was
never laid out (`child.hasSize`). This mirrors `defaultHitTestChildren`; in a
healthy single-tooltip layout every child has a size so it is a no-op, and it
turns any residual never-laid-out hit test into a harmless miss instead of a
crash.
Related to the two-simultaneous-showcases family in issue SimformSolutionsPvtLtd#563.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
RenderBox.hitTestthrows a fatalFlutterError("Cannot hit test a render box that has never been laid out", i.e._size!on a null size) when two tooltips are alive at the same time — e.g. one showcase being dismissed as the next one starts, or a lingering overlay. In our crash reports the signature is two nested_RenderPositionDelegate.hitTestChildrenframes in the hit-test stack. This is the same family as #563 (two simultaneous showcases).Root cause
Tooltip child layout is coordinated through a process-wide
staticmap,RenderObjectManager.renderObjects, keyed only byTooltipLayoutSlot(tooltipBox/actionBox/arrow):_RenderPositionDelegate.performLayout()calls_identifyChildren(), which registers each child into the static map, lays the children out through it, then callsRenderObjectManager.clear()._RenderPositionDelegateexists at the same time, the two share that one map and clobber each other's slots.constraints.biggest(the full screen). The next pointer event then hit-tests a never-laid-out child and crashes.Fix
Per-instance registry. Move the slot →
RenderObjectManagermap off thestaticfield and onto each_RenderPositionDelegateinstance (_renderObjects+_managerFor(slot)). Two delegates can no longer see or overwrite each other's children. This removes thestatic renderObjectsmap,static clear(), and theTooltipLayoutSlot.getObjectManagerextension getter.Defensive hit-test guard.
_RenderPositionDelegate.hitTestChildrennow skips any child that was never laid out (child.hasSize). It mirrorsdefaultHitTestChildren, so in a healthy single-tooltip layout every child has a size and it behaves identically; it only matters during a transient overlap, where it turns a hard crash into a harmless missed hit on a tooltip that is on its way out.No public API changes to
Showcase/ShowcaseView. The removed symbols (RenderObjectManager.renderObjects,.clear(),TooltipLayoutSlot.getObjectManager) are internal to the tooltip layout system.