Picking an emoji via the emoji dialog (Ctrl+Cmd+Space) fires inconsistent beforeinput...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 28 Aug 2017 05:12:56 +0000 (05:12 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 28 Aug 2017 05:12:56 +0000 (05:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170955
<rdar://problem/31697653>

Reviewed by Ryosuke Niwa.

Source/WebKit:

Currently, we insert text with TextEventInputAutocompletion as the text event input type if any text range to
replace was specified by the platform. Instead, limit this only to when the text replacement range is not empty.
This more closely matches the intention of the spec, which states that the "insertReplacementText" inputType
should be used when "[replacing] existing text by means of a spell checker, auto-correct or similar".

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::insertTextAsync):

Source/WebKitLegacy/mac:

Tweak -insertText: to pass TextEventInputAutocompletion to Editor::insertText when inserting text, if existing
text is being replaced.

* WebView/WebHTMLView.mm:
(-[WebHTMLView insertText:]):

Tools:

Replace UIScriptController.insertText with UIScriptController.replaceTextAtRange, and implement
replaceTextAtRange in WebKit1. See corresponding layout tests (input-event-insert-replacement.html and
before-input-prevent-insert-replacement.html) for more detail.

* DumpRenderTree/DumpRenderTree.xcodeproj/project.pbxproj:
* DumpRenderTree/mac/AppKitTestSPI.h: Added.

Introduce an SPI header for private AppKit headers needed to support DumpRenderTree.

* DumpRenderTree/mac/UIScriptControllerMac.mm:
(WTR::UIScriptController::replaceTextAtRange):
(WTR::UIScriptController::insertText): Deleted.
* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::replaceTextAtRange):
(WTR::UIScriptController::insertText): Deleted.
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/mac/UIScriptControllerMac.mm:
(WTR::UIScriptController::replaceTextAtRange):
(WTR::UIScriptController::insertText): Deleted.

Replace UIScriptController.insertText with UIScriptController.replaceTextAtRange, which better describes the
behavior of this function.

LayoutTests:

Augments two existing layout tests to check for additional cases of inserting text with replacement ranges.
Also enables this test for WebKit1 on Mac. Both these tests are currently enabled only for WebKit2, and also only
check the case where we're replacing an existing non-empty range of text.

* fast/events/before-input-prevent-insert-replacement-expected.txt:
* fast/events/before-input-prevent-insert-replacement.html:
* fast/events/input-event-insert-replacement-expected.txt:
* fast/events/input-event-insert-replacement.html:

Tests for cases of replacing existing text ranges, and inserting text at a position.

* platform/mac-wk1/TestExpectations:
* resources/ui-helper.js:

Add a new UIHelper function to insert text at a given replacement range. This codepath is taken when selecting
an emoji using the emoji picker menu on Mac, and also when selecting a dead key option after holding down on a
vowel key.

(window.UIHelper.replaceTextAtRange):

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

19 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/before-input-prevent-insert-replacement-expected.txt
LayoutTests/fast/events/before-input-prevent-insert-replacement.html
LayoutTests/fast/events/input-event-insert-replacement-expected.txt
LayoutTests/fast/events/input-event-insert-replacement.html
LayoutTests/platform/mac-wk1/TestExpectations
LayoutTests/resources/ui-helper.js
Source/WebKit/ChangeLog
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebView/WebHTMLView.mm
Tools/ChangeLog
Tools/DumpRenderTree/DumpRenderTree.xcodeproj/project.pbxproj
Tools/DumpRenderTree/mac/AppKitTestSPI.h [new file with mode: 0644]
Tools/DumpRenderTree/mac/UIScriptControllerMac.mm
Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl
Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp
Tools/TestRunnerShared/UIScriptContext/UIScriptController.h
Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm

