Skip to content

fix: per-instance tooltip render-object registry to stop two-tooltip hit-test crash - #660

Open
mtallenca wants to merge 1 commit into
SimformSolutionsPvtLtd:masterfrom
mtallenca:fix/tooltip-per-instance-render-objects
Open

fix: per-instance tooltip render-object registry to stop two-tooltip hit-test crash#660
mtallenca wants to merge 1 commit into
SimformSolutionsPvtLtd:masterfrom
mtallenca:fix/tooltip-per-instance-render-objects

Conversation

@mtallenca

Copy link
Copy Markdown

Problem

RenderBox.hitTest throws a fatal FlutterError ("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.hitTestChildren frames 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 static map, RenderObjectManager.renderObjects, keyed only by TooltipLayoutSlot (tooltipBox / actionBox / arrow):

  • _RenderPositionDelegate.performLayout() calls _identifyChildren(), which registers each child into the static map, lays the children out through it, then calls RenderObjectManager.clear().
  • When a second _RenderPositionDelegate exists at the same time, the two share that one map and clobber each other's slots.
  • With asserts disabled (release), a clobbered delegate silently skips laying out its own children, yet still sizes itself to constraints.biggest (the full screen). The next pointer event then hit-tests a never-laid-out child and crashes.

Fix

  1. Per-instance registry. Move the slot → RenderObjectManager map off the static field and onto each _RenderPositionDelegate instance (_renderObjects + _managerFor(slot)). Two delegates can no longer see or overwrite each other's children. This removes the static renderObjects map, static clear(), and the TooltipLayoutSlot.getObjectManager extension getter.

  2. Defensive hit-test guard. _RenderPositionDelegate.hitTestChildren now skips any child that was never laid out (child.hasSize). It mirrors defaultHitTestChildren, 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.

…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant