Reviewed by darin.
authoreric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Feb 2008 09:04:22 +0000 (09:04 +0000)
committereric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Feb 2008 09:04:22 +0000 (09:04 +0000)
        Add support for Text.wholeText and Text.replaceWholeText
        http://bugs.webkit.org/show_bug.cgi?id=17125

        Test EntityReferences to make sure they're always treated as read-only
        In doing so I discovered a bug in document.adoptNode(readonlyNode) (and fixed it)

        * dom/Document.cpp:
        (WebCore::Document::adoptNode): throw NO_MODIFICATION_ALLOWED_ERR when passed a readonly node
        * dom/Node.cpp:
        * dom/Node.cpp:
        (WebCore::Node::textContent):
        * dom/Text.cpp:
        (WebCore::earliestLogicallyAdjacentTextNode):
        (WebCore::latestLogicallyAdjacentTextNode):
        (WebCore::Text::wholeText):
        (WebCore::Text::replaceWholeText):
        * dom/Text.h:
        * dom/Text.idl:

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

26 files changed:
LayoutTests/ChangeLog
LayoutTests/dom/xhtml/level3/core/textreplacewholetext01-expected.txt
LayoutTests/dom/xhtml/level3/core/textreplacewholetext02-expected.txt
LayoutTests/dom/xhtml/level3/core/textreplacewholetext03-expected.txt
LayoutTests/dom/xhtml/level3/core/textreplacewholetext04-expected.txt
LayoutTests/dom/xhtml/level3/core/textreplacewholetext05-expected.txt
LayoutTests/dom/xhtml/level3/core/textreplacewholetext06-expected.txt
LayoutTests/dom/xhtml/level3/core/textreplacewholetext07-expected.txt
LayoutTests/dom/xhtml/level3/core/textwholetext01-expected.txt
LayoutTests/dom/xhtml/level3/core/textwholetext02-expected.txt
LayoutTests/dom/xhtml/level3/core/textwholetext03-expected.txt
LayoutTests/fast/dom/EntityReference/readonly-exceptions-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/EntityReference/readonly-exceptions.html [new file with mode: 0644]
LayoutTests/fast/dom/EntityReference/resources/TEMPLATE.html [new file with mode: 0644]
LayoutTests/fast/dom/EntityReference/resources/readonly-exceptions.js [new file with mode: 0644]
LayoutTests/fast/dom/Text/replaceWholeText-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/Text/replaceWholeText.html [new file with mode: 0644]
LayoutTests/fast/dom/Text/resources/TEMPLATE.html [new file with mode: 0644]
LayoutTests/fast/dom/Text/resources/replaceWholeText.js [new file with mode: 0644]
LayoutTests/fast/dom/Window/window-properties-expected.txt
WebCore/ChangeLog
WebCore/dom/Document.cpp
WebCore/dom/Node.cpp
WebCore/dom/Text.cpp
WebCore/dom/Text.h
WebCore/dom/Text.idl

index 533f7f1..ac8c1f4 100644 (file)
@@ -1,3 +1,36 @@
+2008-02-08  Eric Seidel  <eric@webkit.org>
+
+        Reviewed by darin.
+
+        Add support for Text.wholeText and Text.replaceWholeText from DOM Level 3 Core.
+        http://bugs.webkit.org/show_bug.cgi?id=17125
+        
+        Test to make sure EntityReference nodes are always treated as read-only.
+
+        Added fast/dom/Text/replaceWholeText.html to catch a case the W3C missed.
+
+        All of these tests now pass.
+
+        * dom/xhtml/level3/core/textreplacewholetext01-expected.txt:
+        * dom/xhtml/level3/core/textreplacewholetext02-expected.txt:
+        * dom/xhtml/level3/core/textreplacewholetext03-expected.txt:
+        * dom/xhtml/level3/core/textreplacewholetext04-expected.txt:
+        * dom/xhtml/level3/core/textreplacewholetext05-expected.txt:
+        * dom/xhtml/level3/core/textreplacewholetext06-expected.txt:
+        * dom/xhtml/level3/core/textreplacewholetext07-expected.txt:
+        * dom/xhtml/level3/core/textwholetext01-expected.txt:
+        * dom/xhtml/level3/core/textwholetext02-expected.txt:
+        * dom/xhtml/level3/core/textwholetext03-expected.txt:
+        * fast/dom/EntityReference/readonly-exceptions-expected.txt: Added.
+        * fast/dom/EntityReference/readonly-exceptions.html: Added.
+        * fast/dom/EntityReference/resources/TEMPLATE.html: Added.
+        * fast/dom/EntityReference/resources/readonly-exceptions.js: Added.
+        * fast/dom/Text/replaceWholeText-expected.txt: Added.
+        * fast/dom/Text/replaceWholeText.html: Added.
+        * fast/dom/Text/resources/TEMPLATE.html: Added.
+        * fast/dom/Text/resources/replaceWholeText.js: Added.
+        * fast/dom/Window/window-properties-expected.txt: updated to reflect addition of replaceWholeText
+
 2008-02-06  Kimmo Kinnunen  <kimmok@iki.fi>
 
         Reviewed by Tim Hatcher.