index c8554ad67fa301d5861c3d740cb150a2f30dba06..a95e4735ea39df3e78d68dfb727eec96adf7ef15 100644 (file)
@@ -1,3 +1,31 @@
+2017-08-27  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Picking an emoji via the emoji dialog (Ctrl+Cmd+Space) fires inconsistent beforeinput events.
+        https://bugs.webkit.org/show_bug.cgi?id=170955
+        <rdar://problem/31697653>
+
+        Reviewed by Ryosuke Niwa.
+
+        Augments two existing layout tests to check for additional cases of inserting text with replacement ranges.
+        Also enables this test for WebKit1 on Mac. Both these tests are currently enabled only for WebKit2, and also only
+        check the case where we're replacing an existing non-empty range of text.
+
+        * fast/events/before-input-prevent-insert-replacement-expected.txt:
+        * fast/events/before-input-prevent-insert-replacement.html:
+        * fast/events/input-event-insert-replacement-expected.txt:
+        * fast/events/input-event-insert-replacement.html:
+
+        Tests for cases of replacing existing text ranges, and inserting text at a position.
+
+        * platform/mac-wk1/TestExpectations:
+        * resources/ui-helper.js:
+
+        Add a new UIHelper function to insert text at a given replacement range. This codepath is taken when selecting
+        an emoji using the emoji picker menu on Mac, and also when selecting a dead key option after holding down on a
+        vowel key.
+
+        (window.UIHelper.replaceTextAtRange):
+
 2017-08-27  Devin Rousso  <webkit@devinrousso.com>
 
         Web Inspector: Record actions performed on WebGLRenderingContext
index 95a0219490bc440f25e5d7a37a7aa7212dd3b050..cb26ba8e31825547e455fb26399c4fb8866965a4 100644 (file)
@@ -1,7 +1,20 @@
-a
-Typing 'a'...
-(editable): type=beforeinput, inputType=insertText, data=`a`, dataTransfer=`null`
-(editable): type=input, inputType=insertText, data=`a`, dataTransfer=`null`
-Attempting to replace 'a' with 'b'...
-(editable): type=beforeinput, inputType=insertReplacementText, data=`null`, dataTransfer=`plain:"b", html:"b"`
+To manually test, press and hold down 'a' and select one of the accented characters.
+You should observe that the replacement accented character does not replace 'a'.
+Then insert an emoji using the emoji picker. This should insert something into the editor.
+Finally, select all the content and attempt to replace it with an emoji. This should do nothing.
+ac
+(1) Typing 'a'...
+(editable): type=beforeinput, inputType=insertText, data=a, dataTransfer=(null)
+(editable): type=input, inputType=insertText, data=a, dataTransfer=(null)
+The editor now has text content: a
+(2) Preventing default when replacing 'a' with 'b'...
+(editable): type=beforeinput, inputType=insertReplacementText, data=null, dataTransfer=plain:"b", html:"b"
+The editor now has text content: a
+(3) Inserting 'c' after 'a'...
+(editable): type=beforeinput, inputType=insertText, data=c, dataTransfer=(null)
+(editable): type=input, inputType=insertText, data=c, dataTransfer=(null)
+The editor now has text content: ac
+(4) Selecting all and preventing replacement with 'd'...
+(editable): type=beforeinput, inputType=insertReplacementText, data=null, dataTransfer=plain:"d", html:"d"
+The editor now has text content: ac
 
index 0b0e7b5c8617da48876b96d8d64e07488b0a5ddf..74e7b8d328d2a7d2a9d6c47b4221b9625453d037 100644 (file)
@@ -1,60 +1,52 @@
 <!DOCTYPE html>
 <html>
-<body>
-    <div id="editable" onbeforeinput=handleInputEvent(event) oninput=handleInputEvent(event) contenteditable></div>
-    <div id="output"></div>
-    <script type="text/javascript">
-        let write = s => output.innerHTML += s + "<br>";
-        var progress = 0;
-        editable.focus();
+<head>
+<script src="../../resources/ui-helper.js"></script>
+</head>
 
-        if (window.internals && window.testRunner) {
-            internals.settings.setInputEventsEnabled(true);
-            testRunner.dumpAsText();
-            testRunner.waitUntilDone();
-            if (window.eventSender && testRunner.runUIScript) {
-                write("Typing 'a'...");
-                eventSender.keyDown("a");
-                write("Attempting to replace 'a' with 'b'...");
-                testRunner.runUIScript(getUIScript(), (result) => incrementProgress());
-            }
-        } else {
-            write("To manually test, press and hold down 'a' and select one of the accented characters.");
-            write("You should observe that the replacement accented character does not replace 'a'.");
-        }
+<div>To manually test, press and hold down 'a' and select one of the accented characters.</div>
+<div>You should observe that the replacement accented character does not replace 'a'.</div>
+<div>Then insert an emoji using the emoji picker. This should insert something into the editor.</div>
+<div>Finally, select all the content and attempt to replace it with an emoji. This should do nothing.</div>
+<div id="editable" onbeforeinput=handleInputEvent(event) oninput=handleInputEvent(event) contenteditable></div>
+<div id="output"></div>
 
-        function incrementProgress()
-        {
-            progress++;
-            if (progress == 2)
-                testRunner.notifyDone();
-        }
+<script type="text/javascript">
+    let write = s => output.innerHTML += s + "<br>";
+    editable.focus();
 
