WebCore:
authordarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 15 Mar 2008 17:26:28 +0000 (17:26 +0000)
committerdarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 15 Mar 2008 17:26:28 +0000 (17:26 +0000)
        Reviewed by Sam.

        - fix http://bugs.webkit.org/show_bug.cgi?id=11997
          Ranges are not fixed after mutation (affects Acid3 test 13)

        Test: fast/dom/Range/mutation.html

        * WebCore.xcodeproj/project.pbxproj: Added NodeWithIndex.h, NodeWithIndexAfter.h,
        and NodeWithIndexBefore.h.

        * dom/CharacterData.cpp:
        (WebCore::CharacterData::setData): Replaced call to Document::removeMarkers
        with call to Document::textRemoved.
        (WebCore::CharacterData::insertData): Replaced call to Document::shiftMarkers
        with call to Document::textInserted.
        (WebCore::CharacterData::deleteData): Replaced call to Document::removeMarkers
        and Document::shiftMarkers with call to Document::textRemoved.
        (WebCore::CharacterData::replaceData): Replaced call to Document::removeMarkers
        and Document::shiftMarkers with call to Document::textRemoved and
        Document::textInserted.
        (WebCore::CharacterData::containsOnlyWhitespace): Tweaked a bit.

        * dom/ContainerNode.cpp:
        (WebCore::ContainerNode::childrenChanged): Added a call to
        Document::nodeChildrenChanged when the nmber of children was changed (and not
        by the parser).
        (WebCore::dispatchChildRemovalEvents): Updated for name change.

        * dom/Document.cpp:
        (WebCore::Document::~Document): Assert that all ranges are gone.
        (WebCore::Document::nodeChildrenChanged): Added. Calls nodeChildrenChanged on
        all ranges.
        (WebCore::Document::nodeWillBeRemoved): Renamed from notifyBeforeNodeRemoval.
        Added code to call nodeWillBeRemoved on all ranges.
        (WebCore::Document::textInserted): Added. Calls textInserted on all ranges and
        also calls shiftMarkers.
        (WebCore::Document::textRemoved): Added. Calls textRemoved on all ranges and also
        calls removeMarkers and shiftMarkers.
        (WebCore::Document::textNodesMerged): Added. Calls textNodesMerged on all ranges.
        (WebCore::Document::textNodeSplit): Added. Calls textNodeSplit on all ranges.
        (WebCore::Document::attachRange): Added. Adds range to the HashSet of all ranges
        for this document.
        (WebCore::Document::detachRange): Added. Removes range from the HashSet.
        * dom/Document.h: Added the new functions and the data member.

        * dom/Element.cpp:
        (WebCore::Element::normalizeAttributes): Added. Contains the part of the
        normalize function that's specific to Element. Better encapsulation to have it
        here rather than in Node::normalize.
        * dom/Element.h: Added the new function.

        * dom/Node.cpp:
        (WebCore::Node::normalize): Rewrote so it's no longer recursive. Also added
        a call to textNodesMerged after each pair of nodes is merged but before the
        second node is removed.
        (WebCore::Node::traverseNextNodePostOrder): Added. Helper function used by
        normalize, but also useful elsewhere.
        * dom/Node.h: Added the new function.

        * dom/NodeIterator.cpp:
        (WebCore::NodeIterator::nodeWillBeRemoved): Renamed from notifyBeforeNodeRemoval.
        * dom/NodeIterator.h: Ditto.

        * dom/ProcessingInstruction.cpp:
        (WebCore::ProcessingInstruction::setData): Call textRemoved.

        * dom/Range.cpp:
        (WebCore::NodeWithIndex::index): Added. Computes and stores index.
        (WebCore::NodeWithIndexBefore::indexBefore): Added. Computes and stores index.
        (WebCore::NodeWithIndexAfter::indexAfter): Added. Computes and stores index.
        (WebCore::Range::Range): Call attachRange.
        (WebCore::Range::~Range): Call detachRange unless the range is already detached.
        (WebCore::Range::commonAncestorContainer): Removed check for WRONG_DOCUMENT_ERR.
        It's no longer possible to create a range where the two containers are non-zero
        and have no common ancestor.
        (WebCore::Range::isPointInRange): Rewrote expression to be more readable.
        (WebCore::Range::compareNode): Changed local variable to use int for consistency.
        (WebCore::Range::compareBoundaryPoints): Replaced ASSERT with ASSERT_NOT_REACHED.
        (WebCore::Range::deleteContents): Removed check for INVALID_STATE_ERR and
        initialization of ec to 0; both are now inside checkDeleteExtract.
        (WebCore::Range::intersectsNode): Changed local variable to use int for consistency.
        Also changed comparison to use < 0 and >= 0 rather than checking explicitly for 1
        and -1.
        (WebCore::Range::processContents): Changed code to not get the nodeType multiple
        times on the same node, and tweaked formatting. Removed code to update the range
        on deletion, because the normal delete logic will take care of that now.
        (WebCore::Range::extractContents): Removed check for INVALID_STATE_ERR and
        initialization of ec to 0; both are now inside checkDeleteExtract.
        (WebCore::Range::insertNode): Changed local variable to use int for consistency.
        (WebCore::Range::toString): Changed variable name to pastLast.
        (WebCore::Range::detach): Call detachRange.
        (WebCore::Range::checkDeleteExtract): Added check for detached range and code to
        set ec to 0; moved here from the two callers. Also changed variable name to pastLast.
        (WebCore::endpointNodeChildrenChanged): Added.
        (WebCore::Range::nodeChildrenChanged): Added.
        (WebCore::endpointNodeWillBeRemoved): Added.
        (WebCore::Range::nodeWillBeRemoved): Added.
        (WebCore::endpointTextInserted): Added.
        (WebCore::Range::textInserted): Added.
        (WebCore::endpointTextRemoved): Added.
        (WebCore::Range::textRemoved): Added.
        (WebCore::endpointTextNodesMerged): Added.
        (WebCore::Range::textNodesMerged): Added.
        (WebCore::endpointTextNodesSplit): Added.
        (WebCore::Range::textNodeSplit): Added.

        * dom/Range.h: Added new member functions.

        * dom/NodeWithIndex.h: Added. Makes it so we won't find the index for the same
        node more than once.
        * dom/NodeWithIndexAfter.h: Added. Similar to NodeWithIndex but gives the index after a
        node and treats a node pointer of 0 as meaning "before first node in parent container".
        * dom/NodeWithIndexBefore.h: Added.  Similar to NodeWithIndex but treats a node pointer of 0
        as meaning "after last node in parent container".

        * dom/Text.cpp:
        (WebCore::Text::splitText): Call textNodeSplit.

        * editing/ApplyStyleCommand.cpp:
        (WebCore::ApplyStyleCommand::applyInlineStyle): Changed variable name to pastLast.

LayoutTests:

        Reviewed by Sam.

        - test changes for http://bugs.webkit.org/show_bug.cgi?id=11997
          Ranges are not fixed after mutation (affects Acid3 test 13)

        * fast/dom/Range/mutation-expected.txt: Added.
        * fast/dom/Range/mutation.html: Added.
        * fast/dom/Range/resources/mutation.js: Added.

        * editing/execCommand/4920742-2-expected.txt: Updated.
        * editing/execCommand/4920742-2.html: Updated this test. It was testing for a crash in a
        case that's no longer possible -- you can't make a range where one endpoint is in the
        document and the other is not.

        * platform/mac/editing/execCommand/4920488-expected.txt: Removed WRONG_DOCUMENT_ERR.
        The fact that a test was getting this exception was a bug, similar to the case above.
        * platform/qt/editing/execCommand/4920488-expected.txt: Ditto.

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

29 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/execCommand/4920742-2-expected.txt
LayoutTests/editing/execCommand/4920742-2.html
LayoutTests/fast/dom/Range/mutation-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/Range/mutation.html [new file with mode: 0644]
LayoutTests/fast/dom/Range/resources/mutation.js [new file with mode: 0644]
LayoutTests/platform/mac/editing/execCommand/4920488-expected.txt
LayoutTests/platform/qt/editing/execCommand/4920488-expected.txt
WebCore/ChangeLog
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/dom/CharacterData.cpp
WebCore/dom/ContainerNode.cpp
WebCore/dom/Document.cpp
WebCore/dom/Document.h
WebCore/dom/Element.cpp
WebCore/dom/Element.h
WebCore/dom/Node.cpp
WebCore/dom/Node.h
WebCore/dom/NodeIterator.cpp
WebCore/dom/NodeIterator.h
WebCore/dom/NodeWithIndex.h [new file with mode: 0644]
WebCore/dom/NodeWithIndexAfter.h [new file with mode: 0644]
WebCore/dom/NodeWithIndexBefore.h [new file with mode: 0644]
WebCore/dom/ProcessingInstruction.cpp
WebCore/dom/Range.cpp
WebCore/dom/Range.h
WebCore/dom/Text.cpp
WebCore/editing/ApplyStyleCommand.cpp
WebKitTools/Scripts/do-webcore-rename

index c9b3110..d003078 100644 (file)
@@ -1,3 +1,23 @@
+2008-03-15  Darin Adler  <darin@apple.com>
+
+        Reviewed by Sam.
+
+        - test changes for http://bugs.webkit.org/show_bug.cgi?id=11997
+          Ranges are not fixed after mutation (affects Acid3 test 13)
+
+        * fast/dom/Range/mutation-expected.txt: Added.
+        * fast/dom/Range/mutation.html: Added.
+        * fast/dom/Range/resources/mutation.js: Added.
+
+        * editing/execCommand/4920742-2-expected.txt: Updated.
+        * editing/execCommand/4920742-2.html: Updated this test. It was testing for a crash in a
+        case that's no longer possible -- you can't make a range where one endpoint is in the
+        document and the other is not.
+
+        * platform/mac/editing/execCommand/4920488-expected.txt: Removed WRONG_DOCUMENT_ERR.
+        The fact that a test was getting this exception was a bug, similar to the case above.
+        * platform/qt/editing/execCommand/4920488-expected.txt: Ditto.
+
 2008-03-15  Julien Chaffraix  <julien.chaffraix@gmail.com>
 
         Reviewed by Holger.
index 662d759..e9ed18d 100644 (file)
@@ -1,4 +1,6 @@
-This tests for a crash when performing Range::extractContents on a range that has had the start or end removed from the document. When extractContents is called on this kind of invalid range, an exception should be thrown.
+This tested for a crash when performing Range::extractContents on a range that has had the start or end removed from the document.
 
-link
+It's now impossible to create a range like this, but at one point with an earlier version of WebKit, it was possible.
 
+link
+No crash means success!
index 053b32c..598b32d 100644 (file)
@@ -1,4 +1,5 @@
-<p>This tests for a crash when performing Range::extractContents on a range that has had the start or end removed from the document.  When extractContents is called on this kind of invalid range, an exception should be thrown.</p>
+<p>This tested for a crash when performing Range::extractContents on a range that has had the start or end removed from the document.</p>
+<p>It's now impossible to create a range like this, but at one point with an earlier version of WebKit, it was possible.</p>
 <div id="div" contenteditable="true">text <a href="http://www.apple.com/">link</a></div>
 <ul id="console"></ul>
 
@@ -19,15 +20,7 @@ var link = div.lastChild;
 var r = document.createRange();
 r.setStart(text, 0);
 r.setEnd(link, 0);
-
 text.parentNode.removeChild(text);
-
-try {
-    r.extractContents();
-    log("Failure.  Expected an exception to be thrown.");
-} catch (e) {
-    var expected = "Error: WRONG_DOCUMENT_ERR: DOM Exception 4";
-    if (e != expected)
-        log("Failure. Expected: " + expected);
-}
+r.extractContents();
+log("No crash means success!");
 </script>
diff --git a/LayoutTests/fast/dom/Range/mutation-expected.txt b/LayoutTests/fast/dom/Range/mutation-expected.txt
new file mode 100644 (file)
index 0000000..230d115
--- /dev/null
@@ -0,0 +1,35 @@
+PASS description(range) is "text,24 -> text,32: Y blah i"
+PASS description(range) is "text,11 -> text,32: inserted textY blah i"
+PASS description(range) is "text,11 -> text,32: Yinserted text blah i"
+PASS description(range) is "text,11 -> text,32: Y blahinserted text i"
+PASS description(range) is "text,11 -> text,32: Y blah inserted texti"
+PASS description(range) is "text,11 -> text,19: Y blah i"
+PASS description(range) is "text,5 -> text,13:  Range i"
+PASS description(range) is "text,5 -> text,5: "
+PASS description(range) is "text1,5 -> text2,4: ange"
+PASS description(range) is "text,5 -> text,15: he Range i"
+PASS paragraph.childNodes.length is 2
+PASS text1.length is 5
+PASS description(range) is "paragraph,1 -> paragraph,1: "
+PASS paragraph.childNodes.length is 1
+PASS text1.length is 7
+PASS description(range) is "text1,5 -> text1,5: "
+PASS description(range) is "paragraph,2 -> paragraph,4: bc"
+PASS description(range) is "paragraph,1 -> paragraph,4: ebc"
+PASS description(range) is "paragraph,1 -> paragraph,4: bec"
+PASS description(range) is "paragraph,1 -> paragraph,3: bc"
+PASS description(range) is "paragraph,1 -> paragraph,3: bc"
+PASS description(range) is "paragraph,1 -> d,1: cd"
+PASS description(range) is "paragraph,1 -> d,0: c"
+PASS description(range) is "b,0 -> paragraph,3: bc"
+PASS description(range) is "b,1 -> paragraph,3: c"
+PASS description(range) is "a,1 -> a,3: xb"
+PASS description(range) is "a,1 -> b,2: bc"
+PASS description(range) is "a,1 -> b,1: bc"
+PASS description(range) is "a,1 -> a,3: bc"
+PASS description(range) is "a,2 -> section,1: cde"
+PASS description(range) is "section,0 -> section,0: "
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/Range/mutation.html b/LayoutTests/fast/dom/Range/mutation.html
new file mode 100644 (file)
index 0000000..f92cb16
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../../js/resources/js-test-style.css">
+<script src="../../js/resources/js-test-pre.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script src="resources/mutation.js"></script>
+<script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/dom/Range/resources/mutation.js b/LayoutTests/fast/dom/Range/resources/mutation.js
new file mode 100644 (file)
index 0000000..e4ae1cc
--- /dev/null
@@ -0,0 +1,298 @@
+description(
+"This test checks mutation of the DOM and how it affects Ranges."
+);
+
+var a;
+var b;
+var c;
+var d;
+var e;
+var paragraph;
+var section;
+var text;
+var text1;
+var text2;
+var text3;
+
+function name(node)
+{
+    if (node == a)
+        return "a";
+    if (node == b)
+        return "b";
+    if (node == c)
+        return "c";
+    if (node == d)
+        return "d";
+    if (node == e)
+        return "e";
+    if (node == paragraph)
+        return "paragraph";
+    if (node == section)
+        return "section";
+    if (node == text)
+        return "text";
+    if (node == text1)
+        return "text1";
+    if (node == text2)
+        return "text2";
+    if (node == text3)
+        return "text3";
+    return "???";
+}
+
+function description(range)
+{
+    return name(range.startContainer) + "," + range.startOffset + " -> " + name(range.endContainer) + "," + range.endOffset + ": " + range.toString();
+}
+
+function makeRange(sc, so, ec, eo)
+{
+    var newRange = document.createRange();
+    newRange.setStart(sc, so);
+    newRange.setEnd(ec, eo);
+    return newRange;
+}
+
+function setUp1()
+{
+    paragraph = document.createElement("p");
+    text = document.createTextNode("Abcd efgh XY blah ijkl");
+    paragraph.appendChild(text);
+    range = makeRange(text, 11, text, 19);
+}
+
+function createTestElement(name)
+{
+    var element = document.createElement("em");
+    var text = document.createTextNode(name);
+    element.appendChild(text);
+    return element;
+}
+
+function setUp2()
+{
+    paragraph = document.createElement("p");
+    a = createTestElement("a");
+    b = createTestElement("b");
+    c = createTestElement("c");
+    d = createTestElement("d");
+    e = createTestElement("e");
+
+    paragraph.appendChild(a);
+    paragraph.appendChild(b);
+    paragraph.appendChild(c);
+    paragraph.appendChild(d);
+    range = makeRange(paragraph, 1, paragraph, 3);
+}
+
+function setUp3()
+{
+    paragraph = document.createElement("p");
+    a = document.createTextNode("ax");
+    b = document.createTextNode("by");
+
+    paragraph.appendChild(a);
+    paragraph.appendChild(b);
+
+    range = makeRange(a, 1, b, 1);
+}
+
+function setUp4()
+{
+    paragraph = document.createElement("p");
+    a = document.createTextNode("abcd");
+
+    paragraph.appendChild(a);
+
+    range = makeRange(a, 1, a, 3);
+}
+
+function setUp5()
+{
+    section = document.createElement("div");
+    paragraph = document.createElement("p");
+    a = document.createTextNode("abcde");
+
+    section.appendChild(paragraph);
+    paragraph.appendChild(a);
+
+    range = makeRange(a, 2, section, 1);
+}
+
+// First, tests from the DOM Level 2 specification.
+
+// DOM Level 2 Traversal Range, 2.12.1. Insertions, example 1.
+setUp1();
+text.insertData(10, "inserted text");
+shouldBe('description(range)', '"text,24 -> text,32: Y blah i"');
+
+// DOM Level 2 Traversal Range, 2.12.1. Insertions, example 2.
+// Firefox does not match the DOM Level 2 specification on this one.
+setUp1();
+text.insertData(11, "inserted text");
+shouldBe('description(range)', '"text,11 -> text,32: inserted textY blah i"');
+
+// DOM Level 2 Traversal Range, 2.12.1. Insertions, example 3.
+setUp1();
+text.insertData(12, "inserted text");
+shouldBe('description(range)', '"text,11 -> text,32: Yinserted text blah i"');
+
+// DOM Level 2 Traversal Range, 2.12.1. Insertions, example 4.
+setUp1();
+text.insertData(17, "inserted text");
+shouldBe('description(range)', '"text,11 -> text,32: Y blahinserted text i"');
+
+// Similar test at the range end.
+setUp1();
+text.insertData(18, "inserted text");
+shouldBe('description(range)', '"text,11 -> text,32: Y blah inserted texti"');
+
+// Similar test at the range end.
+setUp1();
+text.insertData(19, "inserted text");
+shouldBe('description(range)', '"text,11 -> text,19: Y blah i"');
+
+// DOM Level 2 Traversal Range, 2.12.2. Deletions, example 1.
+paragraph = document.createElement("p");
+text = document.createTextNode("Abcd efgh The Range ijkl");
+paragraph.appendChild(text);
+range = makeRange(text, 11, text, 21);
+text.deleteData(5, 8);
+shouldBe('description(range)', '"text,5 -> text,13:  Range i"');
+
+// DOM Level 2 Traversal Range, 2.12.2. Deletions, example 2.
+paragraph = document.createElement("p");
+text = document.createTextNode("Abcd efgh The Range ijkl");
+paragraph.appendChild(text);
+range = makeRange(text, 11, text, 21);
+text.deleteData(5, 17);
+shouldBe('description(range)', '"text,5 -> text,5: "');
+
+// DOM Level 2 Traversal Range, 2.12.2. Deletions, example 3.
+// Firefox does not match the DOM Level 2 specification on this one.
+// Or maybe I wrote the test wrong?
+paragraph = document.createElement("p");
+text1 = document.createTextNode("ABCD efgh The ");
+paragraph.appendChild(text1);
+em = document.createElement("em");
+paragraph.appendChild(em);
+text2 = document.createTextNode("Range");
+em.appendChild(text2);
+text3 = document.createTextNode(" ijkl");
+paragraph.appendChild(text3);
+range = makeRange(text1, 11, text2, 5);
+makeRange(text1, 5, text2, 1).deleteContents();
+shouldBe('description(range)', '"text1,5 -> text2,4: ange"');
+
+// DOM Level 2 Traversal Range, 2.12.2. Deletions, example 4.
+paragraph = document.createElement("p");
+text = document.createTextNode("Abcd efgh The Range ijkl");
+paragraph.appendChild(text);
+range = makeRange(text, 11, text, 21);
+text.deleteData(5, 6);
+shouldBe('description(range)', '"text,5 -> text,15: he Range i"');
+
+// DOM Level 2 Traversal Range, 2.12.2. Deletions, example 5.
+paragraph = document.createElement("p");
+text1 = document.createTextNode("Abcd ");
+paragraph.appendChild(text1);
+em = document.createElement("em");
+paragraph.appendChild(em);
+text2 = document.createTextNode("efgh The Range ij");
+em.appendChild(text2);
+text3 = document.createTextNode("kl");
+paragraph.appendChild(text3);
+range = makeRange(text2, 6, text2, 16);
+makeRange(paragraph, 1, paragraph, 2).deleteContents();
+shouldBe('paragraph.childNodes.length', '2');
+shouldBe('text1.length', '5');
+shouldBe('description(range)', '"paragraph,1 -> paragraph,1: "');
+paragraph.normalize();
+shouldBe('paragraph.childNodes.length', '1');
+shouldBe('text1.length', '7');
+shouldBe('description(range)', '"text1,5 -> text1,5: "');
+
+// Inserting a node in the start container, before the range start offset.
+setUp2();
+paragraph.insertBefore(e, a);
+shouldBe('description(range)', '"paragraph,2 -> paragraph,4: bc"');
+
+// Inserting a node in the start container, at the range start offset.
+setUp2();
+paragraph.insertBefore(e, b);
+shouldBe('description(range)', '"paragraph,1 -> paragraph,4: ebc"');
+
+// Inserting a node in the start container, between start and end.
+setUp2();
+paragraph.insertBefore(e, c);
+shouldBe('description(range)', '"paragraph,1 -> paragraph,4: bec"');
+
+// Inserting a node in the end container, at the range end offset.
+setUp2();
+paragraph.insertBefore(e, d);
+shouldBe('description(range)', '"paragraph,1 -> paragraph,3: bc"');
+
+// Inserting a node in the end container, after the range end offset.
+setUp2();
+paragraph.appendChild(e);
+shouldBe('description(range)', '"paragraph,1 -> paragraph,3: bc"');
+
+// Removing the start container of a range.
+setUp2();
+range = makeRange(b, 0, d, 1);
+paragraph.removeChild(b);
+shouldBe('description(range)', '"paragraph,1 -> d,1: cd"');
+setUp2();
+range = makeRange(b, 1, d, 0);
+paragraph.removeChild(b);
+shouldBe('description(range)', '"paragraph,1 -> d,0: c"');
+
+// Removing the end container of a range.
+setUp2();
+range = makeRange(b, 0, d, 1);
+paragraph.removeChild(d);
+shouldBe('description(range)', '"b,0 -> paragraph,3: bc"');
+setUp2();
+range = makeRange(b, 1, d, 0);
+paragraph.removeChild(d);
+shouldBe('description(range)', '"b,1 -> paragraph,3: c"');
+
+// Calling normalize with a range that has an endpoint in a text node that gets merged into another.
+// Firefox does not do what the DOM Level 2 specification seems to call for on this one.
+setUp3();
+paragraph.normalize();
+shouldBe('description(range)', '"a,1 -> a,3: xb"');
+
+// Calling splitText when a range has an endpoint on in the piece that gets made into a new text node.
+// Firefox does not do what the DOM Level 2 specification seems to call for on this one.
+setUp4();
+b = a.splitText(1);
+shouldBe('description(range)', '"a,1 -> b,2: bc"');
+setUp4();
+b = a.splitText(2);
+shouldBe('description(range)', '"a,1 -> b,1: bc"');
+setUp4();
+b = a.splitText(3);
+shouldBe('description(range)', '"a,1 -> a,3: bc"');
+
+// Range test in Acid3.
+setUp5();
+shouldBe('description(range)', '"a,2 -> section,1: cde"');
+section.removeChild(paragraph);
+shouldBe('description(range)', '"section,0 -> section,0: "');
+
+// Children in a range being removed, but by DOM mutation (not CharacterData mutation).
+// Test CharacterData.replaceData cases?
+// Test Element.innerHTML cases (setting it)?
+// Test Element.innerText cases (setting it)?
+// Test Element.outerHTML cases (setting it)?
+// Test Element.outerText cases (setting it)?
+// Test Node.replaceChild cases?
+// Test cases where Node.insertBefore/appendChild has a side effect of deleting the node from a range.
+// Test Range.deleteContents cases?
+// Test Range.extractContents cases?
+// Test Range.surroundContents cases?
+// Test Text.replaceWholeText cases?
+
+var successfullyParsed = true;
index be35ca8..b37223c 100644 (file)
@@ -1,4 +1,3 @@
-CONSOLE MESSAGE: line 17: Error: WRONG_DOCUMENT_ERR: DOM Exception 4
 layer at (0,0) size 800x600
   RenderView at (0,0) size 800x600
 layer at (0,0) size 800x600