index 18d0ebe..c233a30 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textreplacewholetext01
-Status error
-Message        TypeError: Value undefined (result of expression textNode.replaceWholeText) is not object.
+Status Success
index d5b936a..f94941d 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textreplacewholetext02
-Status error
-Message        TypeError: Value undefined (result of expression textNode.replaceWholeText) is not object.
+Status Success
index 0ff46f8..0e9c27a 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textreplacewholetext03
-Status error
-Message        TypeError: Value undefined (result of expression textNode.replaceWholeText) is not object.
+Status Success
index d580e77..c6b130b 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textreplacewholetext04
-Status error
-Message        TypeError: Value undefined (result of expression textNode.replaceWholeText) is not object.
+Status Success
index 5c0474e..b9b2cae 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textreplacewholetext05
-Status error
-Message        TypeError: Value undefined (result of expression textNode.replaceWholeText) is not object.
+Status Success
index 96686bd..925a380 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textreplacewholetext06
-Status error
-Message        TypeError: Value undefined (result of expression textNode.replaceWholeText) is not object.
+Status Success
index c0122e4..1075ecb 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textreplacewholetext07
-Status error
-Message        TypeError: Value undefined (result of expression textNode.replaceWholeText) is not object.
+Status Success
index 7c1bf5c..f3daa4d 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textwholetext01
-Status failure
-Message        textwholetext01: assertEquals failed, actual null, expected Margaret Martin.
+Status Success
index 6236883..abf9396 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textwholetext02
-Status failure
-Message        textwholetext02: assertEquals failed, actual null, expected Margaret MartinNew Text.
+Status Success
index b099d94..ae68f7c 100644 (file)
@@ -1,3 +1,2 @@
 Test   http://www.w3.org/2001/DOM-Test-Suite/level3/core/textwholetext03
