iOS autocorrection does not trigger an input event of inputType "insertReplacementText"
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Oct 2016 01:06:13 +0000 (01:06 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Oct 2016 01:06:13 +0000 (01:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=164077
<rdar://problem/28987810>

Reviewed by Simon Fraser.

Source/WebCore:

Fixes candidate insertion on iOS, so that it fires input events of type "insertReplacementText" and adds two
iOS unit tests covering this change as well as the test infrastructure needed to support these tests. See
comments below for more details.

Tests: fast/events/ios/before-input-events-prevent-candidate-insertion.html
       fast/events/ios/input-events-insert-replacement-text.html

* dom/TextEvent.h:

Adds isAutocompletion() to TextEvent, as well as the TextEventInputAutocompletion text input type. When the
Editor handles this TextEvent, it will use this information when creating or modifying the corresponding typing
command.

* dom/TextEventInputType.h:
* editing/Editor.cpp:
(WebCore::Editor::insertText):
(WebCore::Editor::insertTextWithoutSendingTextEvent):
* editing/Editor.h:
* editing/TypingCommand.cpp:
(WebCore::editActionForTypingCommand):

Now takes whether the command is autocorrection into account. If so, the corresponding edit action should be
EditActionInsertReplacement rather than EditActionTypingInsertText.

(WebCore::TypingCommand::TypingCommand):
(WebCore::TypingCommand::deleteSelection):
(WebCore::TypingCommand::deleteKeyPressed):
(WebCore::TypingCommand::forwardDeleteKeyPressed):
(WebCore::TypingCommand::insertText):
(WebCore::TypingCommand::insertLineBreak):
(WebCore::TypingCommand::insertParagraphSeparatorInQuotedContent):
(WebCore::TypingCommand::insertParagraphSeparator):
(WebCore::TypingCommand::inputEventData):
(WebCore::TypingCommand::willAddTypingToOpenCommand):
* editing/TypingCommand.h:

Adds a new TypingCommand option, IsAutocompletion.

Source/WebKit2:

Small tweak to mark text insertion when autocorrecting as such, as opposed to regular keyboard input.

* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::syncApplyAutocorrection):

Tools:

Adds test support for inserting text candidates on iOS in the form of
UIScriptController.selectTextCandidateAtIndex(index, callback), which selects the text candidate at a given
index (this needs to be a value between 0-2 on iOS) and fires the callback when done.

To implement this, we wait for the text prediction view to have predictions (we determine this by polling at a
given interval) and then tap the center of the text prediction view at the given index.

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::selectTextCandidateAtIndex):
* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::selectTextCandidateAtIndex):
(WTR::UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex):
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/ios/UIKitSPI.h:
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::selectTextCandidateAtIndex):
(WTR::UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex):

LayoutTests:

Adds 2 new unit tests verifying that candidate text insertion can be prevented via beforeinput events, and that
beforeinput and input events of type "insertReplacementText" are fired when inserting candidate text on iOS.

* fast/events/ios/before-input-events-prevent-candidate-insertion-expected.txt: Added.
* fast/events/ios/before-input-events-prevent-candidate-insertion.html: Added.
* fast/events/ios/input-events-insert-replacement-text-expected.txt: Added.
* fast/events/ios/input-events-insert-replacement-text.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@208090 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/ios/before-input-events-prevent-candidate-insertion-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/ios/before-input-events-prevent-candidate-insertion.html [new file with mode: 0644]
LayoutTests/fast/events/ios/input-events-insert-replacement-text-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/ios/input-events-insert-replacement-text.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/TextEvent.h
Source/WebCore/dom/TextEventInputType.h
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/editing/TypingCommand.cpp
Source/WebCore/editing/TypingCommand.h
Source/WebKit2/ChangeLog
Source/WebKit2/WebProcess/WebPage/ios/WebPageIOS.mm
Tools/ChangeLog
Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm
Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl
Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp
Tools/TestRunnerShared/UIScriptContext/UIScriptController.h
Tools/WebKitTestRunner/ios/UIKitSPI.h
Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm

index b1a0822..33233a4 100644 (file)
@@ -1,3 +1,19 @@
+2016-10-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        iOS autocorrection does not trigger an input event of inputType "insertReplacementText"
+        https://bugs.webkit.org/show_bug.cgi?id=164077
+        <rdar://problem/28987810>
+
+        Reviewed by Simon Fraser.
+
+        Adds 2 new unit tests verifying that candidate text insertion can be prevented via beforeinput events, and that
+        beforeinput and input events of type "insertReplacementText" are fired when inserting candidate text on iOS.
+
+        * fast/events/ios/before-input-events-prevent-candidate-insertion-expected.txt: Added.
+        * fast/events/ios/before-input-events-prevent-candidate-insertion.html: Added.
+        * fast/events/ios/input-events-insert-replacement-text-expected.txt: Added.
+        * fast/events/ios/input-events-insert-replacement-text.html: Added.
+
 2016-10-28  Alex Christensen  <achristensen@webkit.org>
 
         Partially revert 207805 after resolution in URL spec issue 87
diff --git a/LayoutTests/fast/events/ios/before-input-events-prevent-candidate-insertion-expected.txt b/LayoutTests/fast/events/ios/before-input-events-prevent-candidate-insertion-expected.txt
new file mode 100644 (file)
index 0000000..98d0c2e
--- /dev/null
@@ -0,0 +1,5 @@
+
+To manually test, type 't' into the contenteditable and try to select a candidate. The replacement text should not be inserted.
+
+PASS: The replacement text was prevented.
+
diff --git a/LayoutTests/fast/events/ios/before-input-events-prevent-candidate-insertion.html b/LayoutTests/fast/events/ios/before-input-events-prevent-candidate-insertion.html
new file mode 100644 (file)
index 0000000..b1af490
--- /dev/null
@@ -0,0 +1,92 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+
+<html>
+
+<head>
+    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
+    <script id="ui-script" type="text/plain">
+        (function() {
+            uiController.didShowKeyboardCallback = function() {
+                uiController.typeCharacterUsingHardwareKeyboard("t", function() {
+                    uiController.selectTextCandidateAtIndex(1, function() {
+                        uiController.uiScriptComplete();
+                    });
+                });
+            }
+            uiController.singleTapAtPoint($x, $y, function() {});
+        })();
+    </script>
+
+    <script>
+        var progress = 0;
+        var replacementText = "";
+        let write = (message) => output.innerHTML += (message + "<br>");
+        function getUIScript() {
+            let rect = editable.getBoundingClientRect();
+            let script = document.getElementById("ui-script").text;
+            script = script.replace("$x", rect.left + rect.width / 2);
+            return script.replace("$y", rect.top + rect.height / 2);
+        }
+
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+            internals.settings.setInputEventsEnabled(true);
+        }
+
+        function incrementProgress()
+        {
+            progress++;
+            if (!window.testRunner || progress !== 2)
+                return;
+
+            setTimeout(function() {
+                if (editable.value.indexOf(replacementText) == -1)
+                    write(`PASS: The replacement text was prevented.`);
+                else
+                    write(`FAIL: The input value ${editable.value} should not contain replacement text ${replacementText}.`);
+                testRunner.notifyDone();
+            }, 0);
+        }
+
+        function runTest()
+        {
+            editable.addEventListener("beforeinput", preventBeforeInput);
+            if (!window.testRunner || !testRunner.runUIScript)
+                return;
+
+            testRunner.runUIScript(getUIScript(), function(result) {
+                incrementProgress();
+            });
+        }
+
+        function preventBeforeInput(event)
+        {
+            if (event.inputType === "insertReplacementText") {
+                replacementText = event.data;
+                if (!replacementText)
+                    write("FAIL: The replacement text input event lacks data.");
+                event.preventDefault();
+                incrementProgress();
+            }
+        }
+
+    </script>
+    <style>
+    #editable {
+        width: 200px;
+        height: 100px;
+        top: 0;
+        left: 0;
+        position: absolute;
+    }
+    </style>
+</head>
+
+<body style="margin: 0;" onload=runTest()>
+    <input contenteditable id="editable"></input>
+    <p>To manually test, type 't' into the contenteditable and try to select a candidate. The replacement text should not be inserted.</p>
+    <div id="output"></div>
+</body>
+
+</html>
diff --git a/LayoutTests/fast/events/ios/input-events-insert-replacement-text-expected.txt b/LayoutTests/fast/events/ios/input-events-insert-replacement-text-expected.txt
new file mode 100644 (file)
index 0000000..9fa40f9
--- /dev/null
@@ -0,0 +1,5 @@
+To manually test, type 't' into the contenteditable and try to select a candidate. The output should indicate that the beforeinput and input events for the text replacement were handled, and that the range of the beforeinput event is from 0 to 1.
+
+PASS: Handled text replacement before input event with range: [0, 1].
+PASS: Handled text replacement input event.
+
diff --git a/LayoutTests/fast/events/ios/input-events-insert-replacement-text.html b/LayoutTests/fast/events/ios/input-events-insert-replacement-text.html
new file mode 100644 (file)
index 0000000..8ad9a77
--- /dev/null
@@ -0,0 +1,90 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+
+<html>
+
+<head>
+    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
+    <script id="ui-script" type="text/plain">
+        (function() {
+            uiController.didShowKeyboardCallback = function() {
+                uiController.typeCharacterUsingHardwareKeyboard("t", function() {
+                    uiController.selectTextCandidateAtIndex(1, function() {
+                        uiController.uiScriptComplete();
+                    });
+                });
+            }
+            uiController.singleTapAtPoint($x, $y, function() {});
+        })();
+    </script>
+
+    <script>
+        var progress = 0;
+        let write = (message) => output.innerHTML += (message + "<br>");
+        function getUIScript() {
+            let rect = editable.getBoundingClientRect();
+            let script = document.getElementById("ui-script").text;
+            script = script.replace("$x", rect.left + rect.width / 2);
+            return script.replace("$y", rect.top + rect.height / 2);
+        }
+
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+            internals.settings.setInputEventsEnabled(true);
+        }
+
+        function incrementProgress()
+        {
+            progress++;
+            if (!window.testRunner || progress !== 3)
+                return;
+
+            setTimeout(function() {
+                editable.textContent = "";
+                testRunner.notifyDone();
+            }, 0);
+        }
+
+        function runTest()
+        {
+            editable.addEventListener("input", logInputEvent);
+            editable.addEventListener("beforeinput", logInputEvent);
+            if (!window.testRunner || !testRunner.runUIScript)
+                return;
+
+            testRunner.runUIScript(getUIScript(), function(result) {
+                incrementProgress();
+            });
+        }
+
+        function logInputEvent(event)
+        {
+            if (event.inputType !== "insertReplacementText")
+                return;
+
+            if (event.type === "beforeinput") {
+                let firstRange = event.getTargetRanges()[0];
+                write(`PASS: Handled text replacement before input event with range: [${firstRange.startOffset}, ${firstRange.endOffset}].`);
+            } else
+                write(`PASS: Handled text replacement input event.`);
+            incrementProgress();
+        }
+    </script>
+    <style>
+    #editable {
+        width: 200px;
+        height: 200px;
+        top: 0;
+        left: 0;
+        position: absolute;
+    }
+    </style>
+</head>
+
+<body style="margin: 0;" onload=runTest()>
+    <div contenteditable id="editable"></div>
+    <p>To manually test, type 't' into the contenteditable and try to select a candidate. The output should indicate that the beforeinput and input events for the text replacement were handled, and that the range of the beforeinput event is from 0 to 1.</p>
+    <div id="output"></div>
+</body>
+
+</html>
index 4e1ea44..e4404f1 100644 (file)
@@ -1,3 +1,49 @@
+2016-10-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        iOS autocorrection does not trigger an input event of inputType "insertReplacementText"
+        https://bugs.webkit.org/show_bug.cgi?id=164077
+        <rdar://problem/28987810>
+
+        Reviewed by Simon Fraser.
+
+        Fixes candidate insertion on iOS, so that it fires input events of type "insertReplacementText" and adds two
+        iOS unit tests covering this change as well as the test infrastructure needed to support these tests. See
+        comments below for more details.
+
+        Tests: fast/events/ios/before-input-events-prevent-candidate-insertion.html
+               fast/events/ios/input-events-insert-replacement-text.html
+
+        * dom/TextEvent.h:
+
+        Adds isAutocompletion() to TextEvent, as well as the TextEventInputAutocompletion text input type. When the
+        Editor handles this TextEvent, it will use this information when creating or modifying the corresponding typing
+        command.
+
+        * dom/TextEventInputType.h:
+        * editing/Editor.cpp:
+        (WebCore::Editor::insertText):
+        (WebCore::Editor::insertTextWithoutSendingTextEvent):
+        * editing/Editor.h:
+        * editing/TypingCommand.cpp:
+        (WebCore::editActionForTypingCommand):
+
+        Now takes whether the command is autocorrection into account. If so, the corresponding edit action should be
+        EditActionInsertReplacement rather than EditActionTypingInsertText.
+
+        (WebCore::TypingCommand::TypingCommand):
+        (WebCore::TypingCommand::deleteSelection):
+        (WebCore::TypingCommand::deleteKeyPressed):
+        (WebCore::TypingCommand::forwardDeleteKeyPressed):
+        (WebCore::TypingCommand::insertText):
+        (WebCore::TypingCommand::insertLineBreak):
+        (WebCore::TypingCommand::insertParagraphSeparatorInQuotedContent):
+        (WebCore::TypingCommand::insertParagraphSeparator):
+        (WebCore::TypingCommand::inputEventData):
+        (WebCore::TypingCommand::willAddTypingToOpenCommand):
+        * editing/TypingCommand.h:
+
+        Adds a new TypingCommand option, IsAutocompletion.
+
 2016-10-28  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r207700.
