Title: [261897] trunk
Revision
261897
Author
[email protected]
Date
2020-05-19 17:05:46 -0700 (Tue, 19 May 2020)

Log Message

Blue dotted underline with alternatives only shown for last word, gets lost for previous insertions
https://bugs.webkit.org/show_bug.cgi?id=212097
<rdar://problem/61913405>

Reviewed by Darin Adler.

Source/WebCore:

Fix up two cases, <space> is a literal ' ' and | is the position of the caret:
    1. Space inserted after marker, here's what it looks like BEFORE insertion (i.e. endOfFirstWord == startOfSelection):
        hello|

    This case is detected when the end of the next word (relative to caret) would be at the start of the
    word that begins before or on the caret.

    2. Space inserted before marker, here's what it looks like BEFORE insertion (i.e. startOfLastWord == endOfSelection):
        |hello

    This case is detected when the end of the previous word (relative to caret) would be at the end of the
    word that ends after or on the caret.

Note ^^^ example uses a caret, but code is slightly more general and works when the current selection
is a range. Though I didn't explicitly test that because my bug is specific to having a caret selection.

* editing/Editor.cpp:
(WebCore::Editor::insertTextWithoutSendingTextEvent): Do not remove markers if selection starts at the
beginning of a new word. This is detected by looking at the character before the selection start to see
if it is a space or newline. This will be false when there is no preceding character (e.g. start of document),
but that's OK because it doesn't matter what we pass to updateMarkersForWordsAffectedByEditing() - there's
no markers to remove anyway, let alone text for markers to exist in. I don't use isStartOfWord() here
because that would incorrectly return false if the current selection is at the end of a paragraph. I could have
fixed that up by checking isEndOfParagraph() as well, but isStartOfWord() + isEndOfParagraph() is less
efficient than just looking at the previous character directly. So, I did that instead.
(WebCore::Editor::updateMarkersForWordsAffectedByEditing): Save off the original end of the first word and
start of the last word positions before mutating them. Update early return checks to use these saved values
instead of comparing against the start and end of the current selection, which weren't correct. Saved positioned
are aligned by word, but start and end of current selection may NOT be. So, comparison was asymmetric: lhs was
word aligned position, but rhs may not be.

* editing/Editor.h: While I am here, fix up a param name to match what it is called in the .cpp.

* testing/Internals.cpp:
(WebCore::Internals::hasDictationAlternativesMarker): Added
* testing/Internals.h:
* testing/Internals.idl:
Add new functionality for testing purposes.

Tools:

Add tests. As I was writing them I discovered a few bugs in the existing code:
        1. <https://webkit.org/b/212093> REGRESSION (r259930): Dictation marker at start of text is removed when added trailing whitespace is collapsed
        2. <https://webkit.org/b/212098> Inserting a no-break space before or after a marker removes the marker

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/InsertTextAlternatives.mm: Added.
(TestWebKitAPI::TEST):
(TestWebKitAPI::makeNSStringWithCharacter): Added.

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (261896 => 261897)


