Bug 1409088: Fix destination insertion point removal algorithm. r=bz draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 19 Oct 2017 14:45:16 +0200
changeset 683620 3ed40d5479cd61674214c85f4675cbce537b8e22
parent 683619 93a0ed16ccce0cbab879040e69e7ca71a25cf3ff
child 736673 3e51d24b789ff4d483c72e954bf0dfb2f5d3b612
push id85405
push userbmo:emilio@crisal.io
push dateThu, 19 Oct 2017 21:44:46 +0000
reviewersbz
bugs1409088
milestone58.0a1
Bug 1409088: Fix destination insertion point removal algorithm. r=bz When an insertion point (a) is added to the document before another insertion point (b), and that insertion point matches nodes that used to match (b), the following happens in RedistributeAllNodes: * Loop through (a), and clear the existing insertion points on nodes distributed into it (none, since it was just inserted). * Go through the node pool and add the matched nodes. That makes the node (which already had (b) in the insertion point array) have [(b), (a)] as the insertion points. * Go through (b), and clear the existing insertion points on the nodes distributed to it. That used to do IndexOf() + SetLength(), but since (b) was the first node by then in the insertion point array, we'll leave the insertion point array empty, while (a) would still think that the node is distributed to it. This causes the bloom filter code, which loops through the flattened tree parents, to not insert any (because the node doesn't know about where it's inserted). Also, add a debug phase to verify the flat tree before restyling that would've caught this more clearly (happy to remove it if you don't think it's worth). We still can't assert that the insertion point is properly referenced due to the hacky way mInsertionPoints is cleared in HTMLContentElement::UpdateFallbackDistribution, but we'll still clear the insertion points either there, or on the rest of insertion point removal code in ShadowRoot::DistributeAllNodes. MozReview-Commit-ID: 9k2gnsAKMEe
dom/base/ShadowRoot.cpp
layout/base/ServoRestyleManager.cpp
layout/base/crashtests/1409088.html
layout/base/crashtests/crashtests.list
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -238,19 +238,20 @@ ShadowRoot::RemoveDestInsertionPoint(nsI
                                      nsTArray<nsIContent*>& aDestInsertionPoints)
 {
   // Remove the insertion point from the destination insertion points.
   // Also remove all succeeding insertion points because it is no longer
   // possible for the content to be distributed into deeper node trees.
   int32_t index = aDestInsertionPoints.IndexOf(aInsertionPoint);
 
   // It's possible that we already removed the insertion point while processing
-  // other insertion point removals.
+  // other insertion point removals / fallback content redistribution (which
+  // does DestInsertionPoints().Clear()).
   if (index >= 0) {
-    aDestInsertionPoints.SetLength(index);
+    aDestInsertionPoints.RemoveElementAt(index);
   }
 }
 
 void
 ShadowRoot::DistributionChanged()
 {
   // FIXME(emilio): We could be more granular in a bunch of cases.
   auto* host = GetHost();
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -1202,19 +1202,40 @@ ServoRestyleManager::DoProcessPendingRes
   styleSet->MaybeGCRuleTree();
 
   // Note: We are in the scope of |animationsWithDestroyedFrame|, so
   //       |mAnimationsWithDestroyedFrame| is still valid.
   MOZ_ASSERT(mAnimationsWithDestroyedFrame);
   mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
 }
 
+#ifdef DEBUG
+static void
+VerifyFlatTree(const nsIContent& aContent)
+{
+  StyleChildrenIterator iter(&aContent);
+
+  for (auto* content = iter.GetNextChild();
+       content;
+       content = iter.GetNextChild()) {
+    MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent);
+    VerifyFlatTree(*content);
+  }
+}
+#endif
+
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
+#ifdef DEBUG
+  if (auto* root = mPresContext->Document()->GetRootElement()) {
+    VerifyFlatTree(*root);
+  }
+#endif
+
   DoProcessPendingRestyles(ServoTraversalFlags::Empty);
 }
 
 void
 ServoRestyleManager::ProcessAllPendingAttributeAndStateInvalidations()
 {
   if (mSnapshots.IsEmpty()) {
     return;
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1409088.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<div></div>
+<script>
+let host = document.querySelector('div');
+let shadow = host.createShadowRoot();
+
+// Append two divs and three spans into host.
+host.innerHTML = '<div></div><span></span><div></div><span></span><span></span>';
+shadow.innerHTML = '<content select="div" id="divpoint"></content><content select="div, span" id="allpoint"></content>';
+
+let divPoint = shadow.getElementById("divpoint");
+let allPoint = shadow.getElementById("allpoint");
+
+shadow.removeChild(allPoint);
+shadow.insertBefore(allPoint, divPoint);
+</script>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -505,9 +505,10 @@ load 1398500.html
 load 1400438-1.html
 load 1400599-1.html
 load 1401739.html
 load 1401840.html
 load 1402476.html
 load 1404789-1.html
 load 1404789-2.html
 load 1406562.html
+load 1409088.html
 load 1409147.html