index 958bd74..0d2571b 100644 (file)
@@ -59,6 +59,7 @@ namespace WebCore {
         bool isPaste() const { return m_inputType == TextEventInputPaste; }
         bool isDrop() const { return m_inputType == TextEventInputDrop; }
         bool isDictation() const { return m_inputType == TextEventInputDictation; }
+        bool isAutocompletion() const { return m_inputType == TextEventInputAutocompletion; }
 
         bool shouldSmartReplace() const { return m_shouldSmartReplace; }
         bool shouldMatchStyle() const { return m_shouldMatchStyle; }
index 8be233c..0f23f5f 100644 (file)
@@ -30,6 +30,7 @@ namespace WebCore {
 
 enum TextEventInputType {
     TextEventInputKeyboard, // any newline characters in the text are line breaks only, not paragraph separators.
+    TextEventInputAutocompletion,
     TextEventInputLineBreak, // any tab characters in the text are backtabs.
     TextEventInputComposition,
     TextEventInputBackTab,
index 18ec0d9..8e8ed7b 100644 (file)
@@ -1213,9 +1213,9 @@ void Editor::clear()
     m_defaultParagraphSeparator = EditorParagraphSeparatorIsDiv;
 }
 
-bool Editor::insertText(const String& text, Event* triggeringEvent)
+bool Editor::insertText(const String& text, Event* triggeringEvent, TextEventInputType inputType)
 {
-    return m_frame.eventHandler().handleTextInputEvent(text, triggeringEvent);
+    return m_frame.eventHandler().handleTextInputEvent(text, triggeringEvent, inputType);
 }
 
 bool Editor::insertTextForConfirmedComposition(const String& text)
@@ -1269,6 +1269,8 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn
                     options |= TypingCommand::SelectInsertedText;
                 if (autocorrectionWasApplied)
                     options |= TypingCommand::RetainAutocorrectionIndicator;
+                if (triggeringEvent && triggeringEvent->isAutocompletion())
+                    options |= TypingCommand::IsAutocompletion;
                 TypingCommand::insertText(document, text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionFinal : TypingCommand::TextCompositionNone);
             }
 
index 5f11e35..e234a8a 100644 (file)
@@ -36,6 +36,7 @@
 #include "FindOptions.h"
 #include "FrameSelection.h"
 #include "TextChecking.h"
+#include "TextEventInputType.h"
 #include "TextIteratorBehavior.h"
 #include "VisibleSelection.h"
 #include "WritingDirection.h"
@@ -239,7 +240,7 @@ public:
     Command command(const String& commandName, EditorCommandSource);
     WEBCORE_EXPORT static bool commandIsSupportedFromMenuOrKeyBinding(const String& commandName); // Works without a frame.
 
-    WEBCORE_EXPORT bool insertText(const String&, Event* triggeringEvent);
+    WEBCORE_EXPORT bool insertText(const String&, Event* triggeringEvent, TextEventInputType = TextEventInputKeyboard);
     bool insertTextForConfirmedComposition(const String& text);
     WEBCORE_EXPORT bool insertDictatedText(const String&, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent);
     bool insertTextWithoutSendingTextEvent(const String&, bool selectInsertedText, TextEvent* triggeringEvent);
index 7561340..9e802a4 100644 (file)
@@ -77,7 +77,7 @@ private:
     const String& m_text;
 };
 
