Skip to content

Commit 1124a1f

Browse files
Prevent operations on stale editor state (singerdmx#1256)
1 parent 6fa0579 commit 1124a1f

File tree

3 files changed

+37
-13
lines changed

3 files changed

+37
-13
lines changed

lib/src/widgets/editor.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ abstract class EditorState extends State<RawEditor>
4646
/// The floating cursor is animated to merge with the regular cursor.
4747
AnimationController get floatingCursorResetController;
4848

49+
/// Returns true if the editor has been marked as needing to be rebuilt.
50+
bool get dirty;
51+
4952
bool showToolbar();
5053

5154
void requestKeyboard();

lib/src/widgets/raw_editor.dart

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ class RawEditorState extends EditorState
332332

333333
TextDirection get _textDirection => Directionality.of(context);
334334

335+
@override
336+
bool get dirty => _dirty;
337+
bool _dirty = false;
338+
335339
@override
336340
void insertContent(KeyboardInsertedContent content) {
337341
assert(widget.contentInsertionConfiguration?.allowedMimeTypes
@@ -855,6 +859,7 @@ class RawEditorState extends EditorState
855859
final currentSelection = controller.selection.copyWith();
856860
final attribute = value ? Attribute.checked : Attribute.unchecked;
857861

862+
_markNeedsBuild();
858863
controller
859864
..ignoreFocusOnTextChange = true
860865
..formatText(offset, 0, attribute)
@@ -929,9 +934,11 @@ class RawEditorState extends EditorState
929934

930935
clearIndents = false;
931936
} else {
937+
_dirty = false;
932938
throw StateError('Unreachable.');
933939
}
934940
}
941+
_dirty = false;
935942
return result;
936943
}
937944

@@ -1170,6 +1177,17 @@ class RawEditorState extends EditorState
11701177
_selectionOverlay?.updateForScroll();
11711178
}
11721179

1180+
/// Marks the editor as dirty and trigger a rebuild.
1181+
///
1182+
/// When the editor is dirty methods that depend on the editor
1183+
/// state being in sync with the controller know they may be
1184+
/// operating on stale data.
1185+
void _markNeedsBuild() {
1186+
setState(() {
1187+
_dirty = true;
1188+
});
1189+
}
1190+
11731191
void _didChangeTextEditingValue([bool ignoreFocus = false]) {
11741192
if (kIsWeb) {
11751193
_onChangeTextEditingValue(ignoreFocus);
@@ -1184,10 +1202,9 @@ class RawEditorState extends EditorState
11841202
} else {
11851203
requestKeyboard();
11861204
if (mounted) {
1187-
setState(() {
1188-
// Use controller.value in build()
1189-
// Trigger build and updateChildren
1190-
});
1205+
// Use controller.value in build()
1206+
// Mark widget as dirty and trigger build and updateChildren
1207+
_markNeedsBuild();
11911208
}
11921209
}
11931210

@@ -1222,10 +1239,9 @@ class RawEditorState extends EditorState
12221239
_updateOrDisposeSelectionOverlayIfNeeded();
12231240
});
12241241
if (mounted) {
1225-
setState(() {
1226-
// Use controller.value in build()
1227-
// Trigger build and updateChildren
1228-
});
1242+
// Use controller.value in build()
1243+
// Mark widget as dirty and trigger build and updateChildren
1244+
_markNeedsBuild();
12291245
}
12301246
}
12311247

@@ -1258,6 +1274,11 @@ class RawEditorState extends EditorState
12581274
}
12591275

12601276
void _handleFocusChanged() {
1277+
if (dirty) {
1278+
SchedulerBinding.instance
1279+
.addPostFrameCallback((_) => _handleFocusChanged());
1280+
return;
1281+
}
12611282
openOrCloseConnection();
12621283
_cursorCont.startOrStopCursorTimerIfNeeded(_hasFocus, controller.selection);
12631284
_updateOrDisposeSelectionOverlayIfNeeded();
@@ -1272,10 +1293,9 @@ class RawEditorState extends EditorState
12721293

12731294
void _onChangedClipboardStatus() {
12741295
if (!mounted) return;
1275-
setState(() {
1276-
// Inform the widget that the value of clipboardStatus has changed.
1277-
// Trigger build and updateChildren
1278-
});
1296+
// Inform the widget that the value of clipboardStatus has changed.
1297+
// Trigger build and updateChildren
1298+
_markNeedsBuild();
12791299
}
12801300

12811301
Future<LinkMenuAction> _linkActionPicker(Node linkNode) async {

lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState
9191

9292
void _updateCaretRectIfNeeded() {
9393
if (hasConnection) {
94-
if (renderEditor.selection.isValid &&
94+
if (!dirty &&
95+
renderEditor.selection.isValid &&
9596
renderEditor.selection.isCollapsed) {
9697
final currentTextPosition =
9798
TextPosition(offset: renderEditor.selection.baseOffset);

0 commit comments

Comments
 (0)