Bug 911889. Part 2: Refactor MarkLayersActive code into its own class and be much more explicit about what it does. r=mattwoodrow
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 04 Sep 2013 23:30:57 +1200
changeset 153228 09abc00886bdb5eb9c9b8ff7166b5c3014735623
parent 153227 29a30f4e9ad51ce20d91fda17a2bddcbe459da73
child 153229 4adca2b4f651cd27cd055b1ff4bb6404e69ab9ce
push id35723
push userrocallahan@mozilla.com
push dateSat, 02 Nov 2013 07:59:39 +0000
treeherdermozilla-inbound@eb66a2aa121e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs911889
milestone28.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 911889. Part 2: Refactor MarkLayersActive code into its own class and be much more explicit about what it does. r=mattwoodrow This also changes the functionality a little bit to track independent per-property mutation counts and independent "content active" status.
content/html/content/src/HTMLCanvasElement.cpp
dom/plugins/test/mochitest/test_painting.html
layout/base/ActiveLayerTracker.cpp
layout/base/ActiveLayerTracker.h
layout/base/FrameLayerBuilder.cpp
layout/base/RestyleManager.cpp
layout/base/moz.build
layout/base/nsDisplayList.cpp
layout/build/nsLayoutStatics.cpp
layout/generic/nsFrame.cpp
layout/generic/nsFrame.h
layout/generic/nsHTMLCanvasFrame.cpp
layout/generic/nsIFrame.h
layout/style/nsAnimationManager.cpp
layout/style/nsTransitionManager.cpp
--- a/content/html/content/src/HTMLCanvasElement.cpp
+++ b/content/html/content/src/HTMLCanvasElement.cpp
@@ -28,16 +28,17 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsITimer.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIXPConnect.h"
 #include "nsJSUtils.h"
 #include "nsMathUtils.h"
 #include "nsNetUtil.h"
 #include "nsStreamUtils.h"
+#include "ActiveLayerTracker.h"
 
 #ifdef MOZ_WEBGL
 #include "../canvas/src/WebGL2Context.h"
 #endif
 
 using namespace mozilla::layers;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