-Status failure
-Message        textwholetext03: assertEquals failed, actual null, expected Text I Text II.
+Status Success
diff --git a/LayoutTests/fast/dom/EntityReference/readonly-exceptions-expected.txt b/LayoutTests/fast/dom/EntityReference/readonly-exceptions-expected.txt
new file mode 100644 (file)
index 0000000..41cb36d
--- /dev/null
@@ -0,0 +1,23 @@
+Test to make sure EntityReference nodes are always treated readonly
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS xmlDoc2.adoptNode(entityReference) threw exception Error: NO_MODIFICATION_ALLOWED_ERR: DOM Exception 7.
+PASS entityReference.ownerDocument is xmlDoc
+PASS entityReference.nodeValue = 'foo' threw exception Error: NO_MODIFICATION_ALLOWED_ERR: DOM Exception 7.
+PASS entityReference.nodeValue is null
+PASS entityReference.prefix = 'foo' threw exception Error: NAMESPACE_ERR: DOM Exception 14.
+PASS entityReference.prefix is null
+PASS entityReference.textContent = 'foo' threw exception Error: NO_MODIFICATION_ALLOWED_ERR: DOM Exception 7.
+FAIL entityReference.textContent should be >. Was .
+FAIL childrenBeforeFailedAppend should be 1. Was 0.
+PASS entityReference.appendChild(text) threw exception Error: NO_MODIFICATION_ALLOWED_ERR: DOM Exception 7.
+PASS entityReference.childNodes.length is childrenBeforeFailedAppend
+FAIL childrenBeforeFailedAppend should be 1. Was 0.
+PASS entityReference.insertBefore(text, entityReference.firstChild) threw exception Error: NO_MODIFICATION_ALLOWED_ERR: DOM Exception 7.
+PASS entityReference.childNodes.length is childrenBeforeFailedAppend
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/EntityReference/readonly-exceptions.html b/LayoutTests/fast/dom/EntityReference/readonly-exceptions.html
new file mode 100644 (file)
index 0000000..7c99bbc
--- /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/readonly-exceptions.js"></script>
+<script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/dom/EntityReference/resources/TEMPLATE.html b/LayoutTests/fast/dom/EntityReference/resources/TEMPLATE.html
new file mode 100644 (file)
index 0000000..1951c43
--- /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="YOUR_JS_FILE_HERE"></script>
+<script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/dom/EntityReference/resources/readonly-exceptions.js b/LayoutTests/fast/dom/EntityReference/resources/readonly-exceptions.js
new file mode 100644 (file)
index 0000000..e978728
--- /dev/null
@@ -0,0 +1,33 @@
+description("Test to make sure EntityReference nodes are always treated readonly")
+
+var xmlDoc = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null);
+var xmlDoc2 = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", null);
+var entityReference = xmlDoc.createEntityReference("gt");
+
+shouldThrow("xmlDoc2.adoptNode(entityReference)");
+shouldBe("entityReference.ownerDocument", "xmlDoc")
+
+// nodeValue is defined to be null for Entity Reference nodes, and thus should silently fail to modify
+// Spec is ambigious as to if we should throw here or not.  I've requested clarification:
+// http://lists.w3.org/Archives/Public/www-dom/2008JanMar/0009.html
+shouldThrow("entityReference.nodeValue = 'foo'");
+shouldBe("entityReference.nodeValue", "null");
+
+shouldThrow("entityReference.prefix = 'foo'");
+shouldBe("entityReference.prefix", "null");
+
+shouldThrow("entityReference.textContent = 'foo'");
+shouldBe("entityReference.textContent", "'>'");
+
+var childrenBeforeFailedAppend = entityReference.childNodes.length;
+shouldBe("childrenBeforeFailedAppend", "1");
+var text = document.createTextNode("FAIL");
+shouldThrow("entityReference.appendChild(text)");
+shouldBe("entityReference.childNodes.length", "childrenBeforeFailedAppend");
+
+childrenBeforeFailedAppend = entityReference.childNodes.length;
+shouldBe("childrenBeforeFailedAppend", "1");
+shouldThrow("entityReference.insertBefore(text, entityReference.firstChild)");
+shouldBe("entityReference.childNodes.length", "childrenBeforeFailedAppend");
+
+var successfullyParsed = true;
diff --git a/LayoutTests/fast/dom/Text/replaceWholeText-expected.txt b/LayoutTests/fast/dom/Text/replaceWholeText-expected.txt
new file mode 100644 (file)
index 0000000..8301ec6
--- /dev/null
@@ -0,0 +1,13 @@
+Test wholeText and replaceWholeText
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS textB.wholeText is 'AB'
+PASS para.textContent is 'ABC'
+PASS textB.wholeText is 'XYZ'
+PASS para.textContent is 'XYZC'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/Text/replaceWholeText.html b/LayoutTests/fast/dom/Text/replaceWholeText.html
new file mode 100644 (file)
index 0000000..9b397db
--- /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/replaceWholeText.js"></script>
+<script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/dom/Text/resources/TEMPLATE.html b/LayoutTests/fast/dom/Text/resources/TEMPLATE.html
new file mode 100644 (file)
index 0000000..1951c43
--- /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="YOUR_JS_FILE_HERE"></script>
+<script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/dom/Text/resources/replaceWholeText.js b/LayoutTests/fast/dom/Text/resources/replaceWholeText.js
new file mode 100644 (file)
index 0000000..33b6dc8
--- /dev/null
@@ -0,0 +1,16 @@
+description("Test wholeText and replaceWholeText")
+
+var para = document.createElement('p');
+para.appendChild(document.createTextNode('A'));
+var textB = document.createTextNode('B');
+para.appendChild(textB);
+para.appendChild(document.createElement('p'));
+para.appendChild(document.createTextNode('C'));
+
+shouldBe("textB.wholeText", "'AB'");
+shouldBe("para.textContent", "'ABC'");
+textB.replaceWholeText("XYZ");
+shouldBe("textB.wholeText", "'XYZ'");
+shouldBe("para.textContent", "'XYZC'");
+
+var successfullyParsed = true;
index b4935b9..b38609d 100644 (file)
@@ -68,6 +68,7 @@ window.CDATASection.prototype.removeChild [function]
 window.CDATASection.prototype.removeEventListener [function]
 window.CDATASection.prototype.replaceChild [function]
 window.CDATASection.prototype.replaceData [function]
