Bug 409380, support 'unconnected' ranges , r+sr+a=sicking
authorOlli.Pettay@helsinki.fi
Sun, 24 Feb 2008 04:46:09 -0800
changeset 12178 8522ffb5acb28b4d9e7759587547247da452ecb5
parent 12177 c0a8f4e30783b54efcf9089b1a801caef26e8791
child 12179 cf9467cef3b4bd812751c65287d3cadff3ce8b77
push idunknown
push userunknown
push dateunknown
bugs409380
milestone1.9b4pre
Bug 409380, support 'unconnected' ranges , r+sr+a=sicking
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
content/base/src/nsRange.cpp
content/base/src/nsRange.h
content/base/test/Makefile.in
content/base/test/test_bug409380.html
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -262,21 +262,23 @@ public:
       nsIDOM3Node::DOCUMENT_POSITION_PRECEDING;
   }
 
   /**
    *  Utility routine to compare two "points", where a point is a
    *  node/offset pair
    *  Returns -1 if point1 < point2, 1, if point1 > point2,
    *  0 if error or if point1 == point2.
-   *  NOTE! The two nodes MUST be in the same connected subtree!
-   *  if they are not the result is undefined.
+   *  NOTE! If the two nodes aren't in the same connected subtree,
+   *  the result is undefined, but the optional aDisconnected parameter
+   *  is set to PR_TRUE.
    */
   static PRInt32 ComparePoints(nsINode* aParent1, PRInt32 aOffset1,
-                               nsINode* aParent2, PRInt32 aOffset2);
+                               nsINode* aParent2, PRInt32 aOffset2,
+                               PRBool* aDisconnected = nsnull);
 
   /**
    * Find the first child of aParent with a resolved tag matching
    * aNamespace and aTag. Both the explicit and anonymous children of
    * aParent are examined. The return value is not addrefed.
    *
    * XXXndeakin this should return the first child whether in anonymous or
    * explicit children, but currently XBL doesn't tell us the relative
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -1491,17 +1491,18 @@ nsContentUtils::ComparePosition(nsINode*
      nsIDOM3Node::DOCUMENT_POSITION_CONTAINS) :
     (nsIDOM3Node::DOCUMENT_POSITION_FOLLOWING |
      nsIDOM3Node::DOCUMENT_POSITION_CONTAINED_BY);    
 }
 
 /* static */
 PRInt32
 nsContentUtils::ComparePoints(nsINode* aParent1, PRInt32 aOffset1,
-                              nsINode* aParent2, PRInt32 aOffset2)
+                              nsINode* aParent2, PRInt32 aOffset2,
+                              PRBool* aDisconnected)
 {
   if (aParent1 == aParent2) {
     return aOffset1 < aOffset2 ? -1 :
            aOffset1 > aOffset2 ? 1 :
            0;
   }
 
   nsAutoTArray<nsINode*, 32> parents1, parents2;
@@ -1514,18 +1515,22 @@ nsContentUtils::ComparePoints(nsINode* a
   do {
     parents2.AppendElement(node2);
     node2 = node2->GetNodeParent();
   } while (node2);
 
   PRUint32 pos1 = parents1.Length() - 1;
   PRUint32 pos2 = parents2.Length() - 1;
 
-  NS_ASSERTION(parents1.ElementAt(pos1) == parents2.ElementAt(pos2),
+  NS_ASSERTION(parents1.ElementAt(pos1) == parents2.ElementAt(pos2) ||
+               aDisconnected,
                "disconnected nodes");
+  if (aDisconnected) {
+    *aDisconnected = (parents1.ElementAt(pos1) != parents2.ElementAt(pos2));
+  }
 
   // Find where the parent chains differ
   nsINode* parent = parents1.ElementAt(pos1);
   PRUint32 len;
   for (len = PR_MIN(pos1, pos2); len > 0; --len) {
     nsINode* child1 = parents1.ElementAt(--pos1);
     nsINode* child2 = parents2.ElementAt(--pos2);
     if (child1 != child2) {
--- a/content/base/src/nsRange.cpp
+++ b/content/base/src/nsRange.cpp
@@ -98,16 +98,17 @@ nsRange::CompareNodeToRange(nsIContent* 
   return CompareNodeToRange(aNode, range, outNodeBefore, outNodeAfter);
 }
 
 // static
 nsresult
 nsRange::CompareNodeToRange(nsIContent* aNode, nsIRange* aRange,
                             PRBool *outNodeBefore, PRBool *outNodeAfter)
 {
+  NS_ENSURE_STATE(aNode);
   // create a pair of dom points that expresses location of node:
   //     NODE(start), NODE(end)
   // Let incoming range be:
   //    {RANGE(start), RANGE(end)}
   // if (RANGE(start) <= NODE(start))  and (RANGE(end) => NODE(end))
   // then the Node is contained (completely) by the Range.
   
   nsresult rv;
@@ -137,24 +138,29 @@ nsRange::CompareNodeToRange(nsIContent* 
   }
 
   nsINode* rangeStartParent = range->GetStartParent();
   nsINode* rangeEndParent = range->GetEndParent();
   PRInt32 rangeStartOffset = range->StartOffset();
   PRInt32 rangeEndOffset = range->EndOffset();
 
   // is RANGE(start) <= NODE(start) ?
+  PRBool disconnected = PR_FALSE;
   *outNodeBefore = nsContentUtils::ComparePoints(rangeStartParent,
                                                  rangeStartOffset,
-                                                 parent, nodeStart) > 0;
+                                                 parent, nodeStart,
+                                                 &disconnected) > 0;
+  NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+
   // is RANGE(end) >= NODE(end) ?
   *outNodeAfter = nsContentUtils::ComparePoints(rangeEndParent,
                                                 rangeEndOffset,
-                                                parent, nodeEnd) < 0;
-
+                                                parent, nodeEnd,
+                                                &disconnected) < 0;
+  NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
   return NS_OK;
 }
 
 /******************************************************
  * non members
  ******************************************************/
 
 nsresult
@@ -330,16 +336,28 @@ nsRange::NodeWillBeDestroyed(const nsINo
   NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned");
 
   // No need to detach, but reset positions so that the endpoints don't
   // end up disconnected from each other.
   // An alternative solution would be to make mRoot a strong pointer.
   DoSetRange(nsnull, 0, nsnull, 0, nsnull);
 }
 
+void
+nsRange::ParentChainChanged(nsIContent *aContent)
+{
+  NS_ASSERTION(mRoot == aContent, "Wrong ParentChainChanged notification?");
+  nsINode* newRoot = mRoot;
+  nsINode* tmp;
+  while ((tmp = newRoot->GetNodeParent())) {
+    newRoot = tmp;
+  }
+  DoSetRange(mStartParent, mStartOffset, mEndParent, mEndOffset, newRoot);
+}
+
 /********************************************************
  * Utilities for comparing points: API from nsIDOMNSRange
  ********************************************************/
 NS_IMETHODIMP
 nsRange::IsPointInRange(nsIDOMNode* aParent, PRInt32 aOffset, PRBool* aResult)
 {
   PRInt16 compareResult = 0;
   nsresult rv = ComparePoint(aParent, aOffset, &compareResult);
@@ -428,17 +446,19 @@ nsRange::DoSetRange(nsINode* aStartN, PR
                    aEndN->IsNodeOfType(nsINode::eCONTENT) &&
                    aRoot ==
                     static_cast<nsIContent*>(aStartN)->GetBindingParent() &&
                    aRoot ==
                     static_cast<nsIContent*>(aEndN)->GetBindingParent()) ||
                   (!aRoot->GetNodeParent() &&
                    (aRoot->IsNodeOfType(nsINode::eDOCUMENT) ||
                     aRoot->IsNodeOfType(nsINode::eATTRIBUTE) ||
-                    aRoot->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT))),
+                    aRoot->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) ||
+                     /*For backward compatibility*/
+                    aRoot->IsNodeOfType(nsINode::eCONTENT))),
                   "Bad root");
 
   if (mRoot != aRoot) {
     if (mRoot) {
       mRoot->RemoveMutationObserver(this);
     }
     if (aRoot) {
       aRoot->AddMutationObserver(this);
@@ -587,32 +607,24 @@ nsINode* nsRange::IsValidBoundary(nsINod
   root = aNode;
   while ((aNode = aNode->GetNodeParent())) {
     root = aNode;
   }
 
   NS_ASSERTION(!root->IsNodeOfType(nsINode::eDOCUMENT),
                "GetCurrentDoc should have returned a doc");
 
-  if (root->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) ||
-      root->IsNodeOfType(nsINode::eATTRIBUTE)) {
-    return root;
-  }
-
 #ifdef DEBUG_smaug
-  nsCOMPtr<nsIContent> cont = do_QueryInterface(root);
-  if (cont) {
-    nsAutoString name;
-    cont->Tag()->ToString(name);
-    printf("nsRange::IsValidBoundary: node is not a valid boundary point [%s]\n",
-           NS_ConvertUTF16toUTF8(name).get());
-  }
+  NS_WARN_IF_FALSE(root->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) ||
+                   root->IsNodeOfType(nsINode::eATTRIBUTE),
+                   "Creating a DOM Range using root which isn't in DOM!");
 #endif
 
-  return nsnull;
+  // We allow this because of backward compatibility.
+  return root;
 }
 
 nsresult nsRange::SetStart(nsIDOMNode* aParent, PRInt32 aOffset)
 {
   VALIDATE_ACCESS(aParent);
 
   nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
   nsINode* newRoot = IsValidBoundary(parent);
@@ -1521,17 +1533,17 @@ nsresult nsRange::CloneContents(nsIDOMDo
       tmpNode = node;
       res = tmpNode->GetParentNode(getter_AddRefs(node));
       if (NS_FAILED(res)) return res;
       if (!node) return NS_ERROR_FAILURE;
 
       tmpNode = clone;
       res = tmpNode->GetParentNode(getter_AddRefs(clone));
       if (NS_FAILED(res)) return res;
-      if (!node) return NS_ERROR_FAILURE;
+      if (!clone) return NS_ERROR_FAILURE;
     }
 
     commonCloneAncestor = clone;
   }
 
   *aReturn = clonedFrag;
   NS_IF_ADDREF(*aReturn);
 
@@ -1571,16 +1583,17 @@ nsresult nsRange::InsertNode(nsIDOMNode*
   if(NS_FAILED(res)) return res;
 
   nsCOMPtr<nsIDOMText> startTextNode(do_QueryInterface(tStartContainer));
   if (startTextNode)
   {
     nsCOMPtr<nsIDOMNode> tSCParentNode;
     res = tStartContainer->GetParentNode(getter_AddRefs(tSCParentNode));
     if(NS_FAILED(res)) return res;
+    NS_ENSURE_STATE(tSCParentNode);
     
     PRBool isCollapsed;
     res = GetCollapsed(&isCollapsed);
     if(NS_FAILED(res)) return res;
 
     PRInt32 tEndOffset;
     GetEndOffset(&tEndOffset);
 
--- a/content/base/src/nsRange.h
+++ b/content/base/src/nsRange.h
@@ -105,16 +105,17 @@ public:
                                nsIContent* aContainer,
                                nsIContent* aChild,
                                PRInt32 aIndexInContainer);
   virtual void ContentRemoved(nsIDocument* aDocument,
                               nsIContent* aContainer,
                               nsIContent* aChild,
                               PRInt32 aIndexInContainer);
   virtual void NodeWillBeDestroyed(const nsINode* aNode);
+  virtual void ParentChainChanged(nsIContent *aContent);
 
 private:
   // no copy's or assigns
   nsRange(const nsRange&);
   nsRange& operator=(const nsRange&);
 
   nsINode* IsValidBoundary(nsINode* aNode);
  
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -159,16 +159,17 @@ include $(topsrcdir)/config/rules.mk
 		file_bug326337_multipart.txt^headers^ \
 		test_bug402150.html \
 		test_bug402150.html^headers^ \
 		test_bug401662.html \
 		test_bug403852.html \
 		test_bug403868.xml \
 		test_bug405182.html \
 		test_bug403841.html \
+		test_bug409380.html \
 		test_bug410229.html \
 		test_bug415860.html \
 		test_bug414190.html \
 		test_bug414796.html \
 		test_bug416383.html \
 		test_bug417384.html \
 		test_bug418214.html \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug409380.html
@@ -0,0 +1,379 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=409380
+-->
+<head>
+  <title>Test for Bug 409380</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=409380">Mozilla Bug 409380</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 409380 **/
+
+function runRangeTest()
+{
+  // Bug 336381
+  // This is a case which can't be supported (at least not at the moment)
+  // because DOM Range requires that when the start boundary point is text node,
+  // it must be splitted. But in this case boundary point doesn't have parent,
+  // so splitting doesn't work.
+  var zz = document.getElementById("connectedDiv").firstChild;
+  zz.parentNode.removeChild(zz);
+  var range = document.createRange();
+  var hadException = false;
+  try {
+    range.setStart(zz, 0);
+    range.setEnd(zz, 0);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException ,
+     "It should be possible to select text node even if the node is not in DOM.");
+  hadException = false;
+  try {
+    r.insertNode(document.createTextNode('5'));
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException,
+     "It shouldn't be possible to insert text node to a detached range.");
+
+  // Bug 409380
+  var element = document.createElement('div');
+  var elementContent = "This is the element content";
+  element.innerHTML = elementContent;
+  range = element.ownerDocument.createRange();
+  hadException = false;
+  try {
+    range.selectNodeContents(element);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException,
+     "It should be possible to select node contents of a detached element.");
+  ok(range.toString() == elementContent, "Wrong range selection");
+
+  // range.selectNode can't succeed because selectNode sets boundary points
+  // to be parentNode, which in this testcase is null.
+  element = document.createElement('div');
+  range = element.ownerDocument.createRange();
+  hadException = false;
+  try {
+    range.selectNode(element);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "It shouldn't be possible to select a detached element.");
+
+  // Testing contextual fragment.
+  range = element.ownerDocument.createRange();
+  var cf = null;
+  var testContent = "<span>foo</span><span>bar</span>";
+  try {
+    range.selectNodeContents(element);
+    cf = range.createContextualFragment(testContent);
+    element.appendChild(cf);
+  } catch (ex) {
+  }
+  ok(cf, "Creating contextual fragment didn't succeed!");
+  ok(element.innerHTML == testContent, "Wrong innerHTML!");
+
+  element = document.createElement('div');
+  element.textContent = "foobar";
+  range = element.ownerDocument.createRange();
+  try {
+    range.selectNodeContents(element);
+    element.firstChild.insertData(3, " ");
+  } catch (ex) {
+  }
+  ok(range.toString() == "foo bar");
+
+  // Testing contextual fragment, but inserting element to document
+  // after creating range.
+  element = document.createElement('div');
+  range = element.ownerDocument.createRange();
+  document.body.appendChild(element);
+  cf = null;
+  testContent = "<span>foo</span><span>bar</span>";
+  try {
+    range.selectNodeContents(element);
+    cf = range.createContextualFragment(testContent);
+    element.appendChild(cf);
+  } catch (ex) {
+  }
+  ok(cf, "Creating contextual fragment didn't succeed!");
+  ok(element.innerHTML == testContent, "Wrong innerHTML!");
+
+  // Testing contextual fragment, but inserting element to document
+  // before creating range.
+  element = document.createElement('div');
+  document.body.appendChild(element);
+  range = element.ownerDocument.createRange();
+  cf = null;
+  testContent = "<span>foo</span><span>bar</span>";
+  try {
+    range.selectNodeContents(element);
+    cf = range.createContextualFragment(testContent);
+    element.appendChild(cf);
+  } catch (ex) {
+  }
+  ok(cf, "Creating contextual fragment didn't succeed!");
+  ok(element.innerHTML == testContent, "Wrong innerHTML!");
+
+  element = document.createElement('div');
+  var range2 = element.ownerDocument.createRange();
+  hadException = false;
+  try {
+    range2.selectNodeContents(element);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException,
+     "It should be possible to select node contents of a detached element.");
+
+  // Now the boundary points of range are in DOM, but boundary points of
+  // range2 aren't.
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.START_TO_START, range2);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.START_TO_END, range2);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.END_TO_START, range2);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.END_TO_END, range2);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.START_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.START_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.END_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.END_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  // range3 will be in document
+  element = document.createElement('div');
+  document.body.appendChild(element);
+  range3 = element.ownerDocument.createRange();
+  hadException = false;
+  try {
+    range3.selectNodeContents(element);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException,
+     "It should be possible to select node contents of a detached element.");
+
+  hadException = false;
+  try {
+    range3.compareBoundaryPoints(range.START_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range3.compareBoundaryPoints(range.START_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range3.compareBoundaryPoints(range.END_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range3.compareBoundaryPoints(range.END_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  // range4 won't be in document
+  element = document.createElement('div');
+  var range4 = element.ownerDocument.createRange();
+  hadException = false;
+  try {
+    range4.selectNodeContents(element);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException,
+     "It should be possible to select node contents of a detached element.");
+
+  hadException = false;
+  try {
+    range4.compareBoundaryPoints(range.START_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.START_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range4.compareBoundaryPoints(range.END_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  hadException = false;
+  try {
+    range4.compareBoundaryPoints(range.END_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(hadException, "Should have got an exception!");
+
+  // Compare range to itself.
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.START_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.START_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.END_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range.compareBoundaryPoints(range.END_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  // Attach startContainer of range2 to document.
+  ok(range2.startContainer == range2.endContainer, "Wrong container?");
+  document.body.appendChild(range2.startContainer);
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.START_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.START_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.END_TO_START, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+
+  hadException = false;
+  try {
+    range2.compareBoundaryPoints(range.END_TO_END, range);
+  } catch (ex) {
+    hadException = true;
+  }
+  ok(!hadException, "Shouldn't have got an exception!");
+  
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runRangeTest);
+
+</script>
+</pre>
+<div id="connectedDiv">zz</div>
+</body>
+</html>
+