index 1efb105..a67960f 100644 (file)
@@ -1,4 +1,3 @@
-CONSOLE MESSAGE: line 17: Error: WRONG_DOCUMENT_ERR: DOM Exception 4
 layer at (0,0) size 800x600
   RenderView at (0,0) size 800x600
 layer at (0,0) size 800x600
index 411de4e..349e002 100644 (file)
@@ -1,3 +1,126 @@
+2008-03-15  Darin Adler  <darin@apple.com>
+
+        Reviewed by Sam.
+
+        - fix http://bugs.webkit.org/show_bug.cgi?id=11997
+          Ranges are not fixed after mutation (affects Acid3 test 13)
+
+        Test: fast/dom/Range/mutation.html
+
+        * WebCore.xcodeproj/project.pbxproj: Added NodeWithIndex.h, NodeWithIndexAfter.h,
+        and NodeWithIndexBefore.h.
+
+        * dom/CharacterData.cpp:
+        (WebCore::CharacterData::setData): Replaced call to Document::removeMarkers
+        with call to Document::textRemoved.
+        (WebCore::CharacterData::insertData): Replaced call to Document::shiftMarkers
+        with call to Document::textInserted.
+        (WebCore::CharacterData::deleteData): Replaced call to Document::removeMarkers
+        and Document::shiftMarkers with call to Document::textRemoved.
+        (WebCore::CharacterData::replaceData): Replaced call to Document::removeMarkers
+        and Document::shiftMarkers with call to Document::textRemoved and
+        Document::textInserted.
+        (WebCore::CharacterData::containsOnlyWhitespace): Tweaked a bit.
+
+        * dom/ContainerNode.cpp:
+        (WebCore::ContainerNode::childrenChanged): Added a call to
+        Document::nodeChildrenChanged when the nmber of children was changed (and not
+        by the parser).
+        (WebCore::dispatchChildRemovalEvents): Updated for name change.
+
+        * dom/Document.cpp:
+        (WebCore::Document::~Document): Assert that all ranges are gone.
+        (WebCore::Document::nodeChildrenChanged): Added. Calls nodeChildrenChanged on
+        all ranges.
+        (WebCore::Document::nodeWillBeRemoved): Renamed from notifyBeforeNodeRemoval.
+        Added code to call nodeWillBeRemoved on all ranges.
+        (WebCore::Document::textInserted): Added. Calls textInserted on all ranges and
+        also calls shiftMarkers.
+        (WebCore::Document::textRemoved): Added. Calls textRemoved on all ranges and also
+        calls removeMarkers and shiftMarkers.
+        (WebCore::Document::textNodesMerged): Added. Calls textNodesMerged on all ranges.
+        (WebCore::Document::textNodeSplit): Added. Calls textNodeSplit on all ranges.
+        (WebCore::Document::attachRange): Added. Adds range to the HashSet of all ranges
+        for this document.
+        (WebCore::Document::detachRange): Added. Removes range from the HashSet.
+        * dom/Document.h: Added the new functions and the data member.
+
+        * dom/Element.cpp:
+        (WebCore::Element::normalizeAttributes): Added. Contains the part of the
+        normalize function that's specific to Element. Better encapsulation to have it
+        here rather than in Node::normalize.
+        * dom/Element.h: Added the new function.
+
+        * dom/Node.cpp:
+        (WebCore::Node::normalize): Rewrote so it's no longer recursive. Also added
+        a call to textNodesMerged after each pair of nodes is merged but before the
+        second node is removed.
+        (WebCore::Node::traverseNextNodePostOrder): Added. Helper function used by
+        normalize, but also useful elsewhere.
+        * dom/Node.h: Added the new function.
+
+        * dom/NodeIterator.cpp:
+        (WebCore::NodeIterator::nodeWillBeRemoved): Renamed from notifyBeforeNodeRemoval.
+        * dom/NodeIterator.h: Ditto.
+
+        * dom/ProcessingInstruction.cpp:
+        (WebCore::ProcessingInstruction::setData): Call textRemoved.
+
+        * dom/Range.cpp:
+        (WebCore::NodeWithIndex::index): Added. Computes and stores index.
+        (WebCore::NodeWithIndexBefore::indexBefore): Added. Computes and stores index.
+        (WebCore::NodeWithIndexAfter::indexAfter): Added. Computes and stores index.
+        (WebCore::Range::Range): Call attachRange.
+        (WebCore::Range::~Range): Call detachRange unless the range is already detached.
+        (WebCore::Range::commonAncestorContainer): Removed check for WRONG_DOCUMENT_ERR.
+        It's no longer possible to create a range where the two containers are non-zero
+        and have no common ancestor.
+        (WebCore::Range::isPointInRange): Rewrote expression to be more readable.
+        (WebCore::Range::compareNode): Changed local variable to use int for consistency.
+        (WebCore::Range::compareBoundaryPoints): Replaced ASSERT with ASSERT_NOT_REACHED.
+        (WebCore::Range::deleteContents): Removed check for INVALID_STATE_ERR and
+        initialization of ec to 0; both are now inside checkDeleteExtract.
+        (WebCore::Range::intersectsNode): Changed local variable to use int for consistency.
+        Also changed comparison to use < 0 and >= 0 rather than checking explicitly for 1
+        and -1.
+        (WebCore::Range::processContents): Changed code to not get the nodeType multiple
+        times on the same node, and tweaked formatting. Removed code to update the range
+        on deletion, because the normal delete logic will take care of that now.
+        (WebCore::Range::extractContents): Removed check for INVALID_STATE_ERR and
+        initialization of ec to 0; both are now inside checkDeleteExtract.
+        (WebCore::Range::insertNode): Changed local variable to use int for consistency.
+        (WebCore::Range::toString): Changed variable name to pastLast.
+        (WebCore::Range::detach): Call detachRange.
+        (WebCore::Range::checkDeleteExtract): Added check for detached range and code to
+        set ec to 0; moved here from the two callers. Also changed variable name to pastLast.
+        (WebCore::endpointNodeChildrenChanged): Added.
+        (WebCore::Range::nodeChildrenChanged): Added.
+        (WebCore::endpointNodeWillBeRemoved): Added.
+        (WebCore::Range::nodeWillBeRemoved): Added.
+        (WebCore::endpointTextInserted): Added.
+        (WebCore::Range::textInserted): Added.
+        (WebCore::endpointTextRemoved): Added.
+        (WebCore::Range::textRemoved): Added.
+        (WebCore::endpointTextNodesMerged): Added.
+        (WebCore::Range::textNodesMerged): Added.
+        (WebCore::endpointTextNodesSplit): Added.
+        (WebCore::Range::textNodeSplit): Added.
+
+        * dom/Range.h: Added new member functions.
+
+        * dom/NodeWithIndex.h: Added. Makes it so we won't find the index for the same
+        node more than once.
+        * dom/NodeWithIndexAfter.h: Added. Similar to NodeWithIndex but gives the index after a
+        node and treats a node pointer of 0 as meaning "before first node in parent container".
+        * dom/NodeWithIndexBefore.h: Added.  Similar to NodeWithIndex but treats a node pointer of 0
+        as meaning "after last node in parent container".
+
+        * dom/Text.cpp:
+        (WebCore::Text::splitText): Call textNodeSplit.
+
+        * editing/ApplyStyleCommand.cpp:
+        (WebCore::ApplyStyleCommand::applyInlineStyle): Changed variable name to pastLast.
+
 2008-03-15  Julien Chaffraix  <julien.chaffraix@gmail.com>
 
         Reviewed by Holger.
index 6e52be0..5f306db 100644 (file)
                9380F47309A11AB4001FDB34 /* Widget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9380F47109A11AB4001FDB34 /* Widget.cpp */; };
                9380F47409A11AB4001FDB34 /* Widget.h in Headers */ = {isa = PBXBuildFile; fileRef = 9380F47209A11AB4001FDB34 /* Widget.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9380F47809A11ACC001FDB34 /* WidgetMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9380F47709A11ACC001FDB34 /* WidgetMac.mm */; };
+               9382AAB40D8C386100F357A6 /* NodeWithIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 9382AAB10D8C386100F357A6 /* NodeWithIndex.h */; };
+               9382AAB50D8C386100F357A6 /* NodeWithIndexBefore.h in Headers */ = {isa = PBXBuildFile; fileRef = 9382AAB20D8C386100F357A6 /* NodeWithIndexBefore.h */; };
+               9382AAB60D8C386100F357A6 /* NodeWithIndexAfter.h in Headers */ = {isa = PBXBuildFile; fileRef = 9382AAB30D8C386100F357A6 /* NodeWithIndexAfter.h */; };
                93831B570D087D6000E5C984 /* ExceptionCode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93831B560D087D6000E5C984 /* ExceptionCode.cpp */; };
                938E65F109F09840008A48EC /* JSHTMLElementWrapperFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 938E65F009F09840008A48EC /* JSHTMLElementWrapperFactory.h */; };
                938E65F709F0985D008A48EC /* JSHTMLElementWrapperFactory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 938E65F609F0985D008A48EC /* JSHTMLElementWrapperFactory.cpp */; };
                9380F47109A11AB4001FDB34 /* Widget.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Widget.cpp; sourceTree = "<group>"; };
                9380F47209A11AB4001FDB34 /* Widget.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Widget.h; sourceTree = "<group>"; };
                9380F47709A11ACC001FDB34 /* WidgetMac.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = WidgetMac.mm; sourceTree = "<group>"; };
+               9382AAB10D8C386100F357A6 /* NodeWithIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NodeWithIndex.h; sourceTree = "<group>"; };
+               9382AAB20D8C386100F357A6 /* NodeWithIndexBefore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NodeWithIndexBefore.h; sourceTree = "<group>"; };
+               9382AAB30D8C386100F357A6 /* NodeWithIndexAfter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NodeWithIndexAfter.h; sourceTree = "<group>"; };
                93831B560D087D6000E5C984 /* ExceptionCode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ExceptionCode.cpp; sourceTree = "<group>"; };
                938E65F009F09840008A48EC /* JSHTMLElementWrapperFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSHTMLElementWrapperFactory.h; sourceTree = "<group>"; };
                938E65F609F0985D008A48EC /* JSHTMLElementWrapperFactory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSHTMLElementWrapperFactory.cpp; sourceTree = "<group>"; };
                                1A750D870A90E394000FF215 /* NodeIterator.idl */,
                                A81872100977D3C0005826D9 /* NodeList.h */,
                                85ACA9FA0A9B631000671E90 /* NodeList.idl */,
