Bug 454325 - Range.extractContents doesn't clone partially selected nodes, r+sr=sicking
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sat, 11 Oct 2008 22:46:05 +0300
changeset 20321 d3266dafb723af1fab60f33e8cd641cf42c1273c
parent 20320 57a08a2acc5186cb775e8a1396841979a6183d6f
child 20322 ef4464dc1bd7c4b2de82c8bc67cbcb341acdf60d
push id2804
push useropettay@mozilla.com
push dateSat, 11 Oct 2008 19:53:02 +0000
treeherdermozilla-central@d3266dafb723 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs454325
milestone1.9.1b2pre
Bug 454325 - Range.extractContents doesn't clone partially selected nodes, r+sr=sicking
content/base/src/nsRange.cpp
content/base/src/nsRange.h
content/base/test/Makefile.in
content/base/test/test_bug454325.html
--- a/content/base/src/nsRange.cpp
+++ b/content/base/src/nsRange.cpp
@@ -775,40 +775,41 @@ nsresult nsRange::SelectNodeContents(nsI
   
   DoSetRange(node, 0, node, GetNodeLength(node), newRoot);
   
   return NS_OK;
 }
 
 // The Subtree Content Iterator only returns subtrees that are
 // completely within a given range. It doesn't return a CharacterData
-// node that contains either the start or end point of the range.
+// node that contains either the start or end point of the range.,
+// nor does it return element nodes when nothing in the element is selected.
 // We need an iterator that will also include these start/end points
 // so that our methods/algorithms aren't cluttered with special
 // case code that tries to include these points while iterating.
 //
 // The RangeSubtreeIterator class mimics the nsIContentIterator
 // methods we need, so should the Content Iterator support the
 // start/end points in the future, we can switchover relatively
 // easy.
 
 class NS_STACK_CLASS RangeSubtreeIterator
 {
 private:
 
   enum RangeSubtreeIterState { eDone=0,
-                               eUseStartCData,
+                               eUseStart,
                                eUseIterator,
-                               eUseEndCData };
+                               eUseEnd };
 
   nsCOMPtr<nsIContentIterator>  mIter;
   RangeSubtreeIterState         mIterState;
 
-  nsCOMPtr<nsIDOMCharacterData> mStartCData;
-  nsCOMPtr<nsIDOMCharacterData> mEndCData;
+  nsCOMPtr<nsIDOMNode> mStart;
+  nsCOMPtr<nsIDOMNode> mEnd;
 
 public:
 
   RangeSubtreeIterator()
     : mIterState(eDone)
   {
   }
   ~RangeSubtreeIterator()
