[iOS TextInput] Avoid Unnecessary UndateEditingClient Calls (flutter/engine#21303)

This commit is contained in:
LongCatIsLooong 2020-09-21 15:27:02 -07:00 committed by GitHub
parent 73ae0bb94c
commit 1357779b57
2 changed files with 42 additions and 22 deletions

View File

@ -538,6 +538,8 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
}
// Return true if the new input state needs to be synced back to the framework.
// TODO(LongCatIsLooong): setTextInputState should never call updateEditingState. Sending the
// editing value back may overwrite the framework's updated editing value.
- (BOOL)setTextInputState:(NSDictionary*)state {
NSString* newText = state[@"text"];
BOOL textChanged = ![self.text isEqualToString:newText];
@ -545,19 +547,14 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
[self.inputDelegate textWillChange:self];
[self.text setString:newText];
}
BOOL needsEditingStateUpdate = textChanged;
NSInteger composingBase = [state[@"composingBase"] intValue];
NSInteger composingExtent = [state[@"composingExtent"] intValue];
NSRange composingRange = [self clampSelection:NSMakeRange(MIN(composingBase, composingExtent),
ABS(composingBase - composingExtent))
forText:self.text];
FlutterTextRange* newMarkedRange =
self.markedTextRange =
composingRange.length > 0 ? [FlutterTextRange rangeWithNSRange:composingRange] : nil;
needsEditingStateUpdate =
needsEditingStateUpdate ||
(!newMarkedRange ? self.markedTextRange != nil
: ![newMarkedRange isEqualTo:(FlutterTextRange*)self.markedTextRange]);
self.markedTextRange = newMarkedRange;
NSRange selectedRange = [self clampSelectionFromBase:[state[@"selectionBase"] intValue]
extent:[state[@"selectionExtent"] intValue]
@ -565,7 +562,6 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
NSRange oldSelectedRange = [(FlutterTextRange*)self.selectedTextRange range];
if (!NSEqualRanges(selectedRange, oldSelectedRange)) {
needsEditingStateUpdate = YES;
[self.inputDelegate selectionWillChange:self];
[self setSelectedTextRangeLocal:[FlutterTextRange rangeWithNSRange:selectedRange]];
@ -580,8 +576,8 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
[self.inputDelegate textDidChange:self];
}
// For consistency with Android behavior, send an update to the framework if anything changed.
return needsEditingStateUpdate;
// For consistency with Android behavior, send an update to the framework if the text changed.
return textChanged;
}
// Extracts the selection information from the editing state dictionary.
@ -788,6 +784,8 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) {
}
- (void)unmarkText {
if (!self.markedTextRange)
return;
self.markedTextRange = nil;
[self updateEditingState];
}

View File

@ -226,7 +226,7 @@ FLUTTER_ASSERT_ARC
XCTAssertFalse([inputView setTextInputState:@{@"text" : @"AFTER"}]);
}
- (void)testSelectionChangeTriggersUpdateEditingClient {
- (void)testSelectionChangeDoesNotTriggerUpdateEditingClient {
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
inputView.textInputDelegate = engine;
@ -236,15 +236,15 @@ FLUTTER_ASSERT_ARC
BOOL shouldUpdate = [inputView
setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @3}];
XCTAssertTrue(shouldUpdate);
XCTAssertFalse(shouldUpdate);
shouldUpdate = [inputView
setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @3}];
XCTAssertTrue(shouldUpdate);
XCTAssertFalse(shouldUpdate);
shouldUpdate = [inputView
setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @2}];
XCTAssertTrue(shouldUpdate);
XCTAssertFalse(shouldUpdate);
// Don't send anything if there's nothing new.
shouldUpdate = [inputView
@ -252,7 +252,7 @@ FLUTTER_ASSERT_ARC
XCTAssertFalse(shouldUpdate);
}
- (void)testComposingChangeTriggersUpdateEditingClient {
- (void)testComposingChangeDoesNotTriggerUpdateEditingClient {
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
inputView.textInputDelegate = engine;
@ -263,20 +263,42 @@ FLUTTER_ASSERT_ARC
BOOL shouldUpdate = [inputView
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @0, @"composingExtent" : @3}];
XCTAssertTrue(shouldUpdate);
XCTAssertFalse(shouldUpdate);
shouldUpdate = [inputView
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
XCTAssertTrue(shouldUpdate);
XCTAssertFalse(shouldUpdate);
shouldUpdate = [inputView
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
XCTAssertTrue(shouldUpdate);
// Don't send anything if there's nothing new.
shouldUpdate = [inputView
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
XCTAssertFalse(shouldUpdate);
shouldUpdate = [inputView
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
XCTAssertFalse(shouldUpdate);
}
- (void)testUITextInputAvoidUnnecessaryUndateEditingClientCalls {
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
inputView.textInputDelegate = engine;
__block int updateCount = 0;
OCMStub([engine updateEditingClient:0 withState:[OCMArg isNotNil]])
.andDo(^(NSInvocation* invocation) {
updateCount++;
});
[inputView unmarkText];
// updateEditingClient shouldn't fire as the text is already unmarked.
XCTAssertEqual(updateCount, 0);
[inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
// updateEditingClient fires in response to setMarkedText.
XCTAssertEqual(updateCount, 1);
[inputView unmarkText];
// updateEditingClient fires in response to unmarkText.
XCTAssertEqual(updateCount, 2);
}
- (void)testUpdateEditingClientNegativeSelection {