Bug 1439395: Clear Servo data only when the DOM is in a consistent state. r=bholley

We used to do it this way effectively until I fixed it in bug 1400936.

Per the list of fuzz bugs that bug has in the "Depends on" field, some of those
without a super-clear fix, and others that aren't listed in there, and all the
complexity we had to deal with while receiving restyle requests mid-unbind, etc,
I think this is the right call.

This clears data on RestyleManager::ContentRemoved for non-anonymous nodes, and
on UnbindFromTree for subtrees rooted at anonymous nodes.

This will hopefully yield enforceable invariants.

MozReview-Commit-ID: IMwX5Uh1apv

--HG--
extra : rebase_source : 6cafc4499c9b80cbc96f1c4d1496e524f59e3c4d
This commit is contained in:
Emilio Cobos Álvarez 2018-02-19 14:46:38 +01:00
Родитель c54dd64c7b
Коммит f3fc2e4852
4 изменённых файлов: 19 добавлений и 87 удалений

Просмотреть файл

@ -69,6 +69,7 @@
#include "mozilla/EventStates.h" #include "mozilla/EventStates.h"
#include "mozilla/InternalMutationEvent.h" #include "mozilla/InternalMutationEvent.h"
#include "mozilla/MouseEvents.h" #include "mozilla/MouseEvents.h"
#include "mozilla/ServoRestyleManager.h"
#include "mozilla/SizeOfState.h" #include "mozilla/SizeOfState.h"
#include "mozilla/TextEditor.h" #include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h" #include "mozilla/TextEvents.h"
@ -1925,6 +1926,15 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
// Fully exit full-screen. // Fully exit full-screen.
nsIDocument::ExitFullscreenInDocTree(OwnerDoc()); nsIDocument::ExitFullscreenInDocTree(OwnerDoc());
} }
if (HasServoData()) {
MOZ_ASSERT(IsInAnonymousSubtree());
MOZ_ASSERT(document && document->IsStyledByServo());
ClearServoData(document);
} else if (document && document->GetServoRestyleRoot() == this) {
document->ClearServoRestyleRoot();
}
if (aNullParent) { if (aNullParent) {
if (GetParent() && GetParent()->IsInUncomposedDoc()) { if (GetParent() && GetParent()->IsInUncomposedDoc()) {
// Update the editable descendant count in the ancestors before we // Update the editable descendant count in the ancestors before we
@ -1992,19 +2002,6 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
} }
} }
// Computed style data isn't useful for detached nodes, and we'll need to
// recompute it anyway if we ever insert the nodes back into a document.
if (IsStyledByServo()) {
if (document) {
ClearServoData(document);
} else {
MOZ_ASSERT(!HasServoData());
MOZ_ASSERT(!HasAnyOfFlags(kAllServoDescendantBits | NODE_NEEDS_FRAME));
}
} else {
MOZ_ASSERT(!HasServoData());
}
// Editable descendant count only counts descendants that // Editable descendant count only counts descendants that
// are in the uncomposed document. // are in the uncomposed document.
ResetEditableDescendantCount(); ResetEditableDescendantCount();
@ -2108,24 +2105,8 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
shadowRoot->SetIsComposedDocParticipant(false); shadowRoot->SetIsComposedDocParticipant(false);
} }
// Unbinding of children is the only point in time where we don't enforce the MOZ_ASSERT_IF(IsStyledByServo(), !HasAnyOfFlags(kAllServoDescendantBits));
// "child has style data implies parent has it too" invariant. MOZ_ASSERT(!document || document->GetServoRestyleRoot() != this);
//
// As such, the restyle root tracking may incorrectly end up setting dirty
// bits on the parent chain when moving from a not yet unbound root with
// already unbound parents to a root higher up in the tree, so we clear those
// (again, since they're also cleared in ClearServoData) here.
//
// This can happen when the element changes the state of some ancestor up in
// the tree, for example.
//
// Note that clearing the data itself here would have its own set of problems,
// since the invariant we'd be breaking in that case is "HasServoData()
// implies InComposedDoc()", which we rely on in various places.
UnsetFlags(kAllServoDescendantBits);
if (document && document->GetServoRestyleRoot() == this) {
document->ClearServoRestyleRoot();
}
} }
nsDOMCSSAttributeDeclaration* nsDOMCSSAttributeDeclaration*
@ -4516,7 +4497,6 @@ BitsArePropagated(const Element* aElement, uint32_t aBits, nsINode* aRestyleRoot
nsINode* parentNode = curr->GetParentNode(); nsINode* parentNode = curr->GetParentNode();
curr = curr->GetFlattenedTreeParentElementForStyle(); curr = curr->GetFlattenedTreeParentElementForStyle();
MOZ_ASSERT_IF(!curr, MOZ_ASSERT_IF(!curr,
!parentNode || // can only happen mid-unbind.
parentNode == aElement->OwnerDoc() || parentNode == aElement->OwnerDoc() ||
parentNode == parentNode->OwnerDoc()->GetRootElement()); parentNode == parentNode->OwnerDoc()->GetRootElement());
} }
@ -4532,24 +4512,6 @@ AssertNoBitsPropagatedFrom(nsINode* aRoot)
return; return;
} }
// If we are in the middle of unbinding a subtree, then the bits on elements
// in that subtree (and which we haven't yet unbound) could be dirty.
// Just skip asserting the absence of bits on those element that are in
// the unbinding subtree. (The incorrect bits will only cause us to
// incorrectly choose a new restyle root if the newly dirty element is
// also within the unbinding subtree, so it is OK to leave them there.)
for (nsINode* n = aRoot; n; n = n->GetFlattenedTreeParentElementForStyle()) {
if (!n->IsInComposedDoc()) {
// Find the top-most element that is marked as no longer in the document,
// so we can start checking bits from its parent.
do {
aRoot = n;
n = n->GetFlattenedTreeParentElementForStyle();
} while (n && !n->IsInComposedDoc());
break;
}
}
auto* element = aRoot->GetFlattenedTreeParentElementForStyle(); auto* element = aRoot->GetFlattenedTreeParentElementForStyle();
while (element) { while (element) {
MOZ_ASSERT(!element->HasAnyOfFlags(Element::kAllServoDescendantBits)); MOZ_ASSERT(!element->HasAnyOfFlags(Element::kAllServoDescendantBits));

Просмотреть файл

@ -1193,38 +1193,21 @@ void
nsIContent::SetAssignedSlot(HTMLSlotElement* aSlot) nsIContent::SetAssignedSlot(HTMLSlotElement* aSlot)
{ {
MOZ_ASSERT(aSlot || GetExistingExtendedContentSlots()); MOZ_ASSERT(aSlot || GetExistingExtendedContentSlots());
nsExtendedContentSlots* slots = ExtendedContentSlots(); ExtendedContentSlots()->mAssignedSlot = aSlot;
RefPtr<HTMLSlotElement> oldSlot = slots->mAssignedSlot.forget();
slots->mAssignedSlot = aSlot;
if (oldSlot != aSlot && IsElement() && AsElement()->HasServoData()) {
ServoRestyleManager::ClearServoDataFromSubtree(AsElement());
}
} }
void void
nsIContent::SetXBLInsertionPoint(nsIContent* aContent) nsIContent::SetXBLInsertionPoint(nsIContent* aContent)
{ {
nsCOMPtr<nsIContent> oldInsertionPoint = nullptr;
if (aContent) { if (aContent) {
nsExtendedContentSlots* slots = ExtendedContentSlots(); nsExtendedContentSlots* slots = ExtendedContentSlots();
SetFlags(NODE_MAY_BE_IN_BINDING_MNGR); SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
oldInsertionPoint = slots->mXBLInsertionPoint.forget();
slots->mXBLInsertionPoint = aContent; slots->mXBLInsertionPoint = aContent;
} else { } else {
if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) { if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) {
oldInsertionPoint = slots->mXBLInsertionPoint.forget();
slots->mXBLInsertionPoint = nullptr; slots->mXBLInsertionPoint = nullptr;
} }
} }
// We just changed the flattened tree, so any Servo style data is now invalid.
// We rely on nsXBLService::LoadBindings to re-traverse the subtree afterwards.
if (oldInsertionPoint != aContent && IsElement() &&
AsElement()->HasServoData()) {
ServoRestyleManager::ClearServoDataFromSubtree(AsElement());
}
} }
nsresult nsresult

