Bug 1388625 part 5. Implement wrapper anonymous box restyling in ServoRestyleManager. r?heycam draft
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 11 Aug 2017 00:10:26 -0400
changeset 644574 e846c5a2f4c48e051351736599248a71ba7f50f2
parent 644573 63e5e889b1a436ab4a6faf863738262956500736
child 644575 c5152ed28cf12228a8b49e0ab827771954ce307b
push id73471
push userbzbarsky@mozilla.com
push dateFri, 11 Aug 2017 04:10:55 +0000
reviewersheycam
bugs1388625
milestone57.0a1
Bug 1388625 part 5. Implement wrapper anonymous box restyling in ServoRestyleManager. r?heycam MozReview-Commit-ID: FRW4RCR1GT4
layout/base/ServoRestyleManager.cpp
layout/base/ServoRestyleManager.h
layout/base/crashtests/1388625-1.html
layout/base/crashtests/crashtests.list
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -45,40 +45,44 @@ FirstContinuationOrPartOfIBSplit(const n
   }
 
   return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 }
 
 static const nsIFrame*
 ExpectedOwnerForChild(const nsIFrame& aFrame)
 {
+  const nsIFrame* parent = aFrame.GetParent();
+  if (aFrame.IsTableFrame()) {
+    MOZ_ASSERT(parent->IsTableWrapperFrame());
+    parent = parent->GetParent();
+  }
+
   if (IsAnonBox(aFrame) && !aFrame.IsTextFrame()) {
-    return aFrame.GetParent()->IsViewportFrame() ? nullptr : aFrame.GetParent();
+    if (parent->IsLineFrame()) {
+      parent = parent->GetParent();
+    }
+    return parent->IsViewportFrame() ? nullptr : parent;
   }
 
   if (aFrame.IsBulletFrame()) {
-    return aFrame.GetParent();
+    return parent;
   }
 
   if (aFrame.IsLineFrame()) {
     // A ::first-line always ends up here via its block, which is therefore the
     // right expected owner.  That block can be an
     // anonymous box.  For example, we could have a ::first-line on a columnated
     // block; the blockframe is the column-content anonymous box in that case.
     // So we don't want to end up in the code below, which steps out of anon
     // boxes.  Just return the parent of the line frame, which is the block.
-    return aFrame.GetParent();
+    return parent;
   }
 
-  const nsIFrame* parent = FirstContinuationOrPartOfIBSplit(aFrame.GetParent());
-
-  if (aFrame.IsTableFrame()) {
-    MOZ_ASSERT(parent->IsTableWrapperFrame());
-    parent = FirstContinuationOrPartOfIBSplit(parent->GetParent());
-  }
+  parent = FirstContinuationOrPartOfIBSplit(parent);
 
   // We've handled already anon boxes and bullet frames, so now we're looking at
   // a frame of a DOM element or pseudo. Hop through anon and line-boxes
   // generated by our DOM parent, and go find the owner frame for it.
   while (parent && (IsAnonBox(*parent) || parent->IsLineFrame())) {
     auto* pseudo = parent->StyleContext()->GetPseudo();
     if (pseudo == nsCSSAnonBoxes::tableWrapper) {
       const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild();
@@ -114,16 +118,164 @@ ServoRestyleState::ChangesHandledFor(con
   }
 
   MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame),
              "Missed some frame in the hierarchy?");
   return mChangesHandled;
 }
 #endif
 