-        function handleInputEvent(event)
-        {
-            write(`(${event.target.id}): type=${event.type}, inputType=${event.inputType}, data=\`${event.data}\`, dataTransfer=\`${dataTransferAsString(event.dataTransfer)}\``);
-            if (event.inputType === "insertReplacementText") {
-                event.preventDefault();
-                incrementProgress();
-            }
-        }
+    if (window.testRunner && window.eventSender && testRunner.runUIScript) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+        runTest();
+    }
 
-        function dataTransferAsString(dataTransfer)
-        {
-            if (!dataTransfer)
-                return "null";
+    async function runTest() {
+        write("(1) Typing 'a'...");
+        eventSender.keyDown("a");
+        write(`The editor now has text content: ${editable.textContent}`);
+        write("(2) Preventing default when replacing 'a' with 'b'...");
+        await UIHelper.replaceTextAtRange("b", 0, 1)
+        write(`The editor now has text content: ${editable.textContent}`);
+        write("(3) Inserting 'c' after 'a'...");
+        await UIHelper.replaceTextAtRange("c", 1, 0);
+        write(`The editor now has text content: ${editable.textContent}`);
+        write("(4) Selecting all and preventing replacement with 'd'...");
+        document.execCommand("SelectAll")
+        await UIHelper.replaceTextAtRange("d", 0, 2);
+        write(`The editor now has text content: ${editable.textContent}`);
 
-            return `plain:"${dataTransfer.getData('text/plain')}", html:"${dataTransfer.getData('text/html')}"`;
-        }
+        testRunner.notifyDone();
+    }
 
-        function getUIScript()
-        {
-            return `
-            (function() {
-                uiController.insertText("b", 0, 1);
-                uiController.uiScriptComplete();
-            })();`
-        }
-    </script>
+    function handleInputEvent(event)
+    {
+        let dataTransferString = event.dataTransfer ? `plain:"${event.dataTransfer.getData('text/plain')}", html:"${event.dataTransfer.getData('text/html')}"` : "(null)";
+        write(`(${event.target.id}): type=${event.type}, inputType=${event.inputType}, data=${event.data}, dataTransfer=${dataTransferString}`);
+
+        if (event.type === "beforeinput" && event.inputType === "insertReplacementText")
+            event.preventDefault();
+    }
+</script>
 </body>
-</html>
\ No newline at end of file
+</html>
index cccdab38a049cd1a25374096d6e62db422c1abfc..18e693737615d73e163c025e7ea9c11334398ff7 100644 (file)
@@ -1,11 +1,23 @@
+To manually test, press and hold down 'a' and select one of the accented characters."
+You should observe a pair of beforeinput/input events for both 'a' and the replacement accented character."
+Importantly, the inputType of these four events should be 'insertReplacementText'."
+Then insert a single emoji character. You should observe beforeinput/input events for the inserted emoji."
+Importantly, the inputType of these two events should be 'insertText'."
 
-Typing 'a'...
-(editable): type=beforeinput, inputType=insertText, data=`a`, dataTransfer=`null`
-(editable): type=input, inputType=insertText, data=`a`, dataTransfer=`null`
+(1) Typing 'a'...
+(editable): type=beforeinput, inputType=insertText, data=a, dataTransfer=null
+(editable): type=input, inputType=insertText, data=a, dataTransfer=null
 The value of the input is now: a
-
-Replacing 'a' with 'b'...
-(editable): type=beforeinput, inputType=insertReplacementText, data=`b`, dataTransfer=`null`
-(editable): type=input, inputType=insertReplacementText, data=`b`, dataTransfer=`null`
+(2) Replacing 'a' with 'b'...
+(editable): type=beforeinput, inputType=insertReplacementText, data=b, dataTransfer=null
+(editable): type=input, inputType=insertReplacementText, data=b, dataTransfer=null
 The value of the input is now: b
+(3) Inserting 'c' after 'b'...
+(editable): type=beforeinput, inputType=insertText, data=c, dataTransfer=null
+(editable): type=input, inputType=insertText, data=c, dataTransfer=null
+The value of the input is now: bc
+(4) Selecting all and replacing with 'd'...
+(editable): type=beforeinput, inputType=insertReplacementText, data=d, dataTransfer=null
+(editable): type=input, inputType=insertReplacementText, data=d, dataTransfer=null
+The value of the input is now: d
 