+                               9382AAB10D8C386100F357A6 /* NodeWithIndex.h */,
+                               9382AAB20D8C386100F357A6 /* NodeWithIndexBefore.h */,
+                               9382AAB30D8C386100F357A6 /* NodeWithIndexAfter.h */,
                                A8EA7EB70A1945D000A8EF5F /* Notation.cpp */,
                                A8EA7EB60A1945D000A8EF5F /* Notation.h */,
                                93EEC1F409C2877700C515D1 /* Notation.idl */,
                                A9D248010D757E6900FDF959 /* JSPluginArray.h in Headers */,
                                A9D248070D757E7D00FDF959 /* JSMimeType.h in Headers */,
                                A9D248090D757E7D00FDF959 /* JSMimeTypeArray.h in Headers */,
+                               9382AAB40D8C386100F357A6 /* NodeWithIndex.h in Headers */,
+                               9382AAB50D8C386100F357A6 /* NodeWithIndexBefore.h in Headers */,
+                               9382AAB60D8C386100F357A6 /* NodeWithIndexAfter.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 1228c98..a426ce7 100644 (file)
@@ -38,10 +38,10 @@ CharacterData::CharacterData(Document *doc)
 {
 }
 
-CharacterData::CharacterData(Document *doc, const String &_text)
-    : EventTargetNode(doc)
+CharacterData::CharacterData(Document* document, const String& text)
+    : EventTargetNode(document)
 {
-    m_data = _text.impl() ? _text.impl() : StringImpl::empty();
+    m_data = text.impl() ? text.impl() : StringImpl::empty();
 }
 
 CharacterData::~CharacterData()
@@ -59,6 +59,7 @@ void CharacterData::setData(const String& data, ExceptionCode& ec)
     if (equal(m_data.get(), data.impl()))
         return;
 
+    int oldLength = length();
     RefPtr<StringImpl> oldStr = m_data;
     m_data = data.impl();
     
@@ -70,10 +71,10 @@ void CharacterData::setData(const String& data, ExceptionCode& ec)
     
     dispatchModifiedEvent(oldStr.get());
     
-    document()->removeMarkers(this);
+    document()->textRemoved(this, 0, oldLength);
 }
 
-String CharacterData::substringData( const unsigned offset, const unsigned count, ExceptionCode& ec)
+String CharacterData::substringData(unsigned offset, unsigned count, ExceptionCode& ec)
 {
     ec = 0;
     checkCharDataOperation(offset, ec);
@@ -83,7 +84,7 @@ String CharacterData::substringData( const unsigned offset, const unsigned count
     return m_data->substring(offset, count);
 }
 
-void CharacterData::appendData( const String &arg, ExceptionCode& ec)
+void CharacterData::appendData(const String& arg, ExceptionCode& ec)
 {
     ec = 0;
 
@@ -108,7 +109,7 @@ void CharacterData::appendData( const String &arg, ExceptionCode& ec)
     dispatchModifiedEvent(oldStr.get());
 }
 
-void CharacterData::insertData( const unsigned offset, const String &arg, ExceptionCode& ec)
+void CharacterData::insertData(unsigned offset, const String& arg, ExceptionCode& ec)
 {
     ec = 0;
     checkCharDataOperation(offset, ec);
@@ -126,23 +127,27 @@ void CharacterData::insertData( const unsigned offset, const String &arg, Except
         attach();
     } else if (renderer())
         static_cast<RenderText*>(renderer())->setTextWithOffset(m_data, offset, 0);
-    
+
     dispatchModifiedEvent(oldStr.get());
     
-    // update the markers for spell checking and grammar checking
-    unsigned length = arg.length();
-    document()->shiftMarkers(this, offset, length);
+    document()->textInserted(this, offset, arg.length());
 }
 
-void CharacterData::deleteData( const unsigned offset, const unsigned count, ExceptionCode& ec)
+void CharacterData::deleteData(unsigned offset, unsigned count, ExceptionCode& ec)
 {
     ec = 0;
     checkCharDataOperation(offset, ec);
     if (ec)
         return;
 
+    unsigned realCount;
+    if (offset + count > length())
+        realCount = length() - offset;
+    else
+        realCount = count;
+
     String newStr = m_data;
-    newStr.remove(offset, count);
+    newStr.remove(offset, realCount);
 
     RefPtr<StringImpl> oldStr = m_data;
     m_data = newStr.impl();
@@ -155,12 +160,10 @@ void CharacterData::deleteData( const unsigned offset, const unsigned count, Exc
 
     dispatchModifiedEvent(oldStr.get());
 
-    // update the markers for spell checking and grammar checking
-    document()->removeMarkers(this, offset, count);
-    document()->shiftMarkers(this, offset + count, -static_cast<int>(count));
+    document()->textRemoved(this, offset, realCount);
 }
 
-void CharacterData::replaceData( const unsigned offset, const unsigned count, const String &arg, ExceptionCode& ec)
+void CharacterData::replaceData(unsigned offset, unsigned count, const String& arg, ExceptionCode& ec)
 {
     ec = 0;
     checkCharDataOperation(offset, ec);
@@ -168,8 +171,8 @@ void CharacterData::replaceData( const unsigned offset, const unsigned count, co
         return;
 
     unsigned realCount;
-    if (offset + count > m_data->length())
-        realCount = m_data->length()-offset;
+    if (offset + count > length())
+        realCount = length() - offset;
     else
         realCount = count;
 
@@ -189,9 +192,8 @@ void CharacterData::replaceData( const unsigned offset, const unsigned count, co
     dispatchModifiedEvent(oldStr.get());
     
     // update the markers for spell checking and grammar checking
-    int diff = arg.length() - count;
-    document()->removeMarkers(this, offset, count);
-    document()->shiftMarkers(this, offset + count, diff);
+    document()->textRemoved(this, offset, realCount);
+    document()->textInserted(this, offset, arg.length());
 }
 
 String CharacterData::nodeValue() const
@@ -201,18 +203,16 @@ String CharacterData::nodeValue() const
 
 bool CharacterData::containsOnlyWhitespace() const
 {
-    if (m_data)
-        return m_data->containsOnlyWhitespace();
-    return true;
+    return !m_data || m_data->containsOnlyWhitespace();
 }
 
-void CharacterData::setNodeValue( const String &_nodeValue, ExceptionCode& ec)
+void CharacterData::setNodeValue(const String& nodeValue, ExceptionCode& ec)
 {
     // NO_MODIFICATION_ALLOWED_ERR: taken care of by setData()
-    setData(_nodeValue, ec);
+    setData(nodeValue, ec);
 }
 
-void CharacterData::dispatchModifiedEvent(StringImpl *prevValue)
+void CharacterData::dispatchModifiedEvent(StringImplprevValue)
 {
     if (parentNode())
         parentNode()->childrenChanged();
@@ -223,13 +223,13 @@ void CharacterData::dispatchModifiedEvent(StringImpl *prevValue)
     dispatchSubtreeModifiedEvent();
 }
 
-void CharacterData::checkCharDataOperation( const unsigned offset, ExceptionCode& ec)
+void CharacterData::checkCharDataOperation(unsigned offset, ExceptionCode& ec)
 {
     ec = 0;
 
     // INDEX_SIZE_ERR: Raised if the specified offset is negative or greater than the number of 16-bit
     // units in data.
-    if (offset > m_data->length()) {
+    if (offset > length()) {
         ec = INDEX_SIZE_ERR;
         return;
     }
@@ -241,14 +241,14 @@ void CharacterData::checkCharDataOperation( const unsigned offset, ExceptionCode
     }
 }
 
-int CharacterData::maxCharacterOffset() const 
+int CharacterData::maxCharacterOffset() const
 {
-    return (int)length();
+    return static_cast<int>(length());
 }
 
 bool CharacterData::rendererIsNeeded(RenderStyle *style)
 {
-    if (!m_data || m_data->length() == 0)
+    if (!m_data || !length())
         return false;
     return EventTargetNode::rendererIsNeeded(style);
 }
index bc48ec1..7f68ab7 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
  *           (C) 2001 Dirk Mueller (mueller@kde.org)
- * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -698,6 +698,8 @@ void ContainerNode::removedFromTree(bool deep)
 void ContainerNode::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
 {
     Node::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
+    if (!changedByParser && childCountDelta)
+        document()->nodeChildrenChanged(this, beforeChange, afterChange, childCountDelta);
     if (document()->hasNodeLists())
         notifyNodeListsChildrenChanged();
 }
@@ -973,7 +975,7 @@ static void dispatchChildRemovalEvents(Node* child, ExceptionCode& ec)
     DocPtr<Document> doc = child->document();
 
     // update auxiliary doc info (e.g. iterators) to note that node is being removed
-    doc->notifyBeforeNodeRemoval(child); // ### use events instead
+    doc->nodeWillBeRemoved(child);
 
     // dispatch pre-removal mutation events
     if (c->parentNode() && 
index 2c17f48..cbae8df 100644 (file)
@@ -80,6 +80,9 @@
 #include "NameNodeList.h"
 #include "NodeFilter.h"
 #include "NodeIterator.h"
+#include "NodeWithIndex.h"
+#include "NodeWithIndexAfter.h"
+#include "NodeWithIndexBefore.h"
 #include "OverflowEvent.h"
 #include "Page.h"
 #include "PlatformKeyboardEvent.h"
@@ -399,6 +402,7 @@ Document::~Document()
     ASSERT(!renderer());
     ASSERT(!m_inPageCache);
     ASSERT(!m_savedRenderer);
+    ASSERT(m_ranges.isEmpty());
 
     removeAllEventListeners();
 
@@ -2446,16 +2450,70 @@ void Document::detachNodeIterator(NodeIterator *ni)
     m_nodeIterators.remove(ni);
 }
 
-void Document::notifyBeforeNodeRemoval(Node *n)
+void Document::nodeChildrenChanged(ContainerNode* container, Node* beforeChange, Node* afterChange, int childCountDelta)
 {
-    if (Frame* f = frame()) {
-        f->selectionController()->nodeWillBeRemoved(n);
-        f->dragCaretController()->nodeWillBeRemoved(n);
+    NodeWithIndexAfter beforeChangeWithIndex(beforeChange);
+    NodeWithIndexBefore afterChangeWithIndex(container, afterChange);
+    HashSet<Range*>::const_iterator end = m_ranges.end();
+    for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it)
+        (*it)->nodeChildrenChanged(beforeChangeWithIndex, afterChangeWithIndex, childCountDelta);
+}
+
+void Document::nodeWillBeRemoved(Node* n)
+{
+    HashSet<NodeIterator*>::const_iterator nodeIteratorsEnd = m_nodeIterators.end();
+    for (HashSet<NodeIterator*>::const_iterator it = m_nodeIterators.begin(); it != nodeIteratorsEnd; ++it)
+        (*it)->nodeWillBeRemoved(n);
+
+    NodeWithIndex nodeWithIndex(n);
+    HashSet<Range*>::const_iterator rangesEnd = m_ranges.end();
+    for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != rangesEnd; ++it)
+        (*it)->nodeWillBeRemoved(nodeWithIndex);
+
+    if (Frame* frame = this->frame()) {
+        frame->selectionController()->nodeWillBeRemoved(n);
+        frame->dragCaretController()->nodeWillBeRemoved(n);
     }
+}
+
+void Document::textInserted(Node* text, unsigned offset, unsigned length)
+{
+    HashSet<Range*>::const_iterator end = m_ranges.end();
+    for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it)
+        (*it)->textInserted(text, offset, length);
+
+    // Update the markers for spelling and grammar checking.
+    shiftMarkers(text, offset, length);
+}
+
+void Document::textRemoved(Node* text, unsigned offset, unsigned length)
+{
+    HashSet<Range*>::const_iterator end = m_ranges.end();
+    for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it)
+        (*it)->textRemoved(text, offset, length);
+
+    // Update the markers for spelling and grammar checking.
+    removeMarkers(text, offset, length);
+    shiftMarkers(text, offset + length, 0 - length);
+}
+
+void Document::textNodesMerged(Text* oldNode, unsigned offset)
+{
+    NodeWithIndex oldNodeWithIndex(oldNode);
+    HashSet<Range*>::const_iterator end = m_ranges.end();
+    for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it)
+        (*it)->textNodesMerged(oldNodeWithIndex, offset);
+
+    // FIXME: This should update markers for spelling and grammar checking.
+}
+
+void Document::textNodeSplit(Text* oldNode)
+{
+    HashSet<Range*>::const_iterator end = m_ranges.end();
+    for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it)
+        (*it)->textNodeSplit(oldNode);
 
-    HashSet<NodeIterator*>::const_iterator end = m_nodeIterators.end();
-    for (HashSet<NodeIterator*>::const_iterator it = m_nodeIterators.begin(); it != end; ++it)
-        (*it)->notifyBeforeNodeRemoval(n);
+    // FIXME: This should update markers for spelling and grammar checking.
 }
 
 DOMWindow* Document::defaultView() const
@@ -2500,9 +2558,9 @@ PassRefPtr<Event> Document::createEvent(const String& eventType, ExceptionCode&
     return 0;
 }
 
-CSSStyleDeclaration *Document::getOverrideStyle(Element */*elt*/, const String &/*pseudoElt*/)
+CSSStyleDeclaration* Document::getOverrideStyle(Element*, const String&)
 {
-    return 0; // ###
+    return 0;
 }
 
 void Document::handleWindowEvent(Event *evt, bool useCapture)