+void
+ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame)
+{
+  MOZ_ASSERT(aWrapperFrame->StyleContext()->IsWrapperAnonBox(),
+             "All our wrappers are anon boxes, and why would we restyle "
+             "non-inheriting ones?");
+  MOZ_ASSERT(aWrapperFrame->StyleContext()->IsInheritingAnonBox(),
+             "All our wrappers are anon boxes, and why would we restyle "
+             "non-inheriting ones?");
+  MOZ_ASSERT(aWrapperFrame->StyleContext()->GetPseudo() !=
+             nsCSSAnonBoxes::cellContent,
+             "Someone should be using TableAwareParentFor");
+  MOZ_ASSERT(aWrapperFrame->StyleContext()->GetPseudo() !=
+             nsCSSAnonBoxes::tableWrapper,
+             "Someone should be using TableAwareParentFor");
+  // Make sure we only add first continuations.
+  aWrapperFrame = aWrapperFrame->FirstContinuation();
+  nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr);
+  if (last == aWrapperFrame) {
+    // Already queued up, nothing to do.
+    return;
+  }
+
+  // Make sure to queue up parents before children.  But don't queue up
+  // ancestors of non-anonymous boxes here; those are handled when we traverse
+  // their non-anonymous kids.
+  if (aWrapperFrame->ParentIsWrapperAnonBox()) {
+    AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame));
+  }
+
+  // If the append fails, we'll fail to restyle properly, but that's probably
+  // better than crashing.
+  if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) {
+    aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true);
+  }
+}
+
+void
+ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame)
+{
+  size_t i = mPendingWrapperRestyleOffset;
+  while (i < mPendingWrapperRestyles.Length()) {
+    i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i);
+  }
+
+  mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset);
+}
+
+size_t
+ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
+                                                    size_t aIndex)
+{
+  // The frame at index aIndex is something we should restyle ourselves, but
+  // following frames may need separate ServoRestyleStates to restyle.
+  MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length());
+
+  nsIFrame* cur = mPendingWrapperRestyles[aIndex];
+  MOZ_ASSERT(cur->StyleContext()->IsWrapperAnonBox());
+
+  // Where is cur supposed to inherit from?  From its parent frame, except in
+  // the case when cur is a table, in which case it should be its grandparent.
+  // Also, not in the case when the resulting frame would be a first-line; in
+  // that case we should be inheriting from the block, and the first-line will
+  // do its fixup later if needed.
+  //
+  // Note that after we do all that fixup the parent we get might still not be
+  // aParent; for example aParent could be a scrollframe, in which case we
+  // should inherit from the scrollcontent frame.  Or the parent might be some
+  // continuation of aParent.
+  //
+  // Try to assert as much as we can about the parent we actually end up using
+  // without triggering bogus asserts in all those various edge cases.
+  nsIFrame* parent = cur->GetParent();
+  if (cur->IsTableFrame()) {
+    MOZ_ASSERT(parent->IsTableWrapperFrame());
+    parent = parent->GetParent();
+  }
+  if (parent->IsLineFrame()) {
+    parent = parent->GetParent();
+  }
+  MOZ_ASSERT(parent->FirstContinuation() == aParent ||
+             (parent->StyleContext()->IsInheritingAnonBox() &&
+              parent->GetContent() == aParent->GetContent()));
+
+  // Now "this" is a ServoRestyleState for aParent, so if parent != aParent we
+  // need a new ServoRestyleState for the kid.
+  Maybe<ServoRestyleState> parentRestyleState;
+  if (parent != aParent) {
+    parentRestyleState.emplace(*parent, *this, nsChangeHint_Empty,
+                               Type::InFlow);
+  }
+  ServoRestyleState& curRestyleState =
+    parentRestyleState ? *parentRestyleState : *this;
+
+  // This frame may already have been restyled.  Even if it has, we can't just
+  // return, because the next frame may be a kid of it that does need restyling.
+  if (cur->IsWrapperAnonBoxNeedingRestyle()) {
+    parent->UpdateStyleOfChildAnonBox(cur, curRestyleState);
+    cur->SetIsWrapperAnonBoxNeedingRestyle(false);
+  }
+
+  size_t numProcessed = 1;
+
+  // Note: no overflow possible here, since aIndex < length.
+  if (aIndex + 1 < mPendingWrapperRestyles.Length()) {
+    nsIFrame* next = mPendingWrapperRestyles[aIndex + 1];
+    if (TableAwareParentFor(next) == cur &&
+        next->IsWrapperAnonBoxNeedingRestyle()) {
+      // It might be nice if we could do better than nsChangeHint_Empty.  On
+      // the other hand, presumably our mChangesHandled already has the bits
+      // we really want here so in practice it doesn't matter.
+      ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty,
+                                   Type::InFlow,
+                                   /* aAssertWrapperRestyleLength = */ false);
+      numProcessed += childState.ProcessMaybeNestedWrapperRestyle(cur,
+                                                                  aIndex + 1);
+    }
+  }
+
+  return numProcessed;
+}
+
+nsIFrame*
+ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild)
+{
+  // We want to get the anon box parent for aChild. where aChild has
+  // ParentIsWrapperAnonBox().
+  //
+  // For the most part this is pretty straightforward, but there are two
+  // wrinkles.  First, if aChild is a table, then we really want the parent of
+  // its table wrapper.
+  if (aChild->IsTableFrame()) {
+    aChild = aChild->GetParent();
+    MOZ_ASSERT(aChild->IsTableWrapperFrame());
+  }
+
+  nsIFrame* parent = aChild->GetParent();
+  // Now if parent is a cell-content frame, we actually want the cellframe.
+  if (parent->StyleContext()->GetPseudo() == nsCSSAnonBoxes::cellContent) {
+    parent = parent->GetParent();
+  } else if (parent->IsTableWrapperFrame()) {
+    // Must be a caption.  In that case we want the table here.
+    MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption);
+    parent = parent->PrincipalChildList().FirstChild();
+  }
+  return parent;
+}
+
 ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
   : RestyleManager(StyleBackendType::Servo, aPresContext)
   , mReentrantChanges(nullptr)
 {
 }
 
 void
 ServoRestyleManager::PostRestyleEvent(Element* aElement,
@@ -430,17 +582,18 @@ UpdateBackdropIfNeeded(nsIFrame* aFrame,
     aStyleSet.ResolvePseudoElementStyle(aFrame->GetContent()->AsElement(),
                                         CSSPseudoElementType::backdrop,
                                         aFrame->StyleContext()->AsServo(),
                                         /* aPseudoElement = */ nullptr);
 
   // NOTE(emilio): We can't use the changes handled for the owner of the
   // backdrop frame, since it's out of flow, and parented to the viewport frame.
   MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame());
-  ServoRestyleState state(aStyleSet, aChangeList);
+  nsTArray<nsIFrame*> wrappersToRestyle;
+  ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle);
   aFrame->UpdateStyleOfOwnedChildFrame(backdropFrame, newContext, state);
 }
 
 static void
 UpdateFirstLetterIfNeeded(nsIFrame* aFrame, ServoRestyleState& aRestyleState)
 {
   if (!aFrame->HasFirstLetterChild()) {
     return;
@@ -540,25 +693,27 @@ NeedsToTraverseElementChildren(const Ele
   return false;
 }
 
 bool
 ServoRestyleManager::ProcessPostTraversal(
   Element* aElement,
   ServoStyleContext* aParentContext,
   ServoRestyleState& aRestyleState,
-  ServoTraversalFlags aFlags)
+  ServoTraversalFlags aFlags,
+  bool aParentWasRestyled)
 {
   nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
+  nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
 
   // NOTE(emilio): This is needed because for table frames the bit is set on the
   // table wrapper (which is the primary frame), not on the table itself.
   const bool isOutOfFlow =
-    aElement->GetPrimaryFrame() &&
-    aElement->GetPrimaryFrame()->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+    primaryFrame &&
+    primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
 
   // Grab the change hint from Servo.
   // In case of flushing throttled animations, any restyle hints other than
   // animations are preserved since they are the hints which will be processed
   // in normal restyle later.
   bool wasRestyled;
   nsChangeHint changeHint = Servo_TakeChangeHint(aElement,
                                                  aFlags,
@@ -573,19 +728,34 @@ ServoRestyleManager::ProcessPostTraversa
 
   // Handle lazy frame construction by posting a reconstruct for any lazily-
   // constructed roots.
   if (aElement->HasFlag(NODE_NEEDS_FRAME)) {
     changeHint |= nsChangeHint_ReconstructFrame;
     MOZ_ASSERT(!styleFrame);
   }
 
-  if (styleFrame && !isOutOfFlow) {
-    changeHint = NS_RemoveSubsumedHints(
-      changeHint, aRestyleState.ChangesHandledFor(*styleFrame));
+  if (styleFrame) {
+    MOZ_ASSERT(primaryFrame);
+
+    nsIFrame* maybeAnonBoxChild;
+    if (isOutOfFlow) {
+      maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame();
+    } else {
+      maybeAnonBoxChild = primaryFrame;
+      changeHint = NS_RemoveSubsumedHints(
+        changeHint, aRestyleState.ChangesHandledFor(*styleFrame));
+    }
+
+    // If the parent wasn't restyled, the styles of our anon box parents won't
+    // change either.
+    if (aParentWasRestyled && maybeAnonBoxChild->ParentIsWrapperAnonBox()) {
+      aRestyleState.AddPendingWrapperRestyle(
+        ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild));
+    }
   }
 
   // Although we shouldn't generate non-ReconstructFrame hints for elements with
   // no frames, we can still get them here if they were explicitly posted by
   // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be
   // :visited.  Skip processing these hints if there is no frame.
   if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) && changeHint) {
     aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint);
@@ -709,28 +879,34 @@ ServoRestyleManager::ProcessPostTraversa
     TextPostTraversalState textState(*upToDateContext,
                                      displayContentsNode && wasRestyled,
                                      childrenRestyleState);
     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
       if (traverseElementChildren && n->IsElement()) {
         recreatedAnyContext |= ProcessPostTraversal(n->AsElement(),
                                                     upToDateContext,
                                                     childrenRestyleState,
-                                                    aFlags);
+                                                    aFlags,
+                                                    wasRestyled);
       } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) {
-        recreatedAnyContext |= ProcessPostTraversalForText(n, textState);
+        recreatedAnyContext |= ProcessPostTraversalForText(n, textState,
+                                                           childrenRestyleState,
+                                                           wasRestyled);
       }
     }
   }
 
   // We want to update frame pseudo-element styles after we've traversed our
   // kids, because some of those updates (::first-line/::first-letter) need to
   // modify the styles of the kids, and the child traversal above would just
   // clobber those modifications.
   if (styleFrame) {
+    // Process anon box wrapper frames before ::first-line bits.
+    childrenRestyleState.ProcessWrapperRestyles(styleFrame);
+
     if (wasRestyled) {
       UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState);
     } else if (traverseElementChildren &&
                styleFrame->IsFrameOfType(nsIFrame::eBlockFrame)) {
       // Even if we were not restyled, if we're a block with a first-line and
       // one of our descendant elements which is on the first line was restyled,
       // we need to update the styles of things on the first line, because
       // they're wrong now.
@@ -751,37 +927,47 @@ ServoRestyleManager::ProcessPostTraversa
     }
   }
 
   if (!forThrottledAnimationFlush) {
     aElement->UnsetHasDirtyDescendantsForServo();
     aElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES);
   }
   aElement->UnsetHasAnimationOnlyDirtyDescendantsForServo();