--- trunk/Source/WebCore/ChangeLog	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Source/WebCore/ChangeLog	2020-05-20 00:05:46 UTC (rev 261897)
@@ -1,3 +1,51 @@
+2020-05-19  Daniel Bates  <[email protected]>
+
+        Blue dotted underline with alternatives only shown for last word, gets lost for previous insertions
+        https://bugs.webkit.org/show_bug.cgi?id=212097
+        <rdar://problem/61913405>
+
+        Reviewed by Darin Adler.
+
+        Fix up two cases, <space> is a literal ' ' and | is the position of the caret:
+            1. Space inserted after marker, here's what it looks like BEFORE insertion (i.e. endOfFirstWord == startOfSelection):
+                hello|
+
+            This case is detected when the end of the next word (relative to caret) would be at the start of the
+            word that begins before or on the caret.
+
+            2. Space inserted before marker, here's what it looks like BEFORE insertion (i.e. startOfLastWord == endOfSelection):
+                |hello
+
+            This case is detected when the end of the previous word (relative to caret) would be at the end of the
+            word that ends after or on the caret.
+
+        Note ^^^ example uses a caret, but code is slightly more general and works when the current selection
+        is a range. Though I didn't explicitly test that because my bug is specific to having a caret selection.
+
+        * editing/Editor.cpp:
+        (WebCore::Editor::insertTextWithoutSendingTextEvent): Do not remove markers if selection starts at the
+        beginning of a new word. This is detected by looking at the character before the selection start to see
+        if it is a space or newline. This will be false when there is no preceding character (e.g. start of document),
+        but that's OK because it doesn't matter what we pass to updateMarkersForWordsAffectedByEditing() - there's
+        no markers to remove anyway, let alone text for markers to exist in. I don't use isStartOfWord() here
+        because that would incorrectly return false if the current selection is at the end of a paragraph. I could have
+        fixed that up by checking isEndOfParagraph() as well, but isStartOfWord() + isEndOfParagraph() is less
+        efficient than just looking at the previous character directly. So, I did that instead.
+        (WebCore::Editor::updateMarkersForWordsAffectedByEditing): Save off the original end of the first word and
+        start of the last word positions before mutating them. Update early return checks to use these saved values
+        instead of comparing against the start and end of the current selection, which weren't correct. Saved positioned
+        are aligned by word, but start and end of current selection may NOT be. So, comparison was asymmetric: lhs was
+        word aligned position, but rhs may not be.
+
+        * editing/Editor.h: While I am here, fix up a param name to match what it is called in the .cpp.
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::hasDictationAlternativesMarker): Added
+        * testing/Internals.h:
+        * testing/Internals.idl:
+        Add new functionality for testing purposes.
+
+
 2020-05-19  Oriol Brufau  <[email protected]>
 
         Fix marginLogicalSizeForChild to check auto margins in the right axis

Modified: trunk/Source/WebCore/editing/Editor.cpp (261896 => 261897)


--- trunk/Source/WebCore/editing/Editor.cpp	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Source/WebCore/editing/Editor.cpp	2020-05-20 00:05:46 UTC (rev 261897)
@@ -1270,7 +1270,10 @@
     if (!shouldInsertText(text, createLiveRange(selection.toNormalizedRange()).get(), EditorInsertAction::Typed))
         return true;
 
-    updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0]));
+    // FIXME: Should pass false to updateMarkersForWordsAffectedByEditing() to not remove markers if
+    // a leading or trailing no-break space is being inserted. See <https://webkit.org/b/212098>.
+    bool isStartOfNewWord = isSpaceOrNewline(selection.visibleStart().characterBefore());
+    updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0]) || isStartOfNewWord);
 
     bool shouldConsiderApplyingAutocorrection = false;
     if (text == " " || text == "\t")
@@ -3028,12 +3031,15 @@
         endOfLastWord = endOfWord(endOfSelection, LeftWordIfOnBoundary);
     }
 
+    auto originalEndOfFirstWord = endOfFirstWord;
+    auto originalStartOfLastWord = startOfLastWord;
+
     // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at the start of selection,
     // we choose next word as the first word.
     if (doNotRemoveIfSelectionAtWordBoundary && endOfFirstWord == startOfSelection) {
         startOfFirstWord = nextWordPosition(startOfFirstWord);
         endOfFirstWord = endOfWord(startOfFirstWord, RightWordIfOnBoundary);
-        if (startOfFirstWord == endOfSelection)
+        if (startOfFirstWord == originalStartOfLastWord)
             return;
     }
 
@@ -3042,7 +3048,7 @@
     if (doNotRemoveIfSelectionAtWordBoundary && startOfLastWord == endOfSelection) {
         startOfLastWord = previousWordPosition(startOfLastWord);
         endOfLastWord = endOfWord(startOfLastWord, RightWordIfOnBoundary);
-        if (endOfLastWord == startOfSelection)
+        if (endOfLastWord == originalEndOfFirstWord)
             return;
     }
 

Modified: trunk/Source/WebCore/editing/Editor.h (261896 => 261897)


--- trunk/Source/WebCore/editing/Editor.h	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Source/WebCore/editing/Editor.h	2020-05-20 00:05:46 UTC (rev 261897)
@@ -479,7 +479,7 @@
     WEBCORE_EXPORT void replaceSelectionWithFragment(DocumentFragment&, SelectReplacement, SmartReplace, MatchStyle, EditAction = EditAction::Insert, MailBlockquoteHandling = MailBlockquoteHandling::RespectBlockquote);
     WEBCORE_EXPORT void replaceSelectionWithText(const String&, SelectReplacement, SmartReplace, EditAction = EditAction::Insert);
     WEBCORE_EXPORT bool selectionStartHasMarkerFor(DocumentMarker::MarkerType, int from, int length) const;
