? content/base/public/nsIComment.h
? content/base/public/nsINodeIterator.h
Index: content/base/public/Makefile.in
===================================================================
RCS file: /cvsroot/mozilla/content/base/public/Makefile.in,v
retrieving revision 1.67
diff -u -8 -p -r1.67 Makefile.in
--- content/base/public/Makefile.in	25 Jul 2007 04:29:47 -0000	1.67
+++ content/base/public/Makefile.in	13 May 2008 16:18:45 -0000
@@ -42,16 +42,17 @@ VPATH		= @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= content
 XPIDL_MODULE	= content_base
 GRE_MODULE	= 1
 
 EXPORTS		= \
 mozFlushType.h \
+nsIComment.h \
 nsIContent.h \
 nsIAttribute.h \
 nsIContentIterator.h \
 nsContentErrors.h \
 nsContentPolicyUtils.h \
 nsContentUtils.h \
 nsIDocument.h \
 nsIDocumentObserver.h \
@@ -107,9 +108,8 @@ XPIDLSRCS	= \
 		nsIScriptEventManager.idl \
 		nsIImageLoadingContent.idl \
 		nsIObjectLoadingContent.idl \
 		nsIFrameLoader.idl \
 		nsIXMLHttpRequest.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
-
--- /dev/null	2008-05-13 10:16:31.389852817 +0300
+++ content/base/public/nsIComment.h	2008-05-13 18:59:40.000000000 +0300
@@ -0,0 +1,65 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Alexander J. Vincent <ajvincent@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#ifndef nsIComment_h___
+#define nsIComment_h___
+
+// IID for the nsIComment interface
+// 8275a3e0-3750-4965-927d-2bf36a93b762
+#define NS_ICOMMENT_IID \
+{ 0x8275a3e0, 0x3750, 0x4965, \
+  { 0x92, 0x7d, 0x2b, 0xf3, 0x6a, 0x93, 0xb7, 0x62 } }
+
+#include "nsISupports.h"
+
+class nsIComment : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICOMMENT_IID);
+
+  /**
+   * Split the comment into two nodes, like nsIDOMText.splitText().
+   *
+   * @param aOffset Index at which the comment should be split.
+   * @param aReturn The comment node for the second half.
+   *
+   * @note  The |this| node will take on the value preceding the offset.
+   */
+  virtual nsresult SplitComment(PRUint32 aOffset, nsIComment** aReturn) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIComment, NS_ICOMMENT_IID)
+
+#endif /* nsIComment_h___ */
Index: content/base/src/nsCommentNode.cpp
===================================================================
RCS file: /cvsroot/mozilla/content/base/src/nsCommentNode.cpp,v
retrieving revision 3.89
diff -u -8 -p -r3.89 nsCommentNode.cpp
--- content/base/src/nsCommentNode.cpp	5 Mar 2008 07:14:42 -0000	3.89
+++ content/base/src/nsCommentNode.cpp	13 May 2008 16:19:06 -0000
@@ -35,21 +35,23 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /*
  * Implementations of DOM Core's nsIDOMComment node.
  */
 
 #include "nsIDOMComment.h"
+#include "nsIComment.h"
 #include "nsGenericDOMDataNode.h"
 #include "nsCOMPtr.h"
 #include "nsIDocument.h"
 
 class nsCommentNode : public nsGenericDOMDataNode,
+                      public nsIComment,
                       public nsIDOMComment
 {
 public:
   nsCommentNode(nsINodeInfo *aNodeInfo);
   virtual ~nsCommentNode();
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
@@ -62,16 +64,19 @@ public:
 
   // nsIDOMComment
   // Empty interface
 
   // nsIContent
   virtual PRBool MayHaveFrame() const;
   virtual PRBool IsNodeOfType(PRUint32 aFlags) const;
 
+  // nsIComment
+  virtual nsresult SplitComment(PRUint32 aOffset, nsIComment** aReturn);
+
 #ifdef DEBUG
   virtual void List(FILE* out, PRInt32 aIndent) const;
   virtual void DumpContent(FILE* out = stdout, PRInt32 aIndent = 0,
                            PRBool aDumpAll = PR_TRUE) const
   {
     return;
   }
 #endif
@@ -108,16 +113,17 @@ nsCommentNode::~nsCommentNode()
 }
 
 
 // QueryInterface implementation for nsCommentNode
 NS_INTERFACE_MAP_BEGIN(nsCommentNode)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNode)
   NS_INTERFACE_MAP_ENTRY(nsIDOMCharacterData)
   NS_INTERFACE_MAP_ENTRY(nsIDOMComment)
+  NS_INTERFACE_MAP_ENTRY(nsIComment)
   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(Comment)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericDOMDataNode)
 
 
 NS_IMPL_ADDREF_INHERITED(nsCommentNode, nsGenericDOMDataNode)
 NS_IMPL_RELEASE_INHERITED(nsCommentNode, nsGenericDOMDataNode)
 
 
@@ -166,16 +172,28 @@ nsCommentNode::CloneDataNode(nsINodeInfo
   nsCommentNode *it = new nsCommentNode(aNodeInfo);
   if (it && aCloneText) {
     it->mText = mText;
   }
 
   return it;
 }
 
+// nsIComment
+nsresult
+nsCommentNode::SplitComment(PRUint32 aOffset, nsIComment** aReturn)
+{
+  nsCOMPtr<nsIContent> newChild;
+  nsresult rv = SplitText(aOffset, getter_AddRefs(newChild));
+  if (NS_SUCCEEDED(rv)) {
+    rv = CallQueryInterface(newChild, aReturn);
+  }
+  return rv;
+}
+
 #ifdef DEBUG
 void
 nsCommentNode::List(FILE* out, PRInt32 aIndent) const
 {
   PRInt32 indx;
   for (indx = aIndent; --indx >= 0; ) fputs("  ", out);
 
   fprintf(out, "Comment@%p refcount=%d<!--", (void*)this, mRefCnt.get());
Index: content/base/src/nsGenericDOMDataNode.cpp
===================================================================
RCS file: /cvsroot/mozilla/content/base/src/nsGenericDOMDataNode.cpp,v
retrieving revision 3.261
diff -u -8 -p -r3.261 nsGenericDOMDataNode.cpp
--- content/base/src/nsGenericDOMDataNode.cpp	18 Apr 2008 17:20:11 -0000	3.261
+++ content/base/src/nsGenericDOMDataNode.cpp	13 May 2008 16:19:07 -0000
@@ -897,17 +897,17 @@ nsGenericDOMDataNode::CreateSlots()
   return new nsDataSlots(mFlagsOrSlots);
 }
 
 //----------------------------------------------------------------------
 
 // Implementation of the nsIDOMText interface
 
 nsresult