index 954bc558713e0282903ff5c68309692e37c4edae..8374551d4eb5c6969d0cd1cf80fbd0479487d799 100644 (file)
@@ -1,55 +1,47 @@
 <!DOCTYPE html>
 <html>
-<body>
-    <input id="editable" onbeforeinput=logInputEvent(event) oninput=logInputEvent(event)></input>
-    <div id="output"></div>
-    <script type="text/javascript">
-        let write = s => output.innerHTML += s + "<br>";
-        var progress = 0;
-        editable.focus();
+<head>
+<script src="../../resources/ui-helper.js"></script>
+</head>
 
-        if (window.internals && window.testRunner) {
-            internals.settings.setInputEventsEnabled(true);
-            testRunner.dumpAsText();
-            testRunner.waitUntilDone();
-            if (window.eventSender && testRunner.runUIScript) {
-                write("Typing 'a'...");
-                eventSender.keyDown("a");
-                write(`The value of the input is now: ${editable.value}`);
-                write("");
-                write("Replacing 'a' with 'b'...");
-                testRunner.runUIScript(getUIScript(), (result) => incrementProgress());
-            }
-        } else {
-            write("To manually test, press and hold down 'a' and select one of the accented characters.");
-            write("You should observe a pair of beforeinput/input events for both 'a' and the replacement accented character.");
-            write("Importantly, the inputType of the last two events should be 'insertReplacementText'.");
-        }
+<div>To manually test, press and hold down 'a' and select one of the accented characters."</div>
+<div>You should observe a pair of beforeinput/input events for both 'a' and the replacement accented character."</div>
+<div>Importantly, the inputType of these four events should be 'insertReplacementText'."</div>
+<div>Then insert a single emoji character. You should observe beforeinput/input events for the inserted emoji."</div>
+<div>Importantly, the inputType of these two events should be 'insertText'."</div>
 
-        function incrementProgress()
-        {
-            progress++;
-            if (progress != 5)
-                return;
+<input id="editable" onbeforeinput=logInputEvent(event) oninput=logInputEvent(event)></input>
+<div id="output"></div>
+<script type="text/javascript">
+    let write = s => output.innerHTML += s + "<br>";
+    editable.focus();
+    if (window.testRunner && window.eventSender && testRunner.runUIScript) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+        runTest();
+    }
 
-            write(`The value of the input is now: ${editable.value}`);
-            testRunner.notifyDone();
-        }
+    function logInputEvent(event)
+    {
+        write(`(${event.target.id}): type=${event.type}, inputType=${event.inputType}, data=${event.data}, dataTransfer=${event.dataTransfer}`);
+    }
 
-        function logInputEvent(event)
-        {
-            write(`(${event.target.id}): type=${event.type}, inputType=${event.inputType}, data=\`${event.data}\`, dataTransfer=\`${event.dataTransfer}\``);
-            incrementProgress();
-        }
+    async function runTest() {
+        write("(1) Typing 'a'...");
+        eventSender.keyDown("a");
+        write(`The value of the input is now: ${editable.value}`);
+        write("(2) Replacing 'a' with 'b'...");
+        await UIHelper.replaceTextAtRange("b", 0, 1);
+        write(`The value of the input is now: ${editable.value}`);
+        write("(3) Inserting 'c' after 'b'...");
+        await UIHelper.replaceTextAtRange("c", 1, 0);
+        write(`The value of the input is now: ${editable.value}`);
+        write("(4) Selecting all and replacing with 'd'...");
+        document.execCommand("SelectAll")
+        await UIHelper.replaceTextAtRange("d", 0, 2);
+        write(`The value of the input is now: ${editable.value}`);
 
-        function getUIScript()
-        {
-            return `
-            (function() {
-                uiController.insertText("b", 0, 1);
-                uiController.uiScriptComplete();
-            })();`
-        }
-    </script>
-</body>
-</html>
\ No newline at end of file
+        testRunner.notifyDone();
+    }
+</script>
+</html>
index 8f9fb4c2d296d173fd1f340578405c9dd2af9d2e..345127000764ba7417986af5b45815e0d742fd92 100644 (file)
@@ -85,10 +85,6 @@ fast/forms/file/open-file-panel.html [ Skip ]
 # WK1 and WK2 mousemove events are subtly different in ways that break this test on WK1.
 fast/events/ghostly-mousemoves-in-subframe.html [ Skip ]
 
-# Test support for inserting special characters is not yet implemented on WK1.
-fast/events/before-input-prevent-insert-replacement.html [ Skip ]
-fast/events/input-event-insert-replacement.html [ Skip ]
-
 # Media Stream API testing is not supported for WK1 yet.
 fast/mediastream
 http/tests/media/media-stream