+
   return recreatedAnyContext;
 }
 
 bool
 ServoRestyleManager::ProcessPostTraversalForText(
     nsIContent* aTextNode,
-    TextPostTraversalState& aPostTraversalState)
+    TextPostTraversalState& aPostTraversalState,
+    ServoRestyleState& aRestyleState,
+    bool aParentWasRestyled)
 {
   // Handle lazy frame construction.
   if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
     aPostTraversalState.ChangeList().AppendChange(
       nullptr, aTextNode, nsChangeHint_ReconstructFrame);
     return true;
   }
 
   // Handle restyle.
   nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
   if (!primaryFrame) {
     return false;
   }
 
+  // If the parent wasn't restyled, the styles of our anon box parents won't
+  // change either.
+  if (aParentWasRestyled && primaryFrame->ParentIsWrapperAnonBox()) {
+    aRestyleState.AddPendingWrapperRestyle(
+      ServoRestyleState::TableAwareParentFor(primaryFrame));
+  }
+
   nsStyleContext& newContext = aPostTraversalState.ComputeStyle(aTextNode);
   aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newContext);
 
   // We want to walk all the continuations here, even the ones with different
   // styles.  In practice, the only reasons we get continuations with different
   // styles are ::first-line and ::first-letter.  But in those cases,
   // newContext is the right context for the _later_ continuations anyway (the
   // ones not affected by ::first-line/::first-letter), not the earlier ones,