-static inline EditAction editActionForTypingCommand(TypingCommand::ETypingCommand command, TextGranularity granularity, TypingCommand::TextCompositionType compositionType)
+static inline EditAction editActionForTypingCommand(TypingCommand::ETypingCommand command, TextGranularity granularity, TypingCommand::TextCompositionType compositionType, bool isAutocompletion)
 {
     if (compositionType == TypingCommand::TextCompositionPending) {
         if (command == TypingCommand::InsertText)
@@ -112,7 +112,7 @@ static inline EditAction editActionForTypingCommand(TypingCommand::ETypingComman
             return EditActionTypingDeleteLineForward;
         return EditActionTypingDeleteForward;
     case TypingCommand::InsertText:
-        return EditActionTypingInsertText;
+        return isAutocompletion ? EditActionInsertReplacement : EditActionTypingInsertText;
     case TypingCommand::InsertLineBreak:
         return EditActionTypingInsertLineBreak;
     case TypingCommand::InsertParagraphSeparator:
@@ -140,7 +140,7 @@ static inline bool editActionIsDeleteByTyping(EditAction action)
 }
 
 TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
-    : TextInsertionBaseCommand(document, editActionForTypingCommand(commandType, granularity, compositionType))
+    : TextInsertionBaseCommand(document, editActionForTypingCommand(commandType, granularity, compositionType, options & IsAutocompletion))
     , m_commandType(commandType)
     , m_textToInsert(textToInsert)
     , m_currentTextToInsert(textToInsert)
@@ -150,6 +150,7 @@ TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, con
     , m_granularity(granularity)
     , m_compositionType(compositionType)
     , m_shouldAddToKillRing(options & AddsToKillRing)
+    , m_isAutocompletion(options & IsAutocompletion)
     , m_openedByBackwardDelete(false)
     , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator)
     , m_shouldPreventSpellChecking(options & PreventSpellChecking)
@@ -167,6 +168,7 @@ void TypingCommand::deleteSelection(Document& document, Options options, TextCom
         return;
 
     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
+        lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
         lastTypingCommand->setCompositionType(compositionType);
         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
         lastTypingCommand->deleteSelection(options & SmartDelete);
@@ -181,6 +183,7 @@ void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGr
     if (granularity == CharacterGranularity) {
         if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
             updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), document.frame());
+            lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
             lastTypingCommand->setCompositionType(TextCompositionNone);
             lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
             lastTypingCommand->deleteKeyPressed(granularity, options & AddsToKillRing);
@@ -198,6 +201,7 @@ void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options,
     if (granularity == CharacterGranularity) {
         if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
             updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
+            lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
             lastTypingCommand->setCompositionType(TextCompositionNone);
             lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
             lastTypingCommand->forwardDeleteKeyPressed(granularity, options & AddsToKillRing);
@@ -251,6 +255,7 @@ void TypingCommand::insertText(Document& document, const String& text, const Vis
             lastTypingCommand->setEndingSelection(selectionForInsertion);
         }
 
+        lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
         lastTypingCommand->setCompositionType(compositionType);
         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
@@ -265,6 +270,7 @@ void TypingCommand::insertText(Document& document, const String& text, const Vis
 void TypingCommand::insertLineBreak(Document& document, Options options)
 {
     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
+        lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
         lastTypingCommand->setCompositionType(TextCompositionNone);
         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
         lastTypingCommand->insertLineBreakAndNotifyAccessibility();
@@ -277,6 +283,7 @@ void TypingCommand::insertLineBreak(Document& document, Options options)
 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document)
 {
     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
+        lastTypingCommand->setIsAutocompletion(false);
         lastTypingCommand->setCompositionType(TextCompositionNone);
         lastTypingCommand->insertParagraphSeparatorInQuotedContentAndNotifyAccessibility();
         return;
@@ -288,6 +295,7 @@ void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document)
 void TypingCommand::insertParagraphSeparator(Document& document, Options options)
 {
     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
+        lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
         lastTypingCommand->setCompositionType(TextCompositionNone);
         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
         lastTypingCommand->insertParagraphSeparatorAndNotifyAccessibility();
@@ -398,6 +406,7 @@ String TypingCommand::inputEventData() const
 {
     switch (m_currentTypingEditAction) {
     case EditActionTypingInsertText:
+    case EditActionInsertReplacement:
     case EditActionTypingInsertPendingComposition:
     case EditActionTypingInsertFinalComposition:
         return m_currentTextToInsert;
@@ -469,7 +478,7 @@ void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
 bool TypingCommand::willAddTypingToOpenCommand(ETypingCommand commandType, TextGranularity granularity, const String& text, RefPtr<Range>&& range)
 {
     m_currentTextToInsert = text;
-    m_currentTypingEditAction = editActionForTypingCommand(commandType, granularity, m_compositionType);
+    m_currentTypingEditAction = editActionForTypingCommand(commandType, granularity, m_compositionType, m_isAutocompletion);
 
     if (!shouldDeferWillApplyCommandUntilAddingTypingCommand())
         return true;
index 5083540..b9401b5 100644 (file)
@@ -53,7 +53,8 @@ public:
         AddsToKillRing = 1 << 1,
         RetainAutocorrectionIndicator = 1 << 2,
         PreventSpellChecking = 1 << 3,
-        SmartDelete = 1 << 4
+        SmartDelete = 1 << 4,
+        IsAutocompletion = 1 << 5,
     };
     typedef unsigned Options;
 
@@ -79,6 +80,7 @@ public:
     void forwardDeleteKeyPressed(TextGranularity, bool shouldAddToKillRing);
     void deleteSelection(bool smartDelete);
     void setCompositionType(TextCompositionType type) { m_compositionType = type; }
+    void setIsAutocompletion(bool isAutocompletion) { m_isAutocompletion = isAutocompletion; }
 
 #if PLATFORM(IOS)
     void setEndingSelectionOnLastInsertCommand(const VisibleSelection& selection);
@@ -151,6 +153,7 @@ private:
     TextCompositionType m_compositionType;
     bool m_shouldAddToKillRing;
     bool m_preservesTypingStyle;
+    bool m_isAutocompletion;
     
     // Undoing a series of backward deletes will restore a selection around all of the
     // characters that were deleted, but only if the typing command being undone
index 275b30e..16303f4 100644 (file)
@@ -1,3 +1,16 @@
+2016-10-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        iOS autocorrection does not trigger an input event of inputType "insertReplacementText"
+        https://bugs.webkit.org/show_bug.cgi?id=164077
+        <rdar://problem/28987810>
+
+        Reviewed by Simon Fraser.
+
+        Small tweak to mark text insertion when autocorrecting as such, as opposed to regular keyboard input.
+
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::syncApplyAutocorrection):
+
 2016-10-28  Megan Gardner  <megan_gardner@apple.com>
 
         Rename SharedMemoryMac to SharedMemoryCocoa
index c41c9be..a159256 100644 (file)
@@ -2171,7 +2171,7 @@ void WebPage::syncApplyAutocorrection(const String& correction, const String& or
     
     frame.selection().setSelectedRange(range.get(), UPSTREAM, true);
     if (correction.length())
-        frame.editor().insertText(correction, 0);
+        frame.editor().insertText(correction, 0, originalText.isEmpty() ? TextEventInputKeyboard : TextEventInputAutocompletion);
     else
         frame.editor().deleteWithDirection(DirectionBackward, CharacterGranularity, false, true);
     correctionApplied = true;
index bc17953..23abaf4 100644 (file)
@@ -1,3 +1,30 @@
+2016-10-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        iOS autocorrection does not trigger an input event of inputType "insertReplacementText"
+        https://bugs.webkit.org/show_bug.cgi?id=164077
+        <rdar://problem/28987810>
+
+        Reviewed by Simon Fraser.
+
+        Adds test support for inserting text candidates on iOS in the form of
+        UIScriptController.selectTextCandidateAtIndex(index, callback), which selects the text candidate at a given
+        index (this needs to be a value between 0-2 on iOS) and fires the callback when done.
+
+        To implement this, we wait for the text prediction view to have predictions (we determine this by polling at a
+        given interval) and then tap the center of the text prediction view at the given index.
+
+        * DumpRenderTree/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::selectTextCandidateAtIndex):
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::selectTextCandidateAtIndex):
+        (WTR::UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex):
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        * WebKitTestRunner/ios/UIKitSPI.h:
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::selectTextCandidateAtIndex):
+        (WTR::UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex):
+
 2016-10-28  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r207700.
index a9d3c63..5eac8d8 100644 (file)
@@ -116,6 +116,10 @@ void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef characte
 {
 }
 
+void UIScriptController::selectTextCandidateAtIndex(long, JSValueRef)
+{
+}
+
 void UIScriptController::keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
 {
 }
index 984dcf6..2ec9498 100644 (file)
@@ -49,6 +49,8 @@ interface UIScriptController {
     void keyDownUsingHardwareKeyboard(DOMString character, object callback);
     void keyUpUsingHardwareKeyboard(DOMString character, object callback);
 
+    void selectTextCandidateAtIndex(long index, object callback);
+
     // eventsJSON describes a series of user events in JSON form. For the keys, see HIDEventGenerator.mm.
     // For example, this JSON describes a touch down followed by a touch up (i.e. a single tap).
     //  {
index 4043cd7..a448294 100644 (file)
@@ -214,6 +214,14 @@ void UIScriptController::keyUpUsingHardwareKeyboard(JSStringRef, JSValueRef)
 {
 }
 
+void UIScriptController::selectTextCandidateAtIndex(long, JSValueRef)
+{
+}
+
+void UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex(long, unsigned, float)
+{
+}
+
 void UIScriptController::keyDownUsingHardwareKeyboard(JSStringRef, JSValueRef)
 {
 }
index af60950..d6d7946 100644 (file)
@@ -70,6 +70,8 @@ public:
     void keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback);
     void keyUpUsingHardwareKeyboard(JSStringRef character, JSValueRef callback);
 
+    void selectTextCandidateAtIndex(long index, JSValueRef callback);
+
     void keyboardAccessoryBarNext();
     void keyboardAccessoryBarPrevious();
     
@@ -136,6 +138,7 @@ private:
     JSClassRef wrapperClass() final;
 
     JSObjectRef objectFromRect(const WebCore::FloatRect&) const;
+    void waitForTextPredictionsViewAndSelectCandidateAtIndex(long index, unsigned callbackID, float interval);
 
     UIScriptContext* m_context;
 };
index ef914b2..600b937 100644 (file)
 #import <UIKit/UIApplication_Private.h>
 #import <UIKit/UIWindow_Private.h>
 
+@interface UIKeyboardPredictionView : UIView
++ (UIKeyboardPredictionView *)activeInstance;
+- (BOOL)hasPredictions;
+@end
+
 #else
 
 #import "IOKitSPI.h"
index a888699..ae86fc5 100644 (file)
@@ -255,6 +255,46 @@ void UIScriptController::keyUpUsingHardwareKeyboard(JSStringRef character, JSVal
     }];
 }
 
