Skip to content

Conversation

@tgucio
Copy link
Contributor

@tgucio tgucio commented Feb 16, 2022

This PR ensures EditableText.bringIntoView() is called after the RenderEditable is updated with the new text value. This avoids incorrect scroll offset changes on paste.

Fixes #96658.

All relevants tests are passing:

% ../../bin/flutter test \
  test/widgets/editable_text_cursor_test.dart \
  test/widgets/editable_text_show_on_screen_test.dart \
  test/widgets/editable_text_shortcuts_tests.dart \
  test/widgets/editable_text_test.dart \
  test/cupertino/text_field_restoration_test.dart \
  test/cupertino/text_field_test.dart \
  test/cupertino/text_form_field_row_test.dart \
  test/cupertino/text_selection_test.dart \
  test/cupertino/text_selection_toolbar_button_test.dart \
  test/cupertino/text_selection_toolbar_test.dart \
  test/material/text_field_focus_test.dart \
  test/material/text_field_helper_text_test.dart \
  test/material/text_field_restoration_test.dart \
  test/material/text_field_splash_test.dart \
  test/material/text_field_test.dart \
  test/material/text_form_field_restoration_test.dart \
  test/material/text_form_field_test.dart \
  test/material/text_selection_test.dart \
  test/material/text_selection_toolbar_test.dart \
  test/material/text_selection_toolbar_text_button_test.dart \
  test/rendering/editable_gesture_test.dart \
  test/rendering/editable_test.dart \
  test/widgets/text_selection_test.dart \
  test/widgets/text_selection_toolbar_layout_delegate_test.dart

02:41 +1431: All tests passed!                 

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • 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].
  • All existing and new tests are passing.

@flutter-dashboard flutter-dashboard bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. labels Feb 16, 2022
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

One question about the _didChangeTextEditingValue case, but this approach looks good overall. Thanks for fixing this!


await resetSelectionAndScrollOffset();
textSelectionDelegate.pasteText(SelectionChangedCause.toolbar);
await textSelectionDelegate.pasteText(SelectionChangedCause.toolbar);
Copy link
Contributor

Choose a reason for hiding this comment

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

Good call, these awaits probably should have been here.

Comment on lines +1691 to +1693
// Schedule a call to bringIntoView() after renderEditable updates.
SchedulerBinding.instance.addPostFrameCallback((_) {
bringIntoView(textEditingValue.selection.extent);
Copy link
Contributor

Choose a reason for hiding this comment

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

This problem where RenderEditable is out of date is a recurring problem. I think using an addPostFrameCallback is the best option for now.

Copy link
Contributor Author

@tgucio tgucio Feb 17, 2022

Choose a reason for hiding this comment

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

Indeed, it is a bit prone to more bugs. For bringIntoView() itself, I can see it's called from:

  • text_selection.dart: handleSelectAll(), _handleSelectionHandleChanged()
  • [material|cupertino] text_field.dart: _handleSelectionChanged()
  • selectable_text: _handleSelectionChanged()
  • editable_text: copySelection(), cutSelection(), pasteText(), selectAll(), _scrollToDocumentBoundary(), _updateSelection(), _expandSelection()

The only two calls that may modify text contents/layout are cut and paste, handled by this PR so this should suffice for now.

BTW, another question (unrelated to this PR) is whether only calling bringIntoView() on SelectionChangedCause.toolbar is desired behaviour. This means that if one cuts/pastes in an area outside of the viewport using keyboard shortcuts, we don't scroll there so the user won't see what's been done to the text.

Copy link
Contributor

Choose a reason for hiding this comment

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

Glad that this PR will handle the problem here at least.

About scrolling on paste, I think you're right that it's incorrect. I've created an issue: #98675.

Comment on lines +3099 to +3102
// If there's no change in text and selection (e.g. when selecting and
// pasting identical text), the widget won't be rebuilt on value update.
// Handle this by calling _didChangeTextEditingValue() so caret and scroll
// updates can happen.
Copy link
Contributor

Choose a reason for hiding this comment

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

What corner case is this handling exactly? Maybe consider adding a test that covers this (unless I misunderstand and the existing test does cover it).

Copy link
Contributor Author

@tgucio tgucio Feb 17, 2022

Choose a reason for hiding this comment

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

Imagine you type "Lorem ipsum", select "ipsum", copy it and paste it into the same selection. Theoretically this is user input so you'd like to scroll, reset the caret animation, update selection/toolbar overlays etc. But
_didChangeTextEditingValue() won't be called because:

  • it's a listener on TextEditingController which is a ValueNotifier<TextEditingValue>
  • TextEditingValue equality operator will see the same value (text, selection, composing)
  • ValueNotifier's value setter won't call notifyListeners()

So further, since _didChangeTextEditingValue() isn't called, the widget won't be updated and there won't be a post frame callback with bringIntoView() either.

The interesting thing is that the "Selection will be scrolled into view with SelectionChangedCause" test in editable_text_test.dart already caught that although perhaps it wasn't intentional. What it does is to set a (0, 1) selection on each call to resetSelectionAndScrollOffset() and then do 2x cut and 2x paste. So you always end up pasting the same character that's already there.

Copy link
Contributor

Choose a reason for hiding this comment

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

In the example you gave, wouldn't there be a selection change?

  1. "Lorem [ipsum]"
  2. Paste "ipsum"
  3. "Lorem ipsum|"

Copy link
Contributor Author

@tgucio tgucio Feb 17, 2022

Choose a reason for hiding this comment

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

Just tried in a TextField - no selection change on paste. In MS Word though, selection changes to collapsed on pasted text end, so it does in Chrome and VS Studio so it looks like this is the natural way of things.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah you're right, that's a bug in Flutter. Ok then I think this PR is fine. I'll look into fixing this bug. I tried on native Mac and Android and it collapses after a paste on both.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great, thanks for submitting the two issues!

Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to move this to the pasteText method?

Copy link
Contributor Author

@tgucio tgucio Feb 22, 2022

Choose a reason for hiding this comment

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

Currently _replaceText() is called from:

  • cutSelection()
  • pasteText()
  • ReplaceTextIntent used by _DeleteTextAction

On cut and delete, the extra if(...) _didChangeTextEditingValue will be a no-op as text value should change. Given possible future uses of ReplaceTextIntent won't it be better to leave this in _replaceText()? (e.g. an intent to replace a selected text with the same text coming from semantics, scribble etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

My intention was to avoid bundling side effects that are sometimes unexpected (such as this one) into generic operations like replacing text. It's a bit cleaner that way IMO especially when you are reading the code in pasteText, and you'll know immediately this is going to bring the new selection into view, instead of having to trace to _replaceText and find the "bring into view" logic there. But it's just a nit.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Thanks for fixing this! I'm looking into the collapse-after-paste thing.

Comment on lines +3099 to +3102
// If there's no change in text and selection (e.g. when selecting and
// pasting identical text), the widget won't be rebuilt on value update.
// Handle this by calling _didChangeTextEditingValue() so caret and scroll
// updates can happen.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to move this to the pasteText method?

@fluttergithubbot fluttergithubbot merged commit 533a5dd into flutter:master Feb 24, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 24, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 24, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 24, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 27, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 27, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 27, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 28, 2022
clocksmith pushed a commit to clocksmith/flutter that referenced this pull request Mar 8, 2022
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 framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid TextField scroll offset change on paste from toolbar

4 participants