+window.CDATASection.prototype.replaceWholeText [function]
 window.CDATASection.prototype.splitText [function]
 window.CDATASection.prototype.substringData [function]
 window.CSSCharsetRule [object CSSCharsetRuleConstructor]
index 49bef09..aabb976 100644 (file)
@@ -1,3 +1,26 @@
+2008-02-08  Eric Seidel  <eric@webkit.org>
+
+        Reviewed by darin.
+
+        Add support for Text.wholeText and Text.replaceWholeText
+        http://bugs.webkit.org/show_bug.cgi?id=17125
+
+        Test EntityReferences to make sure they're always treated as read-only
+        In doing so I discovered a bug in document.adoptNode(readonlyNode) (and fixed it)
+
+        * dom/Document.cpp:
+        (WebCore::Document::adoptNode): throw NO_MODIFICATION_ALLOWED_ERR when passed a readonly node
+        * dom/Node.cpp:
+        * dom/Node.cpp:
+        (WebCore::Node::textContent):
+        * dom/Text.cpp:
+        (WebCore::earliestLogicallyAdjacentTextNode):
+        (WebCore::latestLogicallyAdjacentTextNode):
+        (WebCore::Text::wholeText):
+        (WebCore::Text::replaceWholeText):
+        * dom/Text.h:
+        * dom/Text.idl:
+
 2008-02-06  Kimmo Kinnunen  <kimmok@iki.fi>
 
         Reviewed by Tim Hatcher.
index ca9990d..4617133 100644 (file)
@@ -672,7 +672,12 @@ PassRefPtr<Node> Document::adoptNode(PassRefPtr<Node> source, ExceptionCode& ec)
         ec = NOT_SUPPORTED_ERR;
         return 0;
     }
-    
+
+    if (source->isReadOnlyNode()) {
+        ec = NO_MODIFICATION_ALLOWED_ERR;
+        return 0;
+    }
+
     switch (source->nodeType()) {
         case ENTITY_NODE:
         case NOTATION_NODE:
index 4b92678..e53d30f 100644 (file)
@@ -1536,7 +1536,7 @@ String Node::textContent(bool convertBRsToNewlines) const
         case DOCUMENT_TYPE_NODE:
         case NOTATION_NODE:
         default:
-            return String();            
+            return String();
     }
 }
 
index 8483c10..5794833 100644 (file)
@@ -87,6 +87,97 @@ PassRefPtr<Text> Text::splitText(unsigned offset, ExceptionCode& ec)
     return newText.release();
 }
 