Просмотреть файл

@ -63,27 +63,8 @@ nsIDocument::SetServoRestyleRoot(nsINode* aRoot, uint32_t aDirtyBits)
{ {
MOZ_ASSERT(aRoot); MOZ_ASSERT(aRoot);
// NOTE(emilio): The !aRoot->IsElement() check allows us to handle cases where
// we change the restyle root during unbinding of a subtree where the root is
// not unbound yet (and thus hasn't cleared the restyle root yet).
//
// In that case the tree can be in a somewhat inconsistent state (with the
// document no longer being the subtree root of the current root, but the root
// not having being unbound first).
//
// In that case, given there's no common ancestor, aRoot should be the
// document, and we allow that, provided that the previous root will
// eventually be unbound and the dirty bits will be cleared.
//
// If we want to enforce calling into this method with the tree in a
// consistent state, we'd need to move all the state changes that happen on
// content unbinding for parents, like fieldset validity stuff and ancestor
// direction changes off script runners or, alternatively, nulling out the
// document and parent node _after_ nulling out the children's, and then
// remove that line.
MOZ_ASSERT(!mServoRestyleRoot || MOZ_ASSERT(!mServoRestyleRoot ||
mServoRestyleRoot == aRoot || mServoRestyleRoot == aRoot ||
!aRoot->IsElement() ||
nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(mServoRestyleRoot, aRoot)); nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(mServoRestyleRoot, aRoot));
MOZ_ASSERT(aRoot == aRoot->OwnerDocAsNode() || aRoot->IsElement()); MOZ_ASSERT(aRoot == aRoot->OwnerDocAsNode() || aRoot->IsElement());
mServoRestyleRoot = aRoot; mServoRestyleRoot = aRoot;

Просмотреть файл

@ -252,6 +252,12 @@ RestyleManager::ContentRemoved(nsINode* aContainer,
nsIContent* aOldChild, nsIContent* aOldChild,
nsIContent* aFollowingSibling) nsIContent* aFollowingSibling)
{ {
// Computed style data isn't useful for detached nodes, and we'll need to
// recompute it anyway if we ever insert the nodes back into a document.
if (IsServo() && aOldChild->IsElement()) {
ServoRestyleManager::ClearServoDataFromSubtree(aOldChild->AsElement());
}
// The container might be a document or a ShadowRoot. // The container might be a document or a ShadowRoot.
if (!aContainer->IsElement()) { if (!aContainer->IsElement()) {
return; return;