@@ -916,19 +1102,21 @@ ServoRestyleManager::DoProcessPendingRes
     bool anyStyleChanged = false;
 
     // Recreate style contexts, and queue up change hints (which also handle
     // lazy frame construction).
     {
       AutoRestyleTimelineMarker marker(mPresContext->GetDocShell(), forThrottledAnimationFlush);
       DocumentStyleRootIterator iter(doc);
       while (Element* root = iter.GetNextStyleRoot()) {
-        ServoRestyleState state(*styleSet, currentChanges);
+        nsTArray<nsIFrame*> wrappersToRestyle;
+        ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle);
         anyStyleChanged |=
-          ProcessPostTraversal(root, nullptr, state, aFlags);
+          ProcessPostTraversal(root, nullptr, state, aFlags,
+                               /* aParentWasRestyled = */ false);
       }
     }
 
     // Process the change hints.
     //
     // Unfortunately, the frame constructor can generate new change hints while
     // processing existing ones. We redirect those into a secondary queue and
     // iterate until there's nothing left.
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -31,78 +31,141 @@ namespace mozilla {
 /**
  * A stack class used to pass some common restyle state in a slightly more
  * comfortable way than a bunch of individual arguments, and that also checks
  * that the change hint used for optimization is correctly used in debug mode.
  */
 class ServoRestyleState
 {
 public:
-  ServoRestyleState(ServoStyleSet& aStyleSet, nsStyleChangeList& aChangeList)
+  ServoRestyleState(ServoStyleSet& aStyleSet, nsStyleChangeList& aChangeList,
+                    nsTArray<nsIFrame*>& aPendingWrapperRestyles)
     : mStyleSet(aStyleSet)
     , mChangeList(aChangeList)
+    , mPendingWrapperRestyles(aPendingWrapperRestyles)
+    , mPendingWrapperRestyleOffset(aPendingWrapperRestyles.Length())
     , mChangesHandled(nsChangeHint(0))
   {}
 
   // We shouldn't assume that changes handled from our parent are handled for
   // our children too if we're out of flow since they aren't necessarily
   // parented in DOM order, and thus a change handled by a DOM ancestor doesn't
   // necessarily mean that it's handled for an ancestor frame.
   enum class Type
   {
     InFlow,
     OutOfFlow,
   };
 
   ServoRestyleState(const nsIFrame& aOwner,
                     ServoRestyleState& aParentState,
                     nsChangeHint aHintForThisFrame,
-                    Type aType)
+                    Type aType,
+                    bool aAssertWrapperRestyleLength = true)
     : mStyleSet(aParentState.mStyleSet)
     , mChangeList(aParentState.mChangeList)
+    , mPendingWrapperRestyles(aParentState.mPendingWrapperRestyles)
+    , mPendingWrapperRestyleOffset(aParentState.mPendingWrapperRestyles.Length())
     , mChangesHandled(
         aType == Type::InFlow
           ? aParentState.mChangesHandled | aHintForThisFrame
           : aHintForThisFrame)
 #ifdef DEBUG
     , mOwner(&aOwner)
+    , mAssertWrapperRestyleLength(aAssertWrapperRestyleLength)
 #endif
   {
     if (aType == Type::InFlow) {
       AssertOwner(aParentState);
     }
   }
 
+  ~ServoRestyleState() {
+    MOZ_ASSERT(!mAssertWrapperRestyleLength ||
+               mPendingWrapperRestyles.Length() == mPendingWrapperRestyleOffset,
+               "Someone forgot to call ProcessWrapperRestyles!");
+  }
+
   nsStyleChangeList& ChangeList() { return mChangeList; }
   ServoStyleSet& StyleSet() { return mStyleSet; }
 
 #ifdef DEBUG
   void AssertOwner(const ServoRestyleState& aParentState) const;
   nsChangeHint ChangesHandledFor(const nsIFrame&) const;
 #else
   void AssertOwner(const ServoRestyleState&) const {}
   nsChangeHint ChangesHandledFor(const nsIFrame&) const
   {
     return mChangesHandled;
   }
 #endif
 
+  // Add a pending wrapper restyle.  We don't have to do anything if the thing
+  // being added is already last in the list, but otherwise we do want to add
+  // it, in order for ProcessWrapperRestyles to work correctly.
+  void AddPendingWrapperRestyle(nsIFrame* aWrapperFrame);
+
+  // Process wrapper restyles for this restyle state.  This should be done
+  // before it comes off the stack.
+  void ProcessWrapperRestyles(nsIFrame* aParentFrame);
+
+  // Get the table-aware parent for the given child.  This will walk through
+  // outer table and cellcontent frames.
+  static nsIFrame* TableAwareParentFor(const nsIFrame* aChild);
+
 private:
+  // Process a wrapper restyle at the given index, and restyles for any
+  // wrappers nested in it.  Returns the number of entries from
+  // mPendingWrapperRestyles that we processed.  The return value is always at
+  // least 1.
+  size_t ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent, size_t aIndex);
+
   ServoStyleSet& mStyleSet;
   nsStyleChangeList& mChangeList;