@@ -4035,4 +4093,16 @@ void Document::stopDatabases()
 
 #endif
 
+void Document::attachRange(Range* range)
+{
+    ASSERT(!m_ranges.contains(range));
+    m_ranges.add(range);
+}
+
+void Document::detachRange(Range* range)
+{
+    ASSERT(m_ranges.contains(range));
+    m_ranges.remove(range);
+}
+
 } // namespace WebCore
index 927a6f3..84be6a4 100644 (file)
@@ -54,6 +54,7 @@ namespace WebCore {
     class Attribute;
     class CDATASection;
     class CachedCSSStyleSheet;
+    class CharacterData;
     class CSSStyleDeclaration;
     class CSSStyleSelector;
     class CSSStyleSheet;
@@ -475,7 +476,18 @@ public:
 
     void attachNodeIterator(NodeIterator*);
     void detachNodeIterator(NodeIterator*);
-    void notifyBeforeNodeRemoval(Node*);
+
+    void attachRange(Range*);
+    void detachRange(Range*);
+
+    void nodeChildrenChanged(ContainerNode* container, Node* beforeChange, Node* afterChange, int childCountDelta);
+    void nodeWillBeRemoved(Node*);
+
+    void textInserted(Node*, unsigned offset, unsigned length);
+    void textRemoved(Node*, unsigned offset, unsigned length);
+    void textNodesMerged(Text* oldNode, unsigned offset);
+    void textNodeSplit(Text* oldNode);
+
     DOMWindow* defaultView() const;
     PassRefPtr<Event> createEvent(const String& eventType, ExceptionCode&);
 
@@ -751,6 +763,7 @@ private:
     unsigned m_domtree_version;
     
     HashSet<NodeIterator*> m_nodeIterators;
+    HashSet<Range*> m_ranges;
 
     unsigned short m_listenerTypes;
     RefPtr<StyleSheetList> m_styleSheets;
@@ -953,6 +966,7 @@ private:
     typedef HashSet<Database*> DatabaseSet;
     OwnPtr<DatabaseSet> m_openDatabaseSet;
 #endif
+
 #if USE(LOW_BANDWIDTH_DISPLAY)
     bool m_inLowBandwidthDisplay;
 #endif
index 8b4c165..c8d10bf 100644 (file)
@@ -1227,4 +1227,17 @@ bool Element::virtualHasTagName(const QualifiedName& name) const
     return hasTagName(name);
 }
 
+void Element::normalizeAttributes()
+{
+    // Normalize attributes.
+    NamedAttrMap* attrs = attributes(true);
+    if (!attrs)
+        return;
+    unsigned numAttrs = attrs->length();
+    for (unsigned i = 0; i < numAttrs; i++) {
+        if (Attr* attr = attrs->attributeItem(i)->attr())
+            attr->normalize();
+    }
+}
+
 }
index 33727cd..d27c3ea 100644 (file)
@@ -111,7 +111,7 @@ public:
 
     virtual KURL baseURI() const;
 
-    // DOM methods overridden from  parent classes
+    // DOM methods overridden from parent classes
     virtual NodeType nodeType() const;
     virtual PassRefPtr<Node> cloneNode(bool deep);
     virtual String nodeName() const;
@@ -120,6 +120,8 @@ public:
     virtual void removedFromDocument();
     virtual void childrenChanged(bool changedByParser = false, Node* beforeChange = 0, Node* afterChange = 0, int childCountDelta = 0);
 
+    void normalizeAttributes();
+
     virtual bool isInputTypeHidden() const { return false; }
 
     String nodeNamePreservingCase() const;
index 584e06a..ba4cd14 100644 (file)
@@ -264,65 +264,52 @@ void Node::remove(ExceptionCode& ec)
     deref();
 }
 
-bool Node::hasChildNodes(  ) const
+bool Node::hasChildNodes() const
 {
-  return false;
+    return false;
 }
 