@@ -830,17 +831,17 @@ void
 HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect)
 {
   // We don't need to flush anything here; if there's no frame or if
   // we plan to reframe we don't need to invalidate it anyway.
   nsIFrame *frame = GetPrimaryFrame();
   if (!frame)
     return;
 
-  frame->MarkLayersActive(nsChangeHint(0));
+  ActiveLayerTracker::NotifyContentChange(frame);
 
   Layer* layer = nullptr;
   if (damageRect) {
     nsIntSize size = GetWidthHeight();
     if (size.width != 0 && size.height != 0) {
 
       gfx::Rect realRect(*damageRect);
       realRect.RoundOut();
--- a/dom/plugins/test/mochitest/test_painting.html
+++ b/dom/plugins/test/mochitest/test_painting.html
@@ -92,17 +92,17 @@ function waitForPaint(func) {
 
 function waitForPaintHelper(func) {
   if (paint_waiter.getPaintCount() != paint_waiter.last_paint_count) {
     // hide the paint waiter
     paint_waiter.style.height = "0px";
     setTimeout(func, 0);
     return;
   }
-  setTimeout(function() { waitForPaintHelper(func); }, 100);
+  setTimeout(function() { waitForPaintHelper(func); }, 1000);
 }
 
 </script>
 
 <p id="display"></p>
 <div id="container">
   <embed id="paint-waiter" type="application/x-test"/>
   <div id="clip">
new file mode 100644
--- /dev/null
+++ b/layout/base/ActiveLayerTracker.cpp
@@ -0,0 +1,175 @@
+/* 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/. */
+
+#include "ActiveLayerTracker.h"
+
+#include "nsExpirationTracker.h"
+#include "nsIFrame.h"
+#include "nsIContent.h"
+
+namespace mozilla {
+
+/**
+ * This tracks the state of a frame that may need active layers due to
+ * ongoing content changes or style changes that indicate animation.
+ *
+ * When no changes of *any* kind are detected after 75-100ms we remove this
+ * object. Because we only track all kinds of activity with a single
+ * nsExpirationTracker, it's possible a frame might remain active somewhat
+ * spuriously if different kinds of changes kept happening, but that almost
+ * certainly doesn't matter.
+ */
+class LayerActivity {
+public:
+  LayerActivity(nsIFrame* aFrame)
+    : mFrame(aFrame)
+    , mOpacityRestyleCount(0)
+    , mTransformRestyleCount(0)
+    , mContentActive(false)
+  {}
+  ~LayerActivity();
+  nsExpirationState* GetExpirationState() { return &mState; }
+  uint8_t& RestyleCountForProperty(nsCSSProperty aProperty)
+  {
+    switch (aProperty) {
+    case eCSSProperty_opacity: return mOpacityRestyleCount;
+    case eCSSProperty_transform: return mTransformRestyleCount;
+    default: MOZ_ASSERT(false); return mOpacityRestyleCount;
+    }
+  }
+
+  nsIFrame* mFrame;
+  nsExpirationState mState;
+  // Number of restyle operations detected
+  uint8_t mOpacityRestyleCount;
+  uint8_t mTransformRestyleCount;
+  bool mContentActive;
+};
+
+class LayerActivityTracker MOZ_FINAL : public nsExpirationTracker<LayerActivity,4> {
+public:
+  // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
+  enum { GENERATION_MS = 100 };
+  LayerActivityTracker()
+    : nsExpirationTracker<LayerActivity,4>(GENERATION_MS) {}
+  ~LayerActivityTracker() {
+    AgeAllGenerations();
+  }
+
+  virtual void NotifyExpired(LayerActivity* aObject);
+};
+
+static LayerActivityTracker* gLayerActivityTracker = nullptr;
+
+LayerActivity::~LayerActivity()
+{
+  if (mFrame) {
+    NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
+    gLayerActivityTracker->RemoveObject(this);
+  }
+}
+
+static void DestroyLayerActivity(void* aPropertyValue)
+{
+  delete static_cast<LayerActivity*>(aPropertyValue);
+}
+
+NS_DECLARE_FRAME_PROPERTY(LayerActivityProperty, DestroyLayerActivity)
+
+void
+LayerActivityTracker::NotifyExpired(LayerActivity* aObject)
+{
+  RemoveObject(aObject);
+
+  nsIFrame* f = aObject->mFrame;
+  aObject->mFrame = nullptr;
+
+  f->SchedulePaint();
+  f->Properties().Delete(LayerActivityProperty());
+}
+
+static LayerActivity*
+GetLayerActivity(nsIFrame* aFrame)
+{
+  FrameProperties properties = aFrame->Properties();
+  return static_cast<LayerActivity*>(properties.Get(LayerActivityProperty()));
+}
+
+static LayerActivity*
+GetLayerActivityForUpdate(nsIFrame* aFrame)
+{
+  FrameProperties properties = aFrame->Properties();
+  LayerActivity* layerActivity =
+    static_cast<LayerActivity*>(properties.Get(LayerActivityProperty()));
+  if (layerActivity) {
+    gLayerActivityTracker->MarkUsed(layerActivity);
+  } else {
+    if (!gLayerActivityTracker) {
+      gLayerActivityTracker = new LayerActivityTracker();
+    }
+    layerActivity = new LayerActivity(aFrame);
+    gLayerActivityTracker->AddObject(layerActivity);
+    properties.Set(LayerActivityProperty(), layerActivity);
+  }
+  return layerActivity;
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, nsCSSProperty aProperty)
+{
+  LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+  uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+  mutationCount = uint8_t(std::min(0xFF, mutationCount + 1));
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty)
+{
+  LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+  uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+  // We know this is animated, so just hack the mutation count.
+  mutationCount = 0xFF;
+}
+
+/* static */ bool
+ActiveLayerTracker::IsStyleAnimated(nsIFrame* aFrame, nsCSSProperty aProperty)
+{
+  LayerActivity* layerActivity = GetLayerActivity(aFrame);
+  if (layerActivity) {
+    // XXX should we really treat a single change to transform as animation?
+    uint8_t minStyleChangesToBeConsideredAnimation =
+        aProperty == eCSSProperty_opacity ? 2 : 1;
+    if (layerActivity->RestyleCountForProperty(aProperty) >=
+        minStyleChangesToBeConsideredAnimation) {
+      return true;
+    }
+  }
+  if (aProperty == eCSSProperty_transform && aFrame->Preserves3D()) {
+    return IsStyleAnimated(aFrame->GetParent(), aProperty);
+  }
+  return false;
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyContentChange(nsIFrame* aFrame)
+{
+  LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+  layerActivity->mContentActive = true;
+}
+
+/* static */ bool
+ActiveLayerTracker::IsContentActive(nsIFrame* aFrame)
+{
+  LayerActivity* layerActivity = GetLayerActivity(aFrame);
+  return layerActivity && layerActivity->mContentActive;
+}
+
+/* static */ void
+ActiveLayerTracker::Shutdown()
+{
+  delete gLayerActivityTracker;
+  gLayerActivityTracker = nullptr;
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/layout/base/ActiveLayerTracker.h
@@ -0,0 +1,60 @@
+/* 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 ACTIVELAYERTRACKER_H_
+#define ACTIVELAYERTRACKER_H_
+
+#include "nsCSSProperty.h"
+
+class nsIFrame;
+
+namespace mozilla {
+
+/**
+ * This class receives various notifications about style changes and content
+ * changes that affect layerization decisions, and implements the heuristics
+ * that drive those decisions. It manages per-frame state to support those
+ * heuristics.
+ */
+class ActiveLayerTracker {
+public:
+  /*
+   * We track eCSSProperty_transform and eCSSProperty_opacity style changes
+   * and use that information to guess whether style changes are animated.
+   */
+
+  /**
+   * Notify aFrame's style property as having changed due to a restyle,
+   * and therefore possibly wanting an active layer to render that style.
+   * Any such marking will time out after a short period.
+   * @param aProperty the property that has changed
+   */
+  static void NotifyRestyle(nsIFrame* aFrame, nsCSSProperty aProperty);
+  /**
+   * Mark aFrame as being known to have an animation of aProperty.
+   * Any such marking will time out after a short period.
+   */
+  static void NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty);
+  /**
+   * Return true if aFrame's aProperty style should be considered as being animated
+   * for constructing active layers.
+   */
+  static bool IsStyleAnimated(nsIFrame* aFrame, nsCSSProperty aProperty);
+
+  /**
+   * Mark aFrame's content as being active. This marking will time out after
+   * a short period. This is useful for frames such as canvas frames.
+   */
+  static void NotifyContentChange(nsIFrame* aFrame);
+  /**
+   * Return true if this frame's content is still marked as active.
+   */
+  static bool IsContentActive(nsIFrame* aFrame);
+
+  static void Shutdown();
+};
+
+}
+
+#endif /* ACTIVELAYERTRACKER_H_ */
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -15,16 +15,17 @@
 #include "gfxUtils.h"
 #include "nsRenderingContext.h"
 #include "MaskLayerImageCache.h"
 #include "nsIScrollableFrame.h"
 #include "nsPrintfCString.h"
 #include "LayerTreeInvalidation.h"
 #include "nsSVGIntegrationUtils.h"
 #include "ImageContainer.h"
+#include "ActiveLayerTracker.h"
 
 #include "GeckoProfiler.h"
 #include "mozilla/gfx/Tools.h"
 
 #include <algorithm>
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
@@ -2693,26 +2694,26 @@ ChooseScaleAndSetTransform(FrameLayerBui
   if (canDraw2D && isRetained) {
     // If the container's transform is animated off main thread, then use the
     // maximum scale.
     if (aContainerFrame->GetContent() &&
         nsLayoutUtils::HasAnimationsForCompositor(
           aContainerFrame->GetContent(), eCSSProperty_transform)) {
       scale = nsLayoutUtils::GetMaximumAnimatedScale(aContainerFrame->GetContent());
     } else {
-      //Scale factors are normalized to a power of 2 to reduce the number of resolution changes
+      // Scale factors are normalized to a power of 2 to reduce the number of resolution changes
       scale = RoundToFloatPrecision(transform2d.ScaleFactors(true));
       // For frames with a changing transform that's not just a translation,
       // round scale factors up to nearest power-of-2 boundary so that we don't
       // keep having to redraw the content as it scales up and down. Rounding up to nearest
       // power-of-2 boundary ensures we never scale up, only down --- avoiding
       // jaggies. It also ensures we never scale down by more than a factor of 2,
       // avoiding bad downscaling quality.
       gfxMatrix frameTransform;
-      if (aContainerFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer) &&
+      if (ActiveLayerTracker::IsStyleAnimated(aContainerFrame, eCSSProperty_transform) &&
           aTransform &&
           (!aTransform->Is2D(&frameTransform) || frameTransform.HasNonTranslationOrFlip())) {
         // Don't clamp the scale factor when the new desired scale factor matches the old one
         // or it was previously unscaled.
         bool clamp = true;
         gfxMatrix oldFrameTransform2d;
         if (aLayer->GetBaseTransform().Is2D(&oldFrameTransform2d)) {
           gfxSize oldScale = RoundToFloatPrecision(oldFrameTransform2d.ScaleFactors(true));
@@ -2743,17 +2744,17 @@ ChooseScaleAndSetTransform(FrameLayerBui
                       1.0f/float(scale.height));
   aLayer->SetInheritedScale(aIncomingScale.mXScale,
                             aIncomingScale.mYScale);
 
   aOutgoingScale = 
     FrameLayerBuilder::ContainerParameters(scale.width, scale.height, -offset, aIncomingScale);
   if (aTransform) {
     aOutgoingScale.mInTransformedSubtree = true;
-    if (aContainerFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer)) {
+    if (ActiveLayerTracker::IsStyleAnimated(aContainerFrame, eCSSProperty_transform)) {
       aOutgoingScale.mInActiveTransformedSubtree = true;
     }
   }
   if (isRetained && (!canDraw2D || transform2d.HasNonIntegerTranslation())) {
     aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true;
   }
   return true;
 }
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -29,16 +29,17 @@
 #include "nsViewportFrame.h"
 #include "nsSVGTextFrame2.h"
 #include "nsSVGTextPathFrame.h"
 #include "StickyScrollContainer.h"
 #include "nsIRootBox.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsContentUtils.h"
 #include "nsIFrameInlines.h"
+#include "ActiveLayerTracker.h"
 
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
 namespace mozilla {
 
 RestyleManager::RestyleManager(nsPresContext* aPresContext)
@@ -219,40 +220,41 @@ DoApplyRenderingChangeToTree(nsIFrame* a
         NS_ABORT_IF_FALSE(false, "unexpected frame got "
                                  "nsChangeHint_UpdateTextPath");
       }
     }
     if (aChange & nsChangeHint_UpdateOpacityLayer) {
       // FIXME/bug 796697: we can get away with empty transactions for
       // opacity updates in many cases.
       needInvalidatingPaint = true;
-      aFrame->MarkLayersActive(nsChangeHint_UpdateOpacityLayer);
+
+      ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
       if (nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
         // SVG effects paints the opacity without using
         // nsDisplayOpacity. We need to invalidate manually.
         aFrame->InvalidateFrameSubtree();
       }
     }
     if ((aChange & nsChangeHint_UpdateTransformLayer) &&
         aFrame->IsTransformed()) {
-      aFrame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
+      ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
       // If we're not already going to do an invalidating paint, see
       // if we can get away with only updating the transform on a
       // layer for this frame, and not scheduling an invalidating
       // paint.
       if (!needInvalidatingPaint) {
         needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly();
       }
     }
     if (aChange & nsChangeHint_ChildrenOnlyTransform) {
       needInvalidatingPaint = true;
       nsIFrame* childFrame =
         GetFrameForChildrenOnlyTransformHint(aFrame)->GetFirstPrincipalChild();
       for ( ; childFrame; childFrame = childFrame->GetNextSibling()) {
-        childFrame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
+        ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
       }
     }
     aFrame->SchedulePaint(needInvalidatingPaint ?
                           nsIFrame::PAINT_DEFAULT :
                           nsIFrame::PAINT_COMPOSITE_ONLY);
   }
 }
 
--- a/layout/base/moz.build
+++ b/layout/base/moz.build
@@ -21,16 +21,17 @@ if CONFIG['MOZ_DEBUG']:
         'nsAutoLayoutPhase.cpp',
     ]
 
 XPIDL_MODULE = 'layout_base'
 
 MODULE = 'layout'
 
 EXPORTS += [
+    'ActiveLayerTracker.h',
     'DisplayItemClip.h',
     'DisplayListClipState.h',
     'FrameLayerBuilder.h',
     'FramePropertyTable.h',
     'nsArenaMemoryStats.h',
     'nsBidi.h',
     'nsBidiPresUtils.h',
     'nsCaret.h',
@@ -61,16 +62,17 @@ EXPORTS += [
     'Units.h',
 ]
 
 EXPORTS.mozilla += [
     'PaintTracker.h',
 ]
 
 SOURCES += [
+    'ActiveLayerTracker.cpp',
     'DisplayItemClip.cpp',
     'DisplayListClipState.cpp',
     'FrameLayerBuilder.cpp',
     'FramePropertyTable.cpp',
     'MaskLayerImageCache.cpp',
     'nsCaret.cpp',
     'nsCounterManager.cpp',
     'nsCSSColorUtils.cpp',
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -42,16 +42,17 @@
 #include "nsTransitionManager.h"
 #include "nsViewManager.h"
 #include "ImageLayers.h"
 #include "ImageContainer.h"
 #include "nsCanvasFrame.h"
 #include "StickyScrollContainer.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Preferences.h"
+#include "ActiveLayerTracker.h"
 
 #include <stdint.h>
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::layers;
 using namespace mozilla::dom;
@@ -3085,17 +3086,17 @@ IsItemTooSmallForActiveLayer(nsDisplayIt
   return visibleDevPixels.Size() <
     nsIntSize(MIN_ACTIVE_LAYER_SIZE_DEV_PIXELS, MIN_ACTIVE_LAYER_SIZE_DEV_PIXELS);
 }
 
 nsDisplayItem::LayerState
 nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
                                 LayerManager* aManager,
                                 const ContainerParameters& aParameters) {
-  if (mFrame->AreLayersMarkedActive(nsChangeHint_UpdateOpacityLayer) &&
+  if (ActiveLayerTracker::IsStyleAnimated(mFrame, eCSSProperty_opacity) &&
       !IsItemTooSmallForActiveLayer(this))
     return LAYER_ACTIVE;
   if (mFrame->GetContent()) {
     if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
                                                   eCSSProperty_opacity)) {
       return LAYER_ACTIVE;
     }
   }
@@ -4208,17 +4209,17 @@ nsDisplayTransform::GetResultingTransfor
 
   return nsLayoutUtils::ChangeMatrixBasis
     (rounded + aProperties.mToTransformOrigin, result);
 }
 
 bool
 nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
 {
-  if (Frame()->AreLayersMarkedActive(nsChangeHint_UpdateOpacityLayer)) {
+  if (ActiveLayerTracker::IsStyleAnimated(mFrame, eCSSProperty_opacity)) {
     return true;
   }
 
   if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
     nsCString message;
     message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for opacity animation");
     CommonElementAnimationData::LogAsyncAnimationFailure(message,
                                                          Frame()->GetContent());
@@ -4238,17 +4239,17 @@ nsDisplayTransform::CanUseAsyncAnimation
 nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
                                                       nsIFrame* aFrame,
                                                       bool aLogAnimations)
 {
   // Elements whose transform has been modified recently, or which
   // have a compositor-animated transform, can be prerendered. An element
   // might have only just had its transform animated in which case
   // nsChangeHint_UpdateTransformLayer will not be present yet.
-  if (!aFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer) &&
+  if (!ActiveLayerTracker::IsStyleAnimated(aFrame, eCSSProperty_transform) &&
       (!aFrame->GetContent() ||
        !nsLayoutUtils::HasAnimationsForCompositor(aFrame->GetContent(),
                                                   eCSSProperty_transform))) {
     if (aLogAnimations) {
       nsCString message;
       message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for transform animation");
       CommonElementAnimationData::LogAsyncAnimationFailure(message,
                                                            aFrame->GetContent());
@@ -4388,17 +4389,17 @@ nsDisplayTransform::GetLayerState(nsDisp
   // If the transform is 3d, or the layer takes part in preserve-3d sorting
   // then we *always* want this to be an active layer.
   if (!GetTransform(mFrame->PresContext()->AppUnitsPerDevPixel()).Is2D() || 
       mFrame->Preserves3D()) {
     return LAYER_ACTIVE_FORCE;
   }
   // Here we check if the *post-transform* bounds of this item are big enough
   // to justify an active layer.
-  if (mFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer) &&
+  if (ActiveLayerTracker::IsStyleAnimated(mFrame, eCSSProperty_transform) &&
       !IsItemTooSmallForActiveLayer(this))
     return LAYER_ACTIVE;
   if (mFrame->GetContent()) {
     if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
                                                   eCSSProperty_transform)) {
       return LAYER_ACTIVE;
     }
   }
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -56,16 +56,17 @@
 #include "SVGElementFactory.h"
 #include "nsSVGUtils.h"
 #include "nsMathMLAtoms.h"
 #include "nsMathMLOperators.h"
 #include "Navigator.h"
 #include "DOMStorageObserver.h"
 #include "CacheObserver.h"
 #include "DisplayItemClip.h"
+#include "ActiveLayerTracker.h"
 
 #include "AudioChannelService.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #include "nsXULContentUtils.h"
 #include "nsXULPrototypeCache.h"
 #include "nsXULTooltipListener.h"
@@ -306,17 +307,17 @@ nsLayoutStatics::Shutdown()
   nsCSSRuleProcessor::Shutdown();
   nsTextFrameTextRunCache::Shutdown();
   nsHTMLDNSPrefetch::Shutdown();
   nsCSSRendering::Shutdown();
 #ifdef DEBUG
   nsFrame::DisplayReflowShutdown();
 #endif
   nsCellMap::Shutdown();
-  nsFrame::ShutdownLayerActivityTimer();
+  ActiveLayerTracker::Shutdown();
 
   // Release all of our atoms
   nsColorNames::ReleaseTable();
   nsCSSProps::ReleaseTable();
   nsCSSKeywords::ReleaseTable();
   nsRepeatService::Shutdown();
   nsStackLayout::Shutdown();
   nsBox::Shutdown();
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -57,17 +57,16 @@
 // For triple-click pref
 #include "imgIContainer.h"
 #include "imgIRequest.h"
 #include "nsError.h"
 #include "nsContainerFrame.h"
 #include "nsBoxLayoutState.h"
 #include "nsBlockFrame.h"
 #include "nsDisplayList.h"
-#include "nsExpirationTracker.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGEffects.h"
 #include "nsChangeHint.h"
 #include "nsDeckFrame.h"
 #include "nsSubDocumentFrame.h"
 #include "nsSVGTextFrame2.h"
 
 #include "gfxContext.h"
@@ -4557,140 +4556,16 @@ nsFrame::GetType() const
 }
 
 bool
 nsIFrame::IsLeaf() const
 {
   return true;
 }
 
-class LayerActivity {
-public:
-  LayerActivity(nsIFrame* aFrame)
-    : mFrame(aFrame)
-    , mChangeHint(nsChangeHint(0))
-    , mMutationCount(0)
-  {}
-  ~LayerActivity();
-  nsExpirationState* GetExpirationState() { return &mState; }
-  uint32_t GetMutationCount() { return mMutationCount; }
-
-  nsIFrame* mFrame;
-  nsExpirationState mState;
-  // mChangeHint can be some combination of nsChangeHint_UpdateOpacityLayer and
-  // nsChangeHint_UpdateTransformLayer (or neither)
-  // The presence of those bits indicates whether opacity or transform
-  // changes have been detected.
-  nsChangeHint mChangeHint;
-  uint32_t mMutationCount;
-};
-
-class LayerActivityTracker MOZ_FINAL : public nsExpirationTracker<LayerActivity,4> {
-public:
-  // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
-  enum { GENERATION_MS = 100 };
-  LayerActivityTracker()
-    : nsExpirationTracker<LayerActivity,4>(GENERATION_MS) {}
-  ~LayerActivityTracker() {
-    AgeAllGenerations();
-  }
-
-  virtual void NotifyExpired(LayerActivity* aObject);
-};
-
-static LayerActivityTracker* gLayerActivityTracker = nullptr;
-
-LayerActivity::~LayerActivity()
-{
-  if (mFrame) {
-    NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
-    gLayerActivityTracker->RemoveObject(this);
-  }
-}
-
-static void DestroyLayerActivity(void* aPropertyValue)
-{
-  delete static_cast<LayerActivity*>(aPropertyValue);
-}
-
-NS_DECLARE_FRAME_PROPERTY(LayerActivityProperty, DestroyLayerActivity)
-
-void
-LayerActivityTracker::NotifyExpired(LayerActivity* aObject)
-{
-  RemoveObject(aObject);
-
-  nsIFrame* f = aObject->mFrame;
-  aObject->mFrame = nullptr;
-
-  // if there are hints other than transform/opacity, invalidate, since we don't know what else to do.
-  if (aObject->mChangeHint & ~(nsChangeHint_UpdateOpacityLayer|nsChangeHint_UpdateTransformLayer)) {
-    f->InvalidateFrameSubtree();
-  } else {
-    if (aObject->mChangeHint & nsChangeHint_UpdateOpacityLayer) {
-      f->InvalidateFrameSubtree(nsDisplayItem::TYPE_OPACITY);
-    } 
-    if (aObject->mChangeHint & nsChangeHint_UpdateTransformLayer) {
-      f->InvalidateFrameSubtree(nsDisplayItem::TYPE_TRANSFORM);
-    }
-  } 
-  f->Properties().Delete(LayerActivityProperty());
-}
-
-void
-nsIFrame::MarkLayersActive(nsChangeHint aChangeHint)
-{
-  FrameProperties properties = Properties();
-  LayerActivity* layerActivity =
-    static_cast<LayerActivity*>(properties.Get(LayerActivityProperty()));
-  if (layerActivity) {
-    gLayerActivityTracker->MarkUsed(layerActivity);
-  } else {
-    if (!gLayerActivityTracker) {
-      gLayerActivityTracker = new LayerActivityTracker();
-    }
-    layerActivity = new LayerActivity(this);
-    gLayerActivityTracker->AddObject(layerActivity);
-    properties.Set(LayerActivityProperty(), layerActivity);
-  }
-  layerActivity->mMutationCount++;
-  NS_UpdateHint(layerActivity->mChangeHint, aChangeHint);
-}
-
-bool
-nsIFrame::AreLayersMarkedActive()
-{
-  return Properties().Get(LayerActivityProperty()) != nullptr;
-}
-
-bool
-nsIFrame::AreLayersMarkedActive(nsChangeHint aChangeHint)
-{
-  LayerActivity* layerActivity =
-    static_cast<LayerActivity*>(Properties().Get(LayerActivityProperty()));
-  if (layerActivity && (layerActivity->mChangeHint & aChangeHint)) {
-    if (aChangeHint & nsChangeHint_UpdateOpacityLayer) {
-      return layerActivity->GetMutationCount() > 1;
-    }
-    return true;
-  }
-  if (aChangeHint & nsChangeHint_UpdateTransformLayer &&
-      Preserves3D()) {
-    return GetParent()->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer);
-  }
-  return false;
-}
-
-/* static */ void
-nsFrame::ShutdownLayerActivityTimer()
-{
-  delete gLayerActivityTracker;
-  gLayerActivityTracker = nullptr;
-}
-
 gfx3DMatrix
 nsIFrame::GetTransformMatrix(const nsIFrame* aStopAtAncestor,
                              nsIFrame** aOutAncestor)
 {
   NS_PRECONDITION(aOutAncestor, "Need a place to put the ancestor!");
 
   /* If we're transformed, we want to hand back the combination
    * transform/translate matrix that will apply our current transform, then
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -479,18 +479,16 @@ public:
                                         const char* aType,
                                         nsSize aResult,
                                         void* aFrameTreeNode);
 
   static void DisplayReflowStartup();
   static void DisplayReflowShutdown();
 #endif
 
-  static void ShutdownLayerActivityTimer();
-
   /**
    * Adds display items for standard CSS background if necessary.
    * Does not check IsVisibleForPainting.
    * @param aForceBackground draw the background even if the frame
    * background style appears to have no background --- this is useful
    * for frames that might receive a propagated background via
    * nsCSSRendering::FindBackground
    * @return whether a themed background item was created.
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -7,16 +7,17 @@
 
 #include "nsHTMLCanvasFrame.h"
 
 #include "nsGkAtoms.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsDisplayList.h"
 #include "nsLayoutUtils.h"
 #include "Layers.h"
+#include "ActiveLayerTracker.h"
 
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
 class nsDisplayCanvas : public nsDisplayItem {
@@ -63,20 +64,21 @@ public:
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const FrameLayerBuilder::ContainerParameters& aParameters)
   {
     if (HTMLCanvasElement::FromContent(mFrame->GetContent())->ShouldForceInactiveLayer(aManager))
       return LAYER_INACTIVE;
 
     // If compositing is cheap, just do that
-    if (aManager->IsCompositingCheap())
+    if (aManager->IsCompositingCheap() ||
+        ActiveLayerTracker::IsContentActive(mFrame))
       return mozilla::LAYER_ACTIVE;
 
-    return mFrame->AreLayersMarkedActive() ? LAYER_ACTIVE : LAYER_INACTIVE;
+    return LAYER_INACTIVE;
   }
 };
 
 
 nsIFrame*
 NS_NewHTMLCanvasFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsHTMLCanvasFrame(aContext);
@@ -91,19 +93,19 @@ NS_IMPL_FRAMEARENA_HELPERS(nsHTMLCanvasF
 void
 nsHTMLCanvasFrame::Init(nsIContent* aContent,
                         nsIFrame*   aParent,
                         nsIFrame*   aPrevInFlow)
 {
   nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
 
   // We can fill in the canvas before the canvas frame is created, in
-  // which case we never get around to marking the layer active. Therefore,
+  // which case we never get around to marking the content as active. Therefore,
   // we mark it active here when we create the frame.
-  MarkLayersActive(nsChangeHint(0));
+  ActiveLayerTracker::NotifyContentChange(this);
 }
 
 nsHTMLCanvasFrame::~nsHTMLCanvasFrame()
 {
 }
 
 nsIntSize
 nsHTMLCanvasFrame::GetCanvasSize()
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2172,40 +2172,16 @@ public:
 
   /**
    * Is this a flex item? (Is the parent a flex container frame?)
    */
   bool IsFlexItem() const
   { return mParent && mParent->GetType() == nsGkAtoms::flexContainerFrame; }
 
   /**
-   * Mark this frame as using active layers. This marking will time out
-   * after a short period. This call does no immediate invalidation,
-   * but when the mark times out, we'll invalidate the frame's overflow
-   * area.
-   * @param aChangeHint nsChangeHint_UpdateTransformLayer or
-   * nsChangeHint_UpdateOpacityLayer or 0, depending on whether the change
-   * triggering the activity is a changing transform, changing opacity, or
-   * something else.
-   */
-  void MarkLayersActive(nsChangeHint aHint);
-  /**
-   * Return true if this frame is marked as needing active layers.
-   */
-  bool AreLayersMarkedActive();
-  /**
-   * Return true if this frame is marked as needing active layers.
-   * @param aChangeHint nsChangeHint_UpdateTransformLayer or
-   * nsChangeHint_UpdateOpacityLayer. We return true only if
-   * a change in the transform or opacity has been recorded while layers have
-   * been marked active for this frame.
-   */
-  bool AreLayersMarkedActive(nsChangeHint aChangeHint);
-
-  /**
    * Marks all display items created by this frame as needing a repaint,
    * and calls SchedulePaint() if requested and one is not already pending.
    *
    * This includes all display items created by this frame, including
    * container types.
    *
    * @param aDisplayItemKey If specified, only issues an invalidate
    * if this frame painted a display item of that type during the 
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -11,16 +11,17 @@
 #include "nsRuleProcessorData.h"
 #include "nsStyleSet.h"
 #include "nsCSSRules.h"
 #include "nsStyleAnimation.h"
 #include "nsEventDispatcher.h"
 #include "nsLayoutUtils.h"
 #include "nsIFrame.h"
 #include "nsIDocument.h"
+#include "ActiveLayerTracker.h"
 #include <math.h>
 
 using namespace mozilla;
 using namespace mozilla::css;
 
 ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement, nsIAtom *aElementProperty,
                                      nsAnimationManager *aAnimationManager)
   : CommonElementAnimationData(aElement, aElementProperty,
@@ -411,20 +412,20 @@ ElementAnimations::CanPerformOnComposito
       } else if (prop.mProperty == eCSSProperty_transform) {
         hasTransform = true;
       }
     }
   }
   // This animation can be done on the compositor.  Mark the frame as active, in
   // case we are able to throttle this animation.
   if (hasOpacity) {
-    frame->MarkLayersActive(nsChangeHint_UpdateOpacityLayer);
+    ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
   }
   if (hasTransform) {
-    frame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
+    ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
   }
   return true;
 }
 
 ElementAnimations*
 nsAnimationManager::GetElementAnimations(dom::Element *aElement,
                                          nsCSSPseudoElements::Type aPseudoType,
                                          bool aCreateIfNeeded)
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/dom/Element.h"
 #include "nsIFrame.h"
 #include "Layers.h"
 #include "FrameLayerBuilder.h"
 #include "nsDisplayList.h"
 #include "nsStyleChangeList.h"
 #include "nsStyleSet.h"
 #include "RestyleManager.h"
+#include "ActiveLayerTracker.h"
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::css;
 
@@ -192,20 +193,20 @@ ElementTransitions::CanPerformOnComposit
   // No properties to animate
   if (!existsProperty) {
     return false;
   }
 
   // This transition can be done on the compositor.  Mark the frame as active, in
   // case we are able to throttle this transition.
   if (hasOpacity) {
-    frame->MarkLayersActive(nsChangeHint_UpdateOpacityLayer);
+    ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
   }
   if (hasTransform) {
-    frame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
+    ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
   }
   return true;
 }
 
 /*****************************************************************************
  * nsTransitionManager                                                       *
  *****************************************************************************/