Bug 1180120 - Part 7: Add eRestyleResult_StopWithStyleChange. r=dbaron a=sylvestre
authorCameron McCormack <cam@mcc.id.au>
Sat, 29 Aug 2015 06:13:48 +1000
changeset 289223 124b3fa18283e6d689f511f83ee9ef484a3695e7
parent 289222 121de382cd526ac99d0087fdcacef274bf5944aa
child 289224 16b6826480e23698760c9b15c5cd9085eefa0729
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron, sylvestre
bugs1180120
milestone42.0a2
Bug 1180120 - Part 7: Add eRestyleResult_StopWithStyleChange. r=dbaron a=sylvestre
layout/base/RestyleManager.cpp
layout/base/RestyleManager.h
layout/style/nsStyleStruct.h
layout/style/nsStyleStructFwd.h
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -2872,16 +2872,124 @@ ElementRestyler::MustCheckUndisplayedCon
                        GetDocElementContainingBlock();
   }
 
   aUndisplayedParent = mFrame->GetContent();
   return !!aUndisplayedParent;
 }
 
 /**
+ * Helper for MoveStyleContextsForChildren, below.  Appends the style
+ * contexts to be moved to mFrame's current (new) style context to
+ * aContextsToMove.
+ */
+bool
+ElementRestyler::MoveStyleContextsForContentChildren(
+    nsIFrame* aParent,
+    nsStyleContext* aOldContext,
+    nsTArray<nsStyleContext*>& aContextsToMove)
+{
+  nsIFrame::ChildListIterator lists(aParent);
+  for (; !lists.IsDone(); lists.Next()) {
+    nsFrameList::Enumerator childFrames(lists.CurrentList());
+    for (; !childFrames.AtEnd(); childFrames.Next()) {
+      nsIFrame* child = childFrames.get();
+      // Bail out if we have out-of-flow frames.
+      // FIXME: It might be safe to just continue here instead of bailing out.
+      if (child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+        return false;
+      }
+      if (GetPrevContinuationWithSameStyle(child)) {
+        continue;
+      }
+      // Bail out if we have placeholder frames.
+      // FIXME: It is probably safe to just continue here instead of bailing out.
+      if (nsGkAtoms::placeholderFrame == child->GetType()) {
+        return false;
+      }
+      nsStyleContext* sc = child->StyleContext();
+      if (sc->GetParent() != aOldContext) {
+        return false;
+      }
+      nsIAtom* type = child->GetType();
+      if (type == nsGkAtoms::letterFrame ||
+          type == nsGkAtoms::lineFrame) {
+        return false;
+      }
+      if (sc->HasChildThatUsesGrandancestorStyle()) {
+        // XXX Not sure if we need this?
+        return false;
+      }
+      nsIAtom* pseudoTag = sc->GetPseudo();
+      if (pseudoTag && pseudoTag != nsCSSAnonBoxes::mozNonElement) {
+        return false;
+      }
+      aContextsToMove.AppendElement(sc);
+    }
+  }
+  return true;
+}
+
+/**
+ * Traverses to child elements (through the current frame's same style
+ * continuations, just like RestyleChildren does) and moves any style context
+ * for those children to be parented under mFrame's current (new) style
+ * context.
+ *
+ * False is returned if it encounters any conditions on the child elements'
+ * frames and style contexts that means it is impossible to move a
+ * style context.  If false is returned, no style contexts will have been
+ * moved.
+ */
+bool
+ElementRestyler::MoveStyleContextsForChildren(nsStyleContext* aOldContext)
+{
+  // Bail out if there are undisplayed or display:contents children.
+  // FIXME: We could get this to work if we need to.
+  nsIContent* undisplayedParent;
+  if (MustCheckUndisplayedContent(undisplayedParent)) {
+    nsCSSFrameConstructor* fc = mPresContext->FrameConstructor();
+    if (fc->GetAllUndisplayedContentIn(undisplayedParent) ||
+        fc->GetAllDisplayContentsIn(undisplayedParent)) {
+      return false;
+    }
+  }
+
+  nsTArray<nsStyleContext*> contextsToMove;
+
+  MOZ_ASSERT(!MustReframeForBeforePseudo(),
+             "shouldn't need to reframe ::before as we would have had "
+             "eRestyle_Subtree and wouldn't get in here");
+
+  DebugOnly<nsIFrame*> lastContinuation;
+  for (nsIFrame* f = mFrame; f;
+       f = GetNextContinuationWithSameStyle(f, f->StyleContext())) {
+    lastContinuation = f;
+    if (!MoveStyleContextsForContentChildren(f, aOldContext, contextsToMove)) {
+      return false;
+    }
+  }
+
+  MOZ_ASSERT(!MustReframeForAfterPseudo(lastContinuation),
+             "shouldn't need to reframe ::after as we would have had "
+             "eRestyle_Subtree and wouldn't get in here");
+
+  nsStyleContext* newParent = mFrame->StyleContext();
+  for (nsStyleContext* child : contextsToMove) {
+    // We can have duplicate entries in contextsToMove, so only move
+    // each style context once.
+    if (child->GetParent() != newParent) {
+      child->MoveTo(newParent);
+    }
+  }
+
+  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.
  * mParentContent is the content node used to resolve the parent style
  * context.  This means that, for pseudo-elements, it is the content
@@ -2957,41 +3065,45 @@ ElementRestyler::Restyle(nsRestyleHint a
   // eRestyle_ForceDescendants flag down too.
   nsRestyleHint childRestyleHint =
     nsRestyleHint(aRestyleHint & (eRestyle_SomeDescendants |
                                   eRestyle_Subtree |
                                   eRestyle_ForceDescendants));
 
   nsRefPtr<nsStyleContext> oldContext = mFrame->StyleContext();
 
+  nsTArray<SwapInstruction> swaps;
+
   // TEMPORARY (until bug 918064):  Call RestyleSelf for each
   // continuation or block-in-inline sibling.
 
   // We must make a single decision on how to process this frame and
   // its descendants, yet RestyleSelf might return different RestyleResult
   // values for the different same-style continuations.  |result| is our
   // overall decision.
   RestyleResult result = RestyleResult(0);
   uint32_t swappedStructs = 0;
 
   nsRestyleHint thisRestyleHint = aRestyleHint;
 
   bool haveMoreContinuations = false;
   for (nsIFrame* f = mFrame; f; ) {
-    RestyleResult thisResult = RestyleSelf(f, thisRestyleHint, &swappedStructs);
+    RestyleResult thisResult =
+      RestyleSelf(f, thisRestyleHint, &swappedStructs, swaps);
 
     if (thisResult != eRestyleResult_Stop) {
       // Calls to RestyleSelf for later same-style continuations must not
       // return eRestyleResult_Stop, so pass eRestyle_Force in to them.
       thisRestyleHint = nsRestyleHint(thisRestyleHint | eRestyle_Force);
 
       if (result == eRestyleResult_Stop) {
         // We received eRestyleResult_Stop for earlier same-style
-        // continuations, and eRestyleResult_Continue(AndForceDescendants) for
-        // this one; go back and force-restyle the earlier continuations.
+        // continuations, and eRestyleResult_StopWithStyleChange or
+        // eRestyleResult_Continue(AndForceDescendants) for this one; go
+        // back and force-restyle the earlier continuations.
         result = thisResult;
         f = mFrame;
         continue;
       }
     }
 
     if (thisResult > result) {
       // We take the highest RestyleResult value when working out what to do
@@ -3061,16 +3173,60 @@ ElementRestyler::Restyle(nsRestyleHint a
 
     mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
     if (aRestyleHint & eRestyle_SomeDescendants) {
       AddPendingRestylesForDescendantsMatchingSelectors(mContent);
     }
     return;
   }
 
+  if (result == eRestyleResult_StopWithStyleChange &&
+      !(mHintsHandled & nsChangeHint_ReconstructFrame)) {
+    MOZ_ASSERT(mFrame->StyleContext() != oldContext,
+               "eRestyleResult_StopWithStyleChange 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 eRestyleResult_StopWithStyleChange");
+
+    // We need to ensure that all of the frames that inherit their style
+    // from oldContext are able to be moved across to newContext.
+    // MoveStyleContextsForChildren will check for certain conditions
+    // 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)) {
+        InitializeAccessibilityNotifications(mFrame->StyleContext());
+        SendAccessibilityNotifications();
+      }
+
+      mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+      if (aRestyleHint & eRestyle_SomeDescendants) {
+        AddPendingRestylesForDescendantsMatchingSelectors(mContent);
+      }
+      return;
+    }
+
+    // Turns out we couldn't stop restyling here.  Process the struct
+    // swaps that RestyleSelf would've done had we not returned
+    // eRestyleResult_StopWithStyleChange.
+    for (SwapInstruction& swap : swaps) {
+      LOG_RESTYLE("swapping style structs between %p and %p",
+                  swap.mOldContext.get(), swap.mNewContext.get());
+      swap.mOldContext->SwapStyleData(swap.mNewContext, swap.mStructsToSwap);
+      swappedStructs |= swap.mStructsToSwap;
+    }
+    swaps.Clear();
+  }
+
   if (!swappedStructs) {
     // If we swapped any structs from the old context, then we need to keep
     // it alive until after the RestyleChildren call so that we can fix up
     // its descendants' cached structs.
     oldContext = nullptr;
   }
 
   if (result == eRestyleResult_ContinueAndForceDescendants) {
@@ -3117,156 +3273,204 @@ ElementRestyler::Restyle(nsRestyleHint a
  * Depending on the details of the frame we are restyling or its old style
  * context, we may or may not be able to stop restyling after this frame if
  * we find we had no style changes.
  *
  * This function returns eRestyleResult_Stop if it does not find any
  * conditions that would preclude stopping restyling, and
  * eRestyleResult_Continue if it does.
  */
-ElementRestyler::RestyleResult
-ElementRestyler::ComputeRestyleResultFromFrame(nsIFrame* aSelf)
+void
+ElementRestyler::ComputeRestyleResultFromFrame(nsIFrame* aSelf,
+                                               RestyleResult& aRestyleResult,
+                                               bool& aCanStopWithStyleChange)
 {
   // We can't handle situations where the primary style context of a frame
   // has not had any style data changes, but its additional style contexts
   // have, so we don't considering stopping if this frame has any additional
   // style contexts.
   if (aSelf->GetAdditionalStyleContext(0)) {
     LOG_RESTYLE_CONTINUE("there are additional style contexts");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   // Style changes might have moved children between the two nsLetterFrames
   // (the one matching ::first-letter and the one containing the rest of the
   // content).  Continue restyling to the children of the nsLetterFrame so
   // that they get the correct style context parent.  Similarly for
   // nsLineFrames.
   nsIAtom* type = aSelf->GetType();
 
   if (type == nsGkAtoms::letterFrame) {
     LOG_RESTYLE_CONTINUE("frame is a letter frame");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   if (type == nsGkAtoms::lineFrame) {
     LOG_RESTYLE_CONTINUE("frame is a line frame");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   // Some style computations depend not on the parent's style, but a grandparent
   // or one the grandparent's ancestors.  An example is an explicit 'inherit'
   // value for align-self, where if the parent frame's value for the property is
   // 'auto' we end up inheriting the computed value from the grandparent.  We
   // can't stop the restyling process on this frame (the one with 'auto', in
   // this example), as the grandparent's computed value might have changed
   // and we need to recompute the child's 'inherit' to that new value.
   nsStyleContext* oldContext = aSelf->StyleContext();
   if (oldContext->HasChildThatUsesGrandancestorStyle()) {
     LOG_RESTYLE_CONTINUE("the old context uses grandancestor style");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   // We ignore all situations that involve :visited style.
   if (oldContext->GetStyleIfVisited()) {
     LOG_RESTYLE_CONTINUE("the old style context has StyleIfVisited");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   nsStyleContext* parentContext = oldContext->GetParent();
   if (parentContext && parentContext->GetStyleIfVisited()) {
     LOG_RESTYLE_CONTINUE("the old style context's parent has StyleIfVisited");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   // We also ignore frames for pseudos, as their style contexts have
   // inheritance structures that do not match the frame inheritance
   // structure.  To avoid enumerating and checking all of the cases
   // where we have this kind of inheritance, we keep restyling past
   // pseudos.
   nsIAtom* pseudoTag = oldContext->GetPseudo();
   if (pseudoTag && pseudoTag != nsCSSAnonBoxes::mozNonElement) {
     LOG_RESTYLE_CONTINUE("the old style context is for a pseudo");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   nsIFrame* parent = mFrame->GetParent();
 
   if (parent) {
     // Also if the parent has a pseudo, as this frame's style context will
     // be inheriting from a grandparent frame's style context (or a further
     // ancestor).
     nsIAtom* parentPseudoTag = parent->StyleContext()->GetPseudo();
     if (parentPseudoTag && parentPseudoTag != nsCSSAnonBoxes::mozNonElement) {
       LOG_RESTYLE_CONTINUE("the old style context's parent is for a pseudo");
-      return eRestyleResult_Continue;
+      aRestyleResult = eRestyleResult_Continue;
+      // Parent style context pseudo-ness doesn't affect whether we can
+      // return eRestyleResult_StopWithStyleChange.
+      //
+      // If we had later conditions to check in this function, we would
+      // continue to check them, in case we set aCanStopWithStyleChange to
+      // false.
     }
   }
-
-  return eRestyleResult_Stop;
 }
 
-ElementRestyler::RestyleResult
+void
 ElementRestyler::ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
-                                                    nsStyleContext* aNewContext)
+                                                    nsStyleContext* aNewContext,
+                                                    RestyleResult& aRestyleResult,
+                                                    bool& aCanStopWithStyleChange)
 {
+  // If we've already determined that we must continue styling, we don't
+  // need to check anything.
+  if (aRestyleResult == eRestyleResult_Continue && !aCanStopWithStyleChange) {
+    return;
+  }
+
   // Keep restyling if the new style context has any style-if-visted style, so
   // that we can avoid the style context tree surgery having to deal to deal
   // with visited styles.
   if (aNewContext->GetStyleIfVisited()) {
     LOG_RESTYLE_CONTINUE("the new style context has StyleIfVisited");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   // If link-related information has changed, or the pseudo for the frame has
   // changed, or the new style context points to a different rule node, we can't
   // leave the old style context on the frame.
   nsStyleContext* oldContext = aSelf->StyleContext();
   if (oldContext->IsLinkContext() != aNewContext->IsLinkContext() ||
       oldContext->RelevantLinkVisited() != aNewContext->RelevantLinkVisited() ||
       oldContext->GetPseudo() != aNewContext->GetPseudo() ||
-      oldContext->GetPseudoType() != aNewContext->GetPseudoType() ||
-      oldContext->RuleNode() != aNewContext->RuleNode()) {
+      oldContext->GetPseudoType() != aNewContext->GetPseudoType()) {
     LOG_RESTYLE_CONTINUE("the old and new style contexts have different link/"
-                         "visited/pseudo/rulenodes");
-    return eRestyleResult_Continue;
+                         "visited/pseudo");
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
+  }
+
+  if (oldContext->RuleNode() != aNewContext->RuleNode()) {
+    LOG_RESTYLE_CONTINUE("the old and new style contexts have different "
+                         "rulenodes");
+    aRestyleResult = eRestyleResult_Continue;
+    // Continue to check other conditions if aCanStopWithStyleChange might
+    // still need to be set to false.
+    if (!aCanStopWithStyleChange) {
+      return;
+    }
   }
 
   // If the old and new style contexts differ in their
   // NS_STYLE_HAS_TEXT_DECORATION_LINES or NS_STYLE_HAS_PSEUDO_ELEMENT_DATA
   // bits, then we must keep restyling so that those new bit values are
   // propagated.
   if (oldContext->HasTextDecorationLines() !=
         aNewContext->HasTextDecorationLines()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_TEXT_DECORATION_LINES differs between old"
                          " and new style contexts");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   if (oldContext->HasPseudoElementData() !=
         aNewContext->HasPseudoElementData()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_PSEUDO_ELEMENT_DATA differs between old"
                          " and new style contexts");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   if (oldContext->ShouldSuppressLineBreak() !=
         aNewContext->ShouldSuppressLineBreak()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_SUPPRESS_LINEBREAK differs"
                          "between old and new style contexts");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
 
   if (oldContext->IsInDisplayNoneSubtree() !=
         aNewContext->IsInDisplayNoneSubtree()) {
     LOG_RESTYLE_CONTINUE("NS_STYLE_IN_DISPLAY_NONE_SUBTREE differs between old"
                          " and new style contexts");
-    return eRestyleResult_Continue;
+    aRestyleResult = eRestyleResult_Continue;
+    aCanStopWithStyleChange = false;
+    return;
   }
-
-  return eRestyleResult_Stop;
 }
 
 bool
 ElementRestyler::SelectorMatchesForRestyle(Element* aElement)
 {
   if (!aElement) {
     return false;
   }
@@ -3300,17 +3504,18 @@ ElementRestyler::CanReparentStyleContext
                             eRestyle_ForceDescendants |
                             eRestyle_SomeDescendants)) &&
          !mPresContext->StyleSet()->IsInRuleTreeReconstruct();
 }
 
 ElementRestyler::RestyleResult
 ElementRestyler::RestyleSelf(nsIFrame* aSelf,
                              nsRestyleHint aRestyleHint,
-                             uint32_t* aSwappedStructs)
+                             uint32_t* aSwappedStructs,
+                             nsTArray<SwapInstruction>& aSwaps)
 {
   MOZ_ASSERT(!(aRestyleHint & eRestyle_LaterSiblings),
              "eRestyle_LaterSiblings must not be part of aRestyleHint");
 
   // XXXldb get new context from prev-in-flow if possible, to avoid
   // duplication.  (Or should we just let |GetContext| handle that?)
   // Getting the hint would be nice too, but that's harder.
 
@@ -3318,24 +3523,54 @@ ElementRestyler::RestyleSelf(nsIFrame* a
   // We do need a reference to oldContext for the lifetime of this function, and it's possible
   // that the frame has the last reference to it, so AddRef it here.
 
   LOG_RESTYLE("RestyleSelf %s, aRestyleHint = %s",
               FrameTagToString(aSelf).get(),
               RestyleManager::RestyleHintToString(aRestyleHint).get());
   LOG_RESTYLE_INDENT();
 
-  RestyleResult result;
+  // Initially assume that it is safe to stop restyling.
+  //
+  // Throughout most of this function, we update the following two variables
+  // independently.  |result| is set to eRestyleResult_Continue when we
+  // detect a condition that would not allow us to return eRestyleResult_Stop.
+  // |canStopWithStyleChange| is set to false when we detect a condition
+  // that would not allow us to return eRestyleResult_StopWithStyleChange.
+  //
+  // Towards the end of this function, we reconcile these two variables --
+  // if |canStopWithStyleChange| is true, we convert |result| into
+  // eRestyleResult_StopWithStyleChange.
+  RestyleResult result = eRestyleResult_Stop;
+  bool canStopWithStyleChange = true;
 
   if (aRestyleHint & ~eRestyle_SomeDescendants) {
+    // If we are doing any restyling of the current element, or if we're
+    // forced to continue, we must.
     result = eRestyleResult_Continue;
-  } else {
-    result = ComputeRestyleResultFromFrame(aSelf);
+
+    // If we have to restyle children, we can't return
+    // eRestyleResult_StopWithStyleChange.
+    if (aRestyleHint & (eRestyle_Subtree | eRestyle_Force |
+                        eRestyle_ForceDescendants)) {
+      canStopWithStyleChange = false;
+    }
   }
 
+  // We only consider returning eRestyleResult_StopWithStyleChange if this
+  // is the root of the restyle.  (Otherwise, we would need to track the
+  // style changes of the ancestors we just restyled.)
+  if (!mIsRootOfRestyle) {
+    canStopWithStyleChange = false;
+  }
+
+  // Look at the frame and its current style context for conditions
+  // that would change our RestyleResult.
+  ComputeRestyleResultFromFrame(aSelf, result, canStopWithStyleChange);
+
   nsChangeHint assumeDifferenceHint = NS_STYLE_HINT_NONE;
   nsRefPtr<nsStyleContext> oldContext = aSelf->StyleContext();
   nsStyleSet* styleSet = mPresContext->StyleSet();
 
 #ifdef ACCESSIBILITY
   mWasFrameVisible = nsIPresShell::IsAccessibilityActive() ?
     oldContext->StyleVisibility()->IsVisible() : false;
 #endif
@@ -3373,16 +3608,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     // aSelf's context.
     parentContext = providerFrame->StyleContext();
     // Set |mResolvedChild| so we don't bother resolving the
     // provider again.
     mResolvedChild = providerFrame;
     LOG_RESTYLE_CONTINUE("we had a provider frame");
     // Continue restyling past the odd style context inheritance.
     result = eRestyleResult_Continue;
+    canStopWithStyleChange = false;
   }
 
   if (providerFrame != aSelf->GetParent()) {
     // We don't actually know what the parent style context's
     // non-inherited hints were, so assume the worst.
     mParentFrameHintsNotHandledForDescendants =
       nsChangeHint_Hints_NotHandledForDescendants;
   }
@@ -3510,41 +3746,41 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       // style contexts around.  However, we need to start from the
       // same root.
       LOG_RESTYLE("restyling root and keeping old context");
       LOG_RESTYLE_IF(this, result != eRestyleResult_Continue,
                      "continuing restyle since this is the root");
       newContext = oldContext;
       // Never consider stopping restyling at the root.
       result = eRestyleResult_Continue;
+      canStopWithStyleChange = false;
     }
   }
 
   LOG_RESTYLE("oldContext = %p, newContext = %p%s",
               oldContext.get(), newContext.get(),
               oldContext == newContext ? (const char*) " (same)" :
                                          (const char*) "");
 
   if (newContext != oldContext) {
-    if (result == eRestyleResult_Stop) {
-      if (oldContext->IsShared()) {
-        // If the old style context was shared, then we can't return
-        // eRestyleResult_Stop and patch its parent to point to the
-        // new parent style context, as that change might not be valid
-        // for the other frames sharing the style context.
-        LOG_RESTYLE_CONTINUE("the old style context is shared");
-        result = eRestyleResult_Continue;
-      } else {
-        // Look at some details of the new style context to see if it would
-        // be safe to stop restyling, if we discover it has the same style
-        // data as the old style context.
-        result = ComputeRestyleResultFromNewContext(aSelf, newContext);
-      }
+    if (oldContext->IsShared()) {
+      // If the old style context was shared, then we can't return
+      // eRestyleResult_Stop and patch its parent to point to the
+      // new parent style context, as that change might not be valid
+      // for the other frames sharing the style context.
+      LOG_RESTYLE_CONTINUE("the old style context is shared");
+      result = eRestyleResult_Continue;
     }
 
+    // Look at some details of the new style context to see if it would
+    // be safe to stop restyling, if we discover it has the same style
+    // data as the old style context.
+    ComputeRestyleResultFromNewContext(aSelf, newContext,
+                                       result, canStopWithStyleChange);
+
     uint32_t equalStructs = 0;
     uint32_t samePointerStructs = 0;
 
     if (copyFromContinuation) {
       // In theory we should know whether there was any style data difference,
       // since we would have calculated that in the previous call to
       // RestyleSelf, so until we perform only one restyling per chain-of-
       // same-style continuations (bug 918064), we need to check again here to
@@ -3564,29 +3800,49 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       }
     } else {
       bool changedStyle =
         RestyleManager::TryStartingTransition(mPresContext, aSelf->GetContent(),
                                               oldContext, &newContext);
       if (changedStyle) {
         LOG_RESTYLE_CONTINUE("TryStartingTransition changed the new style context");
         result = eRestyleResult_Continue;
+        canStopWithStyleChange = false;
       }
       CaptureChange(oldContext, newContext, assumeDifferenceHint,
                     &equalStructs, &samePointerStructs);
       if (equalStructs != NS_STYLE_INHERIT_MASK) {
         // At least one struct had different data in it, so we must
         // continue restyling children.
         LOG_RESTYLE_CONTINUE("there is different style data: %s",
                     RestyleManager::StructNamesToString(
                       ~equalStructs & NS_STYLE_INHERIT_MASK).get());
         result = eRestyleResult_Continue;
       }
     }
 
+    if (canStopWithStyleChange) {
+      // If any inherited struct pointers are different, or if any
+      // reset struct pointers are different and we have descendants
+      // that rely on those reset struct pointers, we can't return
+      // eRestyleResult_StopWithStyleChange.
+      if ((samePointerStructs & NS_STYLE_INHERITED_STRUCT_MASK) !=
+            NS_STYLE_INHERITED_STRUCT_MASK) {
+        LOG_RESTYLE("can't return eRestyleResult_StopWithStyleChange since "
+                    "there is different inherited data");
+        canStopWithStyleChange = false;
+      } else if ((samePointerStructs & NS_STYLE_RESET_STRUCT_MASK) !=
+                   NS_STYLE_RESET_STRUCT_MASK &&
+                 oldContext->HasChildThatUsesResetStyle()) {
+        LOG_RESTYLE("can't return eRestyleResult_StopWithStyleChange since "
+                    "there is different reset data and descendants use it");
+        canStopWithStyleChange = false;
+      }
+    }
+
     if (result == eRestyleResult_Stop) {
       // Since we currently have eRestyleResult_Stop, we know at this
       // point that all of our style structs are equal in terms of styles.
       // However, some of them might be different pointers.  Since our
       // descendants might share those pointers, we have to continue to
       // restyling our descendants.
       //
       // However, because of the swapping of equal structs we've done on
@@ -3611,16 +3867,31 @@ ElementRestyler::RestyleSelf(nsIFrame* a
             !(samePointerStructs & nsCachedStyleData::GetBitForSID(sid))) {
           LOG_RESTYLE_CONTINUE("there are different struct pointers");
           result = eRestyleResult_Continue;
           break;
         }
       }
     }
 
+    // From this point we no longer do any assignments of
+    // eRestyleResult_Continue to |result|.  If canStopWithStyleChange is true,
+    // it means that we can convert |result| (whether it is
+    // eRestyleResult_Continue or eRestyleResult_Stop) into
+    // eRestyleResult_StopWithStyleChange.
+    if (canStopWithStyleChange) {
+      LOG_RESTYLE("converting %s into eRestyleResult_StopWithStyleChange",
+                  RestyleResultToString(result).get());
+      result = eRestyleResult_StopWithStyleChange;
+    }
+
+    if (aRestyleHint & eRestyle_ForceDescendants) {
+      result = eRestyleResult_ContinueAndForceDescendants;
+    }
+
     if (!(mHintsHandled & 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.
@@ -3634,20 +3905,30 @@ ElementRestyler::RestyleSelf(nsIFrame* a
                       "are shared");
         } else if (oldContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since the old context is "
                       "shared");
         } else if (newContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since the new context is "
                       "shared");
         } else {
-          LOG_RESTYLE("swapping style structs between %p and %p",
-                      oldContext.get(), newContext.get());
-          oldContext->SwapStyleData(newContext, equalStructs);
-          *aSwappedStructs |= equalStructs;
+          if (result == eRestyleResult_StopWithStyleChange) {
+            LOG_RESTYLE("recording a style struct swap between %p and %p to "
+                        "do if eRestyleResult_StopWithStyleChange fails",
+                        oldContext.get(), newContext.get());
+            SwapInstruction* swap = aSwaps.AppendElement();
+            swap->mOldContext = oldContext;
+            swap->mNewContext = newContext;
+            swap->mStructsToSwap = equalStructs;
+          } else {
+            LOG_RESTYLE("swapping style structs between %p and %p",
+                        oldContext.get(), newContext.get());
+            oldContext->SwapStyleData(newContext, equalStructs);
+            *aSwappedStructs |= equalStructs;
+          }
 #ifdef RESTYLE_LOGGING
           uint32_t structs = RestyleManager::StructsToLog() & equalStructs;
           if (structs) {
             LOG_RESTYLE_INDENT();
             LOG_RESTYLE("old style context now has: %s",
                         oldContext->GetCachedStyleDataAsString(structs).get());
             LOG_RESTYLE("new style context now has: %s",
                         newContext->GetCachedStyleDataAsString(structs).get());
@@ -3664,16 +3945,20 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       // This is a similar scenario to the one described in the
       // ElementRestyler::Restyle comment where we append to
       // mSwappedStructOwners.
       //
       // We really only need to do this if we did swap structs on the
       // parent, but we don't have that information here.
       mSwappedStructOwners.AppendElement(newContext->GetParent());
     }
+  } else {
+    if (aRestyleHint & eRestyle_ForceDescendants) {
+      result = eRestyleResult_ContinueAndForceDescendants;
+    }
   }
   oldContext = nullptr;
 
   // do additional contexts
   // XXXbz might be able to avoid selector matching here in some
   // cases; won't worry about it for now.
   int32_t contextIndex = 0;
   for (nsStyleContext* oldExtraContext;
@@ -3738,20 +4023,16 @@ ElementRestyler::RestyleSelf(nsIFrame* a
         LOG_RESTYLE("setting new extra style context");
         aSelf->SetAdditionalStyleContext(contextIndex, newExtraContext);
       } else {
         LOG_RESTYLE("not setting new extra style context, since we'll reframe");
       }
     }
   }
 
-  if (aRestyleHint & eRestyle_ForceDescendants) {
-    result = eRestyleResult_ContinueAndForceDescendants;
-  }
-
   LOG_RESTYLE("returning %s", RestyleResultToString(result).get());
 
   return result;
 }
 
 void
 ElementRestyler::RestyleChildren(nsRestyleHint aChildRestyleHint)
 {
@@ -3919,18 +4200,26 @@ ElementRestyler::ComputeStyleChangeFor(n
                                     presContext->Document());
   Element* parent =
     content ? content->GetParentElementCrossingShadowRoot() : nullptr;
   treeMatchContext.InitAncestors(parent);
   nsTArray<nsCSSSelector*> selectorsForDescendants;
   selectorsForDescendants.AppendElements(
       aRestyleHintData.mSelectorsForDescendants);
   nsTArray<nsIContent*> visibleKidsOfHiddenElement;
-  for (nsIFrame* ibSibling = aFrame; ibSibling;
-       ibSibling = GetNextBlockInInlineSibling(propTable, ibSibling)) {
+  nsIFrame* nextIBSibling;
+  for (nsIFrame* ibSibling = aFrame; ibSibling; ibSibling = nextIBSibling) {
+    nextIBSibling = GetNextBlockInInlineSibling(propTable, ibSibling);
+
+    if (nextIBSibling) {
+      // Don't allow some ib-split siblings to be processed with
+      // eRestyleResult_StopWithStyleChange and others not.
+      aRestyleHint |= eRestyle_Force;
+    }
+
     // Outer loop over ib-split siblings
     for (nsIFrame* cont = ibSibling; cont; cont = cont->GetNextContinuation()) {
       if (GetPrevContinuationWithSameStyle(cont)) {
         // We already handled this element when dealing with its earlier
         // continuation.
         continue;
       }
 
@@ -4587,16 +4876,19 @@ RestyleManager::StructNamesToString(uint
 /* static */ nsCString
 ElementRestyler::RestyleResultToString(RestyleResult aRestyleResult)
 {
   nsCString result;
   switch (aRestyleResult) {
     case eRestyleResult_Stop:
       result.AssignLiteral("eRestyleResult_Stop");
       break;
+    case eRestyleResult_StopWithStyleChange:
+      result.AssignLiteral("eRestyleResult_StopWithStyleChange");
+      break;
     case eRestyleResult_Continue:
       result.AssignLiteral("eRestyleResult_Continue");
       break;
     case eRestyleResult_ContinueAndForceDescendants:
       result.AssignLiteral("eRestyleResult_ContinueAndForceDescendants");
       break;
     default:
       result.AppendPrintf("RestyleResult(%d)", aRestyleResult);
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -615,32 +615,44 @@ public:
 private:
   // Enum for the result of RestyleSelf, which indicates whether the
   // restyle procedure should continue to the children, and how.
   //
   // These values must be ordered so that later values imply that all
   // the work of the earlier values is also done.
   enum RestyleResult {
 
-    // do not restyle children
+    // we left the old style context on the frame; do not restyle children
     eRestyleResult_Stop = 1,
 
+    // we got a new style context on this frame, but we know that children
+    // do not depend on the changed values; do not restyle children
+    eRestyleResult_StopWithStyleChange,
+
     // continue restyling children
     eRestyleResult_Continue,
 
     // continue restyling children with eRestyle_ForceDescendants set
     eRestyleResult_ContinueAndForceDescendants
   };
 
+  struct SwapInstruction
+  {
+    nsRefPtr<nsStyleContext> mOldContext;
+    nsRefPtr<nsStyleContext> mNewContext;
+    uint32_t mStructsToSwap;
+  };
+
   /**
    * First half of Restyle().
    */
   RestyleResult RestyleSelf(nsIFrame* aSelf,
                             nsRestyleHint aRestyleHint,
-                            uint32_t* aSwappedStructs);
+                            uint32_t* aSwappedStructs,
+                            nsTArray<SwapInstruction>& aSwaps);
 
   /**
    * Restyle the children of this frame (and, in turn, their children).
    *
    * Second half of Restyle().
    */
   void RestyleChildren(nsRestyleHint aChildRestyleHint);
 
@@ -669,27 +681,36 @@ private:
    */
   bool CanReparentStyleContext(nsRestyleHint aRestyleHint);
 
   /**
    * Helpers for Restyle().
    */
   void AddLayerChangesForAnimation();
 
+  bool MoveStyleContextsForContentChildren(nsIFrame* aParent,
+                                           nsStyleContext* aOldContext,
+                                           nsTArray<nsStyleContext*>& aContextsToMove);
+  bool MoveStyleContextsForChildren(nsStyleContext* aOldContext);
+
   /**
    * Helpers for RestyleSelf().
    */
   void CaptureChange(nsStyleContext* aOldContext,
                      nsStyleContext* aNewContext,
                      nsChangeHint aChangeToAssume,
                      uint32_t* aEqualStructs,
                      uint32_t* aSamePointerStructs);
-  RestyleResult ComputeRestyleResultFromFrame(nsIFrame* aSelf);
-  RestyleResult ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
-                                                   nsStyleContext* aNewContext);
+  void ComputeRestyleResultFromFrame(nsIFrame* aSelf,
+                                     RestyleResult& aRestyleResult,
+                                     bool& aCanStopWithStyleChange);
+  void ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
+                                          nsStyleContext* aNewContext,
+                                          RestyleResult& aRestyleResult,
+                                          bool& aCanStopWithStyleChange);
 
   // Helpers for RestyleChildren().
   void RestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint);
   bool MustCheckUndisplayedContent(nsIContent*& aUndisplayedParent);
 
   /**
    * In the following two methods, aParentStyleContext is either
    * mFrame->StyleContext() if we have a frame, or a display:contents
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -36,16 +36,24 @@ class imgIContainer;
 
 // Includes nsStyleStructID.
 #include "nsStyleStructFwd.h"
 
 // Bits for each struct.
 // NS_STYLE_INHERIT_BIT defined in nsStyleStructFwd.h
 #define NS_STYLE_INHERIT_MASK              0x000ffffff
 
+// Bits for inherited structs.
+#define NS_STYLE_INHERITED_STRUCT_MASK \
+  ((nsStyleStructID_size_t(1) << nsStyleStructID_Inherited_Count) - 1)
+// Bits for reset structs.
+#define NS_STYLE_RESET_STRUCT_MASK \
+  (((nsStyleStructID_size_t(1) << nsStyleStructID_Reset_Count) - 1) \
+   << nsStyleStructID_Inherited_Count)
+
 // Additional bits for nsStyleContext's mBits:
 // See nsStyleContext::HasTextDecorationLines
 #define NS_STYLE_HAS_TEXT_DECORATION_LINES 0x001000000
 // See nsStyleContext::HasPseudoElementData.
 #define NS_STYLE_HAS_PSEUDO_ELEMENT_DATA   0x002000000
 // See nsStyleContext::RelevantLinkIsVisited
 #define NS_STYLE_RELEVANT_LINK_VISITED     0x004000000
 // See nsStyleContext::IsStyleIfVisited
--- a/layout/style/nsStyleStructFwd.h
+++ b/layout/style/nsStyleStructFwd.h
@@ -60,9 +60,11 @@ nsStyleStructID_Reset_Count =
 // such as nsCSSCompressedDataBlock::mStyleBits.
 eStyleStruct_BackendOnly = nsStyleStructID_Length
 
 };
 
 // A bit corresponding to each struct ID
 #define NS_STYLE_INHERIT_BIT(sid_)        (1 << uint64_t(eStyleStruct_##sid_))
 
+typedef decltype(nsStyleStructID(0) + nsStyleStructID(0)) nsStyleStructID_size_t;
+
 #endif /* nsStyleStructFwd_h_ */