+
+  // A list of pending wrapper restyles.  Anonymous box wrapper frames that need
+  // restyling are added to this list when their non-anonymous kids are
+  // restyled.  This avoids us having to do linear searches along the frame tree
+  // for these anonymous boxes.  The problem then becomes that we can have
+  // multiple kids all with the same anonymous parent, and we don't want to
+  // restyle it more than once.  We use mPendingWrapperRestyles to track which
+  // anonymous wrapper boxes we've requested be restyled and which of them have
+  // already been restyled.  We use a single array propagated through
+  // ServoRestyleStates by reference, because in a situation like this:
+  //
+  //  <div style="display: table"><span></span></div>
+  //
+  // We have multiple wrappers to restyle (cell, row, table-row-group) and we
+  // want to add them in to the list all at once but restyle them using
+  // different ServoRestyleStates with different owners.  When this situation
+  // occurs, the relevant frames will be placed in the array with ancestors
+  // before descendants.
+  nsTArray<nsIFrame*>& mPendingWrapperRestyles;
+
+  // Since we're given a possibly-nonempty mPendingWrapperRestyles to start
+  // with, we need to keep track of where the part of it we're responsible for
+  // starts.
+  size_t mPendingWrapperRestyleOffset;
+
   const nsChangeHint mChangesHandled;
 
   // We track the "owner" frame of this restyle state, that is, the frame that
   // generated the last change that is stored in mChangesHandled, in order to
   // verify that we only use mChangesHandled for actual descendants of that
   // frame (given DOM order isn't always frame order, and that there are a few
   // special cases for stuff like wrapper frames, ::backdrop, and so on).
 #ifdef DEBUG
   const nsIFrame* mOwner { nullptr };
 #endif
