Prevent operations on stale editor state (#1256)

pull/1259/head
Richard Marshall 2 years ago committed by GitHub
parent 6fa0579873
commit 1124a1f26c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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.
AnimationController get floatingCursorResetController;
/// Returns true if the editor has been marked as needing to be rebuilt.
bool get dirty;
bool showToolbar();
void requestKeyboard();

@ -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<LinkMenuAction> _linkActionPicker(Node linkNode) async {

@ -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);

Loading…
Cancel
Save