+static const Text* earliestLogicallyAdjacentTextNode(const Text* t)
+{
+    const Node* n = t;
+    while ((n = n->previousSibling())) {
+        Node::NodeType type = n->nodeType();
+        if (type == Node::TEXT_NODE || type == Node::CDATA_SECTION_NODE) {
+            t = static_cast<const Text*>(n);
+            continue;
+        }
+
+        // We would need to visit EntityReference child text nodes if they existed
+        ASSERT(type != Node::ENTITY_REFERENCE_NODE || !n->hasChildNodes());
+        break;
+    }
+    return t;
+}
+
+static const Text* latestLogicallyAdjacentTextNode(const Text* t)
+{
+    const Node* n = t;
+    while ((n = n->nextSibling())) {
+        Node::NodeType type = n->nodeType();
+        if (type == Node::TEXT_NODE || type == Node::CDATA_SECTION_NODE) {
+            t = static_cast<const Text*>(n);
+            continue;
+        }
+
+        // We would need to visit EntityReference child text nodes if they existed
+        ASSERT(type != Node::ENTITY_REFERENCE_NODE || !n->hasChildNodes());
+        break;
+    }
+    return t;
+}
+
+String Text::wholeText() const
+{
+    const Text* startText = earliestLogicallyAdjacentTextNode(this);
+    const Text* endText = latestLogicallyAdjacentTextNode(this);
+
+    Vector<UChar> result;
+    Node* onePastEndText = endText->nextSibling();
+    for (const Node* n = startText; n != onePastEndText; n = n->nextSibling()) {
+        if (!n->isTextNode())
+            continue;
+        const Text* t = static_cast<const Text*>(n);
+        const String& data = t->data();
+        result.append(data.characters(), data.length());
+    }
+
+    return String::adopt(result);
+}
+
+PassRefPtr<Text> Text::replaceWholeText(const String& newText, ExceptionCode&)
+{
+    // We don't support "read-only" text nodes (no Entity node support)
+    // Thus, we remove all adjacent text nodes, and replace the contents of this one.
+    ASSERT(!isReadOnlyNode());
+    // This method only raises exceptions when dealing with Entity nodes (which we don't support)
+
+    // Protect startText and endText against mutation event handlers removing the last ref
+    RefPtr<Text> startText = const_cast<Text*>(earliestLogicallyAdjacentTextNode(this));
+    RefPtr<Text> endText = const_cast<Text*>(latestLogicallyAdjacentTextNode(this));
+
+    RefPtr<Text> protectedThis(this); // Mutation event handlers could cause our last ref to go away
+    Node* parent = parentNode(); // Protect against mutation handlers moving this node during traversal
+    ExceptionCode ignored = 0;
+    for (RefPtr<Node> n = startText; n && n != this && n->isTextNode() && n->parentNode() == parent;) {
+        RefPtr<Node> nodeToRemove(n.release());
+        n = nodeToRemove->nextSibling();
+        parent->removeChild(nodeToRemove.get(), ignored);
+    }
+
+    if (this != endText) {
+        Node* onePastEndText = endText->nextSibling();
+        for (RefPtr<Node> n = nextSibling(); n && n != onePastEndText && n->isTextNode() && n->parentNode() == parent;) {
+            RefPtr<Node> nodeToRemove(n.release());
+            n = nodeToRemove->nextSibling();
+            parent->removeChild(nodeToRemove.get(), ignored);
+        }
+    }
+
+    if (newText.isEmpty()) {
+        if (parent && parentNode() == parent)
+            parent->removeChild(this, ignored);
+        return 0;
+    }
+
+    setData(newText, ignored);
+    return protectedThis.release();
+}
+
 String Text::nodeName() const
 {
     return textAtom.domString();
index 20ea54a..433d3e6 100644 (file)
@@ -39,6 +39,10 @@ public:
 
     PassRefPtr<Text> splitText(unsigned offset, ExceptionCode&);
 
+    // DOM Level 3: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1312295772
+    String wholeText() const;
+    PassRefPtr<Text> replaceWholeText(const String&, ExceptionCode&);
+
     // DOM methods overridden from parent classes
 
     virtual String nodeName() const;
index c0426ea..1b0009d 100644 (file)
@@ -30,6 +30,10 @@ module core {
         Text splitText(in [IsIndex] unsigned long offset)
             raises (DOMException);
 
+        // Introduced in DOM Level 3:
+        readonly attribute DOMString       wholeText;
+        Text               replaceWholeText(in DOMString content)
+                                            raises(DOMException);
     };
 
 }