-void Node::normalize ()
+void Node::normalize()
 {
-    ExceptionCode ec = 0;
-    Node *child = firstChild();
+    // Go through the subtree beneath us, normalizing all nodes. This means that
+    // any two adjacent text nodes are merged together.
 
-    if (isElementNode()) {
-        // Normalize any attribute children we might have 
-        Element *element = static_cast<Element *>(this);
-        NamedAttrMap *attrMap = element->attributes();
-        
-        if (attrMap) {
-            unsigned numAttrs = attrMap->length();
-            
-            for (unsigned i = 0; i < numAttrs; i++) {
-                Attribute *attribute = attrMap->attributeItem(i);
-                Attr *attr = attribute->attr();
-                
-                if (attr)
-                    attr->normalize();
+    RefPtr<Node> node = this;
+    while (Node* firstChild = node->firstChild())
+        node = firstChild;
+    for (; node; node = node->traverseNextNodePostOrder()) {
+        NodeType type = node->nodeType();
+        if (type == ELEMENT_NODE)
+            static_cast<Element*>(node.get())->normalizeAttributes();
+
+        Node* firstChild = node->firstChild();
+        if (firstChild && !firstChild->nextSibling() && firstChild->isTextNode()) {
+            Text* text = static_cast<Text*>(firstChild);
+            if (!text->length()) {
+                ExceptionCode ec;
+                text->remove(ec);
             }
         }
-    }
-    
-    // Recursively go through the subtree beneath us, normalizing all nodes. In the case
-    // where there are two adjacent text nodes, they are merged together
-    while (child) {
-        Node *nextChild = child->nextSibling();
 
-        if (nextChild && child->nodeType() == TEXT_NODE && nextChild->nodeType() == TEXT_NODE) {
-            // Current child and the next one are both text nodes... merge them
-            Text *currentText = static_cast<Text*>(child);
-            Text *nextText = static_cast<Text*>(nextChild);
-
-            currentText->appendData(nextText->data(),ec);
-            if (ec)
-                return;
+        if (node == this)
+            break;
 
-            nextChild->remove(ec);
-            if (ec)
-                return;
-        }
-        else {
-            child->normalize();
-            child = nextChild;
+        if (type == TEXT_NODE) {
+            while (1) {
+                Node* nextSibling = node->nextSibling();
+                if (!nextSibling || !nextSibling->isTextNode())
+                    break;
+                // Current child and the next one are both text nodes. Merge them.
+                Text* text = static_cast<Text*>(node.get());
+                RefPtr<Text> nextText = static_cast<Text*>(nextSibling);
+                unsigned offset = text->length();
+                ExceptionCode ec;
+                text->appendData(nextText->data(), ec);
+                document()->textNodesMerged(nextText.get(), offset);
+                nextText->remove(ec);
+            }
         }
     }
-    
-    // Check if we have a single empty text node left and remove it if so
-    child = firstChild();
-    if (child && !child->nextSibling() && child->isTextNode()) {
-        Text *text = static_cast<Text*>(child);
-        if (text->data().isEmpty())
-            child->remove(ec);
-    }
 }
 
 const AtomicString& Node::prefix() const
@@ -512,6 +499,16 @@ Node *Node::traverseNextSibling(const Node *stayWithin) const
     return 0;
 }
 
+Node* Node::traverseNextNodePostOrder() const
+{
+    Node* next = nextSibling();
+    if (!next)
+        return parentNode();
+    while (Node* firstChild = next->firstChild())
+        next = firstChild;
+    return next;
+}
+
 Node *Node::traversePreviousNode(const Node *stayWithin) const
 {
     if (this == stayWithin)
index 3abdfc0..65a78b2 100644 (file)
@@ -317,7 +317,7 @@ public:
      */
     Node* traverseNextNode(const Node* stayWithin = 0) const;
     
-    /* Like traverseNextNode, but skips children and starts with the next sibling. */
+    // Like traverseNextNode, but skips children and starts with the next sibling.
     Node* traverseNextSibling(const Node* stayWithin = 0) const;
 
     /**
@@ -327,7 +327,10 @@ public:
      */
     Node* traversePreviousNode(const Node * stayWithin = 0) const;
 
-    /* Like traversePreviousNode, but visits nodes before their children. */
+    // Like traverseNextNode, but visits parents after their children.
+    Node* traverseNextNodePostOrder() const;
+
+    // Like traversePreviousNode, but visits parents before their children.
     Node* traversePreviousNodePostOrder(const Node *stayWithin = 0) const;
     Node* traversePreviousSiblingPostOrder(const Node *stayWithin = 0) const;
 
index 502d393..e623762 100644 (file)
@@ -150,7 +150,7 @@ void NodeIterator::detach()
     m_referenceNode.node.clear();
 }
 
-void NodeIterator::notifyBeforeNodeRemoval(Node* removedNode)
+void NodeIterator::nodeWillBeRemoved(Node* removedNode)
 {
     updateForNodeRemoval(removedNode, m_referenceNode);
 }
index b2a7c70..a6648bf 100644 (file)
@@ -45,7 +45,7 @@ namespace WebCore {
         bool pointerBeforeReferenceNode() const { return m_referenceNode.isPointerBeforeNode; }
 
         // This function is called before any node is removed from the document tree.
-        void notifyBeforeNodeRemoval(Node* nodeToBeRemoved);
+        void nodeWillBeRemoved(Node*);
 
         // For non-JS bindings. Silently ignores the JavaScript exception if any.
         Node* nextNode(ExceptionCode& ec) { KJS::JSValue* exception; return nextNode(ec, exception); }
diff --git a/WebCore/dom/NodeWithIndex.h b/WebCore/dom/NodeWithIndex.h
new file mode 100644 (file)
index 0000000..9ad6566
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 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. ``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
+ * 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. 
+ */
+
+#ifndef NodeWithIndex_h
+#define NodeWithIndex_h
+
+#include "Node.h"
+
+namespace WebCore {
+
+// For use when you want to get the index for a node repeatedly and
+// only want to walk the child list to figure out the index once.
+class NodeWithIndex {
+public:
+    NodeWithIndex(Node* node)
+        : m_node(node)
+        , m_haveIndex(false)
+    {
+        ASSERT(node);
+    }
+
+    Node* node() const { return m_node; }
+
+    int index() const
+    {
+        if (!m_haveIndex) {
+            m_index = m_node->nodeIndex();
+            m_haveIndex = true;
+        }
+        ASSERT(m_index == static_cast<int>(m_node->nodeIndex()));
+        return m_index;
+    }
+
+private:
+    Node* m_node;
+    mutable bool m_haveIndex;
+    mutable int m_index;
+};
+
+}
+
+#endif
diff --git a/WebCore/dom/NodeWithIndexAfter.h b/WebCore/dom/NodeWithIndexAfter.h
new file mode 100644 (file)
index 0000000..6ce0017
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2008 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. ``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
+ * 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. 
+ */
+
+#ifndef NodeWithIndexAfter_h
+#define NodeWithIndexAfter_h
+
+#include "Node.h"
+
+namespace WebCore {
+
+// Like NodeWithIndex, but treats a node pointer of 0 as meaning
+// "before the last node in the container" and returns the index
+// value after the node rather than the value of the node.
+class NodeWithIndexAfter {
+public:
+    NodeWithIndexAfter(Node* node)
+        : m_node(node)
+        , m_haveIndex(false)
+    {
+    }
+
+    int indexAfter() const
+    {
+        if (!m_haveIndex) {
+            m_indexAfter = m_node ? m_node->nodeIndex() + 1 : 0;
+            m_haveIndex = true;
+        }
+        ASSERT(m_indexAfter == (m_node ? static_cast<int>(m_node->nodeIndex()) + 1 : 0));
+        return m_indexAfter;
+    }
+
+private:
+    Node* m_node;
+    mutable bool m_haveIndex;
+    mutable int m_indexAfter;
+};
+
+}
+
+#endif
diff --git a/WebCore/dom/NodeWithIndexBefore.h b/WebCore/dom/NodeWithIndexBefore.h
new file mode 100644 (file)
index 0000000..bf4a8a2
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008 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. ``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
+ * 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. 
+ */
+
+#ifndef NodeWithIndexBefore_h
+#define NodeWithIndexBefore_h
+
+#include "ContainerNode.h"
+
+namespace WebCore {
+
+// Same as NodeWithIndex, but treats a node pointer of 0 as meaning
+// "after the last node in the container".
+class NodeWithIndexBefore {
+public:
+    NodeWithIndexBefore(ContainerNode* parent, Node* node)
+        : m_parent(parent)
+        , m_node(node)
+        , m_haveIndex(false)
+    {
+        ASSERT(parent);
+        ASSERT(!node || node->parentNode() == parent);
+    }
+
+    ContainerNode* parent() const { return m_parent; }
+
+    int indexBefore() const
+    {
+        if (!m_haveIndex) {
+            m_indexBefore = m_node ? m_node->nodeIndex() : m_parent->childNodeCount();
+            m_haveIndex = true;
+        }
+        ASSERT(m_indexBefore == static_cast<int>(m_node ? m_node->nodeIndex() : m_parent->childNodeCount()));
+        return m_indexBefore;
+    }
+
+private:
+    ContainerNode* m_parent;
+    Node* m_node;
+    mutable bool m_haveIndex;
+    mutable int m_indexBefore;
+};
+
+}
+
+#endif
index 34b00d4..23d509f 100644 (file)
@@ -69,7 +69,10 @@ void ProcessingInstruction::setData(const String& data, ExceptionCode& ec)
         ec = NO_MODIFICATION_ALLOWED_ERR;
         return;
     }
+
+    int oldLength = m_data.length();
     m_data = data;
+    document()->textRemoved(this, 0, oldLength);
 }
 
 String ProcessingInstruction::nodeName() const
index b889503..d14b922 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "config.h"
 #include "Range.h"
+#include "RangeException.h"
 
 #include "CString.h"
 #include "Document.h"
 #include "ExceptionCode.h"
 #include "HTMLElement.h"
 #include "HTMLNames.h"
+#include "NodeWithIndex.h"
+#include "NodeWithIndexAfter.h"
+#include "NodeWithIndexBefore.h"
 #include "ProcessingInstruction.h"
-#include "RangeException.h"
 #include "RenderBlock.h"
 #include "Text.h"
 #include "TextIterator.h"
@@ -69,6 +72,8 @@ inline Range::Range(PassRefPtr<Document> ownerDocument)
 #ifndef NDEBUG
     ++RangeCounter::count;
 #endif
+
+    m_ownerDocument->attachRange(this);
 }
 
 PassRefPtr<Range> Range::create(PassRefPtr<Document> ownerDocument)
@@ -85,6 +90,8 @@ inline Range::Range(PassRefPtr<Document> ownerDocument, PassRefPtr<Node> startCo
     ++RangeCounter::count;
 #endif
 
+    m_ownerDocument->attachRange(this);
+
     // Simply setting the containers and offsets directly would not do any of the checking
     // that setStart and setEnd do, so we call those functions.
     ExceptionCode ec = 0;
@@ -106,6 +113,9 @@ PassRefPtr<Range> Range::create(PassRefPtr<Document> ownerDocument, const Positi
 
 Range::~Range()
 {
+    if (m_start.container)
+        m_ownerDocument->detachRange(this);
+
 #ifndef NDEBUG
     --RangeCounter::count;
 #endif
@@ -158,10 +168,7 @@ Node* Range::commonAncestorContainer(ExceptionCode& ec) const
         return 0;
     }
 
-    Node* commonAncestor = commonAncestorContainer(m_start.container.get(), m_end.container.get());
-    if (!commonAncestor) // should never happen 
-        ec = WRONG_DOCUMENT_ERR; 
-    return commonAncestor;
+    return commonAncestorContainer(m_start.container.get(), m_end.container.get());
 }
 
 Node* Range::commonAncestorContainer(Node* containerA, Node* containerB)
@@ -303,12 +310,8 @@ bool Range::isPointInRange(Node* refNode, int offset, ExceptionCode& ec)
     if (ec)
         return false;
 
-    // point is not before the start and not after the end
-    if ((compareBoundaryPoints(refNode, offset, m_start.container.get(), m_start.offset) != -1) && 
-        (compareBoundaryPoints(refNode, offset, m_end.container.get(), m_end.offset) != 1))
-        return true;
-    else
-        return false;
+    return compareBoundaryPoints(refNode, offset, m_start.container.get(), m_start.offset) >= 0
+        && compareBoundaryPoints(refNode, offset, m_end.container.get(), m_end.offset) <= 0;
 }
 
 short Range::comparePoint(Node* refNode, int offset, ExceptionCode& ec)
@@ -381,7 +384,7 @@ Range::CompareResults Range::compareNode(Node* refNode, ExceptionCode& ec)
     }
 
     Node* parentNode = refNode->parentNode();
-    unsigned nodeIndex = refNode->nodeIndex();
+    int nodeIndex = refNode->nodeIndex();
     
     if (!parentNode) {
         // if the node is the top document we should return NODE_BEFORE_AND_AFTER
@@ -541,7 +544,7 @@ short Range::compareBoundaryPoints(Node* containerA, int offsetA, Node* containe
     }
 
     // Should never reach this point.
-    ASSERT(0);
+    ASSERT_NOT_REACHED();
     return 0;
 }
 
@@ -557,17 +560,11 @@ bool Range::boundaryPointsValid() const
 
 void Range::deleteContents(ExceptionCode& ec)
 {
-    if (!m_start.container) {
-        ec = INVALID_STATE_ERR;
-        return;
-    }
-
-    ec = 0;
     checkDeleteExtract(ec);
     if (ec)
         return;
 
-    processContents(DELETE_CONTENTS,ec);
+    processContents(DELETE_CONTENTS, ec);
 }
 
 bool Range::intersectsNode(Node* refNode, ExceptionCode& ec)
@@ -588,7 +585,7 @@ bool Range::intersectsNode(Node* refNode, ExceptionCode& ec)
     }
 
     Node* parentNode = refNode->parentNode();
-    unsigned nodeIndex = refNode->nodeIndex();
+    int nodeIndex = refNode->nodeIndex();
     
     if (!parentNode) {
         // if the node is the top document we should return NODE_BEFORE_AND_AFTER
@@ -597,23 +594,21 @@ bool Range::intersectsNode(Node* refNode, ExceptionCode& ec)
         return false;
     }
 
-    if (comparePoint(parentNode, nodeIndex, ec) == -1 && // starts before start
-        comparePoint(parentNode, nodeIndex + 1, ec) == -1) { // ends before start
+    if (comparePoint(parentNode, nodeIndex, ec) < 0 && // starts before start
+        comparePoint(parentNode, nodeIndex + 1, ec) < 0) { // ends before start
         return false;
-    } else if (comparePoint(parentNode, nodeIndex, ec) == 1 && // starts after end
-               comparePoint(parentNode, nodeIndex + 1, ec) == 1) { // ends after end
+    } else if (comparePoint(parentNode, nodeIndex, ec) > 0 && // starts after end
+               comparePoint(parentNode, nodeIndex + 1, ec) > 0) { // ends after end
         return false;
     }
     
-    return true;    //all other cases
+    return true; // all other cases
 }
 
-PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, ExceptionCode& ec)
+PassRefPtr<DocumentFragment> Range::processContents(ActionType action, ExceptionCode& ec)
 {
-    // ### when mutation events are implemented, we will have to take into account
-    // situations where the tree is being transformed while we delete - ugh!
-
-    // ### perhaps disable node deletion notification for this range while we do this?
+    // FIXME: To work properly with mutation events, we will have to take into account
+    // situations where the tree is being transformed while we work on it - ugh!
 
     RefPtr<DocumentFragment> fragment;
     if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS)
@@ -649,22 +644,17 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
     // Simple case: the start and end containers are the same. We just grab
     // everything >= start offset and < end offset
     if (m_start.container == m_end.container) {
-        if (m_start.container->nodeType() == Node::TEXT_NODE ||
-            m_start.container->nodeType() == Node::CDATA_SECTION_NODE ||
-            m_start.container->nodeType() == Node::COMMENT_NODE) {
-
+        Node::NodeType startNodeType = m_start.container->nodeType();
+        if (startNodeType == Node::TEXT_NODE || startNodeType == Node::CDATA_SECTION_NODE || startNodeType == Node::COMMENT_NODE) {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
                 RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(m_start.container->cloneNode(true));
                 c->deleteData(m_end.offset, c->length() - m_end.offset, ec);
                 c->deleteData(0, m_start.offset, ec);
                 fragment->appendChild(c.release(), ec);
             }
-            if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) {
-                static_cast<CharacterData*>(m_start.container.get())->deleteData(m_start.offset,m_end.offset-m_start.offset,ec);
-                m_start.container->document()->updateLayout();
-            }
-        }
-        else if (m_start.container->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
+            if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS)
+                static_cast<CharacterData*>(m_start.container.get())->deleteData(m_start.offset, m_end.offset - m_start.offset, ec);
+        } else if (startNodeType == Node::PROCESSING_INSTRUCTION_NODE) {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
                 RefPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(m_start.container->cloneNode(true));
                 c->setData(c->data().substring(m_start.offset, m_end.offset - m_start.offset), ec);
@@ -676,26 +666,24 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
                 data.remove(m_start.offset, m_end.offset - m_start.offset);
                 pi->setData(data, ec);
             }
