Bug 1301258 - Part 2: Remove all subsumed hints when generating changes from restyles. r?dbaron draft
authorCameron McCormack <cam@mcc.id.au>
Mon, 20 Mar 2017 16:29:32 +0800
changeset 501419 737b1bc24a1a5b5f25e5517db26235ed8a27a7a0
parent 501418 802e532de182f62af71d982d5e2153831fd60109
child 549876 bb1f42997883eb5979569487b58f62cca508906c
push id49976
push userbmo:cam@mcc.id.au
push dateMon, 20 Mar 2017 08:30:15 +0000
reviewersdbaron
bugs1301258
milestone55.0a1
Bug 1301258 - Part 2: Remove all subsumed hints when generating changes from restyles. r?dbaron This changes ElementRestyler::CaptureChange so that it avoids generating the current frame's entire set of change hints, if only some of the hints were handled by ancestors, and instead to remove the hints that we know are subsumed. MozReview-Commit-ID: JvhxeQC6MmQ
layout/base/GeckoRestyleManager.cpp
layout/base/GeckoRestyleManager.h
layout/base/nsChangeHint.h
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -1080,18 +1080,18 @@ ElementRestyler::ElementRestyler(nsPresC
                                    aSwappedStructOwners)
   : mPresContext(aPresContext)
   , mFrame(aFrame)
   , mParentContent(nullptr)
     // XXXldb Why does it make sense to use aParentContent?  (See
     // comment above assertion at start of ElementRestyler::Restyle.)
   , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
   , mChangeList(aChangeList)
-  , mHintsHandled(aHintsHandledByAncestors &
-                  ~NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors))
+  , mHintsHandledByAncestors(aHintsHandledByAncestors)
+  , mHintsHandledBySelf(nsChangeHint(0))
   , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aRestyleTracker)
   , mSelectorsForDescendants(aSelectorsForDescendants)
   , mTreeMatchContext(aTreeMatchContext)
   , mResolvedChild(nullptr)
   , mContextsToClear(aContextsToClear)
   , mSwappedStructOwners(aSwappedStructOwners)
@@ -1102,30 +1102,47 @@ ElementRestyler::ElementRestyler(nsPresC
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
 #endif
 #ifdef RESTYLE_LOGGING
   , mLoggingDepth(aRestyleTracker.LoggingDepth() + 1)
 #endif
 {
   MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+  MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
+             "why restyle descendants if we are reconstructing the frame for "
+             "an ancestor?");
 }
 
 ElementRestyler::ElementRestyler(const ElementRestyler& aParentRestyler,
                                  nsIFrame* aFrame,
                                  uint32_t aConstructorFlags)
   : mPresContext(aParentRestyler.mPresContext)
   , mFrame(aFrame)
   , mParentContent(aParentRestyler.mContent)
     // XXXldb Why does it make sense to use aParentContent?  (See
     // comment above assertion at start of ElementRestyler::Restyle.)
   , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
   , mChangeList(aParentRestyler.mChangeList)
-  , mHintsHandled(aParentRestyler.mHintsHandled &
-                  ~NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))
+  , mHintsHandledByAncestors(
+      // Note that when FOR_OUT_OF_FLOW_CHILD, the out-of-flow may not be a
+      // geometric descendant of the frame where we started the reresolve.
+      // Therefore, even if mHintsHandledByAncestors already includes
+      // nsChangeHint_AllReflowHints/ we don't want to pass that on to the
+      // out-of-flow reresolve, since that can lead to the out-of-flow not
+      // getting reflowed when it should be (eg a reresolve starting at <body>
+      // that involves reflowing the <body> would miss reflowing fixed-pos
+      // nodes that also need reflow).  In the cases when the out-of-flow _is_
+      // a geometric descendant of a frame we already have a reflow hint
+      // for, reflow coalescing should keep us from doing the work twice.
+      (aParentRestyler.mHintsHandledByAncestors |
+       aParentRestyler.mHintsHandledBySelf) &
+      ((aConstructorFlags & FOR_OUT_OF_FLOW_CHILD) ?
+       ~nsChangeHint_AllReflowHints : ~nsChangeHint(0)))
+  , mHintsHandledBySelf(nsChangeHint(0))
   , mParentFrameHintsNotHandledForDescendants(
       aParentRestyler.mHintsNotHandledForDescendants)
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aParentRestyler.mRestyleTracker)
   , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
   , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
   , mResolvedChild(nullptr)
   , mContextsToClear(aParentRestyler.mContextsToClear)
