Bug 1394935: Make the restyle root thing more obviously correct and less error prone. r?bholley draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 30 Aug 2017 23:35:59 +0200
changeset 656232 804add0a63efba3b852b27a451d763a1748da2d8
parent 656231 513b2039e111a0f9fdb1ab914590fc9346d4ba6e
child 729057 0c82abc4e8a0413c83610b9475f05a46b86757ea
push id77122
push userbmo:emilio@crisal.io
push dateWed, 30 Aug 2017 21:45:24 +0000
reviewersbholley
bugs1394935, 1363805
milestone57.0a1
Bug 1394935: Make the restyle root thing more obviously correct and less error prone. r?bholley This is the actual fix. Using the restyle bits to figure out who is our common ancestor is pretty error prone, and only holds when we're the only thing controlling the flags, which is not true due to the stylesheet invalidation, and probably soon due to bug 1363805. Also, there are other cases that fail the flattened tree assertion without this patch, which I haven't debugged yet. But in any case, this also makes the code hopefully easier to follow. This makes the assertion hold, and should be pretty equivalent perf-wise (sure, we use an AutoTArray instead of just walking the tree, but we do it just once, and that function is pretty optimized anyway). I found a test-case hardish to construct, given the stuff I thought about ended up using other bits and thus we happened to do the right thing. I can try to construct a crashtest tomorrow if you insist, though I think we're well covered, given the descendant assertions failed without my patch. MozReview-Commit-ID: K1ZJVMztwBh
dom/base/Element.cpp
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -4262,29 +4262,27 @@ BitIsPropagated(const Element* aElement,
                   parentNode == parentNode->OwnerDoc()->GetRootElement());
   }
   return true;
 }
 #endif
 
 // Sets |aBits| on aElement and all of its flattened-tree ancestors up to and
 // including aStopAt or the root element (whichever is encountered first).
-static inline Element*
+static void
 PropagateBits(Element* aElement, uint32_t aBits, nsINode* aStopAt)
 {
   Element* curr = aElement;
   while (curr && !curr->HasAllFlags(aBits)) {
     curr->SetFlags(aBits);
     if (curr == aStopAt) {
       break;
     }
     curr = curr->GetFlattenedTreeParentElementForStyle();
   }
-
-  return curr;
 }
 
 // Notes that a given element is "dirty" with respect to the given descendants
 // bit (which may be one of dirty descendants, dirty animation descendants, or
 // need frame construction for descendants).
 //
 // This function operates on the dirty element itself, despite the fact that the
 // bits are generally used to describe descendants. This allows restyle roots
@@ -4343,53 +4341,46 @@ NoteDirtyElement(Element* aElement, uint
   }
 
   nsIDocument* doc = aElement->GetComposedDoc();
   if (nsIPresShell* shell = doc->GetShell()) {
     shell->EnsureStyleFlush();
   }
 
   // If there's no existing restyle root, or if the root is already aElement,
-  // just note the root+bits and return.
+  // just note the root bits and return.
   nsINode* existingRoot = doc->GetServoRestyleRoot();
   uint32_t existingBits = existingRoot ? doc->GetServoRestyleRootDirtyBits() : 0;
   if (!existingRoot || existingRoot == aElement) {
     doc->SetServoRestyleRoot(aElement, existingBits | aBit);
     return;
   }
 
-  // There is an existing restyle root - walk up the tree from our element,
-  // propagating bits as wel go.
-  const bool reachedDocRoot = !parent || !PropagateBits(parent, aBit, existingRoot);
-
-  if (!reachedDocRoot || existingRoot == doc) {
-      // We're a descendant of the existing root. All that's left to do is to
-      // make sure the bit we propagated is also registered on the root.
-      doc->SetServoRestyleRoot(existingRoot, existingBits | aBit);
+  nsINode* newRoot;
+  if (!parent || existingRoot == doc) {
+    newRoot = doc;
+  } else if (existingRoot == parent) {
+    newRoot = existingRoot;
   } else {
-    // We reached the root without crossing the pre-existing restyle root. We
-    // now need to find the nearest common ancestor, so climb up from the
-    // existing root, extending bits along the way.
-    Element* rootParent = existingRoot->GetFlattenedTreeParentElementForStyle();
-    if (Element* commonAncestor = PropagateBits(rootParent, existingBits, aElement)) {
-      // We found a common ancestor. Make that the new style root, and clear the
-      // bits between the new style root and the document root.
-      doc->SetServoRestyleRoot(commonAncestor, existingBits | aBit);
-      Element* curr = commonAncestor;
-      while ((curr = curr->GetFlattenedTreeParentElementForStyle())) {
-        MOZ_ASSERT(curr->HasFlag(aBit));
-        curr->UnsetFlags(aBit);
-      }
-    } else {
-      // We didn't find a common ancestor element. That means we're descended
-      // from two different document style roots, so the common ancestor is the
-      // document.
-      doc->SetServoRestyleRoot(doc, existingBits | aBit);
-    }
-  }
+    newRoot = nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(
+      parent, existingRoot->AsElement());
+  }
+
+  if (!newRoot) {
+    newRoot = doc;
+  }
+
+  PropagateBits(parent, aBit, newRoot);
+  if (existingRoot != newRoot) {
+    PropagateBits(existingRoot->GetFlattenedTreeParentElementForStyle(),
+                  existingBits,
+                  newRoot);
+  }
+
+  doc->SetServoRestyleRoot(newRoot, existingBits | aBit);
 
   MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
              nsContentUtils::ContentIsFlattenedTreeDescendantOf(
                aElement, doc->GetServoRestyleRoot()));
   MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
              BitIsPropagated(parent, aBit, doc->GetServoRestyleRoot()));
   MOZ_ASSERT(doc->GetServoRestyleRootDirtyBits() & aBit);
 }
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -39,16 +39,17 @@
 #include "mozilla/LoadInfo.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/CustomElementRegistry.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMExceptionBinding.h"
 #include "mozilla/dom/DOMTypes.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInlines.h"
 #include "mozilla/dom/FileSystemSecurity.h"
 #include "mozilla/dom/FileBlobImpl.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
 #include "mozilla/dom/HTMLContentElement.h"
 #include "mozilla/dom/HTMLShadowElement.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/Promise.h"
@@ -2768,16 +2769,26 @@ nsContentUtils::GetCommonFlattenedTreeAn
                                                      nsIContent* aContent2)
 {
   return GetCommonAncestorInternal(aContent1, aContent2, [](nsIContent* aContent) {
     return aContent->GetFlattenedTreeParent();
   });
 }
 
 /* static */
+Element*
+nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(Element* aElement1,
+                                                       Element* aElement2)
+{
+  return GetCommonAncestorInternal(aElement1, aElement2, [](Element* aElement) {
+    return aElement->GetFlattenedTreeParentElementForStyle();
+  });
+}
+
+/* static */
 bool
 nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2)
 {
   return (aNode2->CompareDocumentPosition(*aNode1) &
     (nsIDOMNode::DOCUMENT_POSITION_PRECEDING |
      nsIDOMNode::DOCUMENT_POSITION_DISCONNECTED)) ==
     nsIDOMNode::DOCUMENT_POSITION_PRECEDING;
 }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -397,16 +397,23 @@ public:
     if (aContent1 == aContent2) {
       return aContent1;
     }
 
     return GetCommonFlattenedTreeAncestorHelper(aContent1, aContent2);
   }
 
   /**
+   * Returns the common flattened tree ancestor from the point of view of the
+   * style system, if any, for two given content nodes.
+   */
+  static Element* GetCommonFlattenedTreeAncestorForStyle(
+      Element* aElement1, Element* aElement2);
+
+  /**
    * Returns true if aNode1 is before aNode2 in the same connected
    * tree.
    */
   static bool PositionIsBefore(nsINode* aNode1, nsINode* aNode2);
 
   /**
    *  Utility routine to compare two "points", where a point is a
    *  node/offset pair