+
+  // Whether we should assert in our destructor that we've processed all of the
+  // relevant wrapper restyles.
+#ifdef DEBUG
+  const bool mAssertWrapperRestyleLength = true;
+#endif // DEBUG
 };
 
 /**
  * Restyle manager for a Servo-backed style system.
  */
 class ServoRestyleManager : public RestyleManager
 {
   friend class ServoStyleSet;
@@ -202,21 +265,24 @@ private:
    * Returns whether any style did actually change. There may be cases where we
    * didn't need to change any style after all, for example, when a content
    * attribute changes that happens not to have any effect on the style of that
    * element or any descendant or sibling.
    */
   bool ProcessPostTraversal(Element* aElement,
                             ServoStyleContext* aParentContext,
                             ServoRestyleState& aRestyleState,
-                            ServoTraversalFlags aFlags);
+                            ServoTraversalFlags aFlags,
+                            bool aParentWasRestyled);
 
   struct TextPostTraversalState;
   bool ProcessPostTraversalForText(nsIContent* aTextNode,
-                                   TextPostTraversalState& aState);
+                                   TextPostTraversalState& aState,
+                                   ServoRestyleState& aRestyleState,
+                                   bool aParentWasRestyled);
 
   inline ServoStyleSet* StyleSet() const
   {
     MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
                "ServoRestyleManager should only be used with a Servo-flavored "
                "style backend");
     return PresContext()->StyleSet()->AsServo();
   }
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1388625-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+    div::first-line { color: green }
+</style>
+<body style="width: 100px" onload="document.body.style.width = 'auto'">
+  <div>
+    <span style="display: ruby-base-container">Some</span>
+    <span style="display: ruby-base-container">text that is fairly long</span>
+  </div>
+</body>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -488,8 +488,9 @@ load 1308793.svg
 load 1308848-1.html
 load 1308848-2.html
 load 1338772-1.html
 load 1343937.html
 asserts(0-1) load 1343606.html # bug 1343948
 load 1352380.html
 load 1362423-1.html
 load 1381323.html