@@ -1137,43 +1154,34 @@ ElementRestyler::ElementRestyler(const E
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
 #endif
 #ifdef RESTYLE_LOGGING
   , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
 #endif
 {
   MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
-  if (aConstructorFlags & FOR_OUT_OF_FLOW_CHILD) {
-    // Note that the out-of-flow may not be a geometric descendant of
-    // the frame where we started the reresolve.  Therefore, even if
-    // mHintsHandled already includes nsChangeHint_AllReflowHints we
-    // don't want to pass that on to the out-of-flow reresolve, since
-    // that can lead to the out-of-flow not getting reflowed when it
-    // should be (eg a reresolve starting at <body> that involves
-    // reflowing the <body> would miss reflowing fixed-pos nodes that
-    // also need reflow).  In the cases when the out-of-flow _is_ a
-    // geometric descendant of a frame we already have a reflow hint
-    // for, reflow coalescing should keep us from doing the work twice.
-    mHintsHandled &= ~nsChangeHint_AllReflowHints;
-  }
+  MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
+             "why restyle descendants if we are reconstructing the frame for "
+             "an ancestor?");
 }
 
 ElementRestyler::ElementRestyler(ParentContextFromChildFrame,
                                  const ElementRestyler& aParentRestyler,
                                  nsIFrame* aFrame)
   : mPresContext(aParentRestyler.mPresContext)
   , mFrame(aFrame)
   , mParentContent(aParentRestyler.mParentContent)
     // XXXldb Why does it make sense to use aParentContent?  (See
     // comment above assertion at start of ElementRestyler::Restyle.)
   , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
   , mChangeList(aParentRestyler.mChangeList)
-  , mHintsHandled(aParentRestyler.mHintsHandled &
-                  ~NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled))
+  , mHintsHandledByAncestors(aParentRestyler.mHintsHandledByAncestors |
+                             aParentRestyler.mHintsHandledBySelf)
+  , mHintsHandledBySelf(nsChangeHint(0))
   , mParentFrameHintsNotHandledForDescendants(
       // assume the worst
       nsChangeHint_Hints_NotHandledForDescendants)
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aParentRestyler.mRestyleTracker)
   , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
   , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
   , mResolvedChild(nullptr)
@@ -1186,16 +1194,19 @@ ElementRestyler::ElementRestyler(ParentC
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
 #endif
 #ifdef RESTYLE_LOGGING
   , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
 #endif
 {
   MOZ_ASSERT_IF(mContent, !mContent->IsStyledByServo());
+  MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
+             "why restyle descendants if we are reconstructing the frame for "
+             "an ancestor?");
 }
 
 ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
                                  nsIContent* aContent,
                                  nsStyleChangeList* aChangeList,
                                  nsChangeHint aHintsHandledByAncestors,
                                  RestyleTracker& aRestyleTracker,
                                  nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
@@ -1205,34 +1216,37 @@ ElementRestyler::ElementRestyler(nsPresC
                                  nsTArray<ContextToClear>& aContextsToClear,
                                  nsTArray<RefPtr<nsStyleContext>>&
                                    aSwappedStructOwners)
   : mPresContext(aPresContext)
   , mFrame(nullptr)
   , mParentContent(nullptr)
   , mContent(aContent)
   , mChangeList(aChangeList)
