Source/WebKit/ChangeLog

 12020-07-08 Wenson Hsieh <wenson_hsieh@apple.com>
 2
 3 autocapitalize="words" capitalizes every word's second character
 4 https://bugs.webkit.org/show_bug.cgi?id=148504
 5 <rdar://problem/57814304>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 This bug resurfaced in iOS 13 due to timing changes that caused a task added to UIKeyboardTaskQueue to no longer
 10 get dequeued after the next editor state update is received in the UI process; as a result, UIKit asks us for
 11 the context before the caret selection (i.e. `characterBeforeSelection`) before we have a chance to update it,
 12 which means that UIKeyboardImpl's autoshift state will always be off by one character whie typing.
 13
 14 Note that solely deferring this update until after the next editor state update is received is insufficient to
 15 fix this bug, since the keyboard's shift state will still be incorrect when typing characters very quickly (or
 16 if the web process is unresponsive).
 17
 18 Instead, completely address this bug by adding a mechanism for WKContentView to cache the last character in the
 19 string that is currently being inserted using `-insertText:`, and return it in `-_characterBeforeCaretSelection`
 20 until the next editor state update comes in with the real character before the selection. This way, we're able
 21 to respond to UIKit's requests immediately with a (reasonable) guess of what the character before the selection
 22 should be, and then follow it up with the actual up-to-date value from the web process during the next editor
 23 state update (invalidating the stale shift state if necessary). This cached last character should only be
 24 incorrect in the case where the page uses script to influence what was typed, or the typed character was never
 25 inserted at all (e.g. typing a newline character in a single line input element).
 26
 27 Test: fast/forms/ios/autocapitalize-words.html
 28
 29 * Platform/spi/ios/UIKitSPI.h:
 30 * UIProcess/ios/WKContentViewInteraction.h:
 31 * UIProcess/ios/WKContentViewInteraction.mm:
 32 (-[WKContentView cleanUpInteraction]):
 33 (-[WKContentView _characterBeforeCaretSelection]):
 34
 35 Use `_lastInsertedCharacterToOverrideCharacterBeforeSelection`, if it is set.
 36
 37 (-[WKContentView _characterInRelationToCaretSelection:]):
 38 (-[WKContentView insertText:]):
 39
 40 Update `_lastInsertedCharacterToOverrideCharacterBeforeSelection` and ensure an editor state update immediately
 41 after text is inserted, if we're currently editing an element with `autocapitalize="words"`. The editor state
 42 update could be unnecessarily expensive, so we don't want to do this unconditionally. Also,
 43 `autocapitalize="words"` is special in the sense that it is the only autocapitalization type where the platform
 44 (UIKit) will ask for the last character immediately and use it to unshift the keyboard. Even in the case of
 45 `autocapitalize="sentences"`, UIKit makes the decision to unshift using information from `kbd` instead.
 46
 47 (-[WKContentView _elementDidBlur]):
 48 (-[WKContentView _selectionChanged]):
 49
 50 Clear out `_lastInsertedCharacterToOverrideCharacterBeforeSelection` when the next editor state update is
 51 received, and invalidate the current keyboard shift state by calling `-clearShiftState`. This SPI method
 52 automatically schedules a shift state update, as well.
 53
 54 Note the extra `_usingGestureForSelection` check here, which prevents the keyboard from autoshifting and
 55 unshifting while changing the selection via loupe gesture.
 56
1572020-07-06 Chris Dumez <cdumez@apple.com>
258
359 Regression(r249303) Crash under NetworkLoad::NetworkLoad()

Source/WebKit/Platform/spi/ios/UIKitSPI.h

@@typedef enum {
318318- (void)addInputString:(NSString *)string withFlags:(NSUInteger)flags withInputManagerHint:(NSString *)hint;
319319- (BOOL)autocorrectSpellingEnabled;
320320- (BOOL)isAutoShifted;
 321- (void)clearShiftState;
321322- (void)deleteFromInput;
322323- (void)deleteFromInputWithFlags:(NSUInteger)flags;
323324- (void)replaceText:(id)replacement;

Source/WebKit/UIProcess/ios/WKContentViewInteraction.h

@@struct WKAutoCorrectionData {
390390
391391 RetainPtr<NSDictionary> _additionalContextForStrongPasswordAssistance;
392392
 393 Optional<UChar32> _lastInsertedCharacterToOverrideCharacterBeforeSelection;
 394
393395#if ENABLE(DATA_INTERACTION)
394396 WebKit::DragDropInteractionState _dragDropInteractionState;
395397 RetainPtr<UIDragInteraction> _dragInteraction;

Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

@@- (void)cleanUpInteraction
941941 _interactionViewsContainerView = nil;
942942 }
943943
 944 _lastInsertedCharacterToOverrideCharacterBeforeSelection = WTF::nullopt;
 945
944946 [_touchEventGestureRecognizer setDelegate:nil];
945947 [self removeGestureRecognizer:_touchEventGestureRecognizer.get()];
946948

@@- (void)updateSelectionWithExtentPoint:(CGPoint)point withBoundary:(UITextGranul
41354137
41364138- (UTF32Char)_characterBeforeCaretSelection
41374139{
4138  return _page->editorState().postLayoutData().characterBeforeSelection;
 4140 return _lastInsertedCharacterToOverrideCharacterBeforeSelection.valueOr(_page->editorState().postLayoutData().characterBeforeSelection);
41394141}
41404142
41414143- (UTF32Char)_characterInRelationToCaretSelection:(int)amount

@@- (UTF32Char)_characterInRelationToCaretSelection:(int)amount
41444146 case 0:
41454147 return _page->editorState().postLayoutData().characterAfterSelection;
41464148 case -1:
4147  return _page->editorState().postLayoutData().characterBeforeSelection;
 4149 return self._characterBeforeCaretSelection;
41484150 case -2:
41494151 return _page->editorState().postLayoutData().twoCharacterBeforeSelection;
41504152 default:

@@- (void)insertText:(NSString *)aStringValue
48264828 options.processingUserGesture = [keyboard respondsToSelector:@selector(isCallingInputDelegate)] && keyboard.isCallingInputDelegate;
48274829 options.shouldSimulateKeyboardInput = [self _shouldSimulateKeyboardInputOnTextInsertion];
48284830 _page->insertTextAsync(aStringValue, WebKit::EditingRange(), WTFMove(options));
 4831
 4832 if (_focusedElementInformation.autocapitalizeType == WebCore::AutocapitalizeType::Words && aStringValue.length) {
 4833 _lastInsertedCharacterToOverrideCharacterBeforeSelection = [aStringValue characterAtIndex:aStringValue.length - 1];
 4834 _page->scheduleFullEditorStateUpdate();
 4835 }
48294836}
48304837
48314838- (void)insertText:(NSString *)aStringValue alternatives:(NSArray<NSString *> *)alternatives style:(UITextAlternativeStyle)style

@@- (void)_elementDidBlur
60736080
60746081 if (!_isChangingFocus)
60756082 _didAccessoryTabInitiateFocus = NO;
 6083
 6084 _lastInsertedCharacterToOverrideCharacterBeforeSelection = WTF::nullopt;
60766085}
60776086
60786087- (void)_updateInputContextAfterBlurringAndRefocusingElement

