Cache the paint offset adjusted line metrics in TextPainter - #191223
auto-submit[bot] merged 4 commits into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request caches shifted line metrics in _TextPainterLayoutCacheWithOffset to ensure that computeLineMetrics() returns the same list instance when the layout and paint offset do not change, preventing issues with VerticalCaretMovementRun in non-left-aligned text. Regression tests are also added to verify this behavior. The review feedback suggests updating the documentation comment for the lineMetrics getter to use triple-slash comments to align with the Flutter Style Guide.
Renzo-Olivares
left a comment
There was a problem hiding this comment.
The approach looks good to me, just had a few small comments.
|
Thanks for the review! All three addressed in 352d0ea:
|
Renzo-Olivares
left a comment
There was a problem hiding this comment.
LGTM modulo my last comment.
|
Applied the wording fix in 5301b12. The push dismissed your approval, mind re-approving? PTAL @Renzo-Olivares |
Renzo-Olivares
left a comment
There was a problem hiding this comment.
LGTM, this will need a secondary approval before landing. I'll find a reviewer.
|
Hi @LongCatIsLooong! Just wanted to gently follow up on the review request for #191223 whenever you get a chance. Renzo has already approved it, and I’ve addressed the previous review comments. No rush though, I just wanted to make sure it didn’t get lost in the queue. Thanks! |
LongCatIsLooong
left a comment
There was a problem hiding this comment.
Thanks for the fix! LGTM.
|
autosubmit label was removed for flutter/flutter/191223, 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. |
|
autosubmit label was removed for flutter/flutter/191223, 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. |
|
autosubmit label was removed for flutter/flutter/191223, because - The status or check suite ci.yaml validation has failed. Please fix the issues identified (or deflake) before re-applying this label. |
`TextPainter.computeLineMetrics` translates the paragraph's line metrics by the paint offset, which is only non-zero when the text is not left aligned. That translation allocated a brand new list on every call, and `VerticalCaretMovementRun.isValid` uses the identity of that list to detect text layout changes, so a vertical caret run was invalidated as soon as it was created whenever the text was not left aligned. The most visible symptom is a single line `TextField` or `CupertinoTextField` with `TextAlign.center` / `TextAlign.right`: such a field lays its text out with an unbounded width, so the paint offset is non-zero, and pressing the up or down arrow key asserts in debug mode and leaves the caret in place in release mode. Cache the translated list on the layout cache instead, keyed on the paint offset it was computed with, so `computeLineMetrics` keeps returning the same instance while the text layout and the paint offset are unchanged. Fixes #139201
- Drop the VerticalCaretMovementRun rationale from the lineMetrics doc, it is review context rather than lasting documentation. - Rename the local variable that was shadowed by the map closure. - Do not mention the paint offset in computeLineMetrics's public doc, it is not part of the public API.
The method, not the returned list, is what returns the same instance.
87abbc1 to
7719a07
Compare
…#13060) Manual roll Flutter from 4fcd90be0045 to c5a061b18fa2 (222 revisions) Manual roll requested by quncheng@google.com flutter/flutter@4fcd90b...c5a061b 2026-09-28 brackenavaron@gmail.com [cross imports] navigator_test.dart (flutter/flutter#190954) 2026-09-28 154381524+flutteractionsbot@users.noreply.github.com Revert: Reland: Only render views that need to be rendered (flutter/flutter#193360) 2026-09-28 codefu@google.com ci(bringup): android_intent_security_test is green (flutter/flutter#193469) 2026-09-28 154381524+flutteractionsbot@users.noreply.github.com Sync CHANGELOG.md from stable (flutter/flutter#193020) 2026-09-28 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from ukukV5lEkKabtOATk... to QdgqP02_cYpRQQQNN... (flutter/flutter#193454) 2026-09-28 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#193460) 2026-09-28 victorsanniay@gmail.com Un-nest sceneBuildDuration and windowRenderDuration in web SceneBuilderRecorder (flutter/flutter#193260) 2026-09-28 zarah@google.com Fix unresolved doc comment references in dev/integration_tests and dev/benchmarks (flutter/flutter#193433) 2026-09-28 engine-flutter-autoroll@skia.org Roll Packages from e55e7ac to ba0364a (9 revisions) (flutter/flutter#193450) 2026-09-28 bkonyi@google.com [tool] Require explicit dependency injection for FlutterDevice and FlutterDevice.create (flutter/flutter#192830) 2026-09-28 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#193442) 2026-09-28 dacoharkes@google.com [flutter_tools] Include data assets from hooks when pubspec.yaml is empty (flutter/flutter#193434) 2026-09-28 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#193438) 2026-09-28 zarah@google.com Fix unresolved doc comment references in Material and Cupertino (flutter/flutter#193283) 2026-09-28 zarah@google.com Fix doc references to Material and Cupertino in the widgets library and its tests (flutter/flutter#193334) 2026-09-26 kevmoo@users.noreply.github.com wasm: enforce WasmGC opt-in capability checks and Firefox < 147 guard (flutter/flutter#193180) 2026-09-26 43054281+camsim99@users.noreply.github.com [Android] Refuse external setters of engine entrypoint and cached engine arguments via `Intent`s (flutter/flutter#190249) 2026-09-26 1961493+harryterkelsen@users.noreply.github.com [web] Unskip TextPainter.getWordBoundary test (flutter/flutter#193378) 2026-09-26 bkonyi@google.com Specify non-obvious types in pattern variable declarations (flutter/flutter#192632) 2026-09-26 30870216+gaaclarke@users.noreply.github.com Removes feedback loop from advanced filters without offscreen msaa and framebufferfetch (flutter/flutter#193306) 2026-09-26 1961493+harryterkelsen@users.noreply.github.com [web] Unskip 8 passing image tests in painting and rendering (flutter/flutter#193377) 2026-09-26 154381524+flutteractionsbot@users.noreply.github.com Revert: Allow resetting test invariants in `addTearDown` (flutter/flutter#193383) 2026-09-25 bkonyi@google.com [tool] Migrate CoverageCollector to modular dependency injection (flutter/flutter#192924) 2026-09-25 zarah@google.com Fix more unresolved doc comment references (flutter/flutter#193336) 2026-09-25 dkwingsmt@users.noreply.github.com Allow resetting test invariants in `addTearDown` (flutter/flutter#192082) 2026-09-25 jhy03261997@gmail.com [a11y] Add a semantics role for slider (flutter/flutter#193324) 2026-09-25 stuartmorgan@google.com Fix plugin tests after Pigeon plugin template changes (flutter/flutter#193302) 2026-09-25 jesswon@google.com [Android 17] Bump Standard Test Apps in flutter/flutter to AGP 9.3.1 (flutter/flutter#193263) 2026-09-25 97480502+b-luk@users.noreply.github.com SSBO-based gradients in UberSDF (flutter/flutter#192962) 2026-09-25 robert.ancell@canonical.com [Linux] Handle FlView being destroyed before rendering is complete. (flutter/flutter#193268) 2026-09-25 bkonyi@google.com [flutter_tools] Handle Windows reserved characters in test target path (flutter/flutter#191900) 2026-09-25 sneurlax@gmail.com docs(tools): nit: say hook/build.dart in CMake native assets comment (flutter/flutter#192205) 2026-09-25 kevmoo@users.noreply.github.com [web] Enable Firefox Skwasm UI CI suites, configure COI configs, and fail fast on loader rejections (flutter/flutter#193187) 2026-09-25 jesswon@google.com [Android 17] Bumped Engine Dependencies to 9.3.1 (flutter/flutter#193265) 2026-09-25 kevmoo@users.noreply.github.com [web] Omit group role on menu scrollables and assign region role to named routes (flutter/flutter#192965) 2026-09-25 engine-flutter-autoroll@skia.org Roll Skia from 8eedeed98e79 to f441ca223b2b (5 revisions) (flutter/flutter#193353) 2026-09-25 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from EbPpoJW-Lnsu-8dyZ... to ukukV5lEkKabtOATk... (flutter/flutter#193326) 2026-09-25 engine-flutter-autoroll@skia.org Roll Packages from 431ea69 to e55e7ac (6 revisions) (flutter/flutter#193351) 2026-09-25 139053348+shikharish@users.noreply.github.com Support DynamicLibrary.codeAsset in Flutter (flutter/flutter#188947) 2026-09-25 engine-flutter-autoroll@skia.org Roll Dart SDK from 75d9e87d4e3d to 0e7642b85457 (1 revision) (flutter/flutter#193346) 2026-09-25 15619084+vashworth@users.noreply.github.com Exclude iOS PRs from macOS link (flutter/flutter#193315) 2026-09-25 engine-flutter-autoroll@skia.org Roll Skia from 6357608543ec to 8eedeed98e79 (4 revisions) (flutter/flutter#193341) 2026-09-25 engine-flutter-autoroll@skia.org Roll Dart SDK from d3aedb186aab to 75d9e87d4e3d (3 revisions) (flutter/flutter#193335) 2026-09-25 nickolasdeluca@live.com Cache the paint offset adjusted line metrics in TextPainter (flutter/flutter#191223) ...
Fixes #139201
The bug
TextPainter.computeLineMetricstranslates the paragraph's line metrics bypaintOffset, which is only non-zero when the text is not left aligned. That translation built a brand new list on every call:VerticalCaretMovementRun.isValiduses the identity of that list to detect text layout changes:So whenever the paint offset is non-zero, a vertical caret run was invalid the instant it was created. This matches @LongCatIsLooong's diagnosis in #139201 (comment).
The user visible symptom is a single line
TextField/CupertinoTextFieldwithTextAlign.centerorTextAlign.right: a single line field lays its text out with an unbounded width (RenderEditable._adjustConstraintspassesdouble.infinityasmaxWidthwhen!_isMultiline), socontentWidth != paragraph.widthand the paint offset is non-zero. Pressing the up or down arrow key then tripsassert(isValid)inVerticalCaretMovementRun.moveNext/movePreviousin debug mode, and silently leaves the caret where it is in release mode.The fix
Cache the translated list on
_TextPainterLayoutCacheWithOffset, keyed on the paint offset it was computed with (paintOffsetis derived from the mutablecontentWidth, so it can change without a relayout).computeLineMetricsthen keeps returning the same list instance for as long as the text layout and the paint offset are unchanged, which restores the identity contractVerticalCaretMovementRundepends on. It also stops allocating a fresh list ofLineMetricson every call for every non left aligned paragraph.Relation to #160516
#160516 fixed this issue by relaxing
isValidtolistEqualsinstead. That works, but it compares every line's metrics on every arrow key press, it keeps allocating the intermediate list, and it makes a run stay valid across a genuine layout change that happens to produce equal metrics (the run's_positionCachewould then be stale). This PR fixes the identity contract that was broken instead, and leavesisValidalone. That PR was closed for reasons unrelated to this issue (the RTL arrow direction work for #78660).Tests
TextPainter line metrics are cached when the paint offset is not zero— assertscomputeLineMetrics()returns an identical list across calls, for everyTextAlign.TextPainter line metrics are shifted by the paint offset— guards the translation itself, which now happens in a new place.run in a single line field that is not left alignedineditable_text_shortcuts_test.dart— the end to end regression test; it throws the'isValid': is not trueassertion without this patch.All three fail on master and pass with this change.
test/painting,test/rendering/editable_test.dart,test/rendering/paragraph_test.dart,test/widgets/editable_text_test.dart,test/widgets/editable_text_shortcuts_test.dart,test/widgets/text_test.dart,test/widgets/selectable_region_test.dart,test/material/text_field_test.dart,test/material/text_form_field_test.dart,test/material/selectable_text_test.dartandtest/cupertino/text_field_test.dartare green.Pre-launch Checklist
///).