Skip to content

Fix TextSelectionOverlay crash when layout is degenerate#188672

Merged
auto-submit[bot] merged 15 commits into
flutter:masterfrom
Renzo-Olivares:textselectionoverlay_handle_crash_fix
Jul 7, 2026
Merged

Fix TextSelectionOverlay crash when layout is degenerate#188672
auto-submit[bot] merged 15 commits into
flutter:masterfrom
Renzo-Olivares:textselectionoverlay_handle_crash_fix

Conversation

@Renzo-Olivares

@Renzo-Olivares Renzo-Olivares commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #187644

Foldable Selection Handle Drag Crash Reproductions

The example below reproduce the Unsupported operation: Infinity or NaN toInt crash during selection handle drags under transient degenerate layouts (like folding transitions).


Example: Multi-Touch Simulator (For any phone, foldable or non-foldable)

This example uses a Slider to manually scale the height of the TextField down to 1e-310 mid-drag.

Code (main.dart)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Degenerate Transform Simulator')),
        body: const ReproWidget(),
      ),
    );
  }
}

class ReproWidget extends StatefulWidget {
  const ReproWidget({super.key});

  @override
  State<ReproWidget> createState() => _ReproWidgetState();
}

class _ReproWidgetState extends State<ReproWidget> {
  double scaleY = 1.0;
  final TextEditingController _controller = TextEditingController(
    text:
        'Select a word here, hold and drag the handle, then slide the slider to 0.',
  );

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        children: <Widget>[
          const Text('Collapse Height Scale:'),
          Slider(
            value: scaleY == 1e-310 ? 0.0 : scaleY,
            min: 0.0,
            max: 1.0,
            onChanged: (double value) {
              setState(() {
                // Collapse the height axis to a subnormal value close to 0 (1e-310)
                scaleY = value < 0.05 ? 1e-310 : value;
              });
            },
            onChangeEnd: (double value) {
              setState(() {
                scaleY = scaleY == 1e-310 ? 0.0 : value;
              });
            },
          ),
          Text(
            'Current Scale Y: ${scaleY == 1e-310 ? "1e-310 (degenerate)" : scaleY.toStringAsFixed(3)}',
          ),
          const SizedBox(height: 100),
          Center(
            child: Transform(
              transform: Matrix4.diagonal3Values(1.0, scaleY, 1.0),
              alignment: Alignment.center,
              child: SizedBox(
                width: 300,
                height: 120,
                child: TextField(
                  controller: _controller,
                  maxLines: null,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Text Input',
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Steps to Reproduce:

  1. Run the app on a device using an unpatched Flutter framework.
  2. Double-tap a word in the text field to show the selection handles.
  3. Press and hold one of the selection handles and start dragging it.
  4. While keeping your finger down dragging the handle, use your other hand to slide the slider all the way to the left (setting scaleY to 1e-310).
  5. Move your dragging finger slightly.
  6. The app will immediately crash.

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@github-actions github-actions Bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Jun 27, 2026

@navaronbracke navaronbracke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aware that this is still a draft, but just some minor nits on the tests

Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
Comment thread packages/flutter/test/material/text_selection_test.dart Outdated
@Renzo-Olivares
Renzo-Olivares force-pushed the textselectionoverlay_handle_crash_fix branch 2 times, most recently from ed0bfce to 4faf47c Compare June 30, 2026 10:21
@github-actions github-actions Bot removed the f: material design flutter/packages/flutter/material repository. label Jun 30, 2026
@Renzo-Olivares
Renzo-Olivares marked this pull request as ready for review June 30, 2026 17:21

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request prevents crashes during text selection handle dragging when the layout is degenerate (e.g., when coordinates are non-finite or the preferred line height is zero or less) by returning null from _getHandleDy and skipping the drag update. It also adds a regression test for this scenario. The review feedback suggests explicitly checking if preferredLineHeight is finite to handle potential NaN values and correcting the issue number referenced in the test comments.

Comment thread packages/flutter/lib/src/widgets/text_selection.dart
Comment thread packages/flutter/test/widgets/text_selection_test.dart Outdated
@Renzo-Olivares Renzo-Olivares added the CICD Run CI/CD label Jun 30, 2026
@Renzo-Olivares

Copy link
Copy Markdown
Contributor Author

Thank you for the review @navaronbracke! Should be ready for another one.

navaronbracke
navaronbracke previously approved these changes Jun 30, 2026
final List<TextBox> boxes = _getOrCreateLayoutTemplate().getBoxesForRange(
0,
1,
boxHeightStyle: ui.BoxHeightStyle.strut,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dot shorthand isn't enabled in the framework yet?

1,
boxHeightStyle: ui.BoxHeightStyle.strut,
);
if (boxes.isEmpty) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC this should never be empty for the template unless the font doesn't have a glyph for U+0020 which is highly unlikely.

@Renzo-Olivares Renzo-Olivares Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test, dragging selection handle does not crash when layout is degenerate (preferredLineHeight == 0), is able to force this scenario. It crashes without the change in text_painter.dart.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah setting the font size to 0. But I'd assume that would be very rare. Is the crash common?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the retuned box list will be empty?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not able to reproduce the crash on my Pixel fold, outside of the example in this PR description which uses a slider + Transform to force the crash. Seems to be a race condition that does not happen too often but has been reported internally and externally.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the expected behavior is, but does the getRectForBoxes method return a list of a single box with 0 height, or does it return an empty list?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It returns an empty list.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I was able to repo with height set to 1 and font size to 0

double? _getHandleDy(double dragDy, double handleDy) {
final double preferredLineHeight = renderObject.preferredLineHeight;
if (preferredLineHeight <= 0.0 ||
!preferredLineHeight.isFinite ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I think callers should be able to assume preferredLineHeight is a positive finite value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow, does that mean preferredLineHeight can never be 0.0 or less?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferred height should be finite I think (i don't see how skparagraph would give us an infinite paragraph height, barring bugs), so probably make it an assert?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.0 is possible if you give it a font size of 0, but probably not infinite or negative.

/// Returns null if the layout is degenerate (e.g. [RenderEditable.preferredLineHeight]
/// is zero or coordinates are non-finite), indicating that the drag update should
/// be skipped.
double? _getHandleDy(double dragDy, double handleDy) {

@LongCatIsLooong LongCatIsLooong Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this method have to use preferredLineHeight instead of actual text layout? Performance? The line diff calculation can go very wrong since it assumes every line has the same height and there are infinite lines (e.g., if there isn't enough lines then this can move the handle to a line that doesn't exist).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing it was used out of convenience here. I'm not opposed to using LineMetrics instead if that's what the alternative would be.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I was thinking about using hit testing to figure out which line it should snap to. What's the expected behavior if there isn't enough lines?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created an issue for this #188871 with some gemini help. It might be a little more involved than fixing the crash as it seems other behaviors are affected by our use of preferredLineHeight like handle positioning. What do you think about resolving this crash in this PR and doing another one to fix issue I just created.

.getBoxesForRange(0, 1, boxHeightStyle: ui.BoxHeightStyle.strut)
.single;
return textBox.toRect().height;
final List<TextBox> boxes = _getOrCreateLayoutTemplate().getBoxesForRange(

@LongCatIsLooong LongCatIsLooong Jul 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for this API? Just want to make sure when the new issue you filed is fixed we can still catch it if getFullHeightForCaret somehow regresses. Also could you add a comment near the isEmpty check that says the list can be empty when font size is 0 and textStyle.height is non-zero?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added!

Comment thread packages/flutter/test/widgets/text_selection_test.dart
LongCatIsLooong
LongCatIsLooong previously approved these changes Jul 1, 2026
@Renzo-Olivares
Renzo-Olivares force-pushed the textselectionoverlay_handle_crash_fix branch from a46547b to 267a2bd Compare July 6, 2026 19:46
@Renzo-Olivares
Renzo-Olivares force-pushed the textselectionoverlay_handle_crash_fix branch from 267a2bd to 5115698 Compare July 6, 2026 20:24
@Renzo-Olivares
Renzo-Olivares force-pushed the textselectionoverlay_handle_crash_fix branch from 5115698 to 4165593 Compare July 6, 2026 21:10
@Renzo-Olivares Renzo-Olivares added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 7, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Jul 7, 2026
Merged via the queue into flutter:master with commit 228df7e Jul 7, 2026
90 of 92 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jul 7, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Jul 10, 2026
…12169)

Manual roll Flutter from 91939cc4db78 to dc2a8703e12b (50 revisions)

Manual roll requested by [email protected]

flutter/flutter@91939cc...dc2a870

2026-07-09 [email protected] [ios,macos] Update swiftc.py flags to match swiftc (flutter/flutter#189174)
2026-07-09 [email protected] [AGP 9] Update Warn Version to AGP 9+ (flutter/flutter#189109)
2026-07-09 [email protected] Sync CHANGELOG.md from stable (flutter/flutter#189203)
2026-07-09 [email protected] [web] Roll Chrome to 145 (framework) (flutter/flutter#182861)
2026-07-09 [email protected] Roll Packages from 52d84d6 to 20928d5 (6 revisions) (flutter/flutter#189194)
2026-07-09 [email protected] [web] Avoid absolute positioning for base CanvasKit canvas (flutter/flutter#188337)
2026-07-09 [email protected] Roll Dart SDK from cdb7217e65aa to a11fb7ed40a5 (6 revisions) (flutter/flutter#189195)
2026-07-09 [email protected] Fix dereference of nullptr in the moved-to-rect signal in the Linux embedder (flutter/flutter#189152)
2026-07-09 [email protected] Fix data for design packages (flutter/flutter#189140)
2026-07-09 [email protected] Roll Skia from 7b42d1251d54 to ab3a7b98c94d (2 revisions) (flutter/flutter#189181)
2026-07-09 [email protected] Roll Skia from 05d9d214e0b7 to 7b42d1251d54 (2 revisions) (flutter/flutter#189175)
2026-07-09 [email protected] Roll Skia from 542c8bdd7f4f to 05d9d214e0b7 (4 revisions) (flutter/flutter#189169)
2026-07-09 [email protected] UberSDF rect handling for thin (line-like) rectangles (flutter/flutter#188821)
2026-07-09 [email protected] Roll Skia from dd572c07f63c to 542c8bdd7f4f (4 revisions) (flutter/flutter#189160)
2026-07-09 [email protected] [flutter_tools] Fix hot restart for WASM web builds (flutter/flutter#187898)
2026-07-08 [email protected] Split FlViewRenderer into OpenGL and software backends (flutter/flutter#188824)
2026-07-08 [email protected] Promote android_hardware_smoke_tests out of bringup in CI (flutter/flutter#189081)
2026-07-08 [email protected] Roll Skia from 8df24be66531 to dd572c07f63c (4 revisions) (flutter/flutter#189150)
2026-07-08 [email protected] Expose LinuxWindowRegistrar on _window_linux.dart in order to better support out of tree LinuxWindowingOwners (flutter/flutter#188917)
2026-07-08 [email protected] Roll pub packages (flutter/flutter#189149)
2026-07-08 [email protected] fix(ci): harden some workflows (flutter/flutter#189087)
2026-07-08 [email protected] Roll Skia from 51a62da33da0 to 8df24be66531 (1 revision) (flutter/flutter#189139)
2026-07-08 [email protected] Roll Dart SDK to Dart 3.13 beta3 (flutter/flutter#189122)
2026-07-08 [email protected] [flutter_tools] Don't crash on non-UTF-8 plugin pubspec.yaml (flutter/flutter#188976)
2026-07-08 [email protected] [flutter_tools] Watch transitive #include headers for FragmentProgram hot reload (flutter/flutter#187945)
2026-07-08 [email protected] Roll Skia from 040d9f55de00 to 51a62da33da0 (1 revision) (flutter/flutter#189135)
2026-07-08 [email protected] Roll Packages from 92525f5 to 52d84d6 (7 revisions) (flutter/flutter#189134)
2026-07-08 [email protected] [flutter_tools] Forcefully kill hung subprocesses 5 seconds after timeout (flutter/flutter#187178)
2026-07-08 [email protected] Expose the app's build name and number as compile-time constants (flutter/flutter#187935)
2026-07-08 [email protected] Roll Skia from 1ff92f879815 to 040d9f55de00 (1 revision) (flutter/flutter#189131)
2026-07-08 [email protected] Roll Skia from 6137414bef5c to 1ff92f879815 (6 revisions) (flutter/flutter#189126)
2026-07-08 [email protected] [test cross imports] More test/rendering + flutter_test/test fixes (flutter/flutter#188954)
2026-07-08 [email protected] engine: explain why each candidate build was skipped in Flutter web loader (flutter/flutter#186254)
2026-07-08 [email protected] vscode: add missing unicode.h (flutter/flutter#189102)
2026-07-08 [email protected] Roll Fuchsia Linux SDK from 7RjQJBW3m-3Jl-7jr... to QcRFUtvCw2EobfJ8s... (flutter/flutter#189104)
2026-07-08 [email protected] Roll Skia from 075fbe4778d9 to 6137414bef5c (10 revisions) (flutter/flutter#189106)
2026-07-08 [email protected] engine: warn on WASM load failure when not crossOriginIsolated (flutter/flutter#186252)
2026-07-08 [email protected] Roll Dart SDK from c9bccc09e733 to db2155f56bf3 (2 revisions) (flutter/flutter#189105)
2026-07-08 [email protected] [flutter_tools] Prevent interactive device selection in machine mode (flutter/flutter#188267)
2026-07-08 [email protected] [flutter_tools] Fix wireless ADB device discovery when serial contains spaces (flutter/flutter#187943)
2026-07-07 [email protected] [web] Fix grouped autofill on iOS Chrome (flutter/flutter#187459)
2026-07-07 [email protected] Fix TextSelectionOverlay crash when layout is degenerate (flutter/flutter#188672)
2026-07-07 [email protected] [flutter_tools] Provision Android NDK in the main Gradle invocation (flutter/flutter#186337)
2026-07-07 [email protected] Android_hardware_smoke_test: Migrate to AGP 9 (flutter/flutter#189082)
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: text input Entering text in a text field or keyboard related problems CICD Run CI/CD framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

3 participants