Skip to content

Text selection context menu does not reappear on non-fling scroll end #185052

Description

@Renzo-Olivares

Description

When a context menu (toolbar) is visible and the user performs a short, non-fling scroll that keeps the selection in view, the toolbar does not re-appear after the scroll ends. A subsequent scroll causes the toolbar to appear at the beginning and remain visible throughout the entire scroll, overlapping UI elements like AppBars.

Steps to reproduce

  1. Open an app with a scrollable TextField containing enough text to scroll (e.g., TextField(maxLines: 5, controller: TextEditingController(text: 'Hello world! ' * 200)))
  2. Long press to select a word and show the context menu
  3. Perform a very short, slow scroll (non-fling) — just enough to move the content slightly while keeping the selection in view
  4. Release the gesture

Expected:

The toolbar re-appears after the scroll ends since the selection is still in view.

Screen.Recording.2026-04-14.at.3.46.01.PM.mov

Actual:

The toolbar stays hidden. A second scroll causes the toolbar to appear at the start of the scroll and remain visible throughout, never hiding.

Screen.Recording.2026-04-14.at.3.42.55.PM.mov

Root cause

In _handleContextMenuOnScroll, when a ScrollEndNotification is received, a callback is registered via addPostFrameCallback to re-show the toolbar if the selection is still in view. However, addPostFrameCallback does not call scheduleFrame(). If no other mechanism independently schedules a frame, hasScheduledFrame remains false and the callback never fires.

On device without accessibility services, the only other candidate for scheduling a frame during the scroll-end transition is setIgnorePointer in ScrollPosition.beginActivity, which calls markNeedsSemanticsUpdate() on the RenderIgnorePointer. But markNeedsSemanticsUpdate() early-returns without scheduling a frame when no SemanticsOwner exists — which is the case on any device without a screen reader active:

// In RenderObject.markNeedsSemanticsUpdate():

if (!attached || owner!._semanticsOwner == null) {
  _needsSemanticsUpdate = true;
  return; // No frame scheduled
}

This means after a non-fling scroll (where no ballistic simulation drives further frames), no frame is ever scheduled, and the addPostFrameCallback callback sits pending indefinitely.

Metadata

Metadata

Labels

P2Important issues not at the top of the work listteam-text-inputOwned by Text Input teamtriaged-text-inputTriaged by Text Input team

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions