Bug 682463 - "ASSERTION: unexpected disconnected nodes" with DOM range, splitText. r=smaug
authorMats Palmgren <matspal@gmail.com>
Sat, 24 Sep 2011 02:56:38 +0200
changeset 77483 21c337884f31bb89c1ec37790160839d1678a1a2
parent 77482 737c2fdb9148d53789a2d0c389113fbdf1d34f6e
child 77484 8e0213fd66986ee7635dc759277645797e7a1096
push id21202
push usermbrubeck@mozilla.com
push dateSat, 24 Sep 2011 15:19:21 +0000
treeherdermozilla-central@95df67109868 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs682463
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 682463 - "ASSERTION: unexpected disconnected nodes" with DOM range, splitText. r=smaug
content/base/src/nsRange.cpp
content/base/src/nsRange.h
--- a/content/base/src/nsRange.cpp
+++ b/content/base/src/nsRange.cpp
@@ -266,72 +266,106 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 void
 nsRange::CharacterDataChanged(nsIDocument* aDocument,
                               nsIContent* aContent,
                               CharacterDataChangeInfo* aInfo)
 {
   NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned");
 
+  nsINode* newRoot = nsnull;
+  nsINode* newStartNode = nsnull;
+  nsINode* newEndNode = nsnull;
+  PRUint32 newStartOffset = 0;
+  PRUint32 newEndOffset = 0;
+
   // If the changed node contains our start boundary and the change starts
   // before the boundary we'll need to adjust the offset.
   if (aContent == mStartParent &&
       aInfo->mChangeStart < static_cast<PRUint32>(mStartOffset)) {
     if (aInfo->mDetails) {
       // splitText(), aInfo->mDetails->mNextSibling is the new text node
       NS_ASSERTION(aInfo->mDetails->mType ==
                    CharacterDataChangeInfo::Details::eSplit,
                    "only a split can start before the end");
       NS_ASSERTION(static_cast<PRUint32>(mStartOffset) <= aInfo->mChangeEnd,
                    "mStartOffset is beyond the end of this node");
-      mStartOffset = static_cast<PRUint32>(mStartOffset) - aInfo->mChangeStart;
-      mStartParent = aInfo->mDetails->mNextSibling;
+      newStartOffset = static_cast<PRUint32>(mStartOffset) - aInfo->mChangeStart;
+      newStartNode = aInfo->mDetails->mNextSibling;
+      if (NS_UNLIKELY(aContent == mRoot)) {
+        newRoot = IsValidBoundary(newStartNode);
+      }
     } else {
       // If boundary is inside changed text, position it before change
       // else adjust start offset for the change in length.
       mStartOffset = static_cast<PRUint32>(mStartOffset) <= aInfo->mChangeEnd ?
         aInfo->mChangeStart :
         mStartOffset + aInfo->mChangeStart - aInfo->mChangeEnd +
           aInfo->mReplaceLength;
     }
   }
 
-  // Do the same thing for the end boundary.
-  if (aContent == mEndParent && aInfo->mChangeStart < static_cast<PRUint32>(mEndOffset)) {
-    if (aInfo->mDetails) {
+  // Do the same thing for the end boundary, except for splitText of a node
+  // with no parent then only switch to the new node if the start boundary
+  // did so too (otherwise the range would end up with disconnected nodes).
+  if (aContent == mEndParent &&
+      aInfo->mChangeStart < static_cast<PRUint32>(mEndOffset)) {
+    if (aInfo->mDetails && (aContent->GetParent() || newStartNode)) {
       // splitText(), aInfo->mDetails->mNextSibling is the new text node
       NS_ASSERTION(aInfo->mDetails->mType ==
                    CharacterDataChangeInfo::Details::eSplit,
                    "only a split can start before the end");
       NS_ASSERTION(static_cast<PRUint32>(mEndOffset) <= aInfo->mChangeEnd,
                    "mEndOffset is beyond the end of this node");
-      mEndOffset = static_cast<PRUint32>(mEndOffset) - aInfo->mChangeStart;
-      mEndParent = aInfo->mDetails->mNextSibling;
+      newEndOffset = static_cast<PRUint32>(mEndOffset) - aInfo->mChangeStart;
+      newEndNode = aInfo->mDetails->mNextSibling;
     } else {
       mEndOffset = static_cast<PRUint32>(mEndOffset) <= aInfo->mChangeEnd ?
         aInfo->mChangeStart :
         mEndOffset + aInfo->mChangeStart - aInfo->mChangeEnd +
           aInfo->mReplaceLength;
     }
   }
 
   if (aInfo->mDetails &&
       aInfo->mDetails->mType == CharacterDataChangeInfo::Details::eMerge) {
     // normalize(), aInfo->mDetails->mNextSibling is the merged text node
     // that will be removed
     nsIContent* removed = aInfo->mDetails->mNextSibling;
     if (removed == mStartParent) {
-      mStartOffset = static_cast<PRUint32>(mStartOffset) + aInfo->mChangeStart;
-      mStartParent = aContent;
+      newStartOffset = static_cast<PRUint32>(mStartOffset) + aInfo->mChangeStart;
+      newStartNode = aContent;
+      if (NS_UNLIKELY(removed == mRoot)) {
+        newRoot = IsValidBoundary(newStartNode);
+      }
     }
     if (removed == mEndParent) {
-      mEndOffset = static_cast<PRUint32>(mEndOffset) + aInfo->mChangeStart;
-      mEndParent = aContent;
+      newEndOffset = static_cast<PRUint32>(mEndOffset) + aInfo->mChangeStart;
+      newEndNode = aContent;
+      if (NS_UNLIKELY(removed == mRoot)) {
+        newRoot = IsValidBoundary(newEndNode);
+      }
     }
   }
+  if (newStartNode || newEndNode) {
+    if (!newStartNode) {
+      newStartNode = mStartParent;
+      newStartOffset = mStartOffset;
+    }
+    if (!newEndNode) {
+      newEndNode = mEndParent;
+      newEndOffset = mEndOffset;
+    }
+    DoSetRange(newStartNode, newStartOffset, newEndNode, newEndOffset,
+               newRoot ? newRoot : mRoot.get()
+#ifdef DEBUG
+               , !newEndNode->GetParent()
+#endif
+               );
+  }
 }
 
 void
 nsRange::ContentInserted(nsIDocument* aDocument,
                          nsIContent* aContainer,
                          nsIContent* aChild,
                          PRInt32 aIndexInContainer)
 {
@@ -468,22 +502,26 @@ static PRUint32 GetNodeLength(nsINode *a
 // It's important that all setting of the range start/end points 
 // go through this function, which will do all the right voodoo
 // for content notification of range ownership.  
 // Calling DoSetRange with either parent argument null will collapse
 // the range to have both endpoints point to the other node
 void
 nsRange::DoSetRange(nsINode* aStartN, PRInt32 aStartOffset,
                     nsINode* aEndN, PRInt32 aEndOffset,
-                    nsINode* aRoot)
+                    nsINode* aRoot
+#ifdef DEBUG
+                    , bool aNotInsertedYet
+#endif
+                    )
 {
   NS_PRECONDITION((aStartN && aEndN && aRoot) ||
                   (!aStartN && !aEndN && !aRoot),
                   "Set all or none");
-  NS_PRECONDITION(!aRoot ||
+  NS_PRECONDITION(!aRoot || aNotInsertedYet ||
                   (nsContentUtils::ContentIsDescendantOf(aStartN, aRoot) &&
                    nsContentUtils::ContentIsDescendantOf(aEndN, aRoot) &&
                    aRoot == IsValidBoundary(aStartN) &&
                    aRoot == IsValidBoundary(aEndN)),
                   "Wrong root");
   NS_PRECONDITION(!aRoot ||
                   (aStartN->IsNodeOfType(nsINode::eCONTENT) &&
                    aEndN->IsNodeOfType(nsINode::eCONTENT) &&
--- a/content/base/src/nsRange.h
+++ b/content/base/src/nsRange.h
@@ -144,17 +144,23 @@ public:
                                      PRBool *outNodeAfter);
   static nsresult CompareNodeToRange(nsINode* aNode, nsIRange* aRange,
                                      PRBool *outNodeBefore,
                                      PRBool *outNodeAfter);
 
 protected:
   void DoSetRange(nsINode* aStartN, PRInt32 aStartOffset,
                   nsINode* aEndN, PRInt32 aEndOffset,
-                  nsINode* aRoot);
+                  nsINode* aRoot
+#ifdef DEBUG
+                  // CharacterDataChanged use this to disable an assertion since
+                  // the new text node of a splitText hasn't been inserted yet.
+                  , bool aNotInsertedYet = false
+#endif
+                  );
 };
 
 // Make a new nsIDOMRange object
 nsresult NS_NewRange(nsIDOMRange** aInstancePtrResult);
 
 // Make a new nsIRangeUtils object
 nsresult NS_NewRangeUtils(nsIRangeUtils** aInstancePtrResult);