Bug 1394935: Add a special case for marking something as dirty from invalidation code. r?bholley
Not sure about the name, or whether making it a static or not (a static seemed
slightly clearer, but nbd, feel free to bikeshed about it).
MozReview-Commit-ID: FHGmcoprN2Q
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -4240,24 +4240,24 @@ Element::AddSizeOfExcludingThis(nsWindow
}
}
}
}
}
#ifdef DEBUG
static bool
-BitIsPropagated(const Element* aElement, uint32_t aBit, nsINode* aRestyleRoot)
+BitsArePropagated(const Element* aElement, uint32_t aBits, nsINode* aRestyleRoot)
{
const Element* curr = aElement;
while (curr) {
if (curr == aRestyleRoot) {
return true;
}
- if (!curr->HasFlag(aBit)) {
+ if (!curr->HasAllFlags(aBits)) {
return false;
}
nsINode* parentNode = curr->GetParentNode();
curr = curr->GetFlattenedTreeParentElementForStyle();
MOZ_ASSERT_IF(!curr,
parentNode == aElement->OwnerDoc() ||
parentNode == parentNode->OwnerDoc()->GetRootElement());
}
@@ -4322,34 +4322,38 @@ PropagateBits(Element* aElement, uint32_
// * If we reach the document root, we then propagate the bits associated with
// the restyle root up the tree until we cross the path of the new root. Once
// we find this common ancestor, we record it as the restyle root, and then
// clear the bits between the new restyle root and the document root.
// * If we have dirty content beneath multiple "document style traversal roots"
// (which are the main DOM + each piece of document-level native-anoymous
// content), we set the restyle root to the nsINode of the document itself.
// This is the bail-out case where we traverse everything.
+//
+// Note that, since we track a root, we try to optimize the case where an
+// element under the current root is dirtied, that's why we don't trivially use
+// `nsContentUtils::GetCommonFlattenedTreeAncestorForStyle`.
static void
-NoteDirtyElement(Element* aElement, uint32_t aBit)
+NoteDirtyElement(Element* aElement, uint32_t aBits)
{
MOZ_ASSERT(aElement->IsInComposedDoc());
MOZ_ASSERT(aElement->IsStyledByServo());
Element* parent = aElement->GetFlattenedTreeParentElementForStyle();
if (MOZ_LIKELY(parent)) {
// If our parent is unstyled, we can inductively assume that it will be
// traversed when the time is right, and that the traversal will reach us
// when it happens. Nothing left to do.
if (!parent->HasServoData()) {
return;
}
// Similarly, if our parent already has the bit we're propagating, we can
// assume everything is already set up.
- if (parent->HasFlag(aBit)) {
+ if (parent->HasAllFlags(aBits)) {
MOZ_ASSERT(aElement->GetComposedDoc()->GetServoRestyleRoot());
return;
}
// If the parent is styled but is display:none, we're done.
//
// We check for a frame to reduce the cases where we need the FFI call.
if (!parent->GetPrimaryFrame() && Servo_Element_IsDisplayNone(parent)) {
@@ -4367,59 +4371,84 @@ NoteDirtyElement(Element* aElement, uint
// The bit checks below rely on this to arrive to useful conclusions about the
// shape of the tree.
AssertNoBitsPropagatedFrom(existingRoot);
// If there's no existing restyle root, or if the root is already aElement,
// just note the root+bits and return.
if (!existingRoot || existingRoot == aElement) {
- doc->SetServoRestyleRoot(aElement, existingBits | aBit);
+ doc->SetServoRestyleRoot(aElement, existingBits | aBits);
return;
}
// There is an existing restyle root - walk up the tree from our element,
// propagating bits as we go.
- const bool reachedDocRoot = !parent || !PropagateBits(parent, aBit, existingRoot);
+ const bool reachedDocRoot = !parent || !PropagateBits(parent, aBits, 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);
+ doc->SetServoRestyleRoot(existingRoot, existingBits | aBits);
} 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)) {
MOZ_ASSERT(commonAncestor == aElement ||
commonAncestor == nsContentUtils::GetCommonFlattenedTreeAncestorForStyle(aElement, rootParent));
// 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);
+ doc->SetServoRestyleRoot(commonAncestor, existingBits | aBits);
Element* curr = commonAncestor;
while ((curr = curr->GetFlattenedTreeParentElementForStyle())) {
- MOZ_ASSERT(curr->HasFlag(aBit));
- curr->UnsetFlags(aBit);
+ MOZ_ASSERT(curr->HasAllFlags(aBits));
+ curr->UnsetFlags(aBits);
}
} 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);
+ doc->SetServoRestyleRoot(doc, existingBits | aBits);
}
}
MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
aElement, doc->GetServoRestyleRoot()));
MOZ_ASSERT(aElement == doc->GetServoRestyleRoot() ||
- BitIsPropagated(parent, aBit, doc->GetServoRestyleRoot()));
- MOZ_ASSERT(doc->GetServoRestyleRootDirtyBits() & aBit);
+ BitsArePropagated(parent, aBits, doc->GetServoRestyleRoot()));
+ MOZ_ASSERT(doc->GetServoRestyleRootDirtyBits() & aBits);
+}
+
+void
+Element::NoteDirtySubtreeForServo()
+{
+ MOZ_ASSERT(IsInComposedDoc());
+ MOZ_ASSERT(HasServoData());
+
+ nsIDocument* doc = GetComposedDoc();
+ nsINode* existingRoot = doc->GetServoRestyleRoot();
+ uint32_t existingBits = existingRoot ? doc->GetServoRestyleRootDirtyBits() : 0;
+
+ if (existingRoot &&
+ existingRoot->IsElement() &&
+ existingRoot != this &&
+ nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
+ existingRoot->AsElement(), this)) {
+ PropagateBits(existingRoot->AsElement()->GetFlattenedTreeParentElementForStyle(),
+ existingBits,
+ this);
+
+ doc->ClearServoRestyleRoot();
+ }
+
+ NoteDirtyElement(this, existingBits | ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
}
void
Element::NoteDirtyForServo()
{
NoteDirtyElement(this, ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
}
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -468,16 +468,26 @@ public:
Directionality GetComputedDirectionality() const;
static const uint32_t kAllServoDescendantBits =
ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO |
ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO |
NODE_DESCENDANTS_NEED_FRAMES;
+ /**
+ * Notes that something in the given subtree of this element needs dirtying,
+ * and that all the relevant dirty bits have already been propagated up to the
+ * element.
+ *
+ * This is important because `NoteDirtyForServo` uses the dirty bits to reason
+ * about the shape of the tree, so we can't just call into there.
+ */
+ void NoteDirtySubtreeForServo();
+
void NoteDirtyForServo();
void NoteAnimationOnlyDirtyForServo();
void NoteDescendantsNeedFramesForServo();
bool HasDirtyDescendantsForServo() const
{
MOZ_ASSERT(IsStyledByServo());
return HasFlag(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -373,16 +373,23 @@ Gecko_UnsetNodeFlags(RawGeckoNodeBorrowe
void
Gecko_NoteDirtyElement(RawGeckoElementBorrowed aElement)
{
MOZ_ASSERT(NS_IsMainThread());
const_cast<Element*>(aElement)->NoteDirtyForServo();
}
void
+Gecko_NoteDirtySubtreeForInvalidation(RawGeckoElementBorrowed aElement)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ const_cast<Element*>(aElement)->NoteDirtySubtreeForServo();
+}
+
+void
Gecko_NoteAnimationOnlyDirtyElement(RawGeckoElementBorrowed aElement)
{
MOZ_ASSERT(NS_IsMainThread());
const_cast<Element*>(aElement)->NoteAnimationOnlyDirtyForServo();
}
CSSPseudoElementType
Gecko_GetImplementedPseudo(RawGeckoElementBorrowed aElement)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -376,16 +376,17 @@ void Gecko_SetContentDataImageValue(nsSt
mozilla::css::ImageValue* aImageValue);
nsStyleContentData::CounterFunction* Gecko_SetCounterFunction(
nsStyleContentData* content_data, nsStyleContentType type);
// Dirtiness tracking.
void Gecko_SetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
void Gecko_UnsetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
void Gecko_NoteDirtyElement(RawGeckoElementBorrowed element);
+void Gecko_NoteDirtySubtreeForInvalidation(RawGeckoElementBorrowed element);
void Gecko_NoteAnimationOnlyDirtyElement(RawGeckoElementBorrowed element);
// Incremental restyle.
mozilla::CSSPseudoElementType Gecko_GetImplementedPseudo(RawGeckoElementBorrowed element);
// We'd like to return `nsChangeHint` here, but bindgen bitfield enums don't
// work as return values with the Linux 32-bit ABI at the moment because
// they wrap the value in a struct.
uint32_t Gecko_CalcStyleDifference(ServoStyleContextBorrowed old_style,