From 6e06a810f0ab1002f1d10351bc878babde36b1f1 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Wed, 20 Nov 2024 14:05:33 -0800 Subject: [PATCH] Fix possible NSRangeException when updating typing attributes in response to new text content (#47737) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/47737 We may see an NSRangeException when setting new AttributedString content, where setting the AttributedString itself changes selection (before we mutate it later). It seems like the selection here is not in a good state yet in regards to the AttributedString backing exposed (since we are reading it while modifying it). So let's fold the logic for updating typing attributes into the collection of ignored work from non-user-selection updates, since programatically setting an AttributedString will already trigger updating typing attributes. I also added a nil check here, which is unrelated to the crash, but it seems like we should have it for safety... Changelog: [iOS][Fixed] - Fix possible NSRangeException when updating typing attributes in response to new text content Reviewed By: cipolleschi Differential Revision: D66202986 fbshipit-source-id: fded492b5022c5fef5b9563f93a57549d06a7020 --- .../TextInput/RCTTextInputComponentView.mm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index c527c265d10..e67d551c5a8 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -450,10 +450,15 @@ static NSSet *returnKeyTypesSet; - (void)textInputDidChangeSelection { - [self _updateTypingAttributes]; if (_comingFromJS) { return; } + + // T207198334: Setting a new AttributedString (_comingFromJS) will trigger a selection change before the backing + // string is updated, so indicies won't point to what we want yet. Only respond to user selection change, and let + // `_setAttributedString` handle updating typing attributes if content changes. + [self _updateTypingAttributes]; + const auto &props = static_cast(*_props); if (props.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) { [self textInputDidChange]; @@ -722,7 +727,7 @@ static NSSet *returnKeyTypesSet; // https://github.com/facebook/react-native/blob/3102a58df38d96f3dacef0530e4dbb399037fcd2/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt#L30 - (void)_updateTypingAttributes { - if (_backedTextInputView.attributedText.length > 0) { + if (_backedTextInputView.attributedText.length > 0 && _backedTextInputView.selectedTextRange != nil) { NSUInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument toPosition:_backedTextInputView.selectedTextRange.start];