+void UIScriptController::selectTextCandidateAtIndex(long index, JSValueRef callback)
+{
+#if USE(APPLE_INTERNAL_SDK)
+    static const float textPredictionsPollingInterval = 0.1;
+    unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
+    waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, textPredictionsPollingInterval);
+#else
+    // FIXME: This is a no-op on non-internal builds due to UIKeyboardPredictionView being unavailable. Ideally, there should be a better way to
+    // retrieve information and interact with the predictive text view that will be compatible with OpenSource.
+    UNUSED_PARAM(index);
+    UNUSED_PARAM(callback);
+#endif
+}
+
+void UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex(long index, unsigned callbackID, float interval)
+{
+#if USE(APPLE_INTERNAL_SDK)
+    if (![UIKeyboardPredictionView activeInstance].hasPredictions) {
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^() {
+            waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, interval);
+        });
+        return;
+    }
+
+    PlatformWKView webView = TestController::singleton().mainWebView()->platformView();
+    CGRect predictionViewFrame = [UIKeyboardPredictionView activeInstance].frame;
+    // This assumes there are 3 predicted text cells of equal width, which is the case on iOS.
+    float offsetX = (index * 2 + 1) * CGRectGetWidth(predictionViewFrame) / 6;
+    float offsetY = CGRectGetHeight(webView.window.frame) - CGRectGetHeight([UIKeyboardPredictionView activeInstance].superview.frame) + CGRectGetHeight(predictionViewFrame) / 2;
+    [[HIDEventGenerator sharedHIDEventGenerator] tap:CGPointMake(offsetX, offsetY) completionBlock:^{
+        if (m_context)
+            m_context->asyncTaskComplete(callbackID);
+    }];
+#else
+    UNUSED_PARAM(index);
+    UNUSED_PARAM(callbackID);
+    UNUSED_PARAM(interval);
+#endif
+}
+
 void UIScriptController::dismissFormAccessoryView()
 {
     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();