Bug 979133 - Add facility to log the restyle process. r=dbaron
☠☠ backed out by 84a355b48a1a ☠ ☠
authorCameron McCormack <cam@mcc.id.au>
Thu, 25 Sep 2014 13:13:14 +1000
changeset 207108 c0c410aed86fee0501e1f6fdb996e9c43200687e
parent 207107 a6e93c843089fdbb24b3f8802b8ec119d652c699
child 207109 eeb5151c8052cb86bb42a986a124f16ef36d9a90
push id49595
push usercmccormack@mozilla.com
push dateThu, 25 Sep 2014 03:13:17 +0000
treeherdermozilla-inbound@c0c410aed86f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs979133
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 979133 - Add facility to log the restyle process. r=dbaron Set the MOZ_DEBUG_RESTYLE environment variable and every restyle will have detailed logging printed to stderr. By default, restyles for animations are not logged; you can include them by also setting MOZ_DEBUG_RESTYLE_ANIMATIONS. If you wish to limit restyle logging to a particular change, you can call nsPresContext::StartRestyleLogging() and nsPresContext::StopRestyleLogging() at appropriate points. (You might want to add a couple of helper methods temporarily on nsIDocument and then expose them to your page with Web IDL to make them easier to call.) You do not need to have set MOZ_DEBUG_RESTYLE for this to work.
layout/base/RestyleLogging.h
layout/base/RestyleManager.cpp
layout/base/RestyleManager.h
layout/base/RestyleTracker.cpp
layout/base/RestyleTracker.h
layout/base/RestyleTrackerInlines.h
layout/base/moz.build
layout/base/nsChangeHint.h
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/nsStyleContext.cpp
layout/style/nsStyleContext.h
new file mode 100644
--- /dev/null
+++ b/layout/base/RestyleLogging.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Macros used to log restyle events.
+ */
+
+#ifndef mozilla_RestyleLogging_h
+#define mozilla_RestyleLogging_h
+
+#include "mozilla/AutoRestore.h"
+
+#ifdef DEBUG
+#define RESTYLE_LOGGING
+#endif
+
+#ifdef RESTYLE_LOGGING
+#define LOG_RESTYLE_IF(object_, cond_, message_, ...)                         \
+  PR_BEGIN_MACRO                                                              \
+    if (object_->ShouldLogRestyle() && (cond_)) {                             \
+      nsCString line;                                                         \
+      for (int32_t restyle_depth_##__LINE__ = 0;                              \
+           restyle_depth_##__LINE__ < object_->LoggingDepth();                \
+           restyle_depth_##__LINE__++) {                                      \
+        line.AppendLiteral("  ");                                             \
+      }                                                                       \
+      line.AppendPrintf(message_, ##__VA_ARGS__);                             \
+      printf_stderr("%s\n", line.get());                                      \
+    }                                                                         \
+  PR_END_MACRO
+#define LOG_RESTYLE(message_, ...)                                            \
+  LOG_RESTYLE_IF(this, true, message_, ##__VA_ARGS__)
+// Beware that LOG_RESTYLE_INDENT is two statements not wrapped in a block.
+#define LOG_RESTYLE_INDENT()                                                  \
+  AutoRestore<int32_t> ar_depth_##__LINE__(LoggingDepth());                   \
+  ++LoggingDepth();
+#else
+#define LOG_RESTYLE_IF(cond_, message_, ...) /* nothing */
+#define LOG_RESTYLE(message_, ...) /* nothing */
+#define LOG_RESTYLE_INDENT() /* nothing */
+#endif
+
+#endif /* mozilla_RestyleLogging_h */
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -40,31 +40,47 @@
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
 namespace mozilla {
 
 using namespace layers;
 
+#define LOG_RESTYLE_CONTINUE(reason_, ...) \
+  LOG_RESTYLE("continuing restyle since " reason_, ##__VA_ARGS__)
+
+#ifdef RESTYLE_LOGGING
+static nsCString
+FrameTagToString(const nsIFrame* aFrame)
+{
+  nsCString result;
+  aFrame->ListTag(result);
+  return result;
+}
+#endif
+
 RestyleManager::RestyleManager(nsPresContext* aPresContext)
   : mPresContext(aPresContext)
   , mRebuildAllStyleData(false)
   , mObservingRefreshDriver(false)
   , mInStyleRefresh(false)
   , mHoverGeneration(0)
   , mRebuildAllExtraHint(nsChangeHint(0))
   , mLastUpdateForThrottledAnimations(aPresContext->RefreshDriver()->
                                         MostRecentRefresh())
   , mAnimationGeneration(0)
   , mReframingStyleContexts(nullptr)
   , mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE |
                      ELEMENT_IS_POTENTIAL_RESTYLE_ROOT)
   , mPendingAnimationRestyles(ELEMENT_HAS_PENDING_ANIMATION_RESTYLE |
                               ELEMENT_IS_POTENTIAL_ANIMATION_RESTYLE_ROOT)
+#ifdef RESTYLE_LOGGING
+  , mLoggingDepth(0)
+#endif
 {
   mPendingRestyles.Init(this);
   mPendingAnimationRestyles.Init(this);
 }
 
 void
 RestyleManager::NotifyDestroyingFrame(nsIFrame* aFrame)
 {
@@ -2252,16 +2268,19 @@ ElementRestyler::ElementRestyler(nsPresC
   , mTreeMatchContext(aTreeMatchContext)
   , mResolvedChild(nullptr)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(eSendAllNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aVisibleKidsOfHiddenElement)
 #endif
+#ifdef RESTYLE_LOGGING
+  , mLoggingDepth(aRestyleTracker.LoggingDepth() + 1)
+#endif
 {
 }
 
 ElementRestyler::ElementRestyler(const ElementRestyler& aParentRestyler,
                                  nsIFrame* aFrame,
                                  uint32_t aConstructorFlags)
   : mPresContext(aParentRestyler.mPresContext)
   , mFrame(aFrame)
@@ -2279,16 +2298,19 @@ ElementRestyler::ElementRestyler(const E
   , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
   , mResolvedChild(nullptr)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(aParentRestyler.mKidsDesiredA11yNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
 #endif
+#ifdef RESTYLE_LOGGING
+  , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
+#endif
 {
   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
@@ -2320,16 +2342,19 @@ ElementRestyler::ElementRestyler(ParentC
   , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
   , mResolvedChild(nullptr)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(aParentRestyler.mDesiredA11yNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
   , mVisibleKidsOfHiddenElement(aParentRestyler.mVisibleKidsOfHiddenElement)
 #endif
+#ifdef RESTYLE_LOGGING
+  , mLoggingDepth(aParentRestyler.mLoggingDepth + 1)
+#endif
 {
 }
 
 void
 ElementRestyler::CaptureChange(nsStyleContext* aOldContext,
                                nsStyleContext* aNewContext,
                                nsChangeHint aChangeToAssume,
                                uint32_t* aEqualStructs)
@@ -2346,32 +2371,43 @@ ElementRestyler::CaptureChange(nsStyleCo
   nsChangeHint ourChange =
     aOldContext->CalcStyleDifference(aNewContext,
                                      mParentFrameHintsNotHandledForDescendants,
                                      aEqualStructs);
   NS_ASSERTION(!(ourChange & nsChangeHint_AllReflowHints) ||
                (ourChange & nsChangeHint_NeedReflow),
                "Reflow hint bits set without actually asking for a reflow");
 
+  LOG_RESTYLE("CaptureChange, ourChange = %s, aChangeToAssume = %s",
+              RestyleManager::ChangeHintToString(ourChange).get(),
+              RestyleManager::ChangeHintToString(aChangeToAssume).get());
+  LOG_RESTYLE_INDENT();
+
   // nsChangeHint_UpdateEffects is inherited, but it can be set due to changes
   // in inherited properties (fill and stroke).  Avoid propagating it into
   // text nodes.
   if ((ourChange & nsChangeHint_UpdateEffects) &&
       mContent && !mContent->IsElement()) {
     ourChange = NS_SubtractHint(ourChange, nsChangeHint_UpdateEffects);
   }
 
   NS_UpdateHint(ourChange, aChangeToAssume);
   if (NS_UpdateHint(mHintsHandled, ourChange)) {
     if (!(ourChange & nsChangeHint_ReconstructFrame) || mContent) {
+      LOG_RESTYLE("appending change %s",
+                  RestyleManager::ChangeHintToString(ourChange).get());
       mChangeList->AppendChange(mFrame, mContent, ourChange);
+    } else {
+      LOG_RESTYLE("change has already been handled");
     }
   }
   NS_UpdateHint(mHintsNotHandledForDescendants,
                 NS_HintsNotHandledForDescendantsIn(ourChange));
+  LOG_RESTYLE("mHintsNotHandledForDescendants = %s",
+              RestyleManager::ChangeHintToString(mHintsNotHandledForDescendants).get());
 }
 
 /**
  * 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
@@ -2499,16 +2535,18 @@ ElementRestyler::Restyle(nsRestyleHint a
                               "if it returned eRestyleResult_Stop");
 
     nsStyleContext* newParent = providerFrame->StyleContext();
 
     if (oldContext->GetParent() != newParent) {
       // If we received eRestyleResult_Stop, then the old style context was
       // left on mFrame.  Since we ended up restyling our parent, change
       // this old style context to point to its new parent.
+      LOG_RESTYLE("moving style context %p from old parent %p to new parent %p",
+                  oldContext.get(), oldContext->GetParent(), newParent);
       oldContext->MoveTo(newParent);
     }
 
     // Send the accessibility notifications that RestyleChildren otherwise
     // would have sent.
     if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
       InitializeAccessibilityNotifications();
       SendAccessibilityNotifications();
@@ -2563,107 +2601,130 @@ ElementRestyler::Restyle(nsRestyleHint a
 ElementRestyler::RestyleResult
 ElementRestyler::ComputeRestyleResultFromFrame(nsIFrame* aSelf)
 {
   // 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;
   }
 
   // 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 || type == nsGkAtoms::lineFrame) {
+
+  if (type == nsGkAtoms::letterFrame) {
+    LOG_RESTYLE_CONTINUE("frame is a letter frame");
+    return eRestyleResult_Continue;
+  }
+
+  if (type == nsGkAtoms::lineFrame) {
+    LOG_RESTYLE_CONTINUE("frame is a line frame");
     return eRestyleResult_Continue;
   }
 
   // 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;
   }
 
   // We ignore all situations that involve :visited style.
   if (oldContext->GetStyleIfVisited()) {
+    LOG_RESTYLE_CONTINUE("the old style context has StyleIfVisited");
     return eRestyleResult_Continue;
   }
 
   nsStyleContext* parentContext = oldContext->GetParent();
   if (parentContext && parentContext->GetStyleIfVisited()) {
+    LOG_RESTYLE_CONTINUE("the old style context's parent has StyleIfVisited");
     return eRestyleResult_Continue;
   }
 
   // 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;
   }
 
   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;
     }
   }
 
   return eRestyleResult_Stop;
 }
 
 ElementRestyler::RestyleResult
 ElementRestyler::ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
                                                     nsStyleContext* aNewContext)
 {
   // 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;
   }
 
   // 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()) {
+    LOG_RESTYLE_CONTINUE("the old and new style contexts have different link/"
+                         "visited/pseudo/rulenodes");
     return eRestyleResult_Continue;
   }
 
   // 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() ||
-      oldContext->HasPseudoElementData() !=
+        aNewContext->HasTextDecorationLines()) {
+    LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_TEXT_DECORATION_LINES differs between old"
+                         " and new style contexts");
+    return eRestyleResult_Continue;
+  }
+
+  if (oldContext->HasPseudoElementData() !=
         aNewContext->HasPseudoElementData()) {
+    LOG_RESTYLE_CONTINUE("NS_STYLE_HAS_PSEUDO_ELEMENT_DATA differs between old"
+                         " and new style contexts");
     return eRestyleResult_Continue;
   }
 
   return eRestyleResult_Stop;
 }
 
 ElementRestyler::RestyleResult
 ElementRestyler::RestyleSelf(nsIFrame* aSelf,
@@ -2677,16 +2738,21 @@ ElementRestyler::RestyleSelf(nsIFrame* a
   // 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.
 
   // XXXbryner we may be able to avoid some of the refcounting goop here.
   // 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;
 
   if (aRestyleHint & eRestyle_ForceDescendants) {
     result = eRestyleResult_ContinueAndForceDescendants;
   } else if (aRestyleHint) {
     result = eRestyleResult_Continue;
   } else {
     result = ComputeRestyleResultFromFrame(aSelf);
@@ -2717,16 +2783,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
   }
   else {
     MOZ_ASSERT(providerFrame->GetContent() == aSelf->GetContent(),
                "Postcondition for GetParentStyleContextFrame() violated. "
                "That means we need to add the current element to the "
                "ancestor filter.");
 
     // resolve the provider here (before aSelf below).
+    LOG_RESTYLE("resolving child provider frame");
 
     // assumeDifferenceHint forces the parent's change to be also
     // applied to this frame, no matter what
     // nsStyleContext::CalcStyleDifference says. CalcStyleDifference
     // can't be trusted because it assumes any changes to the parent
     // style context provider will be automatically propagated to
     // the frame(s) with child style contexts.
 
@@ -2736,16 +2803,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     assumeDifferenceHint = providerRestyler.HintsHandledForFrame();
 
     // The provider's new context becomes the parent context of
     // 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;
   }
 
   if (providerFrame != aSelf->GetParent()) {
     // We don't actually know what the parent style context's
     // non-inherited hints were, so assume the worst.
     mParentFrameHintsNotHandledForDescendants =
@@ -2763,53 +2831,58 @@ ElementRestyler::RestyleSelf(nsIFrame* a
   // are involved.  This is mostly irrelevant since style attribute
   // changes on pseudo-elements are very rare, though it does mean we
   // don't get the optimization for table elements.
   if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement &&
       (aRestyleHint & eRestyle_StyleAttribute)) {
     aRestyleHint = (aRestyleHint & ~eRestyle_StyleAttribute) | eRestyle_Self;
   }
 
+  LOG_RESTYLE("parentContext = %p", parentContext);
+
   // do primary context
   nsRefPtr<nsStyleContext> newContext;
   nsIFrame *prevContinuation =
     GetPrevContinuationWithPossiblySameStyle(aSelf);
   nsStyleContext *prevContinuationContext;
   bool copyFromContinuation =
     prevContinuation &&
     (prevContinuationContext = prevContinuation->StyleContext())
       ->GetPseudo() == oldContext->GetPseudo() &&
      prevContinuationContext->GetParent() == parentContext;
   if (copyFromContinuation) {
     // Just use the style context from the frame's previous
     // continuation.
+    LOG_RESTYLE("using previous continuation's context");
     newContext = prevContinuationContext;
   }
   else if (pseudoTag == nsCSSAnonBoxes::mozNonElement) {
     NS_ASSERTION(aSelf->GetContent(),
                  "non pseudo-element frame without content node");
     newContext = styleSet->ResolveStyleForNonElement(parentContext);
   }
   else if (!(aRestyleHint & (eRestyle_Self | eRestyle_Subtree))) {
     Element* element = ElementForStyleContext(mParentContent, aSelf, pseudoType);
     if (!(aRestyleHint & ~(eRestyle_Force | eRestyle_ForceDescendants)) &&
         !styleSet->IsInRuleTreeReconstruct()) {
       nsIContent* pseudoElementContent = aSelf->GetContent();
       Element* pseudoElement =
         (pseudoElementContent && pseudoElementContent->IsElement())
           ? pseudoElementContent->AsElement() : nullptr;
+      LOG_RESTYLE("reparenting style context");
       newContext =
         styleSet->ReparentStyleContext(oldContext, parentContext, element,
                                        pseudoElement);
     } else {
       // Use ResolveStyleWithReplacement either for actual replacements
       // or, with no replacements, as a substitute for
       // ReparentStyleContext that rebuilds the path in the rule tree
       // rather than reusing the rule node, as we need to do during a
       // rule tree reconstruct.
+      LOG_RESTYLE("resolving style with replacement");
       newContext =
         styleSet->ResolveStyleWithReplacement(element, parentContext, oldContext,
                                               aRestyleHint);
     }
   } else if (pseudoType == nsCSSPseudoElements::ePseudo_AnonBox) {
     newContext = styleSet->ResolveAnonymousBoxStyle(pseudoTag,
                                                     parentContext);
   }
@@ -2877,29 +2950,38 @@ ElementRestyler::RestyleSelf(nsIFrame* a
         oldContext->IsLinkContext() == newContext->IsLinkContext() &&
         oldContext->RelevantLinkVisited() ==
           newContext->RelevantLinkVisited()) {
       // We're the root of the style context tree and the new style
       // context returned has the same rule node.  This means that
       // we can use FindChildWithRules to keep a lot of the old
       // 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;
     }
   }
 
+  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);
       }
     }
@@ -2913,28 +2995,34 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       // same-style continuations (bug 918064), we need to check again here to
       // determine whether it is safe to stop restyling.
       if (result == eRestyleResult_Stop) {
         oldContext->CalcStyleDifference(newContext, nsChangeHint(0),
                                         &equalStructs);
         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;
         }
       }
     } else {
       RestyleManager::TryStartingTransition(mPresContext, aSelf->GetContent(),
                                             oldContext, &newContext);
 
       CaptureChange(oldContext, newContext, assumeDifferenceHint,
                     &equalStructs);
       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 (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
@@ -2953,49 +3041,66 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       //
       // (b) when we were unable to swap the structs on the parent because
       //     either or both of the old parent and new parent are shared.
       for (nsStyleStructID sid = nsStyleStructID(0);
            sid < nsStyleStructID_Length;
            sid = nsStyleStructID(sid + 1)) {
         if (oldContext->HasCachedInheritedStyleData(sid) &&
             !oldContext->HasSameCachedStyleData(newContext, sid)) {
+          LOG_RESTYLE_CONTINUE("there are different struct pointers");
           result = eRestyleResult_Continue;
           break;
         }
       }
     }
 
     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.
 
       if (result != eRestyleResult_Stop) {
-        if (!oldContext->IsShared() && !newContext->IsShared()) {
+        if (oldContext->IsShared() && newContext->IsShared()) {
+          LOG_RESTYLE("not swapping style structs, since we copied from a "
+                      "continuation");
+        } 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;
         }
+        LOG_RESTYLE("setting new style context");
         aSelf->SetStyleContext(newContext);
       }
+    } else {
+      LOG_RESTYLE("not setting new style context, since we'll reframe");
     }
   }
   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;
        (oldExtraContext = aSelf->GetAdditionalStyleContext(contextIndex));
        ++contextIndex) {
+    LOG_RESTYLE("extra context %d", contextIndex);
+    LOG_RESTYLE_INDENT();
     nsRefPtr<nsStyleContext> newExtraContext;
     nsIAtom* const extraPseudoTag = oldExtraContext->GetPseudo();
     const nsCSSPseudoElements::Type extraPseudoType =
       oldExtraContext->GetPseudoType();
     NS_ASSERTION(extraPseudoTag &&
                  extraPseudoTag != nsCSSAnonBoxes::mozNonElement,
                  "extra style context is not pseudo element");
     if (!(aRestyleHint & (eRestyle_Self | eRestyle_Subtree))) {
@@ -3031,36 +3136,47 @@ ElementRestyler::RestyleSelf(nsIFrame* a
       newExtraContext = styleSet->ResolvePseudoElementStyle(mContent->AsElement(),
                                                             extraPseudoType,
                                                             newContext,
                                                             nullptr);
     }
 
     MOZ_ASSERT(newExtraContext);
 
+    LOG_RESTYLE("newExtraContext = %p", newExtraContext.get());
+
     if (oldExtraContext != newExtraContext) {
       uint32_t equalStructs;
       CaptureChange(oldExtraContext, newExtraContext, assumeDifferenceHint,
                     &equalStructs);
       if (!(mHintsHandled & 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");
       }
     }
   }
 
   if (result == eRestyleResult_Stop) {
     *aProviderFrame = providerFrame;
   }
 
+  LOG_RESTYLE("returning %s", RestyleResultToString(result).get());
+
   return result;
 }
 
 void
 ElementRestyler::RestyleChildren(nsRestyleHint aChildRestyleHint)
 {
+  LOG_RESTYLE("RestyleChildren, aChildRestyleHint = %s",
+              RestyleManager::RestyleHintToString(aChildRestyleHint).get());
+  LOG_RESTYLE_INDENT();
+
   // 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
   // RestyleHintForOp in nsCSSRuleProcessor.cpp (via its
   // HasAttributeDependentStyle or HasStateDependentStyle), given that
@@ -3148,16 +3264,19 @@ ElementRestyler::RestyleUndisplayedChild
       NS_ASSERTION(undisplayedParent ||
                    undisplayed->mContent ==
                      mPresContext->Document()->GetRootElement(),
                    "undisplayed node child of null must be root");
       NS_ASSERTION(!undisplayed->mStyle->GetPseudo(),
                    "Shouldn't have random pseudo style contexts in the "
                    "undisplayed map");
 
+      LOG_RESTYLE("RestyleUndisplayedChildren: undisplayed->mContent = %p",
+                  undisplayed->mContent.get());
+
       // Get the parent of the undisplayed content and check if it is a XBL
       // children element. Push the children element as an ancestor here because it does
       // not have a frame and would not otherwise be pushed as an ancestor.
       nsIContent* parent = undisplayed->mContent->GetParent();
       TreeMatchContext::AutoAncestorPusher insertionPointPusher(mTreeMatchContext);
       if (parent && nsContentUtils::IsContentInsertionPoint(parent)) {
         insertionPointPusher.PushAncestorAndStyleScope(parent);
       }
@@ -3229,16 +3348,18 @@ ElementRestyler::MaybeReframeForBeforePs
       // Checking for a :before frame is cheaper than getting the
       // :before style context.
       if (!nsLayoutUtils::GetBeforeFrame(mFrame) &&
           nsLayoutUtils::HasPseudoStyle(mFrame->GetContent(),
                                         mFrame->StyleContext(),
                                         nsCSSPseudoElements::ePseudo_before,
                                         mPresContext)) {
         // Have to create the new :before frame
+        LOG_RESTYLE("MaybeReframeForBeforePseudo, appending "
+                    "nsChangeHint_ReconstructFrame");
         NS_UpdateHint(mHintsHandled, nsChangeHint_ReconstructFrame);
         mChangeList->AppendChange(mFrame, mContent,
                                   nsChangeHint_ReconstructFrame);
       }
     }
   }
 }
 
@@ -3265,16 +3386,18 @@ ElementRestyler::MaybeReframeForAfterPse
       // Getting the :after frame is more expensive than getting the pseudo
       // context, so get the pseudo context first.
       if (nsLayoutUtils::HasPseudoStyle(aFrame->GetContent(),
                                         aFrame->StyleContext(),
                                         nsCSSPseudoElements::ePseudo_after,
                                         mPresContext) &&
           !nsLayoutUtils::GetAfterFrame(aFrame)) {
         // have to create the new :after frame
+        LOG_RESTYLE("MaybeReframeForAfterPseudo, appending "
+                    "nsChangeHint_ReconstructFrame");
         NS_UpdateHint(mHintsHandled, nsChangeHint_ReconstructFrame);
         mChangeList->AppendChange(aFrame, mContent,
                                   nsChangeHint_ReconstructFrame);
       }
     }
   }
 }
 
@@ -3317,16 +3440,18 @@ ElementRestyler::InitializeAccessibility
   }
 #endif
 }
 
 void
 ElementRestyler::RestyleContentChildren(nsIFrame* aParent,
                                         nsRestyleHint aChildRestyleHint)
 {
+  LOG_RESTYLE("RestyleContentChildren");
+
   nsIFrame::ChildListIterator lists(aParent);
   TreeMatchContext::AutoAncestorPusher ancestorPusher(mTreeMatchContext);
   if (!lists.IsDone()) {
     ancestorPusher.PushAncestorAndStyleScope(mContent);
   }
   for (; !lists.IsDone(); lists.Next()) {
     nsFrameList::Enumerator childFrames(lists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
@@ -3522,9 +3647,143 @@ RestyleManager::ComputeStyleChangeFor(ns
         NS_ASSERTION(!cont->GetPrevContinuation(),
                      "continuing frame had more severe impact than first-in-flow");
         return;
       }
     }
   }
 }
 
+#ifdef DEBUG
+/* static */ nsCString
+RestyleManager::RestyleHintToString(nsRestyleHint aHint)
+{
+  nsCString result;
+  bool any = false;
+  const char* names[] = { "Self", "Subtree", "LaterSiblings", "CSSTransitions",
+                          "CSSAnimations", "SVGAttrAnimations", "StyleAttribute",
+                          "ChangeAnimationPhase", "Force", "ForceDescendants" };
+  uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
+  uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
+  for (uint32_t i = 0; i < ArrayLength(names); i++) {
+    if (hint & (1 << i)) {
+      if (any) {
+        result.AppendLiteral(" | ");
+      }
+      result.AppendPrintf("eRestyle_%s", names[i]);
+      any = true;
+    }
+  }
+  if (rest) {
+    if (any) {
+      result.AppendLiteral(" | ");
+    }
+    result.AppendPrintf("0x%0x", rest);
+  } else {
+    if (!any) {
+      result.AppendLiteral("0");
+    }
+  }
+  return result;
+}
+
+/* static */ nsCString
+RestyleManager::ChangeHintToString(nsChangeHint aHint)
+{
+  nsCString result;
+  bool any = false;
+  const char* names[] = {
+    "RepaintFrame", "NeedReflow", "ClearAncestorIntrinsics",
+    "ClearDescendantIntrinsics", "NeedDirtyReflow", "SyncFrameView",
+    "UpdateCursor", "UpdateEffects", "UpdateOpacityLayer",
+    "UpdateTransformLayer", "ReconstructFrame", "UpdateOverflow",
+    "UpdateSubtreeOverflow", "UpdatePostTransformOverflow",
+    "ChildrenOnlyTransform", "RecomputePosition", "AddOrRemoveTransform",
+    "BorderStyleNoneChange", "UpdateTextPath", "NeutralChange"
+  };
+  uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
+  uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
+  if (hint == nsChangeHint_Hints_NotHandledForDescendants) {
+    result.AppendLiteral("nsChangeHint_Hints_NotHandledForDescendants");
+    hint = 0;
+    any = true;
+  } else {
+    if ((hint & NS_STYLE_HINT_FRAMECHANGE) == NS_STYLE_HINT_FRAMECHANGE) {
+      result.AppendLiteral("NS_STYLE_HINT_FRAMECHANGE");
+      hint = hint & ~NS_STYLE_HINT_FRAMECHANGE;
+      any = true;
+    } else if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
+      result.AppendLiteral("NS_STYLE_HINT_REFLOW");
+      hint = hint & ~NS_STYLE_HINT_REFLOW;
+      any = true;
+    } else if ((hint & nsChangeHint_AllReflowHints) == nsChangeHint_AllReflowHints) {
+      result.AppendLiteral("nsChangeHint_AllReflowHints");
+      hint = hint & ~nsChangeHint_AllReflowHints;
+      any = true;
+    } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
+      result.AppendLiteral("NS_STYLE_HINT_VISUAL");
+      hint = hint & ~NS_STYLE_HINT_VISUAL;
+      any = true;
+    }
+  }
+  for (uint32_t i = 0; i < ArrayLength(names); i++) {
+    if (hint & (1 << i)) {
+      if (any) {
+        result.AppendLiteral(" | ");
+      }
+      result.AppendPrintf("nsChangeHint_%s", names[i]);
+      any = true;
+    }
+  }
+  if (rest) {
+    if (any) {
+      result.AppendLiteral(" | ");
+    }
+    result.AppendPrintf("0x%0x", rest);
+  } else {
+    if (!any) {
+      result.AppendLiteral("NS_STYLE_HINT_NONE");
+    }
+  }
+  return result;
+}
+
+/* static */ nsCString
+RestyleManager::StructNamesToString(uint32_t aSIDs)
+{
+  nsCString result;
+  bool any = false;
+  for (nsStyleStructID sid = nsStyleStructID(0);
+       sid < nsStyleStructID_Length;
+       sid = nsStyleStructID(sid + 1)) {
+    if (aSIDs & nsCachedStyleData::GetBitForSID(sid)) {
+      if (any) {
+        result.AppendLiteral(",");
+      }
+      result.AppendPrintf("%s", nsStyleContext::StructName(sid));
+      any = true;
+    }
+  }
+  return result;
+}
+
+/* static */ nsCString
+ElementRestyler::RestyleResultToString(RestyleResult aRestyleResult)
+{
+  nsCString result;
+  switch (aRestyleResult) {
+    case eRestyleResult_Stop:
+      result.AssignLiteral("eRestyleResult_Stop");
+      break;
+    case eRestyleResult_Continue:
+      result.AssignLiteral("eRestyleResult_Continue");
+      break;
+    case eRestyleResult_ContinueAndForceDescendants:
+      result.AssignLiteral("eRestyleResult_ContinueAndForceDescendants");
+      break;
+    default:
+      result.AppendPrintf("RestyleResult(%d)", aRestyleResult);
+  }
+  return result;
+}
+#endif
+
 } // namespace mozilla
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -6,16 +6,17 @@
 /**
  * Code responsible for managing style changes: tracking what style
  * changes need to happen, scheduling them, and doing them.
  */
 
 #ifndef mozilla_RestyleManager_h
 #define mozilla_RestyleManager_h
 
+#include "mozilla/RestyleLogging.h"
 #include "nsISupportsImpl.h"
 #include "nsChangeHint.h"
 #include "RestyleTracker.h"
 #include "nsPresContext.h"
 #include "nsRefreshDriver.h"
 #include "nsRefPtrHashtable.h"
 #include "nsCSSPseudoElements.h"
 
@@ -311,16 +312,21 @@ public:
     PostRestyleEventInternal(true);
   }
 
   void FlushOverflowChangedTracker()
   {
     mOverflowChangedTracker.Flush();
   }
 
+#ifdef DEBUG
+  static nsCString RestyleHintToString(nsRestyleHint aHint);
+  static nsCString ChangeHintToString(nsChangeHint aHint);
+#endif
+
 private:
   /**
    * Notify the frame constructor that an element needs to have its
    * style recomputed.
    * @param aElement: The element to be restyled.
    * @param aRestyleHint: Which nodes need to have selector matching run
    *                      on them.
    * @param aMinChangeHint: A minimum change hint for aContent and its
@@ -346,16 +352,49 @@ public:
    * This method is used to recompute the style data when some change happens
    * outside of any style rules, like a color preference change or a change
    * in a system font size, or to fix things up when an optimization in the
    * style data has become invalid. We assume that the root frame will not
    * need to be reframed.
    */
   void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint);
 
+#ifdef RESTYLE_LOGGING
+  /**
+   * Returns whether a restyle event currently being processed by this
+   * RestyleManager should be logged.
+   */
+  bool ShouldLogRestyle() {
+    return ShouldLogRestyle(mPresContext);
+  }
+
+  /**
+   * Returns whether a restyle event currently being processed for the
+   * document with the specified nsPresContext should be logged.
+   */
+  static bool ShouldLogRestyle(nsPresContext* aPresContext) {
+    return aPresContext->RestyleLoggingEnabled() &&
+           (!aPresContext->IsProcessingAnimationStyleChange() ||
+            AnimationRestyleLoggingEnabled());
+  }
+
+  static bool RestyleLoggingInitiallyEnabled() {
+    static bool enabled = getenv("MOZ_DEBUG_RESTYLE") != 0;
+    return enabled;
+  }
+
+  static bool AnimationRestyleLoggingEnabled() {
+    static bool animations = getenv("MOZ_DEBUG_RESTYLE_ANIMATIONS") != 0;
+    return animations;
+  }
+
+  static nsCString StructNamesToString(uint32_t aSIDs);
+  int32_t& LoggingDepth() { return mLoggingDepth; }
+#endif
+
 private:
   /* aMinHint is the minimal change that should be made to the element */
   // XXXbz do we really need the aPrimaryFrame argument here?
   void RestyleElement(Element*        aElement,
                       nsIFrame*       aPrimaryFrame,
                       nsChangeHint    aMinHint,
                       RestyleTracker& aRestyleTracker,
                       nsRestyleHint   aRestyleHint);
@@ -388,16 +427,20 @@ private:
   // The total number of animation flushes by this frame constructor.
   // Used to keep the layer and animation manager in sync.
   uint64_t mAnimationGeneration;
 
   ReframingStyleContexts* mReframingStyleContexts;
 
   RestyleTracker mPendingRestyles;
   RestyleTracker mPendingAnimationRestyles;
+
+#ifdef RESTYLE_LOGGING
+  int32_t mLoggingDepth;
+#endif
 };
 
 /**
  * An ElementRestyler is created for *each* element in a subtree that we
  * recompute styles for.
  */
 class ElementRestyler MOZ_FINAL {
 public:
@@ -446,16 +489,22 @@ public:
    * 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.
    */
   nsChangeHint HintsHandledForFrame() { return mHintsHandled; }
 
+#ifdef RESTYLE_LOGGING
+  bool ShouldLogRestyle() {
+    return RestyleManager::ShouldLogRestyle(mPresContext);
+  }
+#endif
+
 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 {
 
@@ -513,16 +562,24 @@ private:
   };
 
   enum A11yNotificationType {
     eDontNotify,
     eNotifyShown,
     eNotifyHidden
   };
 
+#ifdef RESTYLE_LOGGING
+  int32_t& LoggingDepth() { return mLoggingDepth; }
+#endif
+
+#ifdef DEBUG
+  static nsCString RestyleResultToString(RestyleResult aRestyleResult);
+#endif
+
 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;
@@ -542,13 +599,17 @@ private:
 
 #ifdef ACCESSIBILITY
   const DesiredA11yNotifications mDesiredA11yNotifications;
   DesiredA11yNotifications mKidsDesiredA11yNotifications;
   A11yNotificationType mOurA11yNotification;
   nsTArray<nsIContent*>& mVisibleKidsOfHiddenElement;
   bool mWasFrameVisible;
 #endif
+
+#ifdef RESTYLE_LOGGING
+  int32_t mLoggingDepth;
+#endif
 };
 
 } // namespace mozilla
 
 #endif /* mozilla_RestyleManager_h */
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -7,19 +7,48 @@
  * A class which manages pending restyles.  This handles keeping track
  * of what nodes restyles need to happen on and so forth.
  */
 
 #include "RestyleTracker.h"
 #include "nsStyleChangeList.h"
 #include "RestyleManager.h"
 #include "GeckoProfiler.h"
+#include "nsIDocument.h"
+#include "RestyleTrackerInlines.h"
 
 namespace mozilla {
 
+#ifdef RESTYLE_LOGGING
+static nsCString
+GetDocumentURI(nsIDocument* aDocument)
+{
+  nsCString result;
+  nsString url;
+  aDocument->GetDocumentURI(url);
+  result.Append(NS_ConvertUTF16toUTF8(url).get());
+  return result;
+}
+
+static nsCString
+FrameTagToString(dom::Element* aElement)
+{
+  nsCString result;
+  nsIFrame* frame = aElement->GetPrimaryFrame();
+  if (frame) {
+    nsFrame::ListTag(result, frame);
+  } else {
+    nsAutoString buf;
+    aElement->Tag()->ToString(buf);
+    result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
+  }
+  return result;
+}
+#endif
+
 inline nsIDocument*
 RestyleTracker::Document() const {
   return mRestyleManager->PresContext()->Document();
 }
 
 #define RESTYLE_ARRAY_STACKSIZE 128
 
 struct LaterSiblingCollector {
@@ -51,16 +80,19 @@ CollectLaterSiblings(nsISupports* aEleme
 
 struct RestyleEnumerateData : RestyleTracker::Hints {
   nsRefPtr<dom::Element> mElement;
 };
 
 struct RestyleCollector {
   RestyleTracker* tracker;
   RestyleEnumerateData** restyleArrayPtr;
+#ifdef RESTYLE_LOGGING
+  uint32_t count;
+#endif
 };
 
 static PLDHashOperator
 CollectRestyles(nsISupports* aElement,
                 nsAutoPtr<RestyleTracker::RestyleData>& aData,
                 void* aRestyleCollector)
 {
   dom::Element* element =
@@ -68,16 +100,19 @@ CollectRestyles(nsISupports* aElement,
   RestyleCollector* collector =
     static_cast<RestyleCollector*>(aRestyleCollector);
   // Only collect the entries that actually need restyling by us (and
   // haven't, for example, already been restyled).
   // It's important to not mess with the flags on entries not in our
   // document.
   if (element->GetCrossShadowCurrentDoc() != collector->tracker->Document() ||
       !element->HasFlag(collector->tracker->RestyleBit())) {
+    LOG_RESTYLE_IF(collector->tracker, true,
+                   "skipping pending restyle %s, already restyled or no longer "
+                   "in the document", FrameTagToString(element).get());
     return PL_DHASH_NEXT;
   }
 
   NS_ASSERTION(!element->HasFlag(collector->tracker->RootBit()) ||
                // Maybe we're just not reachable via the frame tree?
                (element->GetFlattenedTreeParent() &&
                 (!element->GetFlattenedTreeParent()->GetPrimaryFrame()||
                  element->GetFlattenedTreeParent()->GetPrimaryFrame()->IsLeaf())) ||
@@ -96,16 +131,20 @@ CollectRestyles(nsISupports* aElement,
                       collector->tracker->RootBit());
 
   RestyleEnumerateData** restyleArrayPtr = collector->restyleArrayPtr;
   RestyleEnumerateData* currentRestyle = *restyleArrayPtr;
   currentRestyle->mElement = element;
   currentRestyle->mRestyleHint = aData->mRestyleHint;
   currentRestyle->mChangeHint = aData->mChangeHint;
 
+#ifdef RESTYLE_LOGGING
+  collector->count++;
+#endif
+
   // Increment to the next slot in the array
   *restyleArrayPtr = currentRestyle + 1;
 
   return PL_DHASH_NEXT;
 }
 
 inline void
 RestyleTracker::ProcessOneRestyle(Element* aElement,
@@ -113,16 +152,20 @@ RestyleTracker::ProcessOneRestyle(Elemen
                                   nsChangeHint aChangeHint)
 {
   NS_PRECONDITION((aRestyleHint & eRestyle_LaterSiblings) == 0,
                   "Someone should have handled this before calling us");
   NS_PRECONDITION(Document(), "Must have a document");
   NS_PRECONDITION(aElement->GetCrossShadowCurrentDoc() == Document(),
                   "Element has unexpected document");
 
+  LOG_RESTYLE("aRestyleHint = %s, aChangeHint = %s",
+              RestyleManager::RestyleHintToString(aRestyleHint).get(),
+              RestyleManager::ChangeHintToString(aChangeHint).get());
+
   nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
   if (aRestyleHint & ~eRestyle_LaterSiblings) {
     mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint,
                                     *this, aRestyleHint);
   } else if (aChangeHint &&
              (primaryFrame ||
               (aChangeHint & nsChangeHint_ReconstructFrame))) {
     // Don't need to recompute style; just apply the hint
@@ -135,34 +178,47 @@ RestyleTracker::ProcessOneRestyle(Elemen
 void
 RestyleTracker::DoProcessRestyles()
 {
   PROFILER_LABEL("RestyleTracker", "ProcessRestyles",
     js::ProfileEntry::Category::CSS);
 
   mRestyleManager->BeginProcessingRestyles();
 
+  LOG_RESTYLE("Processing %d pending %srestyles with %d restyle roots for %s",
+              mPendingRestyles.Count(),
+              mRestyleManager->PresContext()->IsProcessingAnimationStyleChange()
+                ? (const char*) "animation " : (const char*) "",
+              static_cast<int>(mRestyleRoots.Length()),
+              GetDocumentURI(Document()).get());
+  LOG_RESTYLE_INDENT();
+
   // loop so that we process any restyle events generated by processing
   while (mPendingRestyles.Count()) {
     if (mHaveLaterSiblingRestyles) {
       // Convert them to individual restyles on all the later siblings
       nsAutoTArray<nsRefPtr<Element>, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr;
       LaterSiblingCollector siblingCollector = { this, &laterSiblingArr };
       mPendingRestyles.Enumerate(CollectLaterSiblings, &siblingCollector);
       for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
         Element* element = laterSiblingArr[i];
         for (nsIContent* sibling = element->GetNextSibling();
              sibling;
              sibling = sibling->GetNextSibling()) {
-          if (sibling->IsElement() &&
-              AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree,
-                                NS_STYLE_HINT_NONE)) {
-              // Nothing else to do here; we'll handle the following
-              // siblings when we get to |sibling| in laterSiblingArr.
-            break;
+          if (sibling->IsElement()) {
+            LOG_RESTYLE("adding pending restyle for %s due to "
+                        "eRestyle_LaterSiblings hint on %s",
+                        FrameTagToString(sibling->AsElement()).get(),
+                        FrameTagToString(element->AsElement()).get());
+            if (AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree,
+                                  NS_STYLE_HINT_NONE)) {
+                // Nothing else to do here; we'll handle the following
+                // siblings when we get to |sibling| in laterSiblingArr.
+              break;
+            }
           }
         }
       }
 
       // Now remove all those eRestyle_LaterSiblings bits
       for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
         Element* element = laterSiblingArr[i];
         NS_ASSERTION(element->HasFlag(RestyleBit()), "How did that happen?");
@@ -171,39 +227,48 @@ RestyleTracker::DoProcessRestyles()
         bool found =
 #endif
           mPendingRestyles.Get(element, &data);
         NS_ASSERTION(found, "Where did our entry go?");
         data->mRestyleHint =
           nsRestyleHint(data->mRestyleHint & ~eRestyle_LaterSiblings);
       }
 
+      LOG_RESTYLE("%d pending restyles after expanding out "
+                  "eRestyle_LaterSiblings", mPendingRestyles.Count());
+
       mHaveLaterSiblingRestyles = false;
     }
 
     uint32_t rootCount;
     while ((rootCount = mRestyleRoots.Length())) {
       // Make sure to pop the element off our restyle root array, so
       // that we can freely append to the array as we process this
       // element.
       nsRefPtr<Element> element;
       element.swap(mRestyleRoots[rootCount - 1]);
       mRestyleRoots.RemoveElementAt(rootCount - 1);
 
+      LOG_RESTYLE("processing style root %s at index %d",
+                  FrameTagToString(element).get(), rootCount - 1);
+      LOG_RESTYLE_INDENT();
+
       // Do the document check before calling GetRestyleData, since we
       // don't want to do the sibling-processing GetRestyleData does if
       // the node is no longer relevant.
       if (element->GetCrossShadowCurrentDoc() != Document()) {
         // Content node has been removed from our document; nothing else
         // to do here
+        LOG_RESTYLE("skipping, no longer in the document");
         continue;
       }
 
       nsAutoPtr<RestyleData> data;
       if (!GetRestyleData(element, data)) {
+        LOG_RESTYLE("skipping, already restyled");
         continue;
       }
 
       ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint);
       AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
     }
 
     if (mHaveLaterSiblingRestyles) {
@@ -222,19 +287,26 @@ RestyleTracker::DoProcessRestyles()
     if (restylesToProcess) {
       RestyleEnumerateData* lastRestyle = restylesToProcess;
       RestyleCollector collector = { this, &lastRestyle };
       mPendingRestyles.Enumerate(CollectRestyles, &collector);
 
       // Clear the hashtable now that we don't need it anymore
       mPendingRestyles.Clear();
 
+#ifdef RESTYLE_LOGGING
+      uint32_t index = 0;
+#endif
       for (RestyleEnumerateData* currentRestyle = restylesToProcess;
            currentRestyle != lastRestyle;
            ++currentRestyle) {
+        LOG_RESTYLE("processing pending restyle %s at index %d/%d",
+                    FrameTagToString(currentRestyle->mElement).get(),
+                    index++, collector.count);
+        LOG_RESTYLE_INDENT();
         ProcessOneRestyle(currentRestyle->mElement,
                           currentRestyle->mRestyleHint,
                           currentRestyle->mChangeHint);
       }
     }
   }
 
   mRestyleManager->EndProcessingRestyles();
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -10,16 +10,17 @@
 
 #ifndef mozilla_RestyleTracker_h
 #define mozilla_RestyleTracker_h
 
 #include "mozilla/dom/Element.h"
 #include "nsClassHashtable.h"
 #include "nsContainerFrame.h"
 #include "mozilla/SplayTree.h"
+#include "mozilla/RestyleLogging.h"
 
 namespace mozilla {
 
 class RestyleManager;
 class ElementRestyler;
 
 /** 
  * Helper class that collects a list of frames that need
@@ -228,19 +229,19 @@ private:
 };
 
 class RestyleTracker {
 public:
   typedef mozilla::dom::Element Element;
 
   friend class ElementRestyler; // for AddPendingRestyleToTable
 
-  explicit RestyleTracker(Element::FlagsType aRestyleBits) :
-    mRestyleBits(aRestyleBits),
-    mHaveLaterSiblingRestyles(false)
+  explicit RestyleTracker(Element::FlagsType aRestyleBits)
+    : mRestyleBits(aRestyleBits)
+    , mHaveLaterSiblingRestyles(false)
   {
     NS_PRECONDITION((mRestyleBits & ~ELEMENT_ALL_RESTYLE_FLAGS) == 0,
                     "Why do we have these bits set?");
     NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
                     "Must have a restyle flag");
     NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) !=
                       ELEMENT_PENDING_RESTYLE_FLAGS,
                     "Shouldn't have both restyle flags set");
@@ -337,16 +338,22 @@ public:
   void AddRestyleRootsIfAwaitingRestyle(
                                   const nsTArray<nsRefPtr<Element>>& aElements);
 
   /**
    * The document we're associated with.
    */
   inline nsIDocument* Document() const;
 
+#ifdef RESTYLE_LOGGING
+  // Defined in RestyleTrackerInlines.h.
+  inline bool ShouldLogRestyle();
+  inline int32_t& LoggingDepth();
+#endif
+
 private:
   bool AddPendingRestyleToTable(Element* aElement, nsRestyleHint aRestyleHint,
                                 nsChangeHint aMinChangeHint);
 
   /**
    * Handle a single mPendingRestyles entry.  aRestyleHint must not
    * include eRestyle_LaterSiblings; that needs to be dealt with
    * before calling this function.
new file mode 100644
--- /dev/null
+++ b/layout/base/RestyleTrackerInlines.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RestyleTrackerInlines_h
+#define mozilla_RestyleTrackerInlines_h
+
+#ifdef RESTYLE_LOGGING
+bool
+mozilla::RestyleTracker::ShouldLogRestyle()
+{
+  return mRestyleManager->ShouldLogRestyle();
+}
+
+int32_t&
+mozilla::RestyleTracker::LoggingDepth()
+{
+  return mRestyleManager->LoggingDepth();
+}
+#endif
+
+#endif // !defined(mozilla_RestyleTrackerInlines_h)
--- a/layout/base/moz.build
+++ b/layout/base/moz.build
@@ -54,16 +54,17 @@ EXPORTS += [
     'Units.h',
     'UnitTransforms.h',
     'WordMovementType.h',
 ]
 
 EXPORTS.mozilla += [
     'GeometryUtils.h',
     'PaintTracker.h',
+    'RestyleLogging.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActiveLayerTracker.cpp',
     'DisplayItemClip.cpp',
     'DisplayListClipState.cpp',
     'FrameLayerBuilder.cpp',
     'FramePropertyTable.cpp',
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -148,17 +148,18 @@ enum nsChangeHint {
    * set, then nsChangeHint_NeutralChange need not also be included, but it is
    * safe to do so.  (An example of style structs having non-meaningfully
    * different data would be cached information that would be re-calculated
    * to the same values, such as nsStyleBorder::mSubImages.)
    */
   nsChangeHint_NeutralChange = 0x100000
 
   // IMPORTANT NOTE: When adding new hints, consider whether you need to
-  // add them to NS_HintsNotHandledForDescendantsIn() below.
+  // add them to NS_HintsNotHandledForDescendantsIn() below.  Please also
+  // add them to RestyleManager::ChangeHintToString.
 };
 
 // Redefine these operators to return nothing. This will catch any use
 // of these operators on hints. We should not be using these operators
 // on nsChangeHints
 inline void operator<(nsChangeHint s1, nsChangeHint s2) {}
 inline void operator>(nsChangeHint s1, nsChangeHint s2) {}
 inline void operator!=(nsChangeHint s1, nsChangeHint s2) {}
@@ -272,16 +273,19 @@ inline nsChangeHint NS_HintsNotHandledFo
  * can stop processing at a frame when it detects no style changes and it is
  * known that the styles of the subtree beneath it will not change, leaving
  * the old style context on the frame.  eRestyle_Force can be used to skip this
  * optimization on a frame, and to force its new style context to be used.
  *
  * Similarly, eRestyle_ForceDescendants will cause the frame and all of its
  * descendants to be traversed and for the new style contexts that are created
  * to be set on the frames.
+ *
+ * NOTE: When adding new restyle hints, please also add them to
+ * RestyleManager::RestyleHintToString.
  */
 enum nsRestyleHint {
   // Rerun selector matching on the element.  If a new style context
   // results, update the style contexts of descendants.  (Irrelevant if
   // eRestyle_Subtree is also set, since that implies a superset of the
   // work.)
   eRestyle_Self = (1<<0),
 
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1069,16 +1069,20 @@ nsPresContext::Init(nsDeviceContext* aDe
                                 "nglayout.debug.paint_flashing_chrome",
                                 this);
 
   nsresult rv = mEventManager->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   mEventManager->SetPresContext(this);
 
+#ifdef RESTYLE_LOGGING
+  mRestyleLoggingEnabled = RestyleManager::RestyleLoggingInitiallyEnabled();
+#endif
+
 #ifdef DEBUG
   mInitialized = true;
 #endif
 
   mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THIN] = CSSPixelsToAppUnits(1);
   mBorderWidthTable[NS_STYLE_BORDER_WIDTH_MEDIUM] = CSSPixelsToAppUnits(3);
   mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THICK] = CSSPixelsToAppUnits(5);
 
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -32,16 +32,17 @@
 #include "nsAutoPtr.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/AppUnits.h"
 #include "prclist.h"
 #include "nsThreadUtils.h"
 #include "ScrollbarStyles.h"
 #include "nsIMessageManager.h"
+#include "mozilla/RestyleLogging.h"
 
 class nsBidiPresUtils;
 class nsAString;
 class nsIPrintSettings;
 class nsDocShell;
 class nsIDocShell;
 class nsIDocument;
 class nsILanguageAtomService;
@@ -1146,16 +1147,24 @@ public:
 
   /**
    * Checks for MozAfterPaint listeners on the document and
    * any subdocuments, except for subdocuments that are non-top-level
    * content documents.
    */
   bool MayHavePaintEventListenerInSubDocument();
 
+#ifdef RESTYLE_LOGGING
+  // Controls for whether debug information about restyling in this
+  // document should be output.
+  bool RestyleLoggingEnabled() const { return mRestyleLoggingEnabled; }
+  void StartRestyleLogging() { mRestyleLoggingEnabled = true; }
+  void StopRestyleLogging() { mRestyleLoggingEnabled = false; }
+#endif
+
 protected:
   void InvalidateThebesLayers();
   void AppUnitsPerDevPixelChanged();
 
   void HandleRebuildUserFontSet() {
     mPostedFlushUserFontSet = false;
     FlushUserFontSet();
   }
@@ -1347,16 +1356,21 @@ protected:
 
   // Should we paint flash in this context? Do not use this variable directly.
   // Use GetPaintFlashing() method instead.
   mutable unsigned mPaintFlashing : 1;
   mutable unsigned mPaintFlashingInitialized : 1;
 
   unsigned mHasWarnedAboutPositionedTableParts : 1;
 
+#ifdef RESTYLE_LOGGING
+  // Should we output debug information about restyling for this document?
+  bool                  mRestyleLoggingEnabled;
+#endif
+
 #ifdef DEBUG
   bool                  mInitialized;
 #endif
 
 
 protected:
 
   virtual ~nsPresContext();
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -139,20 +139,20 @@ nsStyleContext::AssertStructsNotUsedElse
   if (this != aDestroyingContext) {
     nsInheritedStyleData& destroyingInheritedData =
       aDestroyingContext->mCachedInheritedData;
 #define STYLE_STRUCT_INHERITED(name_, checkdata_cb)                            \
     data = destroyingInheritedData.mStyleStructs[eStyleStruct_##name_];        \
     if (data &&                                                                \
         !(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) &&          \
          (mCachedInheritedData.mStyleStructs[eStyleStruct_##name_] == data)) { \
-      printf("style struct %p found on style context %p\n", data, this);       \
+      printf_stderr("style struct %p found on style context %p\n", data, this);       \
       nsString url;                                                            \
       PresContext()->Document()->GetURL(url);                                  \
-      printf("  in %s\n", NS_LossyConvertUTF16toASCII(url).get());             \
+      printf_stderr("  in %s\n", NS_LossyConvertUTF16toASCII(url).get());             \
       MOZ_ASSERT(false, "destroying " #name_ " style struct still present "    \
                         "in style context tree");                              \
     }
 #define STYLE_STRUCT_RESET(name_, checkdata_cb)
 
 #include "nsStyleStructList.h"
 
 #undef STYLE_STRUCT_INHERITED
@@ -163,20 +163,20 @@ nsStyleContext::AssertStructsNotUsedElse
         aDestroyingContext->mCachedResetData;
       if (destroyingResetData) {
 #define STYLE_STRUCT_INHERITED(name_, checkdata_cb_)
 #define STYLE_STRUCT_RESET(name_, checkdata_cb)                                \
         data = destroyingResetData->mStyleStructs[eStyleStruct_##name_];       \
         if (data &&                                                            \
             !(aDestroyingContext->mBits & NS_STYLE_INHERIT_BIT(name_)) &&      \
             (mCachedResetData->mStyleStructs[eStyleStruct_##name_] == data)) { \
-          printf("style struct %p found on style context %p\n", data, this);   \
+          printf_stderr("style struct %p found on style context %p\n", data, this);   \
           nsString url;                                                        \
           PresContext()->Document()->GetURL(url);                              \
-          printf("  in %s\n", NS_LossyConvertUTF16toASCII(url).get());         \
+          printf_stderr("  in %s\n", NS_LossyConvertUTF16toASCII(url).get());         \
           MOZ_ASSERT(false, "destroying " #name_ " style struct still present "\
                             "in style context tree");                          \
         }
 
 #include "nsStyleStructList.h"
 
 #undef STYLE_STRUCT_INHERITED
 #undef STYLE_STRUCT_RESET
@@ -1028,16 +1028,30 @@ nsStyleContext::CombineVisitedColors(nsc
 nsStyleContext::AssertStyleStructMaxDifferenceValid()
 {
 #define STYLE_STRUCT(name, checkdata_cb)                                     \
     MOZ_ASSERT(NS_IsHintSubset(nsStyle##name::MaxDifferenceNeverInherited(), \
                                nsStyle##name::MaxDifference()));
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
 }
+
+/* static */ const char*
+nsStyleContext::StructName(nsStyleStructID aSID)
+{
+  switch (aSID) {
+#define STYLE_STRUCT(name_, checkdata_cb)                                     \
+    case eStyleStruct_##name_:                                                \
+      return #name_;
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+    default:
+      return "Unknown";
+  }
+}
 #endif
 
 bool
 nsStyleContext::HasSameCachedStyleData(nsStyleContext* aOther,
                                        nsStyleStructID aSID)
 {
   return GetCachedStyleData(aSID) == aOther->GetCachedStyleData(aSID);
 }
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -391,16 +391,17 @@ public:
    * On each descendant of this style context, clears out any cached inherited
    * structs indicated in aStructs.
    */
   void ClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs);
 
 #ifdef DEBUG
   void List(FILE* out, int32_t aIndent);
   static void AssertStyleStructMaxDifferenceValid();
+  static const char* StructName(nsStyleStructID aSID);
 #endif
 
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~nsStyleContext();
 
   void AddChild(nsStyleContext* aChild);
   void RemoveChild(nsStyleContext* aChild);