-    void updateMarkersForWordsAffectedByEditing(bool onlyHandleWordsContainingSelection);
+    void updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary);
     void deletedAutocorrectionAtPosition(const Position&, const String& originalString);
     
     WEBCORE_EXPORT void simplifyMarkup(Node* startNode, Node* endNode);

Modified: trunk/Source/WebCore/testing/Internals.cpp (261896 => 261897)


--- trunk/Source/WebCore/testing/Internals.cpp	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Source/WebCore/testing/Internals.cpp	2020-05-20 00:05:46 UTC (rev 261897)
@@ -2356,6 +2356,17 @@
     return document->editor().selectionStartHasMarkerFor(DocumentMarker::Autocorrected, from, length);
 }
 
+bool Internals::hasDictationAlternativesMarker(int from, int length)
+{
+    auto* document = contextDocument();
+    if (!document || !document->frame())
+        return false;
+
+    updateEditorUINowIfScheduled();
+
+    return document->frame()->editor().selectionStartHasMarkerFor(DocumentMarker::DictationAlternatives, from, length);
+}
+
 void Internals::setContinuousSpellCheckingEnabled(bool enabled)
 {
     if (!contextDocument() || !contextDocument()->frame())

Modified: trunk/Source/WebCore/testing/Internals.h (261896 => 261897)


--- trunk/Source/WebCore/testing/Internals.h	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Source/WebCore/testing/Internals.h	2020-05-20 00:05:46 UTC (rev 261897)
@@ -349,6 +349,7 @@
     bool hasSpellingMarker(int from, int length);
     bool hasGrammarMarker(int from, int length);
     bool hasAutocorrectedMarker(int from, int length);
+    bool hasDictationAlternativesMarker(int from, int length);
     void setContinuousSpellCheckingEnabled(bool);
     void setAutomaticQuoteSubstitutionEnabled(bool);
     void setAutomaticLinkDetectionEnabled(bool);

Modified: trunk/Source/WebCore/testing/Internals.idl (261896 => 261897)


--- trunk/Source/WebCore/testing/Internals.idl	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Source/WebCore/testing/Internals.idl	2020-05-20 00:05:46 UTC (rev 261897)
@@ -402,6 +402,7 @@
     boolean hasSpellingMarker(long from, long length);
     boolean hasGrammarMarker(long from, long length);
     boolean hasAutocorrectedMarker(long from, long length);
+    boolean hasDictationAlternativesMarker(long from, long length);
     void setContinuousSpellCheckingEnabled(boolean enabled);
     void setAutomaticQuoteSubstitutionEnabled(boolean enabled);
     void setAutomaticLinkDetectionEnabled(boolean enabled);

Modified: trunk/Tools/ChangeLog (261896 => 261897)


--- trunk/Tools/ChangeLog	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Tools/ChangeLog	2020-05-20 00:05:46 UTC (rev 261897)
@@ -1,3 +1,20 @@
+2020-05-19  Daniel Bates  <[email protected]>
+
+        Blue dotted underline with alternatives only shown for last word, gets lost for previous insertions
+        https://bugs.webkit.org/show_bug.cgi?id=212097
+        <rdar://problem/61913405>
+
+        Reviewed by Darin Adler.
+
+        Add tests. As I was writing them I discovered a few bugs in the existing code:
+                1. <https://webkit.org/b/212093> REGRESSION (r259930): Dictation marker at start of text is removed when added trailing whitespace is collapsed
+                2. <https://webkit.org/b/212098> Inserting a no-break space before or after a marker removes the marker
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/InsertTextAlternatives.mm: Added.
+        (TestWebKitAPI::TEST):
+        (TestWebKitAPI::makeNSStringWithCharacter): Added.
+
 2020-05-19  Sergio Villar Senin  <[email protected]>
 
         [WPE] Sync jhbuild to flatpak

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (261896 => 261897)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2020-05-20 00:01:49 UTC (rev 261896)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2020-05-20 00:05:46 UTC (rev 261897)
@@ -1014,6 +1014,7 @@
 		CEBCA1391E3A807A00C73293 /* page-with-csp-iframe.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CEBCA1341E3A803400C73293 /* page-with-csp-iframe.html */; };
 		CEBCA13A1E3A807A00C73293 /* page-without-csp.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CEBCA1371E3A803400C73293 /* page-without-csp.html */; };
 		CEBCA13B1E3A807A00C73293 /* page-without-csp-iframe.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CEBCA1361E3A803400C73293 /* page-without-csp-iframe.html */; };