-  , mHintsHandled(aHintsHandledByAncestors &
-                  ~NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors))
+  , mHintsHandledByAncestors(aHintsHandledByAncestors)
+  , mHintsHandledBySelf(nsChangeHint(0))
   , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aRestyleTracker)
   , mSelectorsForDescendants(aSelectorsForDescendants)
   , mTreeMatchContext(aTreeMatchContext)
   , mResolvedChild(nullptr)
   , mContextsToClear(aContextsToClear)
   , mSwappedStructOwners(aSwappedStructOwners)
   , mIsRootOfRestyle(true)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(eSendAllNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
 #endif
 {
+  MOZ_ASSERT(!(mHintsHandledByAncestors & nsChangeHint_ReconstructFrame),
+             "why restyle descendants if we are reconstructing the frame for "
+             "an ancestor?");
 }
 
 void
 ElementRestyler::AddLayerChangesForAnimation()
 {
   uint64_t frameGeneration =
     GeckoRestyleManager::GetAnimationGenerationForFrame(mFrame);
 
@@ -1312,25 +1326,42 @@ ElementRestyler::CaptureChange(nsStyleCo
   // set due to changes in inherited properties (fill and stroke).  Avoid
   // propagating it into text nodes.
   if ((ourChange & nsChangeHint_UpdateEffects) &&
       mContent && !mContent->IsElement()) {
     ourChange &= ~nsChangeHint_UpdateEffects;
   }
 
   ourChange |= aChangeToAssume;
-  if (!NS_IsHintSubset(ourChange, mHintsHandled)) {
-    mHintsHandled |= ourChange;
+
+  nsChangeHint changeToAppend =
+    NS_RemoveSubsumedHints(ourChange, mHintsHandledByAncestors);
+
+  // mHintsHandledBySelf starts off as nsChangeHint(0), when restyling a given
+  // frame, and accumulates change hints for each same-style-continuation and
+  // {ib}-split sibling following it.  Most of the time, any subsequent frames
+  // we restyle with this ElementRestyler will generate exactly the same
+  // |changeToAppend| that we have already stored in mHintsHandledBySelf.  If
+  // we generate some hints that weren't handled by an earler same-style-
+  // continuation or {ib}-split sibling, then we record the entire
+  // |changeToAppend| value.  (We could use something like
+  // NS_RemoveSubsumedHints, but aimed at removing hints handled
+  // only for the current element instead.  However, we should probably just
+  // fix these rare cases as part of bug 918064.)
+  if (!NS_IsHintSubset(changeToAppend, mHintsHandledBySelf)) {
+    mHintsHandledBySelf |= changeToAppend;
     if (!(ourChange & nsChangeHint_ReconstructFrame) || mContent) {
       LOG_RESTYLE("appending change %s",
-                  GeckoRestyleManager::ChangeHintToString(ourChange).get());
-      mChangeList->AppendChange(mFrame, mContent, ourChange);
+                  RestyleManager::ChangeHintToString(changeToAppend).get());
+      mChangeList->AppendChange(mFrame, mContent, changeToAppend);
     } else {
-      LOG_RESTYLE("change has already been handled");
+      LOG_RESTYLE("ignoring ReconstructFrame change with no content");
     }
+  } else {
+    LOG_RESTYLE("change has already been handled");
   }
   mHintsNotHandledForDescendants |=
     NS_HintsNotHandledForDescendantsIn(ourChange);
   LOG_RESTYLE("mHintsNotHandledForDescendants = %s",
               GeckoRestyleManager::ChangeHintToString(mHintsNotHandledForDescendants).get());
 }
 
 class MOZ_RAII AutoSelectorArrayTruncater final
@@ -1729,18 +1760,18 @@ ElementRestyler::MoveStyleContextsForChi
   return true;
 }
 
 /**
  * Recompute style for mFrame (which should not have a prev continuation
  * with the same style), all of its next continuations with the same
  * style, and all ib-split siblings of the same type (either block or
  * inline, skipping the intermediates of the other type) and accumulate
- * changes into mChangeList given that mHintsHandled is already accumulated
- * for an ancestor.
+ * changes into mChangeList given that mHintsHandledByAncestors is already
+ * accumulated for an ancestor.
  * mParentContent is the content node used to resolve the parent style
  * context.  This means that, for pseudo-elements, it is the content
  * that should be used for selector matching (rather than the fake
  * content node attached to the frame).
  */
 void
 ElementRestyler::Restyle(nsRestyleHint aRestyleHint)
 {
@@ -1791,19 +1822,23 @@ ElementRestyler::Restyle(nsRestyleHint a
       // and we post a restyle from the transition manager while
       // computing style for the outer frame (to be computed after the
       // descendants have been resolved), we don't want to consume it
       // for the inner frame.
       mContent->GetPrimaryFrame() == mFrame) {
     mContent->OwnerDoc()->FlushPendingLinkUpdates();
     nsAutoPtr<RestyleTracker::RestyleData> restyleData;
     if (mRestyleTracker.GetRestyleData(mContent->AsElement(), restyleData)) {
-      if (!NS_IsHintSubset(restyleData->mChangeHint, mHintsHandled)) {
-        mHintsHandled |= restyleData->mChangeHint;
-        mChangeList->AppendChange(mFrame, mContent, restyleData->mChangeHint);
+      nsChangeHint changeToAppend =
+        NS_RemoveSubsumedHints(restyleData->mChangeHint,
+	                       mHintsHandledByAncestors);
+      // See the comment in CaptureChange about why we use NS_IsHintSubset here.
+      if (!NS_IsHintSubset(changeToAppend, mHintsHandledBySelf)) {
+        mHintsHandledBySelf |= changeToAppend;
+        mChangeList->AppendChange(mFrame, mContent, changeToAppend);
       }
       mSelectorsForDescendants.AppendElements(
           restyleData->mRestyleHintData.mSelectorsForDescendants);
       hintToRestore = restyleData->mRestyleHint;
       hintDataToRestore = Move(restyleData->mRestyleHintData);
       aRestyleHint = nsRestyleHint(aRestyleHint | restyleData->mRestyleHint);
       descendants.SwapElements(restyleData->mDescendants);
     }
@@ -1911,30 +1946,30 @@ ElementRestyler::Restyle(nsRestyleHint a
       //   (d) something under ProcessPendingRestyles then wants to use of those
       //       now destroyed structs (through the old parent's descendants).
       mSwappedStructOwners.AppendElement(newParent);
       oldContext->MoveTo(newParent);
     }
 
     // Send the accessibility notifications that RestyleChildren otherwise
     // would have sent.
-    if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+    if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
       InitializeAccessibilityNotifications(mFrame->StyleContext());
       SendAccessibilityNotifications();
     }
 
     mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
     if (aRestyleHint & eRestyle_SomeDescendants) {
       ConditionallyRestyleChildren();
     }
     return;
   }
 
   if (result == RestyleResult::eStopWithStyleChange &&
-      !(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+      !(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
     MOZ_ASSERT(mFrame->StyleContext() != oldContext,
                "RestyleResult::eStopWithStyleChange should only be returned "
                "if we got a new style context or we will reconstruct");
     MOZ_ASSERT(swappedStructs == 0,
                "should have ensured we didn't swap structs when "
                "returning RestyleResult::eStopWithStyleChange");
 
     // We need to ensure that all of the frames that inherit their style
@@ -1943,17 +1978,17 @@ ElementRestyler::Restyle(nsRestyleHint a
     // to ensure it is safe to move all of the relevant child style
     // contexts to newContext.  If these conditions fail, it will
     // return false, and we'll have to continue restyling.
     const bool canStop = MoveStyleContextsForChildren(oldContext);
 
     if (canStop) {
       // Send the accessibility notifications that RestyleChildren otherwise
       // would have sent.
-      if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+      if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
         InitializeAccessibilityNotifications(mFrame->StyleContext());
         SendAccessibilityNotifications();
       }
 
       mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
       if (aRestyleHint & eRestyle_SomeDescendants) {
         ConditionallyRestyleChildren();
       }
@@ -1980,21 +2015,21 @@ ElementRestyler::Restyle(nsRestyleHint a
   }
 
   if (result == RestyleResult::eContinueAndForceDescendants) {
     childRestyleHint =
       nsRestyleHint(childRestyleHint | eRestyle_ForceDescendants);
   }
 
   // No need to do this if we're planning to reframe already.
-  // It's also important to check mHintsHandled since we use
-  // mFrame->StyleContext(), which is out of date if mHintsHandled
+  // It's also important to check mHintsHandledBySelf since we use
+  // mFrame->StyleContext(), which is out of date if mHintsHandledBySelf
   // has a ReconstructFrame hint.  Using an out of date style
   // context could trigger assertions about mismatched rule trees.
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
     RestyleChildren(childRestyleHint);
   }
 
   if (oldContext && !oldContext->HasSingleReference()) {
     // If we swapped some structs out of oldContext in the RestyleSelf call
     // and after the RestyleChildren call we still have other strong references
     // to it, we need to make ensure its descendants don't cache any of the
     // structs that were swapped out.
@@ -2548,17 +2583,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
             pseudoTag == nsCSSPseudoElements::after) {
           // XXX what other pseudos do we need to treat like this?
           newContext = styleSet->ProbePseudoElementStyle(element,
                                                          pseudoType,
                                                          parentContext,
                                                          mTreeMatchContext);
           if (!newContext) {
             // This pseudo should no longer exist; gotta reframe
-            mHintsHandled |= nsChangeHint_ReconstructFrame;
+            mHintsHandledBySelf |= nsChangeHint_ReconstructFrame;
             mChangeList->AppendChange(aSelf, element,
                                       nsChangeHint_ReconstructFrame);
             // We're reframing anyway; just keep the same context
             newContext = oldContext;
 #ifdef DEBUG
             // oldContext's parent might have had its style structs swapped out
             // with parentContext, so to avoid any assertions that might
             // otherwise trigger in oldContext's parent's destructor, we set a
@@ -2769,17 +2804,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
                   RestyleResultToString(result).get());
       result = RestyleResult::eStopWithStyleChange;
     }
 
     if (aRestyleHint & eRestyle_ForceDescendants) {
       result = RestyleResult::eContinueAndForceDescendants;
     }
 
-    if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+    if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
       // If the frame gets regenerated, let it keep its old context,
       // which is important to maintain various invariants about
       // frame types matching their style contexts.
       // Note that this check even makes sense if we didn't call
       // CaptureChange because of copyFromContinuation being true,
       // since we'll have copied the existing context from the
       // previous continuation, so newContext == oldContext.
 
@@ -2905,34 +2940,34 @@ ElementRestyler::RestyleSelf(nsIFrame* a
 
     LOG_RESTYLE("newExtraContext = %p", newExtraContext.get());
 
     if (oldExtraContext != newExtraContext) {
       uint32_t equalStructs;
       uint32_t samePointerStructs;
       CaptureChange(oldExtraContext, newExtraContext, assumeDifferenceHint,
                     &equalStructs, &samePointerStructs);
-      if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+      if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
         LOG_RESTYLE("setting new extra style context");
         aSelf->SetAdditionalStyleContext(contextIndex, newExtraContext);
       } else {
         LOG_RESTYLE("not setting new extra style context, since we'll reframe");
       }
     }
   }
 
   LOG_RESTYLE("returning %s", RestyleResultToString(result).get());
 
   return result;
 }
 
 void
 ElementRestyler::RestyleChildren(nsRestyleHint aChildRestyleHint)
 {
-  MOZ_ASSERT(!(mHintsHandled & nsChangeHint_ReconstructFrame),
+  MOZ_ASSERT(!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame),
              "No need to do this if we're planning to reframe already.");
 
   // We'd like style resolution to be exact in the sense that an
   // animation-only style flush flushes only the styles it requests
   // flushing and doesn't update any other styles.  This means avoiding
   // constructing new frames during such a flush.
   //
   // For a ::before or ::after, we'll do an eRestyle_Subtree due to
@@ -2944,77 +2979,80 @@ ElementRestyler::RestyleChildren(nsResty
   // and pseudo-elements when we can skip it.
   bool mightReframePseudos = aChildRestyleHint & eRestyle_Subtree;
 
   RestyleUndisplayedDescendants(aChildRestyleHint);
 
   // Check whether we might need to create a new ::before frame.
   // There's no need to do this if we're planning to reframe already
   // or if we're not forcing restyles on kids.
-  // It's also important to check mHintsHandled since we use
-  // mFrame->StyleContext(), which is out of date if mHintsHandled has a
-  // ReconstructFrame hint.  Using an out of date style context could
+  // It's also important to check mHintsHandledBySelf since we use
+  // mFrame->StyleContext(), which is out of date if mHintsHandledBySelf
+  // has a ReconstructFrame hint.  Using an out of date style context could
   // trigger assertions about mismatched rule trees.
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame) &&
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame) &&
       mightReframePseudos) {
     MaybeReframeForBeforePseudo();
   }
 
   // There is no need to waste time crawling into a frame's children
   // on a frame change.  The act of reconstructing frames will force
   // new style contexts to be resolved on all of this frame's
   // descendants anyway, so we want to avoid wasting time processing
   // style contexts that we're just going to throw away anyway. - dwh
-  // It's also important to check mHintsHandled since reresolving the
+  // It's also important to check mHintsHandledBySelf since reresolving the
   // kids would use mFrame->StyleContext(), which is out of date if
-  // mHintsHandled has a ReconstructFrame hint; doing this could trigger
-  // assertions about mismatched rule trees.
+  // mHintsHandledBySelf has a ReconstructFrame hint; doing this could
+  // trigger assertions about mismatched rule trees.
   nsIFrame* lastContinuation;
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
     InitializeAccessibilityNotifications(mFrame->StyleContext());
 
     for (nsIFrame* f = mFrame; f;
          f = GeckoRestyleManager::GetNextContinuationWithSameStyle(f, f->StyleContext())) {
       lastContinuation = f;
       RestyleContentChildren(f, aChildRestyleHint);
     }
 
     SendAccessibilityNotifications();
   }
 
   // Check whether we might need to create a new ::after frame.
   // See comments above regarding :before.
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame) &&
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame) &&
       mightReframePseudos) {
     MaybeReframeForAfterPseudo(lastContinuation);
   }
 }
 
 void
 ElementRestyler::RestyleChildrenOfDisplayContentsElement(
   nsIFrame*              aParentFrame,
   nsStyleContext*        aNewContext,
   nsChangeHint           aMinHint,
   RestyleTracker&        aRestyleTracker,
   nsRestyleHint          aRestyleHint,
   const RestyleHintData& aRestyleHintData)
 {
-  MOZ_ASSERT(!(mHintsHandled & nsChangeHint_ReconstructFrame), "why call me?");
+  MOZ_ASSERT(!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame),
+             "why call me?");
 
   const bool mightReframePseudos = aRestyleHint & eRestyle_Subtree;
   DoRestyleUndisplayedDescendants(nsRestyleHint(0), mContent, aNewContext);
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && mightReframePseudos) {
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame) &&
+      mightReframePseudos) {
     MaybeReframeForPseudo(CSSPseudoElementType::before,
                           aParentFrame, nullptr, mContent, aNewContext);
   }
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame) && mightReframePseudos) {
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame) &&
+      mightReframePseudos) {
     MaybeReframeForPseudo(CSSPseudoElementType::after,
                           aParentFrame, nullptr, mContent, aNewContext);
   }
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
     InitializeAccessibilityNotifications(aNewContext);
 
     // Then process child frames for content that is a descendant of mContent.
     // XXX perhaps it's better to walk child frames (before reresolving
     // XXX undisplayed contexts above) and mark those that has a stylecontext
     // XXX leading up to mContent's old context? (instead of the
     // XXX ContentIsDescendantOf check below)
     nsIFrame::ChildListIterator lists(aParentFrame);