@@ -827,44 +828,70 @@ public:
     return mIterState == eDone;
   }
 };
 
 nsresult
 RangeSubtreeIterator::Init(nsIDOMRange *aRange)
 {
   mIterState = eDone;
+  PRBool collapsed;
+  aRange->GetCollapsed(&collapsed);
+  if (collapsed) {
+    return NS_OK;
+  }
 
   nsCOMPtr<nsIDOMNode> node;
 
   // Grab the start point of the range and QI it to
   // a CharacterData pointer. If it is CharacterData store
   // a pointer to the node.
 
   nsresult res = aRange->GetStartContainer(getter_AddRefs(node));
   if (!node) return NS_ERROR_FAILURE;
 
-  mStartCData = do_QueryInterface(node);
+  nsCOMPtr<nsIDOMCharacterData> startData = do_QueryInterface(node);
+  if (startData) {
+    mStart = node;
+  } else {
+    PRInt32 startIndex;
+    aRange->GetStartOffset(&startIndex);
+    nsCOMPtr<nsINode> iNode = do_QueryInterface(node);
+    if (iNode->IsNodeOfType(nsINode::eELEMENT) && 
+        PRInt32(iNode->GetChildCount()) == startIndex) {
+      mStart = node;
+    }
+  }
 
   // Grab the end point of the range and QI it to
   // a CharacterData pointer. If it is CharacterData store
   // a pointer to the node.
 
   res = aRange->GetEndContainer(getter_AddRefs(node));
   if (!node) return NS_ERROR_FAILURE;
 
-  mEndCData = do_QueryInterface(node);
+  nsCOMPtr<nsIDOMCharacterData> endData = do_QueryInterface(node);
+  if (endData) {
+    mEnd = node;
+  } else {
+    PRInt32 endIndex;
+    aRange->GetEndOffset(&endIndex);
+    nsCOMPtr<nsINode> iNode = do_QueryInterface(node);
+    if (iNode->IsNodeOfType(nsINode::eELEMENT) && endIndex == 0) {
+      mEnd = node;
+    }
+  }
 
-  if (mStartCData && mStartCData == mEndCData)
+  if (mStart && mStart == mEnd)
   {
     // The range starts and stops in the same CharacterData
     // node. Null out the end pointer so we only visit the
     // node once!
 
-    mEndCData = nsnull;
+    mEnd = nsnull;
   }
   else
   {
     // Now create a Content Subtree Iterator to be used
     // for the subtrees between the end points!
 
     res = NS_NewContentSubtreeIterator(getter_AddRefs(mIter));
     if (NS_FAILED(res)) return res;
@@ -890,122 +917,122 @@ RangeSubtreeIterator::Init(nsIDOMRange *
   return NS_OK;
 }
 
 already_AddRefed<nsIDOMNode>
 RangeSubtreeIterator::GetCurrentNode()
 {
   nsIDOMNode *node = nsnull;
 
-  if (mIterState == eUseStartCData && mStartCData) {
-    NS_ADDREF(node = mStartCData);
-  } else if (mIterState == eUseEndCData && mEndCData)
-    NS_ADDREF(node = mEndCData);
+  if (mIterState == eUseStart && mStart) {
+    NS_ADDREF(node = mStart);
+  } else if (mIterState == eUseEnd && mEnd)
+    NS_ADDREF(node = mEnd);
   else if (mIterState == eUseIterator && mIter)
   {
     nsIContent *content = mIter->GetCurrentNode();
 
     if (content) {
       CallQueryInterface(content, &node);
     }
   }
 
   return node;
 }
 
 void
 RangeSubtreeIterator::First()
 {
-  if (mStartCData)
-    mIterState = eUseStartCData;
+  if (mStart)
+    mIterState = eUseStart;
   else if (mIter)
   {
     mIter->First();
 
     mIterState = eUseIterator;
   }
-  else if (mEndCData)
-    mIterState = eUseEndCData;
+  else if (mEnd)
+    mIterState = eUseEnd;
   else
     mIterState = eDone;
 }
 
 void
 RangeSubtreeIterator::Last()
 {
-  if (mEndCData)
-    mIterState = eUseEndCData;
+  if (mEnd)
+    mIterState = eUseEnd;
   else if (mIter)
   {
     mIter->Last();
 
     mIterState = eUseIterator;
   }
-  else if (mStartCData)
-    mIterState = eUseStartCData;
+  else if (mStart)
+    mIterState = eUseStart;
   else
     mIterState = eDone;
 }
 
 void
 RangeSubtreeIterator::Next()
 {
-  if (mIterState == eUseStartCData)
+  if (mIterState == eUseStart)
   {
     if (mIter)
     {
       mIter->First();
 
       mIterState = eUseIterator;
     }
-    else if (mEndCData)
-      mIterState = eUseEndCData;
+    else if (mEnd)
+      mIterState = eUseEnd;
     else
       mIterState = eDone;
   }
   else if (mIterState == eUseIterator)
   {
     mIter->Next();
 
     if (mIter->IsDone())
     {
-      if (mEndCData)
-        mIterState = eUseEndCData;
+      if (mEnd)
+        mIterState = eUseEnd;
       else
         mIterState = eDone;
     }
   }
   else
     mIterState = eDone;
 }
 
 void
 RangeSubtreeIterator::Prev()
 {
-  if (mIterState == eUseEndCData)
+  if (mIterState == eUseEnd)
   {
     if (mIter)
     {
       mIter->Last();
 
       mIterState = eUseIterator;
     }
-    else if (mStartCData)
-      mIterState = eUseStartCData;
+    else if (mStart)
+      mIterState = eUseStart;
     else
       mIterState = eDone;
   }
   else if (mIterState == eUseIterator)
   {
     mIter->Prev();
 
     if (mIter->IsDone())
     {
-      if (mStartCData)
-        mIterState = eUseStartCData;
+      if (mStart)
+        mIterState = eUseStart;
       else
         mIterState = eDone;
     }
   }
   else
     mIterState = eDone;
 }
 
@@ -1150,38 +1177,50 @@ static nsresult SplitDataNode(nsIDOMChar
 
   nsCOMPtr<nsIContent> newData;
   rv = dataNode->SplitData(aStartIndex, getter_AddRefs(newData),
                            aCloneAfterOriginal);
   NS_ENSURE_SUCCESS(rv, rv);
   return CallQueryInterface(newData, aMiddleNode);
 }
 
+nsresult PrependChild(nsIDOMNode* aParent, nsIDOMNode* aChild)
+{
+  nsCOMPtr<nsIDOMNode> first, tmpNode;
+  aParent->GetFirstChild(getter_AddRefs(first));
+  return aParent->InsertBefore(aChild, first, getter_AddRefs(tmpNode));
+}
+
 nsresult nsRange::CutContents(nsIDOMDocumentFragment** aFragment)
 { 
   if (aFragment) {
     *aFragment = nsnull;
   }
 
   if (IsDetached())
     return NS_ERROR_DOM_INVALID_STATE_ERR;
 
   nsresult rv;
 
   nsCOMPtr<nsIDocument> doc =
     do_QueryInterface(mStartParent->GetOwnerDoc());
   if (!doc) return NS_ERROR_UNEXPECTED;
 
+  nsCOMPtr<nsIDOMNode> commonAncestor;
+  rv = GetCommonAncestorContainer(getter_AddRefs(commonAncestor));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // If aFragment isn't null, create a temporary fragment to hold our return.
   nsCOMPtr<nsIDOMDocumentFragment> retval;
   if (aFragment) {
     rv = NS_NewDocumentFragment(getter_AddRefs(retval),
                                 doc->NodeInfoManager());
     NS_ENSURE_SUCCESS(rv, rv);
   }
+  nsCOMPtr<nsIDOMNode> commonCloneAncestor(do_QueryInterface(retval));
 
   // Batch possible DOMSubtreeModified events.
   mozAutoSubtreeModified subtree(mRoot ? mRoot->GetOwnerDoc(): nsnull, nsnull);
 
   // Save the range end points locally to avoid interference
   // of Range gravity during our edits!
 
   nsCOMPtr<nsIDOMNode> startContainer = do_QueryInterface(mStartParent);
@@ -1205,26 +1244,26 @@ nsresult nsRange::CutContents(nsIDOMDocu
       NS_ADDREF(*aFragment = retval);
     }
     return rv;
   }
 
   // We delete backwards to avoid iterator problems!
 
   iter.Last();
-  nsCOMPtr<nsIDOMNode> lastFragmentNode = nsnull;
 
   PRBool handled = PR_FALSE;
 
   // With the exception of text nodes that contain one of the range
   // end points, the subtree iterator should only give us back subtrees
   // that are completely contained between the range's end points.
 
   while (!iter.IsDone())
   {
+    nsCOMPtr<nsIDOMNode> nodeToResult;
     nsCOMPtr<nsIDOMNode> node(iter.GetCurrentNode());
 
     // Before we delete anything, advance the iterator to the
     // next subtree.
 
     iter.Prev();
 
     handled = PR_FALSE;
@@ -1251,28 +1290,17 @@ nsresult nsRange::CutContents(nsIDOMDocu
           if (endOffset > startOffset)
           {
             nsCOMPtr<nsIDOMCharacterData> cutNode;
             nsCOMPtr<nsIDOMCharacterData> endNode;
             rv = SplitDataNode(charData, startOffset, endOffset,
                                getter_AddRefs(cutNode),
                                getter_AddRefs(endNode));
             NS_ENSURE_SUCCESS(rv, rv);
-            nsCOMPtr<nsIDOMNode> returnedNode;
-
-            if (retval) {
-              // Add to fragment.
-              rv = retval->InsertBefore(cutNode, lastFragmentNode,
-                                        getter_AddRefs(returnedNode));
-              NS_ENSURE_SUCCESS(rv, rv);
-              lastFragmentNode = returnedNode;
-            } else {
-              rv = RemoveNode(cutNode);
-              NS_ENSURE_SUCCESS(rv, rv);
-            }
+            nodeToResult = cutNode;
           }
 
           handled = PR_TRUE;
         }
         else
         {
           // Delete or extract everything after startOffset.
 
@@ -1280,28 +1308,17 @@ nsresult nsRange::CutContents(nsIDOMDocu
           NS_ENSURE_SUCCESS(rv, rv);
 
           if (dataLength > (PRUint32)startOffset)
           {
             nsCOMPtr<nsIDOMCharacterData> cutNode;
             rv = SplitDataNode(charData, startOffset, dataLength,
                                getter_AddRefs(cutNode), nsnull);
             NS_ENSURE_SUCCESS(rv, rv);
-
-            if (retval) {
-              // Add to fragment.
-              nsCOMPtr<nsIDOMNode> returnedNode;
-              rv = retval->InsertBefore(cutNode, lastFragmentNode,
-                                        getter_AddRefs(returnedNode));
-              NS_ENSURE_SUCCESS(rv, rv);
-              lastFragmentNode = returnedNode;
-            } else {
-              rv = RemoveNode(cutNode);
-              NS_ENSURE_SUCCESS(rv, rv);
-            }
+            nodeToResult = cutNode;
           }
 
           handled = PR_TRUE;
         }
       }
       else if (node == endContainer)
       {
         // Delete or extract everything before endOffset.
@@ -1310,49 +1327,105 @@ nsresult nsRange::CutContents(nsIDOMDocu
         {
           nsCOMPtr<nsIDOMCharacterData> cutNode;
           /* The Range spec clearly states clones get cut and original nodes
              remain behind, so use PR_FALSE as the last parameter.
           */
           rv = SplitDataNode(charData, endOffset, endOffset,
                              getter_AddRefs(cutNode), nsnull, PR_FALSE);
           NS_ENSURE_SUCCESS(rv, rv);
-
-          if (retval) {
-            // Add to fragment.
-            nsCOMPtr<nsIDOMNode> aReturnedNode;
-            rv = retval->InsertBefore(cutNode, lastFragmentNode,
-                                      getter_AddRefs(aReturnedNode));
-            NS_ENSURE_SUCCESS(rv, rv);
-            lastFragmentNode = aReturnedNode;
-          } else {
-            rv = RemoveNode(cutNode);
-            NS_ENSURE_SUCCESS(rv, rv);
-          }
+          nodeToResult = cutNode;
         }
 
         handled = PR_TRUE;
       }       
     }
 
+    if (!handled && (node == endContainer || node == startContainer))
+    {
+      nsCOMPtr<nsINode> iNode = do_QueryInterface(node);
+      if (iNode && iNode->IsNodeOfType(nsINode::eELEMENT) &&
+          ((node == endContainer && endOffset == 0) ||
+           (node == startContainer &&
+            PRInt32(iNode->GetChildCount()) == startOffset)))
+      {
+        if (retval) {
+          nsCOMPtr<nsIDOMNode> clone;
+          rv = node->CloneNode(PR_FALSE, getter_AddRefs(clone));
+          NS_ENSURE_SUCCESS(rv, rv);
+          nodeToResult = clone;
+        }
+        handled = PR_TRUE;
+      }
+    }
+
     if (!handled)
     {
       // node was not handled above, so it must be completely contained
       // within the range. Just remove it from the tree!
-      if (retval) {
-        // Add to fragment.
-        nsCOMPtr<nsIDOMNode> aReturnedNode;
-        rv = retval->InsertBefore(node, lastFragmentNode,
-                                  getter_AddRefs(aReturnedNode));
-        if (NS_FAILED(rv)) return rv;
-        lastFragmentNode = aReturnedNode;
-      } else {
-        rv = RemoveNode(node);
-        if (NS_FAILED(rv)) return rv;
+      nodeToResult = node;
+    }
+
+    PRUint32 parentCount = 0;
+    nsCOMPtr<nsIDOMNode> tmpNode;
+    // Set the result to document fragment if we have 'retval'.
+    if (retval) {
+      nsCOMPtr<nsIDOMNode> oldCommonAncestor = commonAncestor;
+      if (!iter.IsDone()) {
+        // Setup the parameters for the next iteration of the loop.
+        nsCOMPtr<nsIDOMNode> prevNode(iter.GetCurrentNode());
+        NS_ENSURE_STATE(prevNode);
+
+        // Get node's and prevNode's common parent. Do this before moving
+        // nodes from original DOM to result fragment.
+        nsContentUtils::GetCommonAncestor(node, prevNode,
+                                          getter_AddRefs(commonAncestor));
+        NS_ENSURE_STATE(commonAncestor);
+
+        nsCOMPtr<nsIDOMNode> parentCounterNode = node;
+        while (parentCounterNode && parentCounterNode != commonAncestor)
+        {
+          ++parentCount;
+          tmpNode = parentCounterNode;
+          tmpNode->GetParentNode(getter_AddRefs(parentCounterNode));
+          NS_ENSURE_STATE(parentCounterNode);
+        }
       }
+
+      // Clone the parent hierarchy between commonAncestor and node.
+      nsCOMPtr<nsIDOMNode> closestAncestor, farthestAncestor;
+      rv = CloneParentsBetween(oldCommonAncestor, node,
+                               getter_AddRefs(closestAncestor),
+                               getter_AddRefs(farthestAncestor));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (farthestAncestor)
+      {
+        rv = PrependChild(commonCloneAncestor, farthestAncestor);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      rv = closestAncestor ? PrependChild(closestAncestor, nodeToResult)
+                           : PrependChild(commonCloneAncestor, nodeToResult);
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else if (nodeToResult) {
+      rv = RemoveNode(nodeToResult);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    if (!iter.IsDone() && retval) {
+      // Find the equivalent of commonAncestor in the cloned tree.
+      nsCOMPtr<nsIDOMNode> newCloneAncestor = nodeToResult;
+      for (PRUint32 i = parentCount; i; --i)
+      {
+        tmpNode = newCloneAncestor;
+        tmpNode->GetParentNode(getter_AddRefs(newCloneAncestor));
+        NS_ENSURE_STATE(newCloneAncestor);
+      }
+      commonCloneAncestor = newCloneAncestor;
     }
   }
 
   // XXX_kin: At this point we should be checking for the case
   // XXX_kin: where we have 2 adjacent text nodes left, each
   // XXX_kin: containing one of the range end points. The spec
   // XXX_kin: says the 2 nodes should be merged in that case,
   // XXX_kin: and to use Normalize() to do the merging, but
@@ -1430,23 +1503,21 @@ nsRange::CompareBoundaryPoints(PRUint16 
     return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
 
   *aCmpRet = nsContentUtils::ComparePoints(ourNode, ourOffset,
                                            otherNode, otherOffset);
 
   return NS_OK;
 }
 
-
-
-static nsresult
-CloneParentsBetween(nsIDOMNode *aAncestor,
-                    nsIDOMNode *aNode,
-                    nsIDOMNode **aClosestAncestor,
-                    nsIDOMNode **aFarthestAncestor)
+nsresult
+nsRange::CloneParentsBetween(nsIDOMNode *aAncestor,
+                             nsIDOMNode *aNode,
+                             nsIDOMNode **aClosestAncestor,
+                             nsIDOMNode **aFarthestAncestor)
 {
   NS_ENSURE_ARG_POINTER((aAncestor && aNode && aClosestAncestor && aFarthestAncestor));
 
   *aClosestAncestor  = nsnull;
   *aFarthestAncestor = nsnull;
 
   if (aAncestor == aNode)
     return NS_OK;
@@ -1532,32 +1603,38 @@ nsresult nsRange::CloneContents(nsIDOMDo
     *aReturn = clonedFrag;
     NS_IF_ADDREF(*aReturn);
     return NS_OK;
   }
 
   iter.First();
 
   // With the exception of text nodes that contain one of the range
-  // end points, the subtree iterator should only give us back subtrees
-  // that are completely contained between the range's end points.
+  // end points and elements which don't have any content selected the subtree
+  // iterator should only give us back subtrees that are completely contained
+  // between the range's end points.
   //
   // Unfortunately these subtrees don't contain the parent hierarchy/context
   // that the Range spec requires us to return. This loop clones the
   // parent hierarchy, adds a cloned version of the subtree, to it, then
   // correctly places this new subtree into the doc fragment.
 
   while (!iter.IsDone())
   {
     nsCOMPtr<nsIDOMNode> node(iter.GetCurrentNode());
     nsCOMPtr<nsINode> iNode = do_QueryInterface(node);
+    PRBool deepClone = !(iNode->IsNodeOfType(nsINode::eELEMENT)) ||
+                       (!(iNode == mEndParent && mEndOffset == 0) &&
+                        !(iNode == mStartParent &&
+                          mStartOffset == PRInt32(iNode->GetChildCount())));
+
     // Clone the current subtree!
 
     nsCOMPtr<nsIDOMNode> clone;
-    res = node->CloneNode(PR_TRUE, getter_AddRefs(clone));
+    res = node->CloneNode(deepClone, getter_AddRefs(clone));
     if (NS_FAILED(res)) return res;
 
     // If it's CharacterData, make sure we only clone what
     // is in the range.
     //
     // XXX_kin: We need to also handle ProcessingInstruction
     // XXX_kin: according to the spec.
 
--- a/content/base/src/nsRange.h
+++ b/content/base/src/nsRange.h
@@ -122,16 +122,20 @@ private:
   /**
    * Cut or delete the range's contents.
    *
    * @param aFragment nsIDOMDocumentFragment containing the nodes.
    *                  May be null to indicate the caller doesn't want a fragment.
    */
   nsresult CutContents(nsIDOMDocumentFragment** frag);
 
+  static nsresult CloneParentsBetween(nsIDOMNode *aAncestor,
+                                      nsIDOMNode *aNode,
+                                      nsIDOMNode **aClosestAncestor,
+                                      nsIDOMNode **aFarthestAncestor);
 
 public:
 /******************************************************************************
  *  Utility routine to detect if a content node starts before a range and/or 
  *  ends after a range.  If neither it is contained inside the range.
  *  
  *  XXX - callers responsibility to ensure node in same doc as range!
  *
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -205,16 +205,17 @@ include $(topsrcdir)/config/rules.mk
 		file_bug28293.sjs \
 		test_bug445225.html \
 		file_bug445225_multipart.txt \
 		file_bug445225_multipart.txt^headers^ \
 		test_title.html \
 		test_bug453521.html \
 		test_bug391728.html \
 		file_bug391728.html \
+		test_bug454325.html \
 		file_bug391728_2.html \
 		test_bug368972.html \
 		test_bug450160.html \
 		test_bug454326.html \
 		test_bug457746.html \
 		bug457746.sjs \
 		test_CrossSiteXHR.html \
 		file_CrossSiteXHR_inner.html \
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug454325.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=454325
+-->
+<head>
+  <title>Test for Bug 454325</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=454325">Mozilla Bug 454325</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 454325 **/
+
+function testDocument1() {
+  var doc = document.implementation.createDocument("", "", null);
+  var html = doc.createElement('html');
+  doc.appendChild(html);
+  var body = doc.createElement('body');
+  html.appendChild(body);
+  var h1 = doc.createElement('h1');
+  var t1 = doc.createTextNode('Hello ');
+  h1.appendChild(t1);
+  var em = doc.createElement('em');
+  var t2 = doc.createTextNode('Wonderful');
+  em.appendChild(t2);
+  h1.appendChild(em);
+  var t3 = doc.createTextNode(' Kitty');
+  h1.appendChild(t3);
+  body.appendChild(h1);
+  var p = doc.createElement('p');
+  var t4 = doc.createTextNode(' How are you?');
+  p.appendChild(t4);
+  body.appendChild(p);
+  var r = doc.createRange();
+  r.selectNodeContents(doc);
+  is(r.toString(), "Hello Wonderful Kitty How are you?",
+     "toString() on range selecting Document gave wrong output");
+  r.setStart(h1, 3);
+  r.setEnd(p, 0);
+  // <html><body><h1>Hello <em>Wonder ful<\em> Kitty<\h1><p>How are you?<\p><\body></html>
+  //                                                ^ -----^
+  is(r.toString(), "", "toString() on range crossing text nodes gave wrong output");
+  var c1 = r.cloneContents();
+  is(c1.childNodes.length, 2, "Wrong child nodes");
+  try {
+    is(c1.childNodes[0].localName, "h1", "Wrong child node");
+    is(c1.childNodes[1].localName, "p", "Wrong child node");
+  } catch(ex) {
+    ok(!ex, ex);
+  }
+
+  r.setStart(t2, 6);
+  r.setEnd(p, 0);
+  // <html><body><h1>Hello <em>Wonder ful<\em> Kitty<\h1><p>How are you?<\p><\body></html>
+  //                                 ^----------------------^
+  is(r.toString(), "ful Kitty", "toString() on range crossing text nodes gave wrong output");
+  var c2 = r.cloneContents();
+  is(c2.childNodes.length, 2, "Wrong child nodes");
+  try {
+    is(c1.childNodes[0].localName, "h1", "Wrong child node");
+    is(c1.childNodes[1].localName, "p", "Wrong child node");
+  } catch(ex) {
+    ok(!ex, ex);
+  }
+
+  var e1 = r.extractContents();
+  is(e1.childNodes.length, 2, "Wrong child nodes");
+  try {
+    is(e1.childNodes[0].localName, "h1", "Wrong child node");
+    is(e1.childNodes[1].localName, "p", "Wrong child node");
+  } catch(ex) {
+    ok(!ex, ex);
+  }
+}
+
+function testDocument2() {
+  var doc = document.implementation.createDocument("", "", null);
+  var html = doc.createElement('html');
+  doc.appendChild(html);
+  var head = doc.createElement('head');
+  html.appendChild(head);
+  var foohead = doc.createElement('foohead');
+  html.appendChild(foohead);
+  var body = doc.createElement('body');
+  html.appendChild(body);
+  var d1 = doc.createElement('div');
+  head.appendChild(d1);
+  var t1 = doc.createTextNode("|||");
+  d1.appendChild(t1);
+  var d2 = doc.createElement("div");
+  body.appendChild(d2);
+  var d3 = doc.createElement("div");
+  d2.appendChild(d3);
+  var d4 = doc.createElement("div");
+  d2.appendChild(d4);
+  var r = doc.createRange();
+  r.setStart(t1, 1);
+  r.setEnd(d2, 2);
+  is(r.toString(), "||", "Wrong range");
+  var c1 = r.cloneContents();
+  var e1 = r.extractContents();
+  ok(c1.isEqualNode(e1), "Wrong cloning or extracting!");
+}
+
+function runTest() {
+  testDocument1();
+  testDocument2();
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+
+</script>
+</pre>
+</body>
+</html>
+