+asserts-if(!stylo,1) load 1388625-1.html # bug 1389286
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -10205,17 +10205,24 @@ nsFrame::BoxMetrics() const
   NS_ASSERTION(metrics, "A box layout method was called but InitBoxMetrics was never called");
   return metrics;
 }
 
 void
 nsIFrame::UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
                                     ServoRestyleState& aRestyleState)
 {
-  MOZ_ASSERT(aChildFrame->GetParent() == this,
+  MOZ_ASSERT(aChildFrame->GetParent() == this ||
+             (aChildFrame->IsTableFrame() &&
+              aChildFrame->GetParent()->GetParent() == this) ||
+             (aChildFrame->GetParent()->IsLineFrame() &&
+              aChildFrame->GetParent()->GetParent() == this) ||
+             (aChildFrame->IsTableFrame() &&
+              aChildFrame->GetParent()->GetParent()->IsLineFrame() &&
+              aChildFrame->GetParent()->GetParent()->GetParent() == this),
              "This should only be used for children!");
   MOZ_ASSERT(!GetContent() || !aChildFrame->GetContent() ||
              aChildFrame->GetContent() == GetContent(),
              "What content node is it a frame for?");
   MOZ_ASSERT(!aChildFrame->GetPrevContinuation(),
              "Only first continuations should end up here");
 
   // We could force the caller to pass in the pseudo, since some callers know it
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -3342,16 +3342,19 @@ protected:
   // owned by this frame, and then updates styles on each of them.
   void DoUpdateStyleOfOwnedAnonBoxes(mozilla::ServoRestyleState& aRestyleState);
 
   // A helper for DoUpdateStyleOfOwnedAnonBoxes for the specific case
   // of the owned anon box being a child of this frame.
   void UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
                                  mozilla::ServoRestyleState& aRestyleState);
 
+  // Allow ServoRestyleState to call UpdateStyleOfChildAnonBox.
+  friend class mozilla::ServoRestyleState;
+
 public:
   // A helper both for UpdateStyleOfChildAnonBox, and to update frame-backed
   // pseudo-elements in ServoRestyleManager.
   //
   // This gets a style context that will be the new style context for
   // `aChildFrame`, and takes care of updating it, calling CalcStyleDifference,
   // and adding to the change list as appropriate.
   //