@@- (void)_selectionChanged
65526561#endif
65536562
65546563 [_webView _didChangeEditorState];
 6564
 6565 if (!_page->editorState().isMissingPostLayoutData) {
 6566 _lastInsertedCharacterToOverrideCharacterBeforeSelection = WTF::nullopt;
 6567 if (!_usingGestureForSelection && _focusedElementInformation.autocapitalizeType == WebCore::AutocapitalizeType::Words)
 6568 [UIKeyboardImpl.sharedInstance clearShiftState];
 6569 }
65556570}
65566571
65576572- (void)selectWordForReplacement

LayoutTests/ChangeLog

 12020-07-08 Wenson Hsieh <wenson_hsieh@apple.com>
 2
 3 autocapitalize="words" capitalizes every word's second character
 4 https://bugs.webkit.org/show_bug.cgi?id=148504
 5 <rdar://problem/57814304>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 Add a new layout test to verify that the bug does not occur.
 10
 11 * fast/forms/ios/autocapitalize-words-expected.txt: Added.
 12 * fast/forms/ios/autocapitalize-words.html: Added.
 13
1142020-07-06 Chris Fleizach <cfleizach@apple.com>
215
316 AX: Implement user action spec for Escape action

LayoutTests/fast/forms/ios/autocapitalize-words-expected.txt

 1This test verifies that typing in editable areas with autocapitalize='words' causes every word to be capitalized. To test manually, type into both elements.
 2
 3On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 4
 5
 6PASS input.value is "Do Re"
 7PASS contenteditable.innerText is "Mi Fa\nSo"
 8PASS successfullyParsed is true
 9
 10TEST COMPLETE
 11

LayoutTests/fast/forms/ios/autocapitalize-words.html

 1<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
 2<html>
 3<meta name="viewport" content="width=device-width, initial-scale=1">
 4<head>
 5<script src="../../../resources/js-test.js"></script>
 6<script src="../../../resources/ui-helper.js"></script>
 7<style>
 8input, div[contenteditable] {
 9 width: 100px;
 10 height: 50px;
 11 border: 1px solid black;
 12 font-size: 20px;
 13}
 14
 15#console, #description {
 16 font-size: small;
 17}
 18</style>
 19<script>
 20jsTestIsAsync = true;
 21description("This test verifies that typing in editable areas with autocapitalize='words' causes every word to be capitalized. To test manually, type into both elements.");
 22
 23async function typeStringInElement(element, string)
 24{
 25 await UIHelper.activateElementAndWaitForInputSession(element);
 26 for (const character of [...string])
 27 await UIHelper.callFunctionAndWaitForEvent(() => UIHelper.typeCharacter(character), element, "input");
 28 element.blur();
 29 await UIHelper.waitForKeyboardToHide();
 30}
 31
 32addEventListener("load", async () => {
 33 if (!window.testRunner)
 34 return;
 35
 36 input = document.querySelector("input");
 37 contenteditable = document.querySelector("div[contenteditable]");
 38
 39 await typeStringInElement(input, "do re");
 40 await typeStringInElement(contenteditable, "fa\nso");
 41
 42 shouldBeEqualToString("input.value", "Do Re");
 43 shouldBeEqualToString("contenteditable.innerText", "Mi Fa\nSo");
 44
 45 input.remove();
 46 contenteditable.remove();
 47 finishJSTest();
 48});
 49</script>
 50</head>
 51<body>
 52<div id="console"></div>
 53<div id="description"></div>
 54<input autocapitalize="words">
 55<div contenteditable autocapitalize="words">Mi&nbsp;</div>
 56</body>
 57</html>