@@ -3026,17 +3064,17 @@ ElementRestyler::RestyleChildrenOfDispla
             ComputeStyleChangeFor(f, mChangeList, aMinHint, aRestyleTracker,
                                   aRestyleHint, aRestyleHintData,
                                   mContextsToClear, mSwappedStructOwners);
           }
         }
       }
     }
   }
-  if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+  if (!(mHintsHandledBySelf & nsChangeHint_ReconstructFrame)) {
     SendAccessibilityNotifications();
   }
 }
 
 void
 ElementRestyler::ComputeStyleChangeFor(nsIFrame*          aFrame,
                                        nsStyleChangeList* aChangeList,
                                        nsChangeHint       aMinChange,
@@ -3292,17 +3330,17 @@ ElementRestyler::MaybeReframeForPseudo(C
                                        nsIContent* aContent,
                                        nsStyleContext* aStyleContext)
 {
   if (MustReframeForPseudo(aPseudoType, aGenConParentFrame, aFrame, aContent,
                            aStyleContext)) {
     // Have to create the new ::before/::after frame.
     LOG_RESTYLE("MaybeReframeForPseudo, appending "
                 "nsChangeHint_ReconstructFrame");
-    mHintsHandled |= nsChangeHint_ReconstructFrame;
+    mHintsHandledBySelf |= nsChangeHint_ReconstructFrame;
     mChangeList->AppendChange(aFrame, aContent, nsChangeHint_ReconstructFrame);
   }
 }
 
 bool
 ElementRestyler::MustReframeForPseudo(CSSPseudoElementType aPseudoType,
                                       nsIFrame* aGenConParentFrame,
                                       nsIFrame* aFrame,
--- a/layout/base/GeckoRestyleManager.h
+++ b/layout/base/GeckoRestyleManager.h
@@ -483,23 +483,22 @@ public:
    * "reresolve our style context but not kids", use eRestyle_Subtree
    * to mean "reresolve our style context and kids", and use
    * nsRestyleHint(0) to mean recompute a new style context for our
    * current parent and existing rulenode, and the same for kids.
    */
   void Restyle(nsRestyleHint aRestyleHint);
 
   /**
-   * mHintsHandled changes over time; it starts off as the hints that
-   * have been handled by ancestors, and by the end of Restyle it
-   * represents the hints that have been handled for this frame.  This
-   * method is intended to be called after Restyle, to find out what
-   * hints have been handled for this frame.
+   * mHintsHandledBySelf changes over time; it starts off as nsChangeHint(0),
+   * and by the end of Restyle it represents the hints that have been handled
+   * for this frame.  This method is intended to be called after Restyle, to
+   * find out what hints have been handled for this frame.
    */
-  nsChangeHint HintsHandledForFrame() { return mHintsHandled; }
+  nsChangeHint HintsHandledForFrame() { return mHintsHandledBySelf; }
 
   /**
    * Called from GeckoRestyleManager::ComputeAndProcessStyleChange to restyle
    * children of a display:contents element.
    */
   void RestyleChildrenOfDisplayContentsElement(nsIFrame*       aParentFrame,
                                                nsStyleContext* aNewContext,
                                                nsChangeHint    aMinHint,
@@ -712,22 +711,25 @@ private:
   nsPresContext* const mPresContext;
   nsIFrame* const mFrame;
   nsIContent* const mParentContent;
   // |mContent| is the node that we used for rule matching of
   // normal elements (not pseudo-elements) and for which we generate
   // framechange hints if we need them.
   nsIContent* const mContent;
   nsStyleChangeList* const mChangeList;
-  // We have already generated change list entries for hints listed in
-  // mHintsHandled (initially it's those handled by ancestors, but by
-  // the end of Restyle it is those handled for this frame as well).  We
-  // need to generate a new change list entry for the frame when its
-  // style comparision returns a hint other than one of these hints.
-  nsChangeHint mHintsHandled;
+  // Hints that we computed on an ancestor (and which we already have
+  // generated a change list entry for).  When we traverse to children
+  // after restyling an element, this field accumulates the hints
+  // generated for that element.
+  const nsChangeHint mHintsHandledByAncestors;
+  // Hints that we have computed so far the current node.  This is
+  // initially zero, and accumulates hints for each same-style continuation
+  // and {ib} split sibling we restyle for the node.
+  nsChangeHint mHintsHandledBySelf;
   // See nsStyleContext::CalcStyleDifference
   nsChangeHint mParentFrameHintsNotHandledForDescendants;
   nsChangeHint mHintsNotHandledForDescendants;
   RestyleTracker& mRestyleTracker;
   nsTArray<nsCSSSelector*>& mSelectorsForDescendants;
   TreeMatchContext& mTreeMatchContext;
   nsIFrame* mResolvedChild; // child that provides our parent style context
   // Array of style context subtrees in which we need to clear out cached
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -363,16 +363,36 @@ static_assert(!(nsChangeHint_Hints_Alway
               "HandledForDescendants");
 
 // The most hints that NS_HintsNotHandledForDescendantsIn could possibly return:
 #define nsChangeHint_Hints_NotHandledForDescendants (      \
   nsChangeHint_Hints_NeverHandledForDescendants |          \
   nsChangeHint_Hints_SometimesHandledForDescendants        \
 )
 
+// Redefine the old NS_STYLE_HINT constants in terms of the new hint structure
+#define NS_STYLE_HINT_VISUAL \
+  nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView | \
+               nsChangeHint_SchedulePaint)
+#define nsChangeHint_AllReflowHints                     \
+  nsChangeHint(nsChangeHint_NeedReflow |                \
+               nsChangeHint_ReflowChangesSizeOrPosition|\
+               nsChangeHint_ClearAncestorIntrinsics |   \
+               nsChangeHint_ClearDescendantIntrinsics | \
+               nsChangeHint_NeedDirtyReflow)
+#define NS_STYLE_HINT_REFLOW \
+  nsChangeHint(NS_STYLE_HINT_VISUAL | nsChangeHint_AllReflowHints)
+
+#define nsChangeHint_Hints_CanIgnoreIfNotVisible   \
+  nsChangeHint(NS_STYLE_HINT_VISUAL |              \
+               nsChangeHint_NeutralChange |        \
+               nsChangeHint_UpdateOpacityLayer |   \
+               nsChangeHint_UpdateTransformLayer | \
+               nsChangeHint_UpdateUsesOpacity)
+
 // NB: Once we drop support for the old style system, this logic should be
 // inlined in the Servo style system to eliminate the FFI call.
 inline nsChangeHint NS_HintsNotHandledForDescendantsIn(nsChangeHint aChangeHint) {
   nsChangeHint result =
     aChangeHint & nsChangeHint_Hints_NeverHandledForDescendants;
 
   if (!NS_IsHintSubset(nsChangeHint_NeedDirtyReflow, aChangeHint)) {
     if (NS_IsHintSubset(nsChangeHint_NeedReflow, aChangeHint)) {
@@ -398,35 +418,46 @@ inline nsChangeHint NS_HintsNotHandledFo
 
   MOZ_ASSERT(NS_IsHintSubset(result,
                              nsChangeHint_Hints_NotHandledForDescendants),
              "something is inconsistent");
 
   return result;
 }
 
-// Redefine the old NS_STYLE_HINT constants in terms of the new hint structure
-#define NS_STYLE_HINT_VISUAL \
-  nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView | \
-               nsChangeHint_SchedulePaint)
-#define nsChangeHint_AllReflowHints                     \
-  nsChangeHint(nsChangeHint_NeedReflow |                \
-               nsChangeHint_ReflowChangesSizeOrPosition|\
-               nsChangeHint_ClearAncestorIntrinsics |   \
-               nsChangeHint_ClearDescendantIntrinsics | \
-               nsChangeHint_NeedDirtyReflow)
-#define NS_STYLE_HINT_REFLOW \
-  nsChangeHint(NS_STYLE_HINT_VISUAL | nsChangeHint_AllReflowHints)
+inline nsChangeHint
+NS_HintsHandledForDescendantsIn(nsChangeHint aChangeHint)
+{
+  return aChangeHint & ~NS_HintsNotHandledForDescendantsIn(aChangeHint);
+}
+
+// Returns the change hints in aOurChange that are not subsumed by those
+// in aHintsHandled (which are hints that have been handled by an ancestor).
+inline nsChangeHint
+NS_RemoveSubsumedHints(nsChangeHint aOurChange, nsChangeHint aHintsHandled)
+{
+  nsChangeHint result =
+    aOurChange & ~NS_HintsHandledForDescendantsIn(aHintsHandled);
 
-#define nsChangeHint_Hints_CanIgnoreIfNotVisible   \
-  nsChangeHint(NS_STYLE_HINT_VISUAL |              \
-               nsChangeHint_NeutralChange |        \
-               nsChangeHint_UpdateOpacityLayer |   \
-               nsChangeHint_UpdateTransformLayer | \
-               nsChangeHint_UpdateUsesOpacity)
+  if (result & (nsChangeHint_ClearAncestorIntrinsics |
+                nsChangeHint_ClearDescendantIntrinsics |
+                nsChangeHint_NeedDirtyReflow |
+                nsChangeHint_ReflowChangesSizeOrPosition |
+                nsChangeHint_UpdateComputedBSize)) {
+    result |= nsChangeHint_NeedReflow;
+  }
+
+  if (result & (nsChangeHint_ClearDescendantIntrinsics |
+                nsChangeHint_ReflowChangesSizeOrPosition)) {
+    result |= nsChangeHint_ClearAncestorIntrinsics |
+              nsChangeHint_NeedDirtyReflow;
+  }
+
+  return result;
+}
 
 /**
  * |nsRestyleHint| is a bitfield for the result of
  * |HasStateDependentStyle| and |HasAttributeDependentStyle|.  When no
  * restyling is necessary, use |nsRestyleHint(0)|.
  *
  * Without eRestyle_Force or eRestyle_ForceDescendants, the restyling process
  * can stop processing at a frame when it detects no style changes and it is