+		CED73D37246F29FA00DAE327 /* InsertTextAlternatives.mm in Sources */ = {isa = PBXBuildFile; fileRef = CED73D35246F204C00DAE327 /* InsertTextAlternatives.mm */; };
 		CEDA12412437C9FB00C28A9E /* editable-region-composited-and-non-composited-overlap.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CEDA12402437C9EA00C28A9E /* editable-region-composited-and-non-composited-overlap.html */; };
 		D34E08761E4E42E1005FF14A /* WKWebViewGetContents.mm in Sources */ = {isa = PBXBuildFile; fileRef = D3BE5E341E4CE85E00FD563A /* WKWebViewGetContents.mm */; };
 		DF4B273921A47728009BD1CA /* WKNSDictionaryEmptyDictionaryCrash.mm in Sources */ = {isa = PBXBuildFile; fileRef = DF4B273821A47727009BD1CA /* WKNSDictionaryEmptyDictionaryCrash.mm */; };
@@ -2651,6 +2652,7 @@
 		CEBCA1351E3A803400C73293 /* page-with-csp.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "page-with-csp.html"; sourceTree = "<group>"; };
 		CEBCA1361E3A803400C73293 /* page-without-csp-iframe.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "page-without-csp-iframe.html"; sourceTree = "<group>"; };
 		CEBCA1371E3A803400C73293 /* page-without-csp.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "page-without-csp.html"; sourceTree = "<group>"; };
+		CED73D35246F204C00DAE327 /* InsertTextAlternatives.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = InsertTextAlternatives.mm; sourceTree = "<group>"; };
 		CEDA12402437C9EA00C28A9E /* editable-region-composited-and-non-composited-overlap.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = "editable-region-composited-and-non-composited-overlap.html"; path = "ios/editable-region-composited-and-non-composited-overlap.html"; sourceTree = SOURCE_ROOT; };
 		D3BE5E341E4CE85E00FD563A /* WKWebViewGetContents.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKWebViewGetContents.mm; sourceTree = "<group>"; };
 		DC69AA621CF77C6500C6272F /* ScopedLambda.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScopedLambda.cpp; sourceTree = "<group>"; };
@@ -3114,6 +3116,7 @@
 				CA97B3922193663B0045DF6F /* IndexedDBUserDelete.mm */,
 				0E404A8A2166DDF8008271BA /* InjectedBundleNodeHandleIsSelectElement.mm */,
 				79C5D430209D768300F1E7CA /* InjectedBundleNodeHandleIsTextField.mm */,
+				CED73D35246F204C00DAE327 /* InsertTextAlternatives.mm */,
 				2DB0232E1E4E871800707123 /* InteractionDeadlockAfterCrash.mm */,
 				2D116E1223E0CB3900208900 /* iOSMouseSupport.mm */,
 				5C69BDD41F82A7EB000F4F4B /* _javascript_DuringNavigation.mm */,
@@ -4985,6 +4988,7 @@
 				7CCE7EFC1A411AE600447C4C /* InjectedBundleFrameHitTest.cpp in Sources */,
 				7CCE7EFD1A411AE600447C4C /* InjectedBundleInitializationUserDataCallbackWins.cpp in Sources */,
 				7C83E0B81D0A64BD00FEBCF3 /* InjectedBundleMakeAllShadowRootsOpen.cpp in Sources */,
+				CED73D37246F29FA00DAE327 /* InsertTextAlternatives.mm in Sources */,
 				7CCE7EC31A411A7E00447C4C /* InspectorBar.mm in Sources */,
 				F44A531121B8990300DBB99C /* InstanceMethodSwizzler.mm in Sources */,
 				7CCE7EDA1A411A8700447C4C /* InstanceMethodSwizzler.mm in Sources */,