index 788fdf8cc59302aa5017dc2797a0a8fb73d66ee2..4490d512f1e777595560eabf257bd3bc7e0b0207 100644 (file)
@@ -110,6 +110,15 @@ window.UIHelper = class UIHelper {
         });
     }
 
+    static replaceTextAtRange(text, location, length) {
+        return new Promise(resolve => {
+            testRunner.runUIScript(`(() => {
+                uiController.replaceTextAtRange("${text}", ${location}, ${length});
+                uiController.uiScriptComplete('Done');
+            })()`, resolve);
+        });
+    }
+
     static wait(promise)
     {
         testRunner.waitUntilDone();
index b82b4e0b03ef7f0553d87d676e6b2c0158ba2e6b..dd319ae154d2b900a8361d5a78b3064c26299d23 100644 (file)
@@ -1,3 +1,19 @@
+2017-08-27  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Picking an emoji via the emoji dialog (Ctrl+Cmd+Space) fires inconsistent beforeinput events.
+        https://bugs.webkit.org/show_bug.cgi?id=170955
+        <rdar://problem/31697653>
+
+        Reviewed by Ryosuke Niwa.
+
+        Currently, we insert text with TextEventInputAutocompletion as the text event input type if any text range to
+        replace was specified by the platform. Instead, limit this only to when the text replacement range is not empty.
+        This more closely matches the intention of the spec, which states that the "insertReplacementText" inputType
+        should be used when "[replacing] existing text by means of a spell checker, auto-correct or similar".
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::insertTextAsync):
+
 2017-08-27  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [iOS WK2] Web process crashes after changing selection to the end of the document when speaking a selection