-        }
-        else {
+        } else {
             Node* n = m_start.container->firstChild();
             int i;
-            for (i = 0; n && i < m_start.offset; i++) // skip until m_start.offset
+            for (i = 0; n && i < m_start.offset; i++) // skip until start offset
                 n = n->nextSibling();
-            while (n && i < m_end.offset) { // delete until m_end.offset
+            int endOffset = m_end.offset;
+            while (n && i < endOffset) { // delete until end offset
                 Node* next = n->nextSibling();
                 if (action == EXTRACT_CONTENTS)
-                    fragment->appendChild(n,ec); // will remove n from it's parent
+                    fragment->appendChild(n, ec); // will remove n from its parent
                 else if (action == CLONE_CONTENTS)
-                    fragment->appendChild(n->cloneNode(true),ec);
+                    fragment->appendChild(n->cloneNode(true), ec);
                 else
-                    m_start.container->removeChild(n,ec);
+                    m_start.container->removeChild(n, ec);
                 n = next;
                 i++;
             }
         }
-        if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS)
-            collapse(true,ec);
         return fragment.release();
     }
 
@@ -719,23 +707,18 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
     RefPtr<Node> leftContents;
     if (m_start.container != commonRoot) {
         // process the left-hand side of the range, up until the last ancestor of
-        // m_start.container before commonRoot
-        if (m_start.container->nodeType() == Node::TEXT_NODE ||
-            m_start.container->nodeType() == Node::CDATA_SECTION_NODE ||
-            m_start.container->nodeType() == Node::COMMENT_NODE) {
-
+        // start container before commonRoot
+        Node::NodeType startNodeType = m_start.container->nodeType();
+        if (startNodeType == Node::TEXT_NODE || startNodeType == Node::CDATA_SECTION_NODE || startNodeType == Node::COMMENT_NODE) {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
                 RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(m_start.container->cloneNode(true));
                 c->deleteData(0, m_start.offset, ec);
                 leftContents = c.release();
             }
-            if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) {
+            if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS)
                 static_cast<CharacterData*>(m_start.container.get())->deleteData(
                     m_start.offset, static_cast<CharacterData*>(m_start.container.get())->length() - m_start.offset, ec);
-                m_start.container->document()->updateLayout();
-            }
-        }
-        else if (m_start.container->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
+        } else if (startNodeType == Node::PROCESSING_INSTRUCTION_NODE) {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
                 RefPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(m_start.container->cloneNode(true));
                 c->setData(c->data().substring(m_start.offset), ec);
@@ -746,21 +729,20 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
                 String data(pi->data());
                 pi->setData(data.left(m_start.offset), ec);
             }
-        }
-        else {
+        } else {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS)
                 leftContents = m_start.container->cloneNode(false);
             Node* n = m_start.container->firstChild();
-            for (int i = 0; n && i < m_start.offset; i++) // skip until m_start.offset
+            for (int i = 0; n && i < m_start.offset; i++) // skip until start offset
                 n = n->nextSibling();
             while (n) { // process until end
                 Node* next = n->nextSibling();
                 if (action == EXTRACT_CONTENTS)
-                    leftContents->appendChild(n,ec); // will remove n from m_start.container
+                    leftContents->appendChild(n, ec); // will remove n from start container
                 else if (action == CLONE_CONTENTS)
-                    leftContents->appendChild(n->cloneNode(true),ec);
+                    leftContents->appendChild(n->cloneNode(true), ec);
                 else
-                    m_start.container->removeChild(n,ec);
+                    m_start.container->removeChild(n, ec);
                 n = next;
             }
         }
@@ -788,25 +770,20 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
         }
     }
 
-    RefPtr<Node> rightContents = 0;
+    RefPtr<Node> rightContents;
     if (m_end.container != commonRoot) {
         // delete the right-hand side of the range, up until the last ancestor of
-        // m_end.container before commonRoot
-        if (m_end.container->nodeType() == Node::TEXT_NODE ||
-            m_end.container->nodeType() == Node::CDATA_SECTION_NODE ||
-            m_end.container->nodeType() == Node::COMMENT_NODE) {
-
+        // end container before commonRoot
+        Node::NodeType endNodeType = m_end.container->nodeType();
+        if (endNodeType == Node::TEXT_NODE || endNodeType == Node::CDATA_SECTION_NODE || endNodeType == Node::COMMENT_NODE) {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
                 RefPtr<CharacterData> c = static_pointer_cast<CharacterData>(m_end.container->cloneNode(true));
                 c->deleteData(m_end.offset, static_cast<CharacterData*>(m_end.container.get())->length() - m_end.offset, ec);
                 rightContents = c;
             }
-            if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) {
+            if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS)
                 static_cast<CharacterData*>(m_end.container.get())->deleteData(0, m_end.offset, ec);
-                m_start.container->document()->updateLayout();
-            }
-        }
-        else if (m_end.container->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
+        } else if (endNodeType == Node::PROCESSING_INSTRUCTION_NODE) {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) {
                 RefPtr<ProcessingInstruction> c = static_pointer_cast<ProcessingInstruction>(m_end.container->cloneNode(true));
                 c->setData(c->data().left(m_end.offset), ec);
@@ -816,13 +793,12 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
                 ProcessingInstruction* pi = static_cast<ProcessingInstruction*>(m_end.container.get());
                 pi->setData(pi->data().substring(m_end.offset), ec);
             }
-        }
-        else {
+        } else {
             if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS)
                 rightContents = m_end.container->cloneNode(false);
             Node* n = m_end.container->firstChild();
             if (n && m_end.offset) {
-                for (int i = 0; i+1 < m_end.offset; i++) { // skip to m_end.offset
+                for (int i = 0; i + 1 < m_end.offset; i++) { // skip to end.offset
                     Node* next = n->nextSibling();
                     if (!next)
                         break;
@@ -832,11 +808,11 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
                 for (; n; n = prev) {
                     prev = n->previousSibling();
                     if (action == EXTRACT_CONTENTS)
-                        rightContents->insertBefore(n,rightContents->firstChild(),ec); // will remove n from it's parent
+                        rightContents->insertBefore(n, rightContents->firstChild(), ec); // will remove n from its parent
                     else if (action == CLONE_CONTENTS)
-                        rightContents->insertBefore(n->cloneNode(true),rightContents->firstChild(),ec);
+                        rightContents->insertBefore(n->cloneNode(true), rightContents->firstChild(), ec);
                     else
-                        m_end.container->removeChild(n,ec);
+                        m_end.container->removeChild(n, ec);
                 }
             }
         }
@@ -849,16 +825,15 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
                 rightContentsParent->appendChild(rightContents,ec);
                 rightContents = rightContentsParent;
             }
-
             Node* prev;
             for (; n; n = prev) {
                 prev = n->previousSibling();
                 if (action == EXTRACT_CONTENTS)
-                    rightContents->insertBefore(n,rightContents->firstChild(),ec); // will remove n from it's parent
+                    rightContents->insertBefore(n, rightContents->firstChild(), ec); // will remove n from its parent
                 else if (action == CLONE_CONTENTS)
-                    rightContents->insertBefore(n->cloneNode(true),rightContents->firstChild(),ec);
+                    rightContents->insertBefore(n->cloneNode(true), rightContents->firstChild(), ec);
                 else
-                    rightParent->removeChild(n,ec);
+                    rightParent->removeChild(n, ec);
             }
             n = rightParent->previousSibling();
         }
@@ -868,12 +843,10 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
 
     Node* processStart; // child of commonRoot
     if (m_start.container == commonRoot) {
-        int i;
         processStart = m_start.container->firstChild();
-        for (i = 0; i < m_start.offset; i++)
+        for (int i = 0; i < m_start.offset; i++)
             processStart = processStart->nextSibling();
-    }
-    else {
+    } else {
         processStart = m_start.container.get();
         while (processStart->parentNode() != commonRoot)
             processStart = processStart->parentNode();
@@ -881,12 +854,10 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
     }
     Node* processEnd; // child of commonRoot
     if (m_end.container == commonRoot) {
-        int i;
         processEnd = m_end.container->firstChild();
-        for (i = 0; i < m_end.offset; i++)
+        for (int i = 0; i < m_end.offset; i++)
             processEnd = processEnd->nextSibling();
-    }
-    else {
+    } else {
         processEnd = m_end.container.get();
         while (processEnd->parentNode() != commonRoot)
             processEnd = processEnd->parentNode();
@@ -896,58 +867,35 @@ PassRefPtr<DocumentFragment> Range::processContents ( ActionType action, Excepti
     // (or just delete the stuff in between)
 
     if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && leftContents)
-      fragment->appendChild(leftContents,ec);
+        fragment->appendChild(leftContents, ec);
 
     Node* next;
     Node* n;
     if (processStart) {
         for (n = processStart; n && n != processEnd; n = next) {
             next = n->nextSibling();
-
             if (action == EXTRACT_CONTENTS)
-                fragment->appendChild(n,ec); // will remove from commonRoot
+                fragment->appendChild(n, ec); // will remove from commonRoot
             else if (action == CLONE_CONTENTS)
-                fragment->appendChild(n->cloneNode(true),ec);
+                fragment->appendChild(n->cloneNode(true), ec);
             else
-                commonRoot->removeChild(n,ec);
+                commonRoot->removeChild(n, ec);
         }
     }
 
     if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && rightContents)
-      fragment->appendChild(rightContents,ec);
-
-    // collapse to the proper position - see spec section 2.6
-    if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) {
-        if (!partialStart && !partialEnd)
-            collapse(true,ec);
-        else if (partialStart) {
-            m_start.container = partialStart->parentNode();
-            m_end.container = partialStart->parentNode();
-            m_start.offset = m_end.offset = partialStart->nodeIndex() + 1;
-        }
-        else if (partialEnd) {
-            m_start.container = partialEnd->parentNode();
-            m_end.container = partialEnd->parentNode();
-            m_start.offset = m_end.offset = partialEnd->nodeIndex();
-        }
-    }
+        fragment->appendChild(rightContents, ec);
+
     return fragment.release();
 }
 
-
 PassRefPtr<DocumentFragment> Range::extractContents(ExceptionCode& ec)
 {
-    if (!m_start.container) {
-        ec = INVALID_STATE_ERR;
-        return 0;
-    }
-
-    ec = 0;
     checkDeleteExtract(ec);
     if (ec)
         return 0;
 
-    return processContents(EXTRACT_CONTENTS,ec);
+    return processContents(EXTRACT_CONTENTS, ec);
 }
 
 PassRefPtr<DocumentFragment> Range::cloneContents(ExceptionCode& ec)
@@ -957,7 +905,7 @@ PassRefPtr<DocumentFragment> Range::cloneContents(ExceptionCode& ec)
         return 0;
     }
 
-    return processContents(CLONE_CONTENTS,ec);
+    return processContents(CLONE_CONTENTS, ec);
 }
 
 void Range::insertNode(PassRefPtr<Node> newNode, ExceptionCode& ec)
@@ -1036,7 +984,7 @@ void Range::insertNode(PassRefPtr<Node> newNode, ExceptionCode& ec)
         return;
     }
 
