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
--- 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