@@ -751,7 +751,8 @@ class RawFloatingCursorPoint {
751
751
class TextEditingValue {
752
752
/// Creates information for editing a run of text.
753
753
///
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.
755
756
///
756
757
/// The [text] , [selection] , and [composing] arguments must not be null but
757
758
/// each have default values.
@@ -763,23 +764,32 @@ class TextEditingValue {
763
764
this .selection = const TextSelection .collapsed (offset: - 1 ),
764
765
this .composing = TextRange .empty,
765
766
}) : 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`.
766
771
assert (selection != null ),
767
772
assert (composing != null );
768
773
769
774
/// Creates an instance of this class from a JSON object.
770
775
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));
771
789
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,
783
793
);
784
794
}
785
795
@@ -884,21 +894,27 @@ class TextEditingValue {
884
894
return originalIndex + replacedLength - removedLength;
885
895
}
886
896
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));
887
907
return TextEditingValue (
888
908
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,
897
911
);
898
912
}
899
913
900
914
/// Returns a representation of this object as a JSON object.
901
915
Map <String , dynamic > toJSON () {
916
+ assert (_textRangeIsValid (selection, text));
917
+ assert (_textRangeIsValid (composing, text));
902
918
return < String , dynamic > {
903
919
'text' : text,
904
920
'selectionBase' : selection.baseOffset,
@@ -930,6 +946,24 @@ class TextEditingValue {
930
946
selection.hashCode,
931
947
composing.hashCode,
932
948
);
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
+ }
933
967
}
934
968
935
969
/// Indicates what triggered the change in selected text (including changes to
@@ -1440,7 +1474,7 @@ TextInputAction _toTextInputAction(String action) {
1440
1474
return TextInputAction .next;
1441
1475
case 'TextInputAction.previous' :
1442
1476
return TextInputAction .previous;
1443
- case 'TextInputAction.continue_action ' :
1477
+ case 'TextInputAction.continueAction ' :
1444
1478
return TextInputAction .continueAction;
1445
1479
case 'TextInputAction.join' :
1446
1480
return TextInputAction .join;
@@ -1534,7 +1568,7 @@ RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, d
1534
1568
class TextInput {
1535
1569
TextInput ._() {
1536
1570
_channel = SystemChannels .textInput;
1537
- _channel.setMethodCallHandler (_handleTextInputInvocation );
1571
+ _channel.setMethodCallHandler (_loudlyHandleTextInputInvocation );
1538
1572
}
1539
1573
1540
1574
/// Set the [MethodChannel] used to communicate with the system's text input
@@ -1546,7 +1580,7 @@ class TextInput {
1546
1580
@visibleForTesting
1547
1581
static void setChannel (MethodChannel newChannel) {
1548
1582
assert (() {
1549
- _instance._channel = newChannel..setMethodCallHandler (_instance._handleTextInputInvocation );
1583
+ _instance._channel = newChannel..setMethodCallHandler (_instance._loudlyHandleTextInputInvocation );
1550
1584
return true ;
1551
1585
}());
1552
1586
}
@@ -1603,17 +1637,20 @@ class TextInput {
1603
1637
return connection;
1604
1638
}
1605
1639
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.
1609
1643
void _attach (TextInputConnection connection, TextInputConfiguration configuration) {
1610
1644
assert (connection != null );
1611
1645
assert (connection._client != null );
1612
1646
assert (configuration != null );
1613
1647
assert (_debugEnsureInputActionWorksOnPlatform (configuration.inputAction));
1614
1648
_channel.invokeMethod <void >(
1615
1649
'TextInput.setClient' ,
1616
- < dynamic > [ connection._id, configuration.toJson () ],
1650
+ < Object > [
1651
+ connection._id,
1652
+ configuration.toJson (),
1653
+ ],
1617
1654
);
1618
1655
_currentConnection = connection;
1619
1656
_currentConfiguration = configuration;
@@ -1656,6 +1693,23 @@ class TextInput {
1656
1693
/// Returns true if a scribble interaction is currently happening.
1657
1694
bool get scribbleInProgress => _scribbleInProgress;
1658
1695
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
+
1659
1713
Future <dynamic > _handleTextInputInvocation (MethodCall methodCall) async {
1660
1714
final String method = methodCall.method;
1661
1715
if (method == 'TextInputClient.focusElement' ) {
0 commit comments