index c7ee31bbe44777fb751a63e3ebcc1260595438dd..a166cde92be47d7bd8b5316eaf54716228a7666b 100644 (file)
@@ -4843,11 +4843,10 @@ void WebPage::insertTextAsync(const String& text, const EditingRange& replacemen
 
     bool replacesText = false;
     if (replacementEditingRange.location != notFound) {
-        RefPtr<Range> replacementRange = rangeFromEditingRange(frame, replacementEditingRange, static_cast<EditingRangeIsRelativeTo>(editingRangeIsRelativeTo));
-        if (replacementRange) {
+        if (auto replacementRange = rangeFromEditingRange(frame, replacementEditingRange, static_cast<EditingRangeIsRelativeTo>(editingRangeIsRelativeTo))) {
             SetForScope<bool> isSelectingTextWhileInsertingAsynchronously(m_isSelectingTextWhileInsertingAsynchronously, suppressSelectionUpdate);
             frame.selection().setSelection(VisibleSelection(*replacementRange, SEL_DEFAULT_AFFINITY));
-            replacesText = true;
+            replacesText = replacementEditingRange.length;
         }
     }
     
index 80444e672d02366cbd292831ea553fe0d3e98d53..97b4df8cc3c6279766a2ada79aa7b4d4730d6f38 100644 (file)
@@ -1,3 +1,17 @@
+2017-08-27  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Picking an emoji via the emoji dialog (Ctrl+Cmd+Space) fires inconsistent beforeinput events.
+        https://bugs.webkit.org/show_bug.cgi?id=170955
+        <rdar://problem/31697653>
+
+        Reviewed by Ryosuke Niwa.
+
+        Tweak -insertText: to pass TextEventInputAutocompletion to Editor::insertText when inserting text, if existing
+        text is being replaced.
+
+        * WebView/WebHTMLView.mm:
+        (-[WebHTMLView insertText:]):
+
 2017-08-24  Chris Dumez  <cdumez@apple.com>
 
         [Directory Upload] Add basic support for input.webkitdirectory
index 86d2911e3c726ed0d6eee052a7cdedfde8d40d20..8fc7f63c5dc4e1ecd7841e5f4cda746b49c2e588 100644 (file)
@@ -7154,11 +7154,13 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
     _private->softSpaceRange = NSMakeRange(NSNotFound, 0);
 #endif
 
+    bool replacesText = false;
     if (replacementRange.location != NSNotFound) {
         WebRangeIsRelativeTo rangeIsRelativeTo = needToRemoveSoftSpace ? WebRangeIsRelativeTo::Paragraph : WebRangeIsRelativeTo::EditableRoot;
-        RefPtr<Range> domRange = [[self _frame] _convertToDOMRange:replacementRange rangeIsRelativeTo:rangeIsRelativeTo];
-        if (domRange)
+        if (auto domRange = [[self _frame] _convertToDOMRange:replacementRange rangeIsRelativeTo:rangeIsRelativeTo]) {
             coreFrame->selection().setSelection(VisibleSelection(*domRange, SEL_DEFAULT_AFFINITY));
+            replacesText = replacementRange.length;
+        }
     }
 
     bool eventHandled = false;
@@ -7171,7 +7173,7 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
         if (!dictationAlternativeLocations.isEmpty())
             eventHandled = coreFrame->editor().insertDictatedText(eventText, dictationAlternativeLocations, event);
         else
-            eventHandled = coreFrame->editor().insertText(eventText, event);
+            eventHandled = coreFrame->editor().insertText(eventText, event, replacesText ? TextEventInputAutocompletion : TextEventInputKeyboard);
         
 #if USE(INSERTION_UNDO_GROUPING)
         if (registerUndoGroup)
index 2531cb0dcf0717ed40907f5f96a37340e5ed6c63..6c5b3cd13e5af3abf4ccf88a71922c185a141671 100644 (file)
@@ -1,3 +1,35 @@
+2017-08-27  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Picking an emoji via the emoji dialog (Ctrl+Cmd+Space) fires inconsistent beforeinput events.
+        https://bugs.webkit.org/show_bug.cgi?id=170955
+        <rdar://problem/31697653>
+
+        Reviewed by Ryosuke Niwa.
+
+        Replace UIScriptController.insertText with UIScriptController.replaceTextAtRange, and implement
+        replaceTextAtRange in WebKit1. See corresponding layout tests (input-event-insert-replacement.html and
+        before-input-prevent-insert-replacement.html) for more detail.
+
+        * DumpRenderTree/DumpRenderTree.xcodeproj/project.pbxproj:
+        * DumpRenderTree/mac/AppKitTestSPI.h: Added.
+
+        Introduce an SPI header for private AppKit headers needed to support DumpRenderTree.
+
+        * DumpRenderTree/mac/UIScriptControllerMac.mm:
+        (WTR::UIScriptController::replaceTextAtRange):
+        (WTR::UIScriptController::insertText): Deleted.
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::replaceTextAtRange):
+        (WTR::UIScriptController::insertText): Deleted.
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        * WebKitTestRunner/mac/UIScriptControllerMac.mm:
+        (WTR::UIScriptController::replaceTextAtRange):
+        (WTR::UIScriptController::insertText): Deleted.
+
+        Replace UIScriptController.insertText with UIScriptController.replaceTextAtRange, which better describes the
+        behavior of this function.
+
 2017-08-27  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [iOS WK2] Web process crashes after changing selection to the end of the document when speaking a selection
index d6310e750deb87b2b961c1fb4fba9b11e71d9d0e..7ae7a3b525a3bcb8765dbae25a2c71b4625da79a 100644 (file)
                2D403F19150871F9005358D2 /* LayoutTestHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = LayoutTestHelper; sourceTree = BUILT_PRODUCTS_DIR; };
                2DA2E3A31E1BA54100A3BBD0 /* DumpRenderTreeSpellChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DumpRenderTreeSpellChecker.h; path = mac/DumpRenderTreeSpellChecker.h; sourceTree = "<group>"; };
                2DA2E3A41E1BA54100A3BBD0 /* DumpRenderTreeSpellChecker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = DumpRenderTreeSpellChecker.mm; path = mac/DumpRenderTreeSpellChecker.mm; sourceTree = "<group>"; };
+               2EDE0DAA1F5131DE00D5F8DF /* AppKitTestSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppKitTestSPI.h; path = mac/AppKitTestSPI.h; sourceTree = "<group>"; };
                31117B3A15D9A56A00163BC8 /* MockWebNotificationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockWebNotificationProvider.h; path = mac/MockWebNotificationProvider.h; sourceTree = "<group>"; };
                31117B3B15D9A56A00163BC8 /* MockWebNotificationProvider.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MockWebNotificationProvider.mm; path = mac/MockWebNotificationProvider.mm; sourceTree = "<group>"; };
                31268EBA1EF06A95001963E2 /* UIKitTestSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UIKitTestSPI.h; path = ../TestRunnerShared/spi/UIKitTestSPI.h; sourceTree = "<group>"; };
                0F6A0E001D6E0F8A00F1C9A8 /* mac */ = {
                        isa = PBXGroup;
                        children = (
+                               2EDE0DAA1F5131DE00D5F8DF /* AppKitTestSPI.h */,
                                0F18E70E1D6BACB60027E547 /* UIScriptControllerMac.mm */,
                        );
                        name = mac;
                                BCA18B6E0C9B08DB00114369 /* NavigationController.m */,
                                BCA18B2F0C9B01B400114369 /* ObjCController.h */,
                                BCA18B300C9B01B400114369 /* ObjCController.m */,
-                               F4D423601DD5046900678290 /* TextInputController.h */,
                                BC0131D80C9772010087317D /* TestRunner.cpp */,
                                BC0131D90C9772010087317D /* TestRunner.h */,
                                BCA18B220C9B014B00114369 /* TestRunnerMac.mm */,
+                               F4D423601DD5046900678290 /* TextInputController.h */,
                        );
                        name = Controllers;
                        sourceTree = "<group>";
                        files = (
                                A134E53618905EFF00901D06 /* AccessibilityCommonMac.mm in Sources */,
                                BCD08B3A0E1057EF00A7D0C1 /* AccessibilityController.cpp in Sources */,
-                               2DA2E3A51E1BA54100A3BBD0 /* DumpRenderTreeSpellChecker.mm in Sources */,
                                AA5A15EF16E15CD000F7C561 /* AccessibilityControllerIOS.mm in Sources */,
                                BCD08B710E1059D200A7D0C1 /* AccessibilityControllerMac.mm in Sources */,
                                80045AEE147718E7008290A8 /* AccessibilityNotificationHandler.mm in Sources */,
                                BCA18B7B0C9B08F100114369 /* DumpRenderTreeDraggingInfo.mm in Sources */,
                                A8D79CEB0FC28B2C004AC8FE /* DumpRenderTreeFileDraggingSource.m in Sources */,
                                A8B91ADA0CF3B32F008F91FF /* DumpRenderTreePasteboard.mm in Sources */,
+                               2DA2E3A51E1BA54100A3BBD0 /* DumpRenderTreeSpellChecker.mm in Sources */,
                                A8B91ADC0CF3B32F008F91FF /* DumpRenderTreeWindow.mm in Sources */,
                                BCA18B620C9B08C200114369 /* EditingDelegate.mm in Sources */,
                                BCA18B700C9B08DB00114369 /* EventSendingController.mm in Sources */,
                                BCF6C6500C98E9C000AC063E /* GCController.cpp in Sources */,
                                BCA18B230C9B014B00114369 /* GCControllerMac.mm in Sources */,
                                5185F6B210714E07007AA393 /* HistoryDelegate.mm in Sources */,
+                               312943F91E71F2B4001EE2CC /* IOSLayoutTestCommunication.cpp in Sources */,
                                2CE88FA217124D8C00734FC0 /* JavaScriptThreading.cpp in Sources */,
                                0F18E7061D6BA0230027E547 /* JSUIScriptController.cpp in Sources */,
                                0F18E7131D6BC43A0027E547 /* JSWrapper.cpp in Sources */,
                                BCA18B800C9B08F100114369 /* ObjCPluginFunction.m in Sources */,
                                8465E2C70FFA8DF2003B8342 /* PixelDumpSupport.cpp in Sources */,
                                BCB284CD0CFA83C8007E533E /* PixelDumpSupportCG.cpp in Sources */,
-                               F4D423611DD5048200678290 /* TextInputControllerIOS.m in Sources */,
                                A1158D59189274360088C17B /* PixelDumpSupportIOS.mm in Sources */,
                                BCB284D60CFA83D1007E533E /* PixelDumpSupportMac.mm in Sources */,
                                BCA18B660C9B08C200114369 /* PolicyDelegate.mm in Sources */,
                                BCA18B680C9B08C200114369 /* ResourceLoadDelegate.mm in Sources */,
+                               7CBBC3231DDFCF9A00786B9D /* TestOptions.mm in Sources */,
                                BC0131DA0C9772010087317D /* TestRunner.cpp in Sources */,
                                BCA18B240C9B014B00114369 /* TestRunnerMac.mm in Sources */,
+                               F4D423611DD5048200678290 /* TextInputControllerIOS.m in Sources */,
                                BCA18B490C9B02C400114369 /* TextInputControllerMac.m in Sources */,
                                BCA18B6A0C9B08C200114369 /* UIDelegate.mm in Sources */,
                                0F18E6EC1D6B9C070027E547 /* UIScriptContext.cpp in Sources */,
                                0F18E6ED1D6B9C070027E547 /* UIScriptController.cpp in Sources */,
                                0F18E70D1D6BAC8C0027E547 /* UIScriptControllerIOS.mm in Sources */,
-                               7CBBC3231DDFCF9A00786B9D /* TestOptions.mm in Sources */,
                                0F18E70F1D6BACB60027E547 /* UIScriptControllerMac.mm in Sources */,
                                BC9D90240C97472E0099A4A3 /* WorkQueue.cpp in Sources */,
-                               312943F91E71F2B4001EE2CC /* IOSLayoutTestCommunication.cpp in Sources */,
                                BCA18B260C9B015C00114369 /* WorkQueueItemMac.mm in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
diff --git a/Tools/DumpRenderTree/mac/AppKitTestSPI.h b/Tools/DumpRenderTree/mac/AppKitTestSPI.h
new file mode 100644 (file)
index 0000000..4b76ca7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#if PLATFORM(MAC)
+
+#if USE(APPLE_INTERNAL_SDK)
+
+#import <AppKit/NSTextInputContext_Private.h>
+
+#else
+
+extern "C" {
+extern NSString *NSTextInputReplacementRangeAttributeName;
+}
+
+#endif
+
+#endif // PLATFORM(MAC)
+
index 23d40ed7f0412ae0d0cea59aeae449f54e862236..82e4dce7b2d0d9dd8a2672deb2df6eb5edda042b 100644 (file)
@@ -36,6 +36,8 @@
 
 #if PLATFORM(MAC)
 
+#import "AppKitTestSPI.h"
+
 namespace WTR {
 
 void UIScriptController::doAsyncTask(JSValueRef callback)
@@ -64,8 +66,13 @@ void UIScriptController::doAfterVisibleContentRectUpdate(JSValueRef callback)
     doAsyncTask(callback);
 }
 
-void UIScriptController::insertText(JSStringRef, int, int)
+void UIScriptController::replaceTextAtRange(JSStringRef text, int location, int length)
 {
+    auto textToInsert = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, text));
+    auto rangeAttribute = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:NSStringFromRange(NSMakeRange(location == -1 ? NSNotFound : location, length)), NSTextInputReplacementRangeAttributeName, nil]);
+    auto textAndRange = adoptNS([[NSAttributedString alloc] initWithString:(NSString *)textToInsert.get() attributes:rangeAttribute.get()]);
+
+    [mainFrame.webView insertText:textAndRange.get()];
 }
 
 void UIScriptController::zoomToScale(double scale, JSValueRef callback)
index 32a95c06fe93892ab9e37b46b6487055743be3e0..e373230c56a1e64abe4297d7ddb97c2fec6053e0 100644 (file)
@@ -226,7 +226,7 @@ interface UIScriptController {
     readonly attribute object textSelectionCaretRect; // An object with 'left', 'top', 'width', 'height' properties.
     readonly attribute object inputViewBounds;
 
-    void insertText(DOMString text, long location, long length);
+    void replaceTextAtRange(DOMString text, long location, long length);
     void removeAllDynamicDictionaries();
 
     readonly attribute DOMString scrollingTreeAsText;
index 7b08956fd62d87b909aa311ac19fd0d0add2b319..ba10dbfa9092b466e5077eae5e9a189b6170f900 100644 (file)
@@ -461,7 +461,7 @@ void UIScriptController::overridePreference(JSStringRef, JSStringRef)
 {
 }
 
-void UIScriptController::insertText(JSStringRef, int, int)
+void UIScriptController::replaceTextAtRange(JSStringRef, int, int)
 {
 }
 
index d9983e23b240d07a183aa08e0f2848d197acecd5..546e0ff64d926d347236125d7ed37e40fb531bb2 100644 (file)
@@ -151,7 +151,7 @@ public:
     JSObjectRef textSelectionCaretRect() const;
     JSObjectRef inputViewBounds() const;
 
-    void insertText(JSStringRef, int location, int length);
+    void replaceTextAtRange(JSStringRef, int location, int length);
     void removeAllDynamicDictionaries();
     
     JSRetainPtr<JSStringRef> scrollingTreeAsText() const;
index 41354c004f2c27fa8e5aa00c6dc5574e58a73578..650830bcdbf6b1fa028a4ce551d89c447c18b5b4 100644 (file)
@@ -75,7 +75,7 @@ void UIScriptController::doAfterVisibleContentRectUpdate(JSValueRef callback)
     doAsyncTask(callback);
 }
 
-void UIScriptController::insertText(JSStringRef text, int location, int length)
+void UIScriptController::replaceTextAtRange(JSStringRef text, int location, int length)
 {
 #if WK_API_ENABLED
     if (location == -1)