From 85ed66745a1abb921393305f867a8eae0a9a22da Mon Sep 17 00:00:00 2001 From: Richard Marshall Date: Tue, 6 Jun 2023 16:54:30 -0700 Subject: [PATCH] Prevent operations on stale editor state --- lib/src/widgets/editor.dart | 3 ++ lib/src/widgets/raw_editor.dart | 44 ++++++++++++++----- ..._editor_state_text_input_client_mixin.dart | 3 +- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index aefa21dc..667d6d7e 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -46,6 +46,9 @@ abstract class EditorState extends State /// The floating cursor is animated to merge with the regular cursor. AnimationController get floatingCursorResetController; + /// Returns true if the editor has been marked as needing to be rebuilt. + bool get dirty; + bool showToolbar(); void requestKeyboard(); diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 657c63f7..8a669c0c 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -332,6 +332,10 @@ class RawEditorState extends EditorState TextDirection get _textDirection => Directionality.of(context); + @override + bool get dirty => _dirty; + bool _dirty = false; + @override void insertContent(KeyboardInsertedContent content) { assert(widget.contentInsertionConfiguration?.allowedMimeTypes @@ -855,6 +859,7 @@ class RawEditorState extends EditorState final currentSelection = controller.selection.copyWith(); final attribute = value ? Attribute.checked : Attribute.unchecked; + _markNeedsBuild(); controller ..ignoreFocusOnTextChange = true ..formatText(offset, 0, attribute) @@ -929,9 +934,11 @@ class RawEditorState extends EditorState clearIndents = false; } else { + _dirty = false; throw StateError('Unreachable.'); } } + _dirty = false; return result; } @@ -1170,6 +1177,17 @@ class RawEditorState extends EditorState _selectionOverlay?.updateForScroll(); } + /// Marks the editor as dirty and trigger a rebuild. + /// + /// When the editor is dirty methods that depend on the editor + /// state being in sync with the controller know they may be + /// operating on stale data. + void _markNeedsBuild() { + setState(() { + _dirty = true; + }); + } + void _didChangeTextEditingValue([bool ignoreFocus = false]) { if (kIsWeb) { _onChangeTextEditingValue(ignoreFocus); @@ -1184,10 +1202,9 @@ class RawEditorState extends EditorState } else { requestKeyboard(); if (mounted) { - setState(() { - // Use controller.value in build() - // Trigger build and updateChildren - }); + // Use controller.value in build() + // Mark widget as dirty and trigger build and updateChildren + _markNeedsBuild(); } } @@ -1222,10 +1239,9 @@ class RawEditorState extends EditorState _updateOrDisposeSelectionOverlayIfNeeded(); }); if (mounted) { - setState(() { - // Use controller.value in build() - // Trigger build and updateChildren - }); + // Use controller.value in build() + // Mark widget as dirty and trigger build and updateChildren + _markNeedsBuild(); } } @@ -1258,6 +1274,11 @@ class RawEditorState extends EditorState } void _handleFocusChanged() { + if (dirty) { + SchedulerBinding.instance + .addPostFrameCallback((_) => _handleFocusChanged()); + return; + } openOrCloseConnection(); _cursorCont.startOrStopCursorTimerIfNeeded(_hasFocus, controller.selection); _updateOrDisposeSelectionOverlayIfNeeded(); @@ -1272,10 +1293,9 @@ class RawEditorState extends EditorState void _onChangedClipboardStatus() { if (!mounted) return; - setState(() { - // Inform the widget that the value of clipboardStatus has changed. - // Trigger build and updateChildren - }); + // Inform the widget that the value of clipboardStatus has changed. + // Trigger build and updateChildren + _markNeedsBuild(); } Future _linkActionPicker(Node linkNode) async { diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index fff4c5ac..396a76e1 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -91,7 +91,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState void _updateCaretRectIfNeeded() { if (hasConnection) { - if (renderEditor.selection.isValid && + if (!dirty && + renderEditor.selection.isValid && renderEditor.selection.isCollapsed) { final currentTextPosition = TextPosition(offset: renderEditor.selection.baseOffset);