AX: Unable to use AccessibilityObject::replaceTextInRange to insert text at first time when the text fields are empty
https://bugs.webkit.org/show_bug.cgi?id=206093
<rdar://problem/58491448>

Patch by Canhai Chen <canhai_chen@apple.com> on 2020-01-17
Reviewed by Chris Fleizach.

Source/WebCore:

When we are trying to insert text in an empty text field with (0, 0) range, the frame selection will create a
new VisibleSelection in FrameSelection::setSelectedRange, and the container node that this new VisibleSelection
returns is the parent node of the text field element, which could be a HTMLDivElement or HTMLBodyElement.
Because the container node is not editable, it failed to insert text in Editor::replaceSelectionWithText later.

Return nullptr if the range is (0, 0) and the text length is 0 in AccessibilityObject::rangeForPlainTextRange,
so that when the frame selection is trying to setSelectedRange before replacing text, instead of creating an
uneditable VisibleSelection, it will just return and later in Editor::replaceSelectionWithText, it will use
the default VisibleSelection, of which the container node is an editable TextControlInnerTextElement.

This change does not affect the existing behaviors of text replacement. Add a new test for text replacement
with empty range in editable div, text input, and textarea.

Test: accessibility/mac/replace-text-with-empty-range.html

* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::rangeForPlainTextRange const):

LayoutTests:

Test text replacement with empty range in editable div, text input, and textarea.

* accessibility/mac/replace-text-with-empty-range-expected.txt: Added.
* accessibility/mac/replace-text-with-empty-range.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@254782 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 3f49c80..c1f00af 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,16 @@
+2020-01-17  Canhai Chen  <canhai_chen@apple.com>
+
+        AX: Unable to use AccessibilityObject::replaceTextInRange to insert text at first time when the text fields are empty
+        https://bugs.webkit.org/show_bug.cgi?id=206093
+        <rdar://problem/58491448>
+
+        Reviewed by Chris Fleizach.
+
+        Test text replacement with empty range in editable div, text input, and textarea.
+
+        * accessibility/mac/replace-text-with-empty-range-expected.txt: Added.
+        * accessibility/mac/replace-text-with-empty-range.html: Added.
+
 2020-01-17  Jer Noble  <jer.noble@apple.com>
 
         [MSE] Decode glitches when watching videos on CNN.com
diff --git a/LayoutTests/accessibility/mac/replace-text-with-empty-range-expected.txt b/LayoutTests/accessibility/mac/replace-text-with-empty-range-expected.txt
new file mode 100644
index 0000000..d4bcc15
--- /dev/null
+++ b/LayoutTests/accessibility/mac/replace-text-with-empty-range-expected.txt
@@ -0,0 +1,15 @@
+This tests that when we are adding text to empty text fields, the accessibility replace with range API functions as expected.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS axContentEditableDiv.replaceTextInRange('Hello', 0, 0) is true
+PASS axContentEditableDiv.stringValue is 'AXValue: Hello'
+PASS axText.replaceTextInRange('Hello', 0, 0) is true
+PASS axText.stringValue is 'AXValue: Hello'
+PASS axTextarea.replaceTextInRange('Hello', 0, 0) is true
+PASS axTextarea.stringValue is 'AXValue: Hello'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/mac/replace-text-with-empty-range.html b/LayoutTests/accessibility/mac/replace-text-with-empty-range.html
new file mode 100644
index 0000000..f6995d5
--- /dev/null
+++ b/LayoutTests/accessibility/mac/replace-text-with-empty-range.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test.js"></script>
+</head>
+<body id="body">
+
+<div id="content">
+    <div id="content-editable-div" contenteditable="true" role="textbox"></div>
+    <input id="text" type="text" value="">
+    <textarea id="textarea"></textarea>
+</div>
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+    description("This tests that when we are adding text to empty text fields, the accessibility replace with range API functions as expected.");
+
+    if (window.accessibilityController) {
+        var axContentEditableDiv = accessibilityController.accessibleElementById("content-editable-div");
+        var contentEditableDiv = document.getElementById("content-editable-div");
+        contentEditableDiv.focus();
+        shouldBeTrue("axContentEditableDiv.replaceTextInRange('Hello', 0, 0)");
+        shouldBe("axContentEditableDiv.stringValue", "'AXValue: Hello'");
+        contentEditableDiv.blur();
+
+        var axText = accessibilityController.accessibleElementById("text");
+        var text = document.getElementById("text");
+        text.focus();
+        shouldBeTrue("axText.replaceTextInRange('Hello', 0, 0)");
+        shouldBe("axText.stringValue", "'AXValue: Hello'");
+        text.blur();
+
+        var axTextarea = accessibilityController.accessibleElementById("textarea");
+        var textarea = document.getElementById("textarea");
+        textarea.focus();
+        shouldBeTrue("axTextarea.replaceTextInRange('Hello', 0, 0)");
+        shouldBe("axTextarea.stringValue", "'AXValue: Hello'");
+        textarea.blur();
+
+        document.getElementById("content").style.visibility = "hidden";
+    }
+</script>
+
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index b610920..e8f3ff7 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,29 @@
+2020-01-17  Canhai Chen  <canhai_chen@apple.com>
+
+        AX: Unable to use AccessibilityObject::replaceTextInRange to insert text at first time when the text fields are empty
+        https://bugs.webkit.org/show_bug.cgi?id=206093
+        <rdar://problem/58491448>
+
+        Reviewed by Chris Fleizach.
+
+        When we are trying to insert text in an empty text field with (0, 0) range, the frame selection will create a 
+        new VisibleSelection in FrameSelection::setSelectedRange, and the container node that this new VisibleSelection 
+        returns is the parent node of the text field element, which could be a HTMLDivElement or HTMLBodyElement. 
+        Because the container node is not editable, it failed to insert text in Editor::replaceSelectionWithText later.
+
+        Return nullptr if the range is (0, 0) and the text length is 0 in AccessibilityObject::rangeForPlainTextRange, 
+        so that when the frame selection is trying to setSelectedRange before replacing text, instead of creating an 
+        uneditable VisibleSelection, it will just return and later in Editor::replaceSelectionWithText, it will use 
+        the default VisibleSelection, of which the container node is an editable TextControlInnerTextElement.
+
+        This change does not affect the existing behaviors of text replacement. Add a new test for text replacement 
+        with empty range in editable div, text input, and textarea.
+
+        Test: accessibility/mac/replace-text-with-empty-range.html
+
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::AccessibilityObject::rangeForPlainTextRange const):
+
 2020-01-17  Zalan Bujtas  <zalan@apple.com>
 
         [LFC][IFC] Do not construct a dedicated run for the trailing (fully) collapsed whitespace.
diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp
index 6b9e0ef..d803044 100644
--- a/Source/WebCore/accessibility/AccessibilityObject.cpp
+++ b/Source/WebCore/accessibility/AccessibilityObject.cpp
@@ -1189,6 +1189,9 @@
     unsigned textLength = getLengthForTextRange();
     if (range.start + range.length > textLength)
         return nullptr;
+    // Avoid setting selection to uneditable parent node in FrameSelection::setSelectedRange. See webkit.org/b/206093.
+    if (range.isNull() && !textLength)
+        return nullptr;
     
     if (AXObjectCache* cache = axObjectCache()) {
         CharacterOffset start = cache->characterOffsetForIndex(range.start, this);