Prevent operations on stale editor state

pull/1256/head
Richard Marshall 2 years ago
parent 4dd4212b9a
commit 85ed66745a
No known key found for this signature in database
GPG Key ID: 45CD5A14FB29C4D7
  1. 3
      lib/src/widgets/editor.dart
  2. 44
      lib/src/widgets/raw_editor.dart
  3. 3
      lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart

@ -46,6 +46,9 @@ abstract class EditorState extends State<RawEditor>
/// The floating cursor is animated to merge with the regular cursor. /// The floating cursor is animated to merge with the regular cursor.
AnimationController get floatingCursorResetController; AnimationController get floatingCursorResetController;
/// Returns true if the editor has been marked as needing to be rebuilt.
bool get dirty;
bool showToolbar(); bool showToolbar();
void requestKeyboard(); void requestKeyboard();

@ -332,6 +332,10 @@ class RawEditorState extends EditorState
TextDirection get _textDirection => Directionality.of(context); TextDirection get _textDirection => Directionality.of(context);
@override
bool get dirty => _dirty;
bool _dirty = false;
@override @override
void insertContent(KeyboardInsertedContent content) { void insertContent(KeyboardInsertedContent content) {
assert(widget.contentInsertionConfiguration?.allowedMimeTypes assert(widget.contentInsertionConfiguration?.allowedMimeTypes
@ -855,6 +859,7 @@ class RawEditorState extends EditorState
final currentSelection = controller.selection.copyWith(); final currentSelection = controller.selection.copyWith();
final attribute = value ? Attribute.checked : Attribute.unchecked; final attribute = value ? Attribute.checked : Attribute.unchecked;
_markNeedsBuild();
controller controller
..ignoreFocusOnTextChange = true ..ignoreFocusOnTextChange = true
..formatText(offset, 0, attribute) ..formatText(offset, 0, attribute)
@ -929,9 +934,11 @@ class RawEditorState extends EditorState
clearIndents = false; clearIndents = false;
} else { } else {
_dirty = false;
throw StateError('Unreachable.'); throw StateError('Unreachable.');
} }
} }
_dirty = false;
return result; return result;
} }
@ -1170,6 +1177,17 @@ class RawEditorState extends EditorState
_selectionOverlay?.updateForScroll(); _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]) { void _didChangeTextEditingValue([bool ignoreFocus = false]) {
if (kIsWeb) { if (kIsWeb) {
_onChangeTextEditingValue(ignoreFocus); _onChangeTextEditingValue(ignoreFocus);
@ -1184,10 +1202,9 @@ class RawEditorState extends EditorState
} else { } else {
requestKeyboard(); requestKeyboard();
if (mounted) { if (mounted) {
setState(() { // Use controller.value in build()
// Use controller.value in build() // Mark widget as dirty and trigger build and updateChildren
// Trigger build and updateChildren _markNeedsBuild();
});
} }
} }
@ -1222,10 +1239,9 @@ class RawEditorState extends EditorState
_updateOrDisposeSelectionOverlayIfNeeded(); _updateOrDisposeSelectionOverlayIfNeeded();
}); });
if (mounted) { if (mounted) {
setState(() { // Use controller.value in build()
// Use controller.value in build() // Mark widget as dirty and trigger build and updateChildren
// Trigger build and updateChildren _markNeedsBuild();
});
} }
} }
@ -1258,6 +1274,11 @@ class RawEditorState extends EditorState
} }
void _handleFocusChanged() { void _handleFocusChanged() {
if (dirty) {
SchedulerBinding.instance
.addPostFrameCallback((_) => _handleFocusChanged());
return;
}
openOrCloseConnection(); openOrCloseConnection();
_cursorCont.startOrStopCursorTimerIfNeeded(_hasFocus, controller.selection); _cursorCont.startOrStopCursorTimerIfNeeded(_hasFocus, controller.selection);
_updateOrDisposeSelectionOverlayIfNeeded(); _updateOrDisposeSelectionOverlayIfNeeded();
@ -1272,10 +1293,9 @@ class RawEditorState extends EditorState
void _onChangedClipboardStatus() { void _onChangedClipboardStatus() {
if (!mounted) return; if (!mounted) return;
setState(() { // Inform the widget that the value of clipboardStatus has changed.
// Inform the widget that the value of clipboardStatus has changed. // Trigger build and updateChildren
// Trigger build and updateChildren _markNeedsBuild();
});
} }
Future<LinkMenuAction> _linkActionPicker(Node linkNode) async { Future<LinkMenuAction> _linkActionPicker(Node linkNode) async {

@ -91,7 +91,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState
void _updateCaretRectIfNeeded() { void _updateCaretRectIfNeeded() {
if (hasConnection) { if (hasConnection) {
if (renderEditor.selection.isValid && if (!dirty &&
renderEditor.selection.isValid &&
renderEditor.selection.isCollapsed) { renderEditor.selection.isCollapsed) {
final currentTextPosition = final currentTextPosition =
TextPosition(offset: renderEditor.selection.baseOffset); TextPosition(offset: renderEditor.selection.baseOffset);

Loading…
Cancel
Save