-nsGenericDOMDataNode::SplitText(PRUint32 aOffset, nsIDOMText** aReturn)
+nsGenericDOMDataNode::SplitText(PRUint32 aOffset, nsIContent** aReturn)
 {
   nsresult rv = NS_OK;
   nsAutoString cutText;
   PRUint32 length = TextLength();
 
   if (aOffset > length) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
@@ -929,30 +929,36 @@ nsGenericDOMDataNode::SplitText(PRUint32
 
   nsCOMPtr<nsIContent> newContent = CloneDataNode(mNodeInfo, PR_FALSE);
   if (!newContent) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   newContent->SetText(cutText, PR_TRUE);
 
-  nsIContent* parent = GetParent();
+  nsINode* parent = GetNodeParent();
 
   if (parent) {
     PRInt32 index = parent->IndexOf(this);
-
-    nsCOMPtr<nsIContent> content(do_QueryInterface(newContent));
-
-    parent->InsertChildAt(content, index+1, PR_TRUE);
+    parent->InsertChildAt(newContent, index + 1, PR_TRUE);
   }
 
-  // No need to handle the case of document being the parent since text
-  // isn't allowed as direct child of documents
+  NS_ADDREF(*aReturn = newContent);
+  return rv;
+}
 
-  return CallQueryInterface(newContent, aReturn);
+nsresult
+nsGenericDOMDataNode::SplitText(PRUint32 aOffset, nsIDOMText** aReturn)
+{
+  nsCOMPtr<nsIContent> newChild;
+  nsresult rv = SplitText(aOffset, getter_AddRefs(newChild));
+  if (NS_SUCCEEDED(rv)) {
+    rv = CallQueryInterface(newChild, aReturn);
+  }
+  return rv;
 }
 
 //----------------------------------------------------------------------
 
 // Implementation of the nsIContent interface text functions
 
 const nsTextFragment *
 nsGenericDOMDataNode::GetText()
Index: content/base/src/nsGenericDOMDataNode.h
===================================================================
RCS file: /cvsroot/mozilla/content/base/src/nsGenericDOMDataNode.h,v
retrieving revision 3.149
diff -u -8 -p -r3.149 nsGenericDOMDataNode.h
--- content/base/src/nsGenericDOMDataNode.h	10 Apr 2008 22:47:02 -0000	3.149
+++ content/base/src/nsGenericDOMDataNode.h	13 May 2008 16:19:07 -0000
@@ -303,16 +303,17 @@ protected:
     return static_cast<nsDataSlots*>(GetSlots());
   }
 
   nsDataSlots *GetExistingDataSlots() const
   {
     return static_cast<nsDataSlots*>(GetExistingSlots());
   }
 
+  nsresult SplitText(PRUint32 aOffset, nsIContent** aReturn);
   nsresult SplitText(PRUint32 aOffset, nsIDOMText** aReturn);
 
   nsresult SetTextInternal(PRUint32 aOffset, PRUint32 aCount,
                            const PRUnichar* aBuffer, PRUint32 aLength,
                            PRBool aNotify);
 
   /**
    * Method to clone this node. This needs to be overriden by all derived
Index: content/base/src/nsRange.cpp
===================================================================
RCS file: /cvsroot/mozilla/content/base/src/nsRange.cpp,v
retrieving revision 1.229
diff -u -8 -p -r1.229 nsRange.cpp
--- content/base/src/nsRange.cpp	13 Mar 2008 10:12:51 -0000	1.229
+++ content/base/src/nsRange.cpp	13 May 2008 16:19:07 -0000
@@ -43,24 +43,26 @@
 #include "nsRange.h"
 
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMNSDocument.h"
 #include "nsIDOMDocumentFragment.h"
+#include "nsIComment.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIDOMText.h"
 #include "nsDOMError.h"
 #include "nsIContentIterator.h"
 #include "nsIDOMNodeList.h"
 #include "nsGkAtoms.h"
 #include "nsContentUtils.h"
+#include "nsGenericDOMDataNode.h"
 
 nsresult NS_NewContentIterator(nsIContentIterator** aInstancePtrResult);
 nsresult NS_NewContentSubtreeIterator(nsIContentIterator** aInstancePtrResult);
 
 /******************************************************
  * stack based utilty class for managing monitor
  ******************************************************/
 
@@ -1003,17 +1005,16 @@ RangeSubtreeIterator::Prev()
       else
         mIterState = eDone;
     }
   }
   else
     mIterState = eDone;
 }
 
-
 // CollapseRangeAfterDelete() is a utility method that is used by
 // DeleteContents() and ExtractContents() to collapse the range
 // in the correct place, under the range's root container (the
 // range end points common container) as outlined by the Range spec:
 //
 // http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/ranges.html
 // The assumption made by this method is that the delete or extract
 // has been done already, and left the range in a state where there is
@@ -1093,49 +1094,147 @@ CollapseRangeAfterDelete(nsIDOMRange *aR
     return NS_ERROR_FAILURE; // This should never happen!
 
   res = aRange->SelectNode(nodeToSelect);
   if (NS_FAILED(res)) return res;
 
   return aRange->Collapse(PR_FALSE);
 }
 
