Text::splitText doesn't update Range end points anchored on parent nodes
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Jan 2016 10:47:11 +0000 (10:47 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Jan 2016 10:47:11 +0000 (10:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=153227

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Rebaseline the test now that we're passing more test cases.

* web-platform-tests/dom/ranges/Range-mutations-expected.txt:

Source/WebCore:

When a Text node is split into two and there is a Range whose boundary points' container node
is its parent and offset appears after the Text node, we must update the boundary points as specified
in step 7 of the concept "split" a Text node at https://dom.spec.whatwg.org/#concept-text-split

1. Insert new node into parent before node’s next sibling.
2. For each range whose start node is node and start offset is greater than offset, set its start node
   to new node and decrease its start offset by offset.
3. For each range whose end node is node and end offset is greater than offset, set its end node to
   new node and decrease its end offset by offset.
4. For each range whose start node is parent and start offset is equal to the index of node + 1,
   increase its start offset by one.
5. For each range whose end node is parent and end offset is equal to the index of node + 1, increase
   its end offset by one.

Fixed the bug by implementing steps 4 and 5 in boundaryTextNodesSplit. New behavior matches the DOM spec
as well as the behavior of Firefox.

Test: fast/dom/Range/update-range-in-split-text.html

* dom/Range.cpp:
(WebCore::boundaryTextNodesSplit): See above.
* dom/RangeBoundaryPoint.h:
(WebCore::RangeBoundaryPoint::setToAfterChild): Added.

LayoutTests:

Added a regression test since the rebaselined W3C test is incomprehensible.

* fast/dom/Range/update-range-in-split-text-expected.txt: Added.
* fast/dom/Range/update-range-in-split-text.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/fast/dom/Range/update-range-in-split-text-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/Range/update-range-in-split-text.html [new file with mode: 0644]
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/dom/ranges/Range-mutations-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/dom/Range.cpp
Source/WebCore/dom/RangeBoundaryPoint.h

index 19bc1a6..0f73d17 100644 (file)
@@ -1,5 +1,17 @@
 2016-01-19  Ryosuke Niwa  <rniwa@webkit.org>
 
+        Text::splitText doesn't update Range end points anchored on parent nodes
+        https://bugs.webkit.org/show_bug.cgi?id=153227
+
+        Reviewed by Antti Koivisto.
+
+        Added a regression test since the rebaselined W3C test is incomprehensible.
+
+        * fast/dom/Range/update-range-in-split-text-expected.txt: Added.
+        * fast/dom/Range/update-range-in-split-text.html: Added.
+
+2016-01-19  Ryosuke Niwa  <rniwa@webkit.org>
+
         innerHTML should always add a mutation record for removing all children
         https://bugs.webkit.org/show_bug.cgi?id=148782
         <rdar://problem/22571962>
diff --git a/LayoutTests/fast/dom/Range/update-range-in-split-text-expected.txt b/LayoutTests/fast/dom/Range/update-range-in-split-text-expected.txt
new file mode 100644 (file)
index 0000000..3509c14
--- /dev/null
@@ -0,0 +1,40 @@
+
+text = createTextWithParent(); range = createRange(text.parentNode, 0, text.parentNode, 0); text.splitText(0)
+PASS range.startContainer is text.parentNode
+PASS range.startOffset is 0
+PASS range.endContainer is text.parentNode
+PASS range.endOffset is 0
+
+text = createTextWithParent(); range = createRange(text.parentNode, 1, text.parentNode, 1); text.splitText(0)
+PASS range.startContainer is text.parentNode
+PASS range.startOffset is 2
+PASS range.endContainer is text.parentNode
+PASS range.endOffset is 2
+
+text = createTextWithParent(); range = createRange(text.parentNode, 0, text.parentNode, 1); text.splitText(0)
+PASS range.startContainer is text.parentNode
+PASS range.startOffset is 0
+PASS range.endContainer is text.parentNode
+PASS range.endOffset is 2
+
+text = createTextWithParentAndSiblings(); range = createRange(text.parentNode, 1, text.parentNode, 1); text.splitText(0)
+PASS range.startContainer is text.parentNode
+PASS range.startOffset is 1
+PASS range.endContainer is text.parentNode
+PASS range.endOffset is 1
+
+text = createTextWithParentAndSiblings(); range = createRange(text.parentNode, 2, text.parentNode, 2); text.splitText(0)
+PASS range.startContainer is text.parentNode
+PASS range.startOffset is 3
+PASS range.endContainer is text.parentNode
+PASS range.endOffset is 3
+
+text = createTextWithParentAndSiblings(); range = createRange(text.parentNode, 1, text.parentNode, 2); text.splitText(0)
+PASS range.startContainer is text.parentNode
+PASS range.startOffset is 1
+PASS range.endContainer is text.parentNode
+PASS range.endOffset is 3
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/Range/update-range-in-split-text.html b/LayoutTests/fast/dom/Range/update-range-in-split-text.html
new file mode 100644 (file)
index 0000000..b16d591
--- /dev/null
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<body>
+<script src="../../../resources/js-test-pre.js"></script>
+<div id="container" style="display: none;"></div>
+<script>
+
+function createTextWithParent()
+{
+    var container = document.getElementById('container');
+
+    var element = document.createElement('div');
+    element.textContent = 'hello';
+    container.appendChild(element);
+
+    return element.firstChild;
+}
+
+function createTextWithParentAndSiblings()
+{
+    var container = document.getElementById('container');
+
+    var element = document.createElement('div');
+    element.textContent = 'hello';
+    container.appendChild(element);
+
+    var sibling = document.createElement('b');
+    element.insertBefore(sibling, element.firstChild);
+
+    var sibling = document.createElement('i');
+    element.appendChild(sibling);
+
+    return element.childNodes[1];
+}
+
+function createRange(startContainer, startOffset, endContainer, endOffset) {
+    var range = document.createRange();
+    range.setStart(startContainer, startOffset);
+    range.setEnd(endContainer, endOffset);
+    return range;
+}
+
+debug('');
+evalAndLog('text = createTextWithParent(); range = createRange(text.parentNode, 0, text.parentNode, 0); text.splitText(0)');
+shouldBe('range.startContainer', 'text.parentNode');
+shouldBe('range.startOffset', '0');
+shouldBe('range.endContainer', 'text.parentNode');
+shouldBe('range.endOffset', '0');
+
+debug('');
+evalAndLog('text = createTextWithParent(); range = createRange(text.parentNode, 1, text.parentNode, 1); text.splitText(0)');
+shouldBe('range.startContainer', 'text.parentNode');
+shouldBe('range.startOffset', '2');
+shouldBe('range.endContainer', 'text.parentNode');
+shouldBe('range.endOffset', '2');
+
+debug('');
+evalAndLog('text = createTextWithParent(); range = createRange(text.parentNode, 0, text.parentNode, 1); text.splitText(0)');
+shouldBe('range.startContainer', 'text.parentNode');
+shouldBe('range.startOffset', '0');
+shouldBe('range.endContainer', 'text.parentNode');
+shouldBe('range.endOffset', '2');
+
+debug('');
+evalAndLog('text = createTextWithParentAndSiblings(); range = createRange(text.parentNode, 1, text.parentNode, 1); text.splitText(0)');
+shouldBe('range.startContainer', 'text.parentNode');
+shouldBe('range.startOffset', '1');
+shouldBe('range.endContainer', 'text.parentNode');
+shouldBe('range.endOffset', '1');
+
+debug('');
+evalAndLog('text = createTextWithParentAndSiblings(); range = createRange(text.parentNode, 2, text.parentNode, 2); text.splitText(0)');
+shouldBe('range.startContainer', 'text.parentNode');
+shouldBe('range.startOffset', '3');
+shouldBe('range.endContainer', 'text.parentNode');
+shouldBe('range.endOffset', '3');
+
+debug('');
+evalAndLog('text = createTextWithParentAndSiblings(); range = createRange(text.parentNode, 1, text.parentNode, 2); text.splitText(0)');
+shouldBe('range.startContainer', 'text.parentNode');
+shouldBe('range.startOffset', '1');
+shouldBe('range.endContainer', 'text.parentNode');
+shouldBe('range.endOffset', '3');
+
+</script>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
index 8a2513e..0256a0d 100644 (file)
@@ -1,5 +1,16 @@
 2016-01-19  Ryosuke Niwa  <rniwa@webkit.org>
 
+        Text::splitText doesn't update Range end points anchored on parent nodes
+        https://bugs.webkit.org/show_bug.cgi?id=153227
+
+        Reviewed by Antti Koivisto.
+
+        Rebaseline the test now that we're passing more test cases.
+
+        * web-platform-tests/dom/ranges/Range-mutations-expected.txt:
+
+2016-01-19  Ryosuke Niwa  <rniwa@webkit.org>
+
         innerHTML should always add a mutation record for removing all children
         https://bugs.webkit.org/show_bug.cgi?id=148782
         <rdar://problem/22571962>
index 835a25b..2183b27 100644 (file)
@@ -99,18 +99,18 @@ PASS detachedXmlTextNode.splitText(3), with unselected range on detachedXmlTextN
 FAIL detachedXmlTextNode.splitText(3), with selected range on detachedXmlTextNode from 1 to 3 assert_equals: Sanity check: selection must have exactly one range after adding the range expected 1 but got 0
 PASS paras[0].firstChild.splitText(1), with unselected range collapsed at (paras[0], 0) 
 FAIL paras[0].firstChild.splitText(1), with selected range collapsed at (paras[0], 0) assert_equals: Sanity check: selection's range must initially be the same as the range we added expected object "" but got object ""
-FAIL paras[0].firstChild.splitText(1), with unselected range on paras[0] from 0 to 1 assert_equals: Wrong end offset expected 2 but got 1
+PASS paras[0].firstChild.splitText(1), with unselected range on paras[0] from 0 to 1 
 FAIL paras[0].firstChild.splitText(1), with selected range on paras[0] from 0 to 1 assert_equals: Sanity check: selection's range must initially be the same as the range we added expected object "Äb̈c̈d̈ëf̈g̈ḧ
 " but got object "Äb̈c̈d̈ëf̈g̈ḧ"
-FAIL paras[0].firstChild.splitText(1), with unselected range collapsed at (paras[0], 1) assert_equals: Wrong start offset expected 2 but got 1
+PASS paras[0].firstChild.splitText(1), with unselected range collapsed at (paras[0], 1) 
 FAIL paras[0].firstChild.splitText(1), with selected range collapsed at (paras[0], 1) assert_equals: Sanity check: selection's range must initially be the same as the range we added expected object "" but got object ""
-FAIL paras[0].firstChild.splitText(1), with unselected range from (paras[0].firstChild, 1) to (paras[0], 1) assert_equals: Wrong end offset expected 2 but got 1
+PASS paras[0].firstChild.splitText(1), with unselected range from (paras[0].firstChild, 1) to (paras[0], 1) 
 FAIL paras[0].firstChild.splitText(1), with selected range from (paras[0].firstChild, 1) to (paras[0], 1) assert_equals: Sanity check: selection's range must initially be the same as the range we added expected object "̈b̈c̈d̈ëf̈g̈ḧ
 " but got object "b̈c̈d̈ëf̈g̈ḧ"
-FAIL paras[0].firstChild.splitText(2), with unselected range from (paras[0].firstChild, 1) to (paras[0], 1) assert_equals: Wrong end offset expected 2 but got 1
+PASS paras[0].firstChild.splitText(2), with unselected range from (paras[0].firstChild, 1) to (paras[0], 1) 
 FAIL paras[0].firstChild.splitText(2), with selected range from (paras[0].firstChild, 1) to (paras[0], 1) assert_equals: Sanity check: selection's range must initially be the same as the range we added expected object "̈b̈c̈d̈ëf̈g̈ḧ
 " but got object "b̈c̈d̈ëf̈g̈ḧ"
-FAIL paras[0].firstChild.splitText(3), with unselected range from (paras[0].firstChild, 1) to (paras[0], 1) assert_equals: Wrong end offset expected 2 but got 1
+PASS paras[0].firstChild.splitText(3), with unselected range from (paras[0].firstChild, 1) to (paras[0], 1) 
 FAIL paras[0].firstChild.splitText(3), with selected range from (paras[0].firstChild, 1) to (paras[0], 1) assert_equals: Sanity check: selection's range must initially be the same as the range we added expected object "̈b̈c̈d̈ëf̈g̈ḧ
 " but got object "b̈c̈d̈ëf̈g̈ḧ"
 PASS paras[0].firstChild.splitText(1), with unselected range from (paras[0], 0) to (paras[0].firstChild, 3) 
index 1841089..7b9a81c 100644 (file)
@@ -1,5 +1,36 @@
 2016-01-19  Ryosuke Niwa  <rniwa@webkit.org>
 
+        Text::splitText doesn't update Range end points anchored on parent nodes
+        https://bugs.webkit.org/show_bug.cgi?id=153227
+
+        Reviewed by Antti Koivisto.
+
+        When a Text node is split into two and there is a Range whose boundary points' container node
+        is its parent and offset appears after the Text node, we must update the boundary points as specified
+        in step 7 of the concept "split" a Text node at https://dom.spec.whatwg.org/#concept-text-split
+
+        1. Insert new node into parent before node’s next sibling.
+        2. For each range whose start node is node and start offset is greater than offset, set its start node
+           to new node and decrease its start offset by offset.
+        3. For each range whose end node is node and end offset is greater than offset, set its end node to
+           new node and decrease its end offset by offset.
+        4. For each range whose start node is parent and start offset is equal to the index of node + 1,
+           increase its start offset by one.
+        5. For each range whose end node is parent and end offset is equal to the index of node + 1, increase
+           its end offset by one.
+
+        Fixed the bug by implementing steps 4 and 5 in boundaryTextNodesSplit. New behavior matches the DOM spec
+        as well as the behavior of Firefox.
+
+        Test: fast/dom/Range/update-range-in-split-text.html
+
+        * dom/Range.cpp:
+        (WebCore::boundaryTextNodesSplit): See above.
+        * dom/RangeBoundaryPoint.h:
+        (WebCore::RangeBoundaryPoint::setToAfterChild): Added.
+
+2016-01-19  Ryosuke Niwa  <rniwa@webkit.org>
+
         CharacterData::setData doesn't need ExceptionCode as an out argument
         https://bugs.webkit.org/show_bug.cgi?id=153225
 
index 73fb1d2..a9c6ef2 100644 (file)
@@ -1712,12 +1712,19 @@ void Range::textNodesMerged(NodeWithIndex& oldNode, unsigned offset)
 
 static inline void boundaryTextNodesSplit(RangeBoundaryPoint& boundary, Text* oldNode)
 {
-    if (boundary.container() != oldNode)
+    if (boundary.container() == oldNode) {
+        unsigned splitOffset = oldNode->length();
+        unsigned boundaryOffset = boundary.offset();
+        if (boundaryOffset > splitOffset)
+            boundary.set(oldNode->nextSibling(), boundaryOffset - splitOffset, 0);
         return;
-    unsigned boundaryOffset = boundary.offset();
-    if (boundaryOffset <= oldNode->length())
-        return;
-    boundary.set(oldNode->nextSibling(), boundaryOffset - oldNode->length(), 0);
+    }
+    auto* parent = oldNode->parentNode();
+    if (boundary.container() == parent && boundary.childBefore() == oldNode) {
+        auto* newChild = oldNode->nextSibling();
+        ASSERT(newChild);
+        boundary.setToAfterChild(*newChild);
+    }
 }
 
 void Range::textNodeSplit(Text* oldNode)
index d09915d..1907102 100644 (file)
@@ -49,6 +49,7 @@ public:
     void setOffset(int offset);
 
     void setToBeforeChild(Node&);
+    void setToAfterChild(Node&);
     void setToStartOfNode(PassRefPtr<Node>);
     void setToEndOfNode(PassRefPtr<Node>);
 
@@ -142,6 +143,14 @@ inline void RangeBoundaryPoint::setToBeforeChild(Node& child)
     m_offsetInContainer = m_childBeforeBoundary ? invalidOffset : 0;
 }
 
+inline void RangeBoundaryPoint::setToAfterChild(Node& child)
+{
+    ASSERT(child.parentNode());
+    m_childBeforeBoundary = &child;
+    m_containerNode = child.parentNode();
+    m_offsetInContainer = m_childBeforeBoundary ? invalidOffset : 0;
+}
+
 inline void RangeBoundaryPoint::setToStartOfNode(PassRefPtr<Node> container)
 {
     ASSERT(container);