@@ -751,7 +751,8 @@ class RawFloatingCursorPoint {
751751class TextEditingValue {
752752 /// Creates information for editing a run of text.
753753 ///
754- /// The selection and composing range must be within the text.
754+ /// The selection and composing range must be within the text. This is not
755+ /// checked during construction, and must be guaranteed by the caller.
755756 ///
756757 /// The [text] , [selection] , and [composing] arguments must not be null but
757758 /// each have default values.
@@ -763,23 +764,32 @@ class TextEditingValue {
763764 this .selection = const TextSelection .collapsed (offset: - 1 ),
764765 this .composing = TextRange .empty,
765766 }) : assert (text != null ),
767+ // The constructor does not verify that `selection` and `composing` are
768+ // valid ranges within `text`, and is unable to do so due to limitation
769+ // of const constructors. Some checks are performed by assertion in
770+ // other occasions. See `_textRangeIsValid`.
766771 assert (selection != null ),
767772 assert (composing != null );
768773
769774 /// Creates an instance of this class from a JSON object.
770775 factory TextEditingValue .fromJSON (Map <String , dynamic > encoded) {
776+ final String text = encoded['text' ] as String ;
777+ final TextSelection selection = TextSelection (
778+ baseOffset: encoded['selectionBase' ] as int ? ?? - 1 ,
779+ extentOffset: encoded['selectionExtent' ] as int ? ?? - 1 ,
780+ affinity: _toTextAffinity (encoded['selectionAffinity' ] as String ? ) ?? TextAffinity .downstream,
781+ isDirectional: encoded['selectionIsDirectional' ] as bool ? ?? false ,
782+ );
783+ final TextRange composing = TextRange (
784+ start: encoded['composingBase' ] as int ? ?? - 1 ,
785+ end: encoded['composingExtent' ] as int ? ?? - 1 ,
786+ );
787+ assert (_textRangeIsValid (selection, text));
788+ assert (_textRangeIsValid (composing, text));
771789 return TextEditingValue (
772- text: encoded['text' ] as String ,
773- selection: TextSelection (
774- baseOffset: encoded['selectionBase' ] as int ? ?? - 1 ,
775- extentOffset: encoded['selectionExtent' ] as int ? ?? - 1 ,
776- affinity: _toTextAffinity (encoded['selectionAffinity' ] as String ? ) ?? TextAffinity .downstream,
777- isDirectional: encoded['selectionIsDirectional' ] as bool ? ?? false ,
778- ),
779- composing: TextRange (
780- start: encoded['composingBase' ] as int ? ?? - 1 ,
781- end: encoded['composingExtent' ] as int ? ?? - 1 ,
782- ),
790+ text: text,
791+ selection: selection,
792+ composing: composing,
783793 );
784794 }
785795
@@ -884,21 +894,27 @@ class TextEditingValue {
884894 return originalIndex + replacedLength - removedLength;
885895 }
886896
897+ final TextSelection adjustedSelection = TextSelection (
898+ baseOffset: adjustIndex (selection.baseOffset),
899+ extentOffset: adjustIndex (selection.extentOffset),
900+ );
901+ final TextRange adjustedComposing = TextRange (
902+ start: adjustIndex (composing.start),
903+ end: adjustIndex (composing.end),
904+ );
905+ assert (_textRangeIsValid (adjustedSelection, newText));
906+ assert (_textRangeIsValid (adjustedComposing, newText));
887907 return TextEditingValue (
888908 text: newText,
889- selection: TextSelection (
890- baseOffset: adjustIndex (selection.baseOffset),
891- extentOffset: adjustIndex (selection.extentOffset),
892- ),
893- composing: TextRange (
894- start: adjustIndex (composing.start),
895- end: adjustIndex (composing.end),
896- ),
909+ selection: adjustedSelection,
910+ composing: adjustedComposing,
897911 );
898912 }
899913
900914 /// Returns a representation of this object as a JSON object.
901915 Map <String , dynamic > toJSON () {
916+ assert (_textRangeIsValid (selection, text));
917+ assert (_textRangeIsValid (composing, text));
902918 return < String , dynamic > {
903919 'text' : text,
904920 'selectionBase' : selection.baseOffset,
@@ -930,6 +946,24 @@ class TextEditingValue {
930946 selection.hashCode,
931947 composing.hashCode,
932948 );
949+
950+ // Verify that the given range is within the text.
951+ //
952+ // The verification can't be perform during the constructor of
953+ // [TextEditingValue], which are `const` and are allowed to retrieve
954+ // properties of [TextRange]s. [TextEditingValue] should perform this
955+ // wherever it is building other values (such as toJson) or is built in a
956+ // non-const way (such as fromJson).
957+ static bool _textRangeIsValid (TextRange range, String text) {
958+ if (range.start == - 1 && range.end == - 1 ) {
959+ return true ;
960+ }
961+ assert (range.start >= 0 && range.start <= text.length,
962+ 'Range start ${range .start } is out of text of length ${text .length }' );
963+ assert (range.end >= 0 && range.end <= text.length,
964+ 'Range end ${range .end } is out of text of length ${text .length }' );
965+ return true ;
966+ }
933967}
934968
935969/// Indicates what triggered the change in selected text (including changes to
@@ -1440,7 +1474,7 @@ TextInputAction _toTextInputAction(String action) {
14401474 return TextInputAction .next;
14411475 case 'TextInputAction.previous' :
14421476 return TextInputAction .previous;
1443- case 'TextInputAction.continue_action ' :
1477+ case 'TextInputAction.continueAction ' :
14441478 return TextInputAction .continueAction;
14451479 case 'TextInputAction.join' :
14461480 return TextInputAction .join;
@@ -1534,7 +1568,7 @@ RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, d
15341568class TextInput {
15351569 TextInput ._() {
15361570 _channel = SystemChannels .textInput;
1537- _channel.setMethodCallHandler (_handleTextInputInvocation );
1571+ _channel.setMethodCallHandler (_loudlyHandleTextInputInvocation );
15381572 }
15391573
15401574 /// Set the [MethodChannel] used to communicate with the system's text input
@@ -1546,7 +1580,7 @@ class TextInput {
15461580 @visibleForTesting
15471581 static void setChannel (MethodChannel newChannel) {
15481582 assert (() {
1549- _instance._channel = newChannel..setMethodCallHandler (_instance._handleTextInputInvocation );
1583+ _instance._channel = newChannel..setMethodCallHandler (_instance._loudlyHandleTextInputInvocation );
15501584 return true ;
15511585 }());
15521586 }
@@ -1603,17 +1637,20 @@ class TextInput {
16031637 return connection;
16041638 }
16051639
1606- /// This method actually notifies the embedding of the client. It is utilized
1607- /// by [attach] and by [_handleTextInputInvocation] for the
1608- /// `TextInputClient.requestExistingInputState` method.
1640+ // This method actually notifies the embedding of the client. It is utilized
1641+ // by [attach] and by [_handleTextInputInvocation] for the
1642+ // `TextInputClient.requestExistingInputState` method.
16091643 void _attach (TextInputConnection connection, TextInputConfiguration configuration) {
16101644 assert (connection != null );
16111645 assert (connection._client != null );
16121646 assert (configuration != null );
16131647 assert (_debugEnsureInputActionWorksOnPlatform (configuration.inputAction));
16141648 _channel.invokeMethod <void >(
16151649 'TextInput.setClient' ,
1616- < dynamic > [ connection._id, configuration.toJson () ],
1650+ < Object > [
1651+ connection._id,
1652+ configuration.toJson (),
1653+ ],
16171654 );
16181655 _currentConnection = connection;
16191656 _currentConfiguration = configuration;
@@ -1656,6 +1693,23 @@ class TextInput {
16561693 /// Returns true if a scribble interaction is currently happening.
16571694 bool get scribbleInProgress => _scribbleInProgress;
16581695
1696+ Future <dynamic > _loudlyHandleTextInputInvocation (MethodCall call) async {
1697+ try {
1698+ return await _handleTextInputInvocation (call);
1699+ } catch (exception, stack) {
1700+ FlutterError .reportError (FlutterErrorDetails (
1701+ exception: exception,
1702+ stack: stack,
1703+ library: 'services library' ,
1704+ context: ErrorDescription ('during method call ${call .method }' ),
1705+ informationCollector: () => < DiagnosticsNode > [
1706+ DiagnosticsProperty <MethodCall >('call' , call, style: DiagnosticsTreeStyle .errorProperty),
1707+ ],
1708+ ));
1709+ rethrow ;
1710+ }
1711+ }
1712+
16591713 Future <dynamic > _handleTextInputInvocation (MethodCall methodCall) async {
16601714 final String method = methodCall.method;
16611715 if (method == 'TextInputClient.focusElement' ) {
0 commit comments