-nsresult nsRange::DeleteContents()
+/**
+ * Remove a node from the DOM entirely.
+ *
+ * @param aNode The node to remove.
+ */
+static nsresult
+RemoveNode(nsIDOMNode* aNode)
+{
+  nsresult rv;
+
+  nsCOMPtr<nsIDOMNode> parent;
+  rv = aNode->GetParentNode(getter_AddRefs(parent));
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsCOMPtr<nsIDOMNode> junk;
+  rv = parent->RemoveChild(aNode, getter_AddRefs(junk));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return rv;
+}
+
+/**
+ * Split a data node into two or three parts.
+ *
+ * @param aStartNode  The original node we are trying to split, and first of three.
+ * @param aStartIndex The index at which to split the first and second parts.
+ * @param aEndIndex   The index at which to split the second and third parts.
+ * @param aMiddleNode The second node of three.
+ * @param aEndNode    The third node of three.  May be null to indicate aEndIndex doesn't apply.
+ */
+nsresult SplitDataNode(nsIDOMCharacterData* aStartNode,
+                       PRUint32 aStartIndex,
+                       PRUint32 aEndIndex,
+                       nsIDOMCharacterData** aMiddleNode,
+                       nsIDOMCharacterData** aEndNode)
+{
+  NS_ENSURE_ARG(aStartNode);
+  NS_ENSURE_ARG_POINTER(aMiddleNode);
+  nsresult rv;
+
+  // Block for text nodes.
+  {
+    nsCOMPtr<nsIDOMText> node = do_QueryInterface(aStartNode);
+    if (node) {
+      // Split the main node, starting with the end.
+      if (aEndNode && aEndIndex > aStartIndex) {
+        nsCOMPtr<nsIDOMText> newNode;
+        rv = node->SplitText(aEndIndex, getter_AddRefs(newNode));
+        NS_ENSURE_SUCCESS(rv, rv);
+        NS_ADDREF(*aEndNode = newNode);
+      }
+
+      nsCOMPtr<nsIDOMText> newNode;
+      rv = node->SplitText(aStartIndex, getter_AddRefs(newNode));
+      NS_ENSURE_SUCCESS(rv, rv);
+      NS_ADDREF(*aMiddleNode = newNode);
+      return rv;
+    }
+  }
+
+  // Block for comment nodes.
+  {
+    nsCOMPtr<nsIComment> node = do_QueryInterface(aStartNode);
+    if (node) {
+      // Split the main node, starting with the end.
+      if (aEndNode && aEndIndex > aStartIndex) {
+        nsCOMPtr<nsIComment> newNode;
+        rv = node->SplitComment(aEndIndex, getter_AddRefs(newNode));
+        NS_ENSURE_SUCCESS(rv, rv);
+        rv = CallQueryInterface(newNode, aEndNode);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      nsCOMPtr<nsIComment> newNode;
+      rv = node->SplitComment(aStartIndex, getter_AddRefs(newNode));
+      NS_ENSURE_SUCCESS(rv, rv);
+      return CallQueryInterface(newNode, aMiddleNode);
+    }
+  }
+
+  NS_NOTREACHED("Not a text or a comment node?");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult nsRange::CutContents(nsIDOMDocumentFragment** aFragment)
 { 
-  if(IsDetached())
+  if (IsDetached())
     return NS_ERROR_DOM_INVALID_STATE_ERR;
 
+  nsresult rv;
+
+  nsCOMPtr<nsIDocument> doc =
+    do_QueryInterface(mStartParent->GetOwnerDoc());
+  if (!doc) return NS_ERROR_UNEXPECTED;
+
+  // 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);
+  }
+
   // 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);
   PRInt32              startOffset = mStartOffset;
   nsCOMPtr<nsIDOMNode> endContainer = do_QueryInterface(mEndParent);
   PRInt32              endOffset = mEndOffset;
 
   // Create and initialize a subtree iterator that will give
   // us all the subtrees within the range.
 
   RangeSubtreeIterator iter;
 
-  nsresult res = iter.Init(this);
-  if (NS_FAILED(res)) return res;
+  rv = iter.Init(this);
+  if (NS_FAILED(rv)) return rv;
 
   if (iter.IsDone())
   {
     // There's nothing for us to delete.
     return CollapseRangeAfterDelete(this);
   }
 
   // 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())
@@ -1161,86 +1260,164 @@ nsresult nsRange::DeleteContents()
     {
       PRUint32 dataLength = 0;
 
       if (node == startContainer)
       {
         if (node == endContainer)
         {
           // This range is completely contained within a single text node.
-          // Delete the data between startOffset and endOffset.
+          // Delete or extract the data between startOffset and endOffset.
 
           if (endOffset > startOffset)
           {
-            res = charData->DeleteData(startOffset, endOffset - startOffset);
-            if (NS_FAILED(res)) return res;
+            nsCOMPtr<nsIDOMCharacterData> cutNode;
+            nsCOMPtr<nsIDOMCharacterData> endNode;
+            rv = SplitDataNode(charData, startOffset, endOffset,
+                               getter_AddRefs(cutNode),
+                               getter_AddRefs(endNode));
+            if (NS_FAILED(rv)) return rv;
+            nsCOMPtr<nsIDOMNode> returnedNode;
+
+            if (retval) {
+              // Add to fragment.
+              rv = retval->InsertBefore(cutNode, lastFragmentNode,
+                                        getter_AddRefs(returnedNode));
+              if (NS_FAILED(rv)) return rv;
+              lastFragmentNode = returnedNode;
+            } else {
+              rv = RemoveNode(cutNode);
+              if (NS_FAILED(rv)) return rv;
+            }
           }
 
           handled = PR_TRUE;
         }
         else
         {
-          // Delete everything after startOffset.
+          // Delete or extract everything after startOffset.
 
-          res = charData->GetLength(&dataLength);
-          if (NS_FAILED(res)) return res;
+          rv = charData->GetLength(&dataLength);
+          if (NS_FAILED(rv)) return rv;
 
           if (dataLength > (PRUint32)startOffset)
           {
-            res = charData->DeleteData(startOffset, dataLength - startOffset);
-            if (NS_FAILED(res)) return res;
+            nsCOMPtr<nsIDOMCharacterData> cutNode;
+            rv = SplitDataNode(charData, startOffset, dataLength,
+                               getter_AddRefs(cutNode), nsnull);
+            if (NS_FAILED(rv)) return rv;
+
+            if (retval) {
+              // Add to fragment.
+              nsCOMPtr<nsIDOMNode> returnedNode;
+              rv = retval->InsertBefore(cutNode, lastFragmentNode,
+                                        getter_AddRefs(returnedNode));
+              if (NS_FAILED(rv)) return rv;
+              lastFragmentNode = returnedNode;
+            } else {
+              rv = RemoveNode(cutNode);
+              if (NS_FAILED(rv)) return rv;
+            }
           }
 
           handled = PR_TRUE;
         }
       }
       else if (node == endContainer)
       {
-        // Delete the data between 0 and endOffset.
+        // Delete or extract everything before endOffset.
 
         if (endOffset > 0)
         {
-          res = charData->DeleteData(0, endOffset);
-          if (NS_FAILED(res)) return res;
+          nsCOMPtr<nsIDOMCharacterData> cutNode;
+          rv = SplitDataNode(charData, endOffset, endOffset,
+                             getter_AddRefs(cutNode), nsnull);
+          if (NS_FAILED(rv)) return rv;
+
+          /* The Range spec clearly states clones get cut and original nodes
+             remain behind, but SplitDataNode places the remaining portion into
+             cutNode and the extracted portion remains in charData.  Switch the
+             values.
+          */
+          nsString cutData;
+          nsString retainedData;
+          rv = cutNode->GetData(retainedData);
+          if (NS_FAILED(rv)) return rv;
+          rv = charData->GetData(cutData);
+          if (NS_FAILED(rv)) return rv;
+          rv = cutNode->SetData(cutData);
+          if (NS_FAILED(rv)) return rv;
+          rv = charData->SetData(retainedData);
+          if (NS_FAILED(rv)) return rv;
+
+          if (retval) {
+            // Add to fragment.
+            nsCOMPtr<nsIDOMNode> aReturnedNode;
+            rv = retval->InsertBefore(cutNode, lastFragmentNode,
+                                      getter_AddRefs(aReturnedNode));
+            if (NS_FAILED(rv)) return rv;
+            lastFragmentNode = aReturnedNode;
+          } else {
+            rv = RemoveNode(cutNode);
+            if (NS_FAILED(rv)) return rv;
+          }
         }
 
         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!
-
-      nsCOMPtr<nsIDOMNode> parent, tmpNode;
-
-      node->GetParentNode(getter_AddRefs(parent));
-
-      if (parent) {
-        res = parent->RemoveChild(node, getter_AddRefs(tmpNode));
-        if (NS_FAILED(res)) return res;
+      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;
       }
     }
   }
 
   // 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
   // XXX_kin: calling Normalize() on the common parent to accomplish
   // XXX_kin: this might also normalize nodes that are outside the
   // XXX_kin: range but under the common parent. Need to verify
   // XXX_kin: with the range commitee members that this was the
   // XXX_kin: desired behavior. For now we don't merge anything!
+  // XXX ajvincent Filed as https://bugzilla.mozilla.org/show_bug.cgi?id=401276
+
+  if (aFragment) {
+    NS_ADDREF(*aFragment = retval);
+  }
 
   return CollapseRangeAfterDelete(this);
 }
 
+nsresult nsRange::DeleteContents()
+{
+  return CutContents(nsnull);
+}
+
+nsresult nsRange::ExtractContents(nsIDOMDocumentFragment** aReturn)
+{
+  NS_ENSURE_ARG_POINTER(aReturn);
+  return CutContents(aReturn);
+}
+
 NS_IMETHODIMP
 nsRange::CompareBoundaryPoints(PRUint16 aHow, nsIDOMRange* aOtherRange,
                                PRInt16* aCmpRet)
 {
   nsCOMPtr<nsIRange> otherRange = do_QueryInterface(aOtherRange);
   NS_ENSURE_TRUE(otherRange, NS_ERROR_NULL_POINTER);
 
   if(mIsDetached || otherRange->IsDetached())
@@ -1285,36 +1462,17 @@ nsRange::CompareBoundaryPoints(PRUint16 
     return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
 
   *aCmpRet = nsContentUtils::ComparePoints(ourNode, ourOffset,
                                            otherNode, otherOffset);
 
   return NS_OK;
 }
 
-nsresult nsRange::ExtractContents(nsIDOMDocumentFragment** aReturn)
-{ 
-  if(mIsDetached)
-    return NS_ERROR_DOM_INVALID_STATE_ERR;
 
-  // Batch possible DOMSubtreeModified events.
-  mozAutoSubtreeModified subtree(mRoot ? mRoot->GetOwnerDoc(): nsnull, nsnull);
-
-  // XXX_kin: The spec says that nodes that are completely in the
-  // XXX_kin: range should be moved into the document fragment, not
-  // XXX_kin: copied. This method will have to be rewritten using
-  // XXX_kin: DeleteContents() as a template, with the charData cloning
-  // XXX_kin: code from CloneContents() merged in.
-
-  nsresult res = CloneContents(aReturn);
-  if (NS_FAILED(res))
-    return res;
-  res = DeleteContents();
-  return res; 
-}
 
 static nsresult
 CloneParentsBetween(nsIDOMNode *aAncestor,
                     nsIDOMNode *aNode,
                     nsIDOMNode **aClosestAncestor,
                     nsIDOMNode **aFarthestAncestor)
 {
   NS_ENSURE_ARG_POINTER((aAncestor && aNode && aClosestAncestor && aFarthestAncestor));
Index: content/base/src/nsRange.h
===================================================================
RCS file: /cvsroot/mozilla/content/base/src/nsRange.h,v
retrieving revision 1.60
diff -u -8 -p -r1.60 nsRange.h
--- content/base/src/nsRange.h	24 Feb 2008 12:46:09 -0000	1.60
+++ content/base/src/nsRange.h	13 May 2008 16:19:07 -0000
@@ -114,16 +114,25 @@ public:
 
 private:
   // no copy's or assigns
   nsRange(const nsRange&);
   nsRange& operator=(const nsRange&);
 
   nsINode* IsValidBoundary(nsINode* aNode);
  
+  /**
+   * 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);
+
+
 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!
  *
  *****************************************************************************/
--- /dev/null	2008-05-13 10:16:31.389852817 +0300
+++ content/test/unit/test_delete_range.xml	2008-05-13 18:59:40.000000000 +0300
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+This file holds serialized tests for DOM Range tests on extractContents.
+The <test/> elements designate individual tests.  Each one has the following:
+
+* A <source/> element, designating the start conditions of the test,
+* A <result/> element, designating what the source element should look like
+  after the extraction,
+* An <extract/> element, designating what the extracted content should look like.
+
+The <split/> element designates a split between two DOM nodes. This element will
+be removed before the actual test, and the two nodes on either side will not be
+merged.
+
+The <empty-cdata/> element designates an empty character data section.  Before
+the test executes, this element is replaced with an actual CDATASection node.
+
+For the <source/> element, there are four attributes:
+
+* startContainer:  A XPath to the startContainer of the range.
+* endContainer:  A XPath to the endContainer of the range.
+* startOffset:  The startOffset of the range.
+* endOffset:  The endOffset of the range.
+
+Note this test may need updating with a fix for bug 401276.  The spec states
+adjacent nodes after an extraction should be merged if possible, but using the
+normalize() method, which could have unintended side effects... For now, we're
+not permitting that, pending clarification.
+
+Please make sure the first test in this document always tests a range where the
+start container and end container are the same text node, and where the start
+offset and end offsets are valid and inequal.  Some of the additional range
+tests (after the bulk of the delete/extract tests) depend on it.
+  -->
+<root>
+  <!-- Extracting from a text node. -->
+  <test>
+    <source startContainer="text()[1]"
+            endContainer="text()[1]"
+            startOffset="4"
+            endOffset="10">The quick fox</source>
+    <result>The <split/>fox</result>
+    <extract>quick </extract>
+  </test>
+
+  <!-- Extracting from a CDATA section. -->
+  <test>
+    <source startContainer="text()[1]"
+            endContainer="text()[1]"
+            startOffset="4"
+            endOffset="10"><![CDATA[The quick fox]]></source>
+    <result><![CDATA[The ]]><split/><![CDATA[fox]]></result>
+    <extract><![CDATA[quick ]]></extract>
+  </test>
+
+  <!-- Snipping the start of a text node. -->
+  <test>
+    <source startContainer="text()[1]"
+            endContainer="text()[1]"
+            startOffset="0"
+            endOffset="4"><![CDATA[The quick fox]]></source>
+    <result><empty-cdata/><![CDATA[quick fox]]></result>
+    <extract><![CDATA[The ]]></extract>
+  </test>
+
+  <!-- Extracting from a comment. -->
+  <test>
+    <source startContainer="comment()[1]"
+            endContainer="comment()[1]"
+            startOffset="4"
+            endOffset="10"><!--The quick fox--></source>
+    <result><!--The --><split/><!--fox--></result>
+    <extract><!--quick --></extract>
+  </test>
+
+  <!-- Snipping whole nodes -->
+  <test>
+    <source startContainer="."
+            endContainer="."
+            startOffset="0"
+            endOffset="2">Fox<fox/>Fox<bear/><!--comment--></source>
+    <result>Fox<bear/><!--comment--></result>
+    <extract>Fox<fox/></extract>
+  </test>
+
+  <!-- Snipping whole nodes -->
+  <test>
+    <source startContainer="."
+            endContainer="."
+            startOffset="1"
+            endOffset="3">Fox<fox/>Fox<bear/><!--comment--></source>
+    <result>Fox<bear/><!--comment--></result>
+    <extract><fox/>Fox</extract>
+  </test>
+
+  <!-- Snipping a mixture of nodes and portions of text -->
+  <test>
+    <source startContainer="text()[2]"
+            startOffset="1"
+            endContainer="comment()[1]"
+            endOffset="3">Fox<fox/>Fox<bear><?cow ?></bear><!--comment--></source>
+    <result>Fox<fox/>F<!--ment--></result>
+    <extract>ox<bear><?cow ?></bear><!--com--></extract>
+  </test>
+
+  <!-- Extracting with a collapsed range from a text node. -->
+  <test>
+    <source startContainer="text()[1]"
+            endContainer="text()[1]"
+            startOffset="4"
+            endOffset="4">The quick fox</source>
+    <result>The quick fox</result>
+    <extract/>
+  </test>
+
+  <!-- Extracting with a collapsed range from a non-text node. -->
+  <test>
+    <source startContainer="."
+            endContainer="."
+            startOffset="0"
+            endOffset="0">Fox<fox/>Fox<bear/><!--comment--></source>
+    <result>Fox<fox/>Fox<bear/><!--comment--></result>
+    <extract/>
+  </test>
+</root>
--- /dev/null	2008-05-13 10:16:31.389852817 +0300
+++ content/test/unit/test_range.js	2008-05-13 18:59:40.000000000 +0300
@@ -0,0 +1,540 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is DOM Range tests.
+ *
+ * The Initial Developer of the Original Code is
+ * Alexander J. Vincent <ajvincent@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const C_i = Components.interfaces;
+const C_r = Components.results;
+
+const UNORDERED_TYPE = C_i.nsIDOMXPathResult.ANY_UNORDERED_NODE_TYPE;
+
+const INVALID_STATE_ERR = 0x8053000b;     // NS_ERROR_DOM_INVALID_STATE_ERR
+const INDEX_SIZE_ERR = 0x80530001;        // NS_ERROR_DOM_INDEX_SIZE_ERR
+const INVALID_NODE_TYPE_ERR = 0x805c0002; // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
+const NOT_OBJECT_ERR = 0x805303eb;        // NS_ERROR_DOM_NOT_OBJECT_ERR
+const SECURITY_ERR = 0x805303e8;          // NS_ERROR_DOM_SECURITY_ERR
+
+/**
+ * Determine if the data node has only ignorable white-space.
+ *
+ * @return nsIDOMNodeFilter.FILTER_SKIP if it does.
+ * @return nsIDOMNodeFilter.FILTER_ACCEPT otherwise.
+ */
+function isWhitespace(aNode) {
+  return ((/\S/).test(aNode.nodeValue)) ?
+         C_i.nsIDOMNodeFilter.FILTER_SKIP :
+         C_i.nsIDOMNodeFilter.FILTER_ACCEPT;
+}
+
+/**
+ * Create a DocumentFragment with cloned children equaling a node's children.
+ *
+ * @param aNode The node to copy from.
+ *
+ * @return DocumentFragment node.
+ */
+function getFragment(aNode) {
+  var frag = aNode.ownerDocument.createDocumentFragment();
+  do_check_true(frag instanceof C_i.nsIDOM3Node);
+  for (var i = 0; i < aNode.childNodes.length; i++) {
+    frag.appendChild(aNode.childNodes.item(i).cloneNode(true));
+  }
+  return frag;
+}
+
+// Goodies from head_content.js
+const serializer = new DOMSerializer();
+const parser = new DOMParser();
+
+/**
+ * Dump the contents of a document fragment to the console.
+ *
+ * @param aFragment The fragment to serialize.
+ */
+function dumpFragment(aFragment) {
+  dump(serializer.serializeToString(aFragment) + "\n\n");
+}
+
+/**
+ * Translate a XPath to a DOM node.
+ *
+ * @param aContextNode The context node to apply the XPath to.
+ * @param aPath        The XPath to use.
+ *
+ * @return nsIDOMNode  The target node retrieved from the XPath.
+ */
+function evalXPath(aContextNode, aPath) {
+  /* XXX ajvincent XPath Evaluator in Mozilla has a weakness:  it chokes on
+   * document fragments as context nodes.  We're trying to beat that here.
+   */
+  do_check_true(aContextNode instanceof C_i.nsIDOMDocumentFragment);
+  do_check_true(aContextNode.childNodes.length > 0);
+  if (aPath == ".") {
+    return aContextNode;
+  }
+
+  // Separate the fragment's xpath lookup from the rest.
+  var firstSlash = aPath.indexOf("/");
+  if (firstSlash == -1) {
+    firstSlash = aPath.length;
+  }
+  var prefix = aPath.substr(0, firstSlash);
+  var realPath = aPath.substr(firstSlash + 1);
+  if (!realPath) {
+    realPath = ".";
+  }
+
+  // Set up a special node filter to look among the fragment's child nodes.
+  var childIndex = 1;
+  var bracketIndex = prefix.indexOf("[");
+  if (bracketIndex != -1) {
+    childIndex = Number(prefix.substring(bracketIndex + 1, prefix.indexOf("]")));
+    do_check_true(childIndex > 0);
+    prefix = prefix.substr(0, bracketIndex);
+  }
+
+  var targetType = C_i.nsIDOMNodeFilter.SHOW_ELEMENT;
+  var targetNodeName = prefix;
+  if (prefix.indexOf("processing-instruction(") == 0) {
+    targetType = C_i.nsIDOMNodeFilter.SHOW_PROCESSING_INSTRUCTION;
+    targetNodeName = prefix.substring(prefix.indexOf("(") + 2, prefix.indexOf(")") - 1);
+  }
+  switch (prefix) {
+    case "text()":
+      targetType = C_i.nsIDOMNodeFilter.SHOW_TEXT |
+                   C_i.nsIDOMNodeFilter.SHOW_CDATA_SECTION;
+      targetNodeName = null;
+      break;
+    case "comment()":
+      targetType = C_i.nsIDOMNodeFilter.SHOW_COMMENT;
+      targetNodeName = null;
+      break;
+    case "node()":
+      targetType = C_i.nsIDOMNodeFilter.SHOW_ALL;
+      targetNodeName = null;
+  }
+
+  var filter = {
+    count: 0,
+
+    // nsIDOMNodeFilter
+    acceptNode: function acceptNode(aNode) {
+      if (aNode.parentNode != aContextNode) {
+        // Don't bother looking at kids either.
+        return C_i.nsIDOMNodeFilter.FILTER_REJECT;
+      }
+
+      if (targetNodeName && targetNodeName != aNode.nodeName) {
+        return C_i.nsIDOMNodeFilter.FILTER_SKIP;
+      }
+
+      this.count++;
+      if (this.count != childIndex) {
+        return C_i.nsIDOMNodeFilter.FILTER_SKIP;
+      }
+
+      return C_i.nsIDOMNodeFilter.FILTER_ACCEPT;
+    }
+  };
+
+  // Look for the node matching the step from the document fragment.
+  var walker = aContextNode.ownerDocument.createTreeWalker(
+                 aContextNode,
+                 targetType,
+                 filter,
+                 true);
+  var targetNode = walker.nextNode();
+  do_check_neq(targetNode, null);
+
+  // Apply our remaining xpath to the found node.
+  var expr = aContextNode.ownerDocument.createExpression(realPath, null);
+  var result = expr.evaluate(targetNode, UNORDERED_TYPE, null);
+  do_check_true(result instanceof C_i.nsIDOMXPathResult);
+  return result.singleNodeValue;
+}
+
+/**
+ * Get a DOM range corresponding to the test's source node.
+ *
+ * @param aSourceNode <source/> element with range information.
+ * @param aFragment   DocumentFragment generated with getFragment().
+ *
+ * @return Range object.
+ */
+function getRange(aSourceNode, aFragment) {
+  do_check_true(aSourceNode instanceof C_i.nsIDOMElement);
+  do_check_true(aFragment instanceof C_i.nsIDOMDocumentFragment);
+  var doc = aSourceNode.ownerDocument;
+
+  var containerPath = aSourceNode.getAttribute("startContainer");
+  var startContainer = evalXPath(aFragment, containerPath);
+  var startOffset = Number(aSourceNode.getAttribute("startOffset"));
+
+  containerPath = aSourceNode.getAttribute("endContainer");
+  var endContainer = evalXPath(aFragment, containerPath);
+  var endOffset = Number(aSourceNode.getAttribute("endOffset"));
+
+  var range = doc.createRange();
+  range.setStart(startContainer, startOffset);
+  range.setEnd(endContainer, endOffset);
+  return range;
+}
+
+/**
+ * Get the document for a given path, and clean it up for our tests.
+ *
+ * @param aPath The path to the local document.
+ */
+function getParsedDocument(aPath) {
+  var doc = do_parse_document(aPath, "application/xml");
+  do_check_true(doc.documentElement.localName != "parsererror");
+  do_check_true(doc instanceof C_i.nsIDOMDocumentTraversal);
+  do_check_true(doc instanceof C_i.nsIDOMXPathEvaluator);
+  do_check_true(doc instanceof C_i.nsIDOMDocumentRange);
+
+  // Clean out whitespace.
+  var walker = doc.createTreeWalker(doc,
+                                    C_i.nsIDOMNodeFilter.SHOW_TEXT |
+                                    C_i.nsIDOMNodeFilter.SHOW_CDATA_SECTION,
+                                    isWhitespace,
+                                    false);
+  while (walker.nextNode()) {
+    var parent = walker.currentNode.parentNode;
+    parent.removeChild(walker.currentNode);
+    walker.currentNode = parent;
+  }
+
+  // Clean out mandatory splits between nodes.
+  var splits = doc.getElementsByTagName("split");
+  var i;
+  for (i = splits.length - 1; i >= 0; i--) {
+    var node = splits.item(i);
+    node.parentNode.removeChild(node);
+  }
+  splits = null;
+
+  // Replace empty CDATA sections.
+  var emptyData = doc.getElementsByTagName("empty-cdata");
+  for (i = emptyData.length - 1; i >= 0; i--) {
+    var node = emptyData.item(i);
+    var cdata = doc.createCDATASection("");
+    node.parentNode.replaceChild(cdata, node);
+  }
+
+  return doc;
+}
+
+/**
+ * Run the extraction tests.
+ */
+function run_extract_test() {
+  var filePath = "/content/test/unit/test_delete_range.xml";
+  var doc = getParsedDocument(filePath);
+  var tests = doc.getElementsByTagName("test");
+
+  // Run our deletion, extraction tests.
+  for (i = 0; i < tests.length; i++) {
+    dump("Configuring for test " + i + "\n");
+    var currentTest = tests.item(i);
+
+    // Validate the test is properly formatted for what this harness expects.
+    var baseSource = currentTest.firstChild;
+    do_check_eq(baseSource.nodeName, "source");
+    var baseResult = baseSource.nextSibling;
+    do_check_eq(baseResult.nodeName, "result");
+    var baseExtract = baseResult.nextSibling;
+    do_check_eq(baseExtract.nodeName, "extract");
+    do_check_eq(baseExtract.nextSibling, null);
+
+    /* We do all our tests on DOM document fragments, derived from the test
+       element's children.  This lets us rip the various fragments to shreds,
+       while preserving the original elements so we can make more copies of
+       them.
+
+       After the range's extraction or deletion is done, we use
+       nsIDOM3Node.isEqualNode() between the altered source fragment and the
+       result fragment.  We also run isEqualNode() between the extracted
+       fragment and the fragment from the baseExtract node.  If they are not
+       equal, we have failed a test.
+
+       We also have to ensure the original nodes on the end points of the
+       range are still in the source fragment.  This is bug 332148.  The nodes
+       may not be replaced with equal but separate nodes.  The range extraction
+       may alter these nodes - in the case of text containers, they will - but
+       the nodes must stay there, to preserve references such as user data,
+       event listeners, etc.
+
+       First, an extraction test.
+     */
+
+    var resultFrag = getFragment(baseResult);
+    var extractFrag = getFragment(baseExtract);
+
+    dump("Extract contents test " + i + "\n\n");
+    var baseFrag = getFragment(baseSource);
+    var baseRange = getRange(baseSource, baseFrag);
+    var startContainer = baseRange.startContainer;
+    var endContainer = baseRange.endContainer;
+
+    var cutFragment = baseRange.extractContents();
+    dump("cutFragment: " + cutFragment + "\n");
+    if (cutFragment) {
+      do_check_true(extractFrag.isEqualNode(cutFragment));
+    } else {
+      do_check_eq(extractFrag.firstChild, null);
+    }
+    do_check_true(baseFrag.isEqualNode(resultFrag));
+
+    dump("Ensure the original nodes weren't extracted - test " + i + "\n\n");
+    walker = doc.createTreeWalker(baseFrag,
+                                  C_i.nsIDOMNodeFilter.SHOW_ALL,
+                                  null,
+                                  false);
+    var foundStart = false;
+    var foundEnd = false;
+    do {
+      do_check_true(walker.currentNode instanceof C_i.nsIDOM3Node);
+
+      if (walker.currentNode.isSameNode(startContainer)) {
+        foundStart = true;
+      }
+
+      if (walker.currentNode.isSameNode(endContainer)) {
+        // An end container node should not come before the start container node.
+        do_check_true(foundStart);
+        foundEnd = true;
+        break;
+      }
+    } while (walker.nextNode())
+    do_check_true(foundEnd);
+
+    /* Now, we reset our test for the deleteContents case.  This one differs
+       from the extractContents case only in that there is no extracted document
+       fragment to compare against.  So we merely compare the starting fragment,
+       minus the extracted content, against the result fragment.
+     */
+    dump("Delete contents test " + i + "\n\n");
+    baseFrag = getFragment(baseSource);
+    baseRange = getRange(baseSource, baseFrag);
+    var startContainer = baseRange.startContainer;
+    var endContainer = baseRange.endContainer;
+    baseRange.deleteContents();
+    do_check_true(baseFrag.isEqualNode(resultFrag));
+
+    dump("Ensure the original nodes weren't deleted - test " + i + "\n\n");
+    walker = doc.createTreeWalker(baseFrag,
+                                  C_i.nsIDOMNodeFilter.SHOW_ALL,
+                                  null,
+                                  false);
+    foundStart = false;
+    foundEnd = false;
+    do {
+      do_check_true(walker.currentNode instanceof C_i.nsIDOM3Node);
+
+      if (walker.currentNode.isSameNode(startContainer)) {
+        foundStart = true;
+      }
+
+      if (walker.currentNode.isSameNode(endContainer)) {
+        // An end container node should not come before the start container node.
+        do_check_true(foundStart);
+        foundEnd = true;
+        break;
+      }
+    } while (walker.nextNode())
+    do_check_true(foundEnd);
+
+    // Clean up after ourselves.
+    walker = null;
+
+    /* Whenever a DOM range is detached, we cannot use any methods on
+       it - including extracting its contents or deleting its contents.  It
+       should throw a NS_ERROR_DOM_INVALID_STATE_ERR exception.
+    */
+    dump("Detached range test\n");
+    var compareFrag = getFragment(baseSource);
+    baseFrag = getFragment(baseSource);
+    baseRange = getRange(baseSource, baseFrag);
+    baseRange.detach();
+    try {
+      var cutFragment = baseRange.extractContents();
+      do_throw("Should have thrown INVALID_STATE_ERR!");
+    } catch (e if (e instanceof C_i.nsIException &&
+                   e.result == INVALID_STATE_ERR)) {
+      // do nothing
+    }
+    do_check_true(compareFrag.isEqualNode(baseFrag));
+
+    try {
+      baseRange.deleteContents();
+      do_throw("Should have thrown INVALID_STATE_ERR!");
+    } catch (e if (e instanceof C_i.nsIException &&
+                   e.result == INVALID_STATE_ERR)) {
+      // do nothing
+    }
+    do_check_true(compareFrag.isEqualNode(baseFrag));
+  }
+}
+
+/**
+ * Miscellaneous tests not covered above.
+ */
+function run_miscellaneous_tests() {
+  var filePath = "/content/test/unit/test_delete_range.xml";
+  var doc = getParsedDocument(filePath);
+  var tests = doc.getElementsByTagName("test");
+
+  // Let's try some invalid inputs to our DOM range and see what happens.
+  var currentTest = tests.item(0);
+  baseSource = currentTest.firstChild;
+  baseResult = baseSource.nextSibling;
+  baseExtract = baseResult.nextSibling;
+
+  baseFrag = getFragment(baseSource);
+
+  baseRange = getRange(baseSource, baseFrag);
+  var startContainer = baseRange.startContainer;
+  var endContainer = baseRange.endContainer;
+  var startOffset = baseRange.startOffset;
+  var endOffset = baseRange.endOffset;
+
+  // Text range manipulation.
+  if ((endOffset > startOffset) &&
+      (startContainer == endContainer) &&
+      (startContainer instanceof C_i.nsIDOMText)) {
+    // Invalid start node
+    try {
+      baseRange.setStart(null, 0);
+      do_throw("Should have thrown NOT_OBJECT_ERR!");
+    } catch (e if (e instanceof C_i.nsIException &&
+                   e.result == NOT_OBJECT_ERR)) {
+      // do nothing
+    }
+
+    // Invalid start node
+    try {
+      baseRange.setStart({}, 0);
+      do_throw("Should have thrown SECURITY_ERR!");
+    } catch (e if (e instanceof C_i.nsIException &&
+                   e.result == SECURITY_ERR)) {
+      // do nothing
+    }
+
+    // Invalid index
+    try {
+      baseRange.setStart(startContainer, -1);
+      do_throw("Should have thrown INVALID_STATE_ERR!");
+    } catch (e if (e instanceof C_i.nsIException &&
+                   e.result == INDEX_SIZE_ERR)) {
+      // do nothing
+    }
+  
+    // Invalid index
+    var newOffset = startContainer instanceof C_i.nsIDOMText ?
+                      startContainer.nodeValue.length + 1 :
+                      startContainer.childNodes.length + 1;
+    try {
+      baseRange.setStart(startContainer, newOffset);
+      do_throw("Should have thrown INVALID_STATE_ERR!");
+    } catch (e if (e instanceof C_i.nsIException &&
+                   e.result == INDEX_SIZE_ERR)) {
+      // do nothing
+    }
+  
+    newOffset--;
+    // Valid index
+    baseRange.setStart(startContainer, newOffset);
+    do_check_eq(baseRange.startContainer, baseRange.endContainer);
+    do_check_eq(baseRange.startOffset, newOffset);
+    do_check_true(baseRange.collapsed);
+
+    // Valid index
+    baseRange.setEnd(startContainer, 0);
+    do_check_eq(baseRange.startContainer, baseRange.endContainer);
+    do_check_eq(baseRange.startOffset, 0);
+    do_check_true(baseRange.collapsed);
+  } else {
+    do_throw("The first test should be a text-only range test.  Test is invalid.")
+  }
+
+  /* See what happens when a range has a startContainer in one fragment, and an
+     endContainer in another.  According to the DOM spec, section 2.4, the range
+     should collapse to the new container and offset. */
+  baseRange = getRange(baseSource, baseFrag);
+  startContainer = baseRange.startContainer;
+  var startOffset = baseRange.startOffset;
+  endContainer = baseRange.endContainer;
+  var endOffset = baseRange.endOffset;
+
+  dump("External fragment test\n\n");
+
+  var externalTest = tests.item(1);
+  var externalSource = externalTest.firstChild;
+  var externalFrag = getFragment(externalSource);
+  var externalRange = getRange(externalSource, externalFrag);
+
+  baseRange.setEnd(externalRange.endContainer, 0);
+  do_check_eq(baseRange.startContainer, externalRange.endContainer);
+  do_check_eq(baseRange.startOffset, 0);
+  do_check_true(baseRange.collapsed);
+
+  /*
+  // XXX ajvincent if rv == WRONG_DOCUMENT_ERR, return false?
+  do_check_true(baseRange instanceof C_i.nsIDOMNSRange);
+  do_check_false(baseRange.isPointInRange(startContainer, startOffset));
+  do_check_false(baseRange.isPointInRange(startContainer, startOffset + 1));
+  do_check_false(baseRange.isPointInRange(endContainer, endOffset));
+  */
+
+  // Requested by smaug:  A range involving a comment as a document child.
+  doc = parser.parseFromString("<!-- foo --><foo/>", "application/xml");
+  do_check_true(doc instanceof C_i.nsIDOMDocumentRange);
+  do_check_eq(doc.childNodes.length, 2);
+  baseRange = doc.createRange();
+  baseRange.setStart(doc.firstChild, 1);
+  baseRange.setEnd(doc.firstChild, 2);
+  var frag = baseRange.extractContents();
+  do_check_eq(frag.childNodes.length, 1);
+  do_check_true(frag.firstChild instanceof C_i.nsIDOMComment);
+  do_check_eq(frag.firstChild.nodeValue, "f");
+
+  /* smaug also requested attribute tests.  Sadly, those are not yet supported
+     in ranges - see https://bugzilla.mozilla.org/show_bug.cgi?id=302775.
+   */
+}
+
+function run_test() {
+  run_extract_test();
+  run_miscellaneous_tests();
+}