Added: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/InsertTextAlternatives.mm (0 => 261897)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/InsertTextAlternatives.mm	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/InsertTextAlternatives.mm	2020-05-20 00:05:46 UTC (rev 261897)
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if PLATFORM(IOS_FAMILY)
+
+#import "PlatformUtilities.h"
+#import "TestInputDelegate.h"
+#import "TestWKWebView.h"
+#import "UIKitSPI.h"
+#import "WKWebViewConfigurationExtras.h"
+#import <WebKit/WKWebViewPrivate.h>
+#import <wtf/unicode/CharacterNames.h>
+
+namespace TestWebKitAPI {
+
+TEST(InsertTextAlternatives, Simple)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'>A big&nbsp;</body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToEndOfDocument" argument:nil];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 1)"] boolValue]); // A
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(2, 3)"] boolValue]); // big
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 6)"] boolValue]); // "A big "
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(6, 5)"] boolValue]); // hello
+}
+
+TEST(InsertTextAlternatives, InsertLeadingSpace)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView waitForNextPresentationUpdate];
+
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [[webView textInputContentView] insertText:@" "];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 1)"] boolValue]); // <space>
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(1, 5)"] boolValue]); // hello
+}
+
+TEST(InsertTextAlternatives, InsertLeadingNewline)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    // Set CSS white-space and user-modify so that \n is inserted literally. Otherwise, it would be converted into a <br> if <body>
+    // only had "contenteditable='true'" because it is considered richly editable.
+    [webView synchronouslyLoadHTMLString:@"<body style='white-space: pre; -webkit-user-modify: read-write-plaintext-only'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [[webView textInputContentView] insertText:@"\n"];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 1)"] boolValue]); // \n
+    [webView _synchronouslyExecuteEditCommand:@"MoveDown" argument:nil];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 5)"] boolValue]); // hello
+}
+
+static inline NSString *makeNSStringWithCharacter(unichar c)
+{
+    return [NSString stringWithCharacters:&c length:1];
+};
+
+TEST(InsertTextAlternatives, InsertLeadingNoBreakSpace_ExpectedFailure)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView waitForNextPresentationUpdate];
+
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [[webView textInputContentView] insertText:makeNSStringWithCharacter(noBreakSpace)];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 1)"] boolValue]); // <no-break space>
+    // FIXME: Change this to EXPECT_TRUE() once leading no-break spaces do not cause removal of alternatives.
+    // See <https://webkit.org/b/212098>.
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(1, 5)"] boolValue]); // hello
+}
+
+TEST(InsertTextAlternatives, InsertTrailingSpace)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    // Set CSS white-space so that ' ' is inserted literally. Otherwise, it would be converted into a no-break space.
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true' style='white-space: pre'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [[webView textInputContentView] insertText:@" "];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 5)"] boolValue]); // hello
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(5, 1)"] boolValue]); // <space>
+}
+
+TEST(InsertTextAlternatives, InsertTrailingSpaceWhitespaceRebalance_ExpectedFailure)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [[webView textInputContentView] insertText:@" "];
+    [webView waitForNextPresentationUpdate];
+
+    // FIXME: Change this to EXPECT_TRUE() once <https://webkit.org/b/212093> is fixed.
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 5)"] boolValue]); // hello
+}
+
+TEST(InsertTextAlternatives, InsertTrailingNewline)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    // Set CSS white-space and user-modify so that \n is inserted literally. Otherwise, it would be converted into a <br> if <body>
+    // only had "contenteditable='true'" because it is considered richly editable.
+    [webView synchronouslyLoadHTMLString:@"<body style='white-space: pre; -webkit-user-modify: read-write-plaintext-only'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [[webView textInputContentView] insertText:@"\n"];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 5)"] boolValue]); // hello
+    [webView _synchronouslyExecuteEditCommand:@"MoveDown" argument:nil];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 1)"] boolValue]); // \n
+}
+
+TEST(InsertTextAlternatives, InsertTrailingNoBreakSpace_ExpectedFailure)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [[webView textInputContentView] insertText:makeNSStringWithCharacter(noBreakSpace)];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    // FIXME: Change first EXPECT to EXPECT_TRUE() once trailing no-break spaces do not cause removal of alternatives.
+    // See <https://webkit.org/b/212098>.
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 5)"] boolValue]); // hello
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(5, 1)"] boolValue]); // <no-break space>
+}
+
+TEST(InsertTextAlternatives, InsertSpaceInMiddle)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView waitForNextPresentationUpdate];
+
+    [webView stringByEvaluatingJavaScript:@"getSelection().setPosition(document.body.firstChild, 2)"];
+    [[webView textInputContentView] insertText:@" "];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 2)"] boolValue]); // he
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(2, 1)"] boolValue]); // <space>
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(3, 3)"] boolValue]); // llo
+}
+
+TEST(InsertTextAlternatives, InsertNewlineInMiddle_ExpectedFailure)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    // Set CSS white-space and user-modify so that \n is inserted literally. Otherwise, it would be converted into a <br> if <body>
+    // only had "contenteditable='true'" because it is considered richly editable.
+    [webView synchronouslyLoadHTMLString:@"<body style='white-space: pre; -webkit-user-modify: read-write-plaintext-only'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView waitForNextPresentationUpdate];
+
+    [webView stringByEvaluatingJavaScript:@"getSelection().setPosition(document.body.firstChild, 2)"];
+    [[webView textInputContentView] insertText:@"\n"];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    // FIXME: These results coincidentally match UIKit, but they just feel weird: they are asymmetric to
+    // what happens when a space is inserted in the middle. Marker should be removed when marker is split.
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 2)"] boolValue]); // he
+    [webView _synchronouslyExecuteEditCommand:@"MoveDown" argument:nil];
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 3)"] boolValue]); // llo
+}
+
+TEST(InsertTextAlternatives, InsertNoBreakSpaceInMiddle)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    // Set CSS white-space and user-modify so that \n is inserted literally. Otherwise, it would be converted into a <br> if <body>
+    // only had "contenteditable='true'" because it is considered richly editable.
+    [webView synchronouslyLoadHTMLString:@"<body style='white-space: pre; -webkit-user-modify: read-write-plaintext-only'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView waitForNextPresentationUpdate];
+
+    [webView stringByEvaluatingJavaScript:@"getSelection().setPosition(document.body.firstChild, 2)"];
+    [[webView textInputContentView] insertText:makeNSStringWithCharacter(noBreakSpace)];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 2)"] boolValue]); // he
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(2, 1)"] boolValue]); // <no-break space>
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(3, 3)"] boolValue]); // llo
+}
+
+TEST(InsertTextAlternatives, InsertLeadingNonWhitespaceCharacter)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView waitForNextPresentationUpdate];
+
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [[webView textInputContentView] insertText:@"a"];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 1)"] boolValue]); // a
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(1, 5)"] boolValue]); // hello
+}
+
+TEST(InsertTextAlternatives, InsertTrailingNonWhitespaceCharacter)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [[webView textInputContentView] insertText:@"a"];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 5)"] boolValue]); // hello
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(5, 1)"] boolValue]); // a
+}
+
+TEST(InsertTextAlternatives, InsertNonWhitespaceCharacterInMiddle)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [webView waitForNextPresentationUpdate];
+
+    [webView stringByEvaluatingJavaScript:@"getSelection().setPosition(document.body.firstChild, 2)"];
+    [[webView textInputContentView] insertText:@"a"];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 2)"] boolValue]); // he
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(2, 1)"] boolValue]); // a
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(3, 3)"] boolValue]); // llo
+}
+
+TEST(InsertTextAlternatives, InsertMultipleWordsWithAlternatives)
+{
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    // Set CSS white-space so that ' ' is inserted literally. Otherwise, it would be converted into a no-break space.
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable='true' style='white-space: pre'></body>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    [[webView textInputContentView] insertText:@"hello" alternatives:@[@"yellow"] style:UITextAlternativeStyleNone];
+    [[webView textInputContentView] insertText:@" "];
+    [[webView textInputContentView] insertText:@"worlds" alternatives:@[@"worm"] style:UITextAlternativeStyleNone];
+    [webView _synchronouslyExecuteEditCommand:@"MoveToBeginningOfDocument" argument:nil];
+    [webView waitForNextPresentationUpdate];
+
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(0, 5)"] boolValue]); // hello
+    EXPECT_FALSE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(5, 1)"] boolValue]); // <space>
+    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"internals.hasDictationAlternativesMarker(6, 5)"] boolValue]); // worlds
+}
+
+} // namespace TestWebKitAPI
+
+#endif // PLATFORM(IOS_FAMILY)
+
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to