-    unsigned endOffsetDelta = 0;
+    int endOffsetDelta = 0;
     if (m_start.container->nodeType() == Node::TEXT_NODE ||
         m_start.container->nodeType() == Node::CDATA_SECTION_NODE) {
         RefPtr<Text> newText = static_cast<Text*>(m_start.container.get())->splitText(m_start.offset, ec);
@@ -1070,8 +1018,8 @@ String Range::toString(ExceptionCode& ec) const
 
     Vector<UChar> result;
 
-    Node* pastEnd = pastLastNode();
-    for (Node* n = firstNode(); n != pastEnd; n = n->traverseNextNode()) {
+    Node* pastLast = pastLastNode();
+    for (Node* n = firstNode(); n != pastLast; n = n->traverseNextNode()) {
         if (n->nodeType() == Node::TEXT_NODE || n->nodeType() == Node::CDATA_SECTION_NODE) {
             String data = static_cast<CharacterData*>(n)->data();
             int length = data.length();
@@ -1131,6 +1079,8 @@ void Range::detach(ExceptionCode& ec)
         return;
     }
 
+    m_ownerDocument->detachRange(this);
+
     m_start.clear();
     m_end.clear();
 }
@@ -1533,11 +1483,17 @@ void Range::setStartBefore(Node* refNode, ExceptionCode& ec)
 
 void Range::checkDeleteExtract(ExceptionCode& ec)
 {
+    if (!m_start.container) {
+        ec = INVALID_STATE_ERR;
+        return;
+    }
+
+    ec = 0;
     if (!commonAncestorContainer(ec) || ec)
         return;
         
-    Node* pastEnd = pastLastNode();
-    for (Node* n = firstNode(); n != pastEnd; n = n->traverseNextNode()) {
+    Node* pastLast = pastLastNode();
+    for (Node* n = firstNode(); n != pastLast; n = n->traverseNextNode()) {
         if (n->isReadOnlyNode()) {
             ec = NO_MODIFICATION_ALLOWED_ERR;
             return;
@@ -1715,4 +1671,128 @@ int Range::maxEndOffset() const
     return m_end.container->maxCharacterOffset();
 }
 
+static inline void endpointNodeChildrenChanged(Position& endpoint,
+    NodeWithIndexAfter& beforeChange, NodeWithIndexBefore& afterChange, int childCountDelta)
+{
+    if (endpoint.container != afterChange.parent())
+        return;
+    if (beforeChange.indexAfter() >= endpoint.offset)
+        return;
+    if (childCountDelta < 0 && afterChange.indexBefore() >= endpoint.offset)
+        endpoint.offset = afterChange.indexBefore();
+    else
+        endpoint.offset += childCountDelta;
+}
+
+void Range::nodeChildrenChanged(NodeWithIndexAfter& beforeChange, NodeWithIndexBefore& afterChange, int childCountDelta)
+{
+    ASSERT(afterChange.parent());
+    ASSERT(afterChange.parent()->document() == m_ownerDocument);
+    ASSERT(childCountDelta);
+    endpointNodeChildrenChanged(m_start, beforeChange, afterChange, childCountDelta);
+    endpointNodeChildrenChanged(m_end, beforeChange, afterChange, childCountDelta);
+}
+
+static inline void endpointNodeWillBeRemoved(Position& endpoint, NodeWithIndex& nodeToBeRemoved)
+{
+    for (Node* n = endpoint.container.get(); n; n = n->parentNode()) {
+        if (n == nodeToBeRemoved.node()) {
+            endpoint.container = nodeToBeRemoved.node()->parentNode();
+            endpoint.offset = nodeToBeRemoved.index();
+        }
+    }
+}
+
+void Range::nodeWillBeRemoved(NodeWithIndex& nodeToBeRemoved)
+{
+    ASSERT(nodeToBeRemoved.node());
+    ASSERT(nodeToBeRemoved.node()->document() == m_ownerDocument);
+    ASSERT(nodeToBeRemoved.node() != m_ownerDocument);
+    ASSERT(nodeToBeRemoved.node()->parentNode());
+    endpointNodeWillBeRemoved(m_start, nodeToBeRemoved);
+    endpointNodeWillBeRemoved(m_end, nodeToBeRemoved);
+}
+
+static inline void endpointTextInserted(Position& endpoint, Node* text, unsigned offset, unsigned length)
+{
+    if (endpoint.container != text)
+        return;
+    if (offset >= static_cast<unsigned>(endpoint.offset))
+        return;
+    endpoint.offset += length;
+}
+
+void Range::textInserted(Node* text, unsigned offset, unsigned length)
+{
+    ASSERT(text);
+    ASSERT(text->document() == m_ownerDocument);
+    endpointTextInserted(m_start, text, offset, length);
+    endpointTextInserted(m_end, text, offset, length);
+}
+
+static inline void endpointTextRemoved(Position& endpoint, Node* text, unsigned offset, unsigned length)
+{
+    if (endpoint.container != text)
+        return;
+    if (offset >= static_cast<unsigned>(endpoint.offset))
+        return;
+    if (offset + length >= static_cast<unsigned>(endpoint.offset))
+        endpoint.offset = offset;
+    else
+        endpoint.offset -= length;
+}
+
+void Range::textRemoved(Node* text, unsigned offset, unsigned length)
+{
+    ASSERT(text);
+    ASSERT(text->document() == m_ownerDocument);
+    endpointTextRemoved(m_start, text, offset, length);
+    endpointTextRemoved(m_end, text, offset, length);
+}
+
+static inline void endpointTextNodesMerged(Position& endpoint, NodeWithIndex& oldNode, unsigned offset)
+{
+    if (endpoint.container == oldNode.node()) {
+        endpoint.container = oldNode.node()->previousSibling();
+        endpoint.offset += offset;
+    } else if (endpoint.container == oldNode.node()->parentNode() && endpoint.offset == oldNode.index()) {
+        endpoint.container = oldNode.node()->previousSibling();
+        endpoint.offset = offset;
+    }
+}
+
+void Range::textNodesMerged(NodeWithIndex& oldNode, unsigned offset)
+{
+    ASSERT(oldNode.node());
+    ASSERT(oldNode.node()->document() == m_ownerDocument);
+    ASSERT(oldNode.node()->parentNode());
+    ASSERT(oldNode.node()->isTextNode());
+    ASSERT(oldNode.node()->previousSibling());
+    ASSERT(oldNode.node()->previousSibling()->isTextNode());
+    endpointTextNodesMerged(m_start, oldNode, offset);
+    endpointTextNodesMerged(m_end, oldNode, offset);
+}
+
+static inline void endpointTextNodesSplit(Position& endpoint, Text* oldNode)
+{
+    if (endpoint.container != oldNode)
+        return;
+    if (endpoint.offset <= static_cast<int>(oldNode->length()))
+        return;
+    endpoint.container = oldNode->nextSibling();
+    endpoint.offset -= oldNode->length();
+}
+
+void Range::textNodeSplit(Text* oldNode)
+{
+    ASSERT(oldNode);
+    ASSERT(oldNode->document() == m_ownerDocument);
+    ASSERT(oldNode->parentNode());
+    ASSERT(oldNode->isTextNode());
+    ASSERT(oldNode->nextSibling());
+    ASSERT(oldNode->nextSibling()->isTextNode());
+    endpointTextNodesSplit(m_start, oldNode);
+    endpointTextNodesSplit(m_end, oldNode);
+}
+
 }
index 302f8f9..f2342be 100644 (file)
@@ -35,10 +35,12 @@ typedef int ExceptionCode;
 
 class DocumentFragment;
 class Document;
+class NodeWithIndex;
+class NodeWithIndexAfter;
+class NodeWithIndexBefore;
 class IntRect;
-class Node;
-class Position;
 class String;
+class Text;
 
 class Range : public RefCounted<Range> {
 public:
@@ -107,6 +109,14 @@ public:
     IntRect boundingBox();
     void addLineBoxRects(Vector<IntRect>&, bool useSelectionHeight = false);
 
+    void nodeChildrenChanged(NodeWithIndexAfter& beforeChange, NodeWithIndexBefore& afterChange, int childCountDelta);
+    void nodeWillBeRemoved(NodeWithIndex&);
+
+    void textInserted(Node*, unsigned offset, unsigned length);
+    void textRemoved(Node*, unsigned offset, unsigned length);
+    void textNodesMerged(NodeWithIndex& oldNode, unsigned offset);
+    void textNodeSplit(Text* oldNode);
+
 #ifndef NDEBUG
     void formatForDebugger(char* buffer, unsigned length) const;
 #endif
index d61214f..1b65876 100644 (file)
@@ -36,15 +36,13 @@ namespace WebCore {
 
 // DOM Section 1.1.1
 
-// ### allow having children in text nodes for entities, comments etc.
-
-Text::Text(Document *doc, const String &_text)
-    : CharacterData(doc, _text)
+Text::Text(Document* document, const String& text)
+    : CharacterData(document, text)
 {
 }
 
-Text::Text(Document *doc)
-    : CharacterData(doc)
+Text::Text(Document* document)
+    : CharacterData(document)
 {
 }
 
@@ -56,8 +54,6 @@ PassRefPtr<Text> Text::splitText(unsigned offset, ExceptionCode& ec)
 {
     ec = 0;
 
-    // FIXME: This does not copy markers
-    
     // INDEX_SIZE_ERR: Raised if the specified offset is negative or greater than
     // the number of 16-bit units in data.
     if (offset > m_data->length()) {
@@ -82,6 +78,9 @@ PassRefPtr<Text> Text::splitText(unsigned offset, ExceptionCode& ec)
     if (ec)
         return 0;
 
+    if (parentNode())
+        document()->textNodeSplit(this);
+
     if (renderer())
         static_cast<RenderText*>(renderer())->setText(m_data);
 
@@ -252,11 +251,12 @@ void Text::attach()
     CharacterData::attach();
 }
 
-void Text::recalcStyle( StyleChange change )
+void Text::recalcStyle(StyleChange change)
 {
-    if (change != NoChange && parentNode())
+    if (change != NoChange && parentNode()) {
         if (renderer())
             renderer()->setStyle(parentNode()->renderer()->style());
+    }
     if (changed() && renderer() && renderer()->isText())
         static_cast<RenderText*>(renderer())->setText(m_data);
     setChanged(NoStyleChange);
@@ -275,7 +275,6 @@ PassRefPtr<Text> Text::createNew(PassRefPtr<StringImpl> string)
 
 String Text::toString() const
 {
-    // FIXME: substitute entity references as needed!
     return nodeValue();
 }
 
index 70e412d..23dcc88 100644 (file)
@@ -656,8 +656,8 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
         if (start == end && start.node()->hasTagName(brTag))
             end = positionAfterNode(start.node());
         // Add the style to selected inline runs.
-        Node* pastEnd = Range::create(document(), rangeCompliantEquivalent(start), rangeCompliantEquivalent(end))->pastLastNode();
-        for (Node* next; node && node != pastEnd; node = next) {
+        Node* pastLast = Range::create(document(), rangeCompliantEquivalent(start), rangeCompliantEquivalent(end))->pastLastNode();
+        for (Node* next; node && node != pastLast; node = next) {
             
             next = node->traverseNextNode();
             
@@ -691,7 +691,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
             Node* runStart = node;
             // Find the end of the run.
             Node* sibling = node->nextSibling();
-            while (sibling && sibling != pastEnd && (!sibling->isElementNode() || sibling->hasTagName(brTag)) && !isBlock(sibling)) {
+            while (sibling && sibling != pastLast && (!sibling->isElementNode() || sibling->hasTagName(brTag)) && !isBlock(sibling)) {
                 node = sibling;
                 sibling = node->nextSibling();
             }
index 0d4502a..7a47693 100755 (executable)
@@ -68,6 +68,9 @@ sub wanted
 }
 
 my %renames = (
+    "DELETE_CONTENTS" => "Delete",
+    "EXTRACT_CONTENTS" => "Extract",
+    "CLONE_CONTENTS" => "Clone",
     "XSLTProcessorPrototypeTable" => "JSXSLTProcessorPrototypeTable",
     "ActivationImp" => "Activation",
     "ActivationImpData" => "ActivationData",