summaryrefslogtreecommitdiffstats
path: root/layout/base/ActiveLayerTracker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/ActiveLayerTracker.cpp')
-rw-r--r--layout/base/ActiveLayerTracker.cpp555
1 files changed, 555 insertions, 0 deletions
diff --git a/layout/base/ActiveLayerTracker.cpp b/layout/base/ActiveLayerTracker.cpp
new file mode 100644
index 000000000..4f60f82d7
--- /dev/null
+++ b/layout/base/ActiveLayerTracker.cpp
@@ -0,0 +1,555 @@
+/* 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 "mozilla/ArrayUtils.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/PodOperations.h"
+#include "gfx2DGlue.h"
+#include "nsExpirationTracker.h"
+#include "nsContainerFrame.h"
+#include "nsIContent.h"
+#include "nsRefreshDriver.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsAnimationManager.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsTransitionManager.h"
+#include "nsDisplayList.h"
+#include "nsDOMCSSDeclaration.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+/**
+ * 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:
+ enum ActivityIndex {
+ ACTIVITY_OPACITY,
+ ACTIVITY_TRANSFORM,
+ ACTIVITY_LEFT,
+ ACTIVITY_TOP,
+ ACTIVITY_RIGHT,
+ ACTIVITY_BOTTOM,
+ ACTIVITY_MARGIN_LEFT,
+ ACTIVITY_MARGIN_TOP,
+ ACTIVITY_MARGIN_RIGHT,
+ ACTIVITY_MARGIN_BOTTOM,
+ ACTIVITY_BACKGROUND_POSITION,
+
+ ACTIVITY_SCALE,
+
+ // keep as last item
+ ACTIVITY_COUNT
+ };
+
+ explicit LayerActivity(nsIFrame* aFrame)
+ : mFrame(aFrame)
+ , mContent(nullptr)
+ , mContentActive(false)
+ {
+ PodArrayZero(mRestyleCounts);
+ }
+ ~LayerActivity();
+ nsExpirationState* GetExpirationState() { return &mState; }
+ uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty)
+ {
+ return mRestyleCounts[GetActivityIndexForProperty(aProperty)];
+ }
+
+ static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty)
+ {
+ switch (aProperty) {
+ case eCSSProperty_opacity: return ACTIVITY_OPACITY;
+ case eCSSProperty_transform: return ACTIVITY_TRANSFORM;
+ case eCSSProperty_left: return ACTIVITY_LEFT;
+ case eCSSProperty_top: return ACTIVITY_TOP;
+ case eCSSProperty_right: return ACTIVITY_RIGHT;
+ case eCSSProperty_bottom: return ACTIVITY_BOTTOM;
+ case eCSSProperty_margin_left: return ACTIVITY_MARGIN_LEFT;
+ case eCSSProperty_margin_top: return ACTIVITY_MARGIN_TOP;
+ case eCSSProperty_margin_right: return ACTIVITY_MARGIN_RIGHT;
+ case eCSSProperty_margin_bottom: return ACTIVITY_MARGIN_BOTTOM;
+ case eCSSProperty_background_position: return ACTIVITY_BACKGROUND_POSITION;
+ case eCSSProperty_background_position_x: return ACTIVITY_BACKGROUND_POSITION;
+ case eCSSProperty_background_position_y: return ACTIVITY_BACKGROUND_POSITION;
+ default: MOZ_ASSERT(false); return ACTIVITY_OPACITY;
+ }
+ }
+
+ // While tracked, exactly one of mFrame or mContent is non-null, depending
+ // on whether this property is stored on a frame or on a content node.
+ // When this property is expired by the layer activity tracker, both mFrame
+ // and mContent are nulled-out and the property is deleted.
+ nsIFrame* mFrame;
+ nsIContent* mContent;
+
+ nsExpirationState mState;
+
+ // Previous scale due to the CSS transform property.
+ Maybe<gfxSize> mPreviousTransformScale;
+
+ // The scroll frame during for which we most recently received a call to
+ // NotifyAnimatedFromScrollHandler.
+ nsWeakFrame mAnimatingScrollHandlerFrame;
+ // The set of activities that were triggered during
+ // mAnimatingScrollHandlerFrame's scroll event handler.
+ EnumSet<ActivityIndex> mScrollHandlerInducedActivity;
+
+ // Number of restyle operations detected
+ uint8_t mRestyleCounts[ACTIVITY_COUNT];
+ bool mContentActive;
+};
+
+class LayerActivityTracker 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")
+ , mDestroying(false)
+ {}
+ ~LayerActivityTracker() {
+ mDestroying = true;
+ AgeAllGenerations();
+ }
+
+ virtual void NotifyExpired(LayerActivity* aObject);
+
+public:
+ nsWeakFrame mCurrentScrollHandlerFrame;
+
+private:
+ bool mDestroying;
+};
+
+static LayerActivityTracker* gLayerActivityTracker = nullptr;
+
+LayerActivity::~LayerActivity()
+{
+ if (mFrame || mContent) {
+ NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
+ gLayerActivityTracker->RemoveObject(this);
+ }
+}
+
+// Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity)
+
+void
+LayerActivityTracker::NotifyExpired(LayerActivity* aObject)
+{
+ if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) {
+ // Reset the restyle counts, but let the layer activity survive.
+ PodArrayZero(aObject->mRestyleCounts);
+ MarkUsed(aObject);
+ return;
+ }
+
+ RemoveObject(aObject);
+
+ nsIFrame* f = aObject->mFrame;
+ nsIContent* c = aObject->mContent;
+ aObject->mFrame = nullptr;
+ aObject->mContent = nullptr;
+
+ MOZ_ASSERT((f == nullptr) != (c == nullptr),
+ "A LayerActivity object should always have a reference to either its frame or its content");
+
+ if (f) {
+ // The pres context might have been detached during the delay -
+ // that's fine, just skip the paint.
+ if (f->PresContext()->GetContainerWeak()) {
+ f->SchedulePaint();
+ }
+ f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ f->Properties().Delete(LayerActivityProperty());
+ } else {
+ c->DeleteProperty(nsGkAtoms::LayerActivity);
+ }
+}
+
+static LayerActivity*
+GetLayerActivity(nsIFrame* aFrame)
+{
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
+ return nullptr;
+ }
+ FrameProperties properties = aFrame->Properties();
+ return properties.Get(LayerActivityProperty());
+}
+
+static LayerActivity*
+GetLayerActivityForUpdate(nsIFrame* aFrame)
+{
+ FrameProperties properties = aFrame->Properties();
+ LayerActivity* layerActivity = properties.Get(LayerActivityProperty());
+ if (layerActivity) {
+ gLayerActivityTracker->MarkUsed(layerActivity);
+ } else {
+ if (!gLayerActivityTracker) {
+ gLayerActivityTracker = new LayerActivityTracker();
+ }
+ layerActivity = new LayerActivity(aFrame);
+ gLayerActivityTracker->AddObject(layerActivity);
+ aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ properties.Set(LayerActivityProperty(), layerActivity);
+ }
+ return layerActivity;
+}
+
+static void
+IncrementMutationCount(uint8_t* aCount)
+{
+ *aCount = uint8_t(std::min(0xFF, *aCount + 1));
+}
+
+/* static */ void
+ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent)
+{
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
+ return;
+ }
+ FrameProperties properties = aFrame->Properties();
+ LayerActivity* layerActivity = properties.Remove(LayerActivityProperty());
+ aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ if (!layerActivity) {
+ return;
+ }
+ layerActivity->mFrame = nullptr;
+ layerActivity->mContent = aContent;
+ aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity,
+ nsINode::DeleteProperty<LayerActivity>, true);
+}
+
+/* static */ void
+ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = static_cast<LayerActivity*>(
+ aContent->UnsetProperty(nsGkAtoms::LayerActivity));
+ if (!layerActivity) {
+ return;
+ }
+ layerActivity->mContent = nullptr;
+ layerActivity->mFrame = aFrame;
+ aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
+ aFrame->Properties().Set(LayerActivityProperty(), layerActivity);
+}
+
+static void
+IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity)
+{
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+ if (!display->mSpecifiedTransform) {
+ // The transform was removed.
+ aActivity->mPreviousTransformScale = Nothing();
+ IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+ return;
+ }
+
+ // Compute the new scale due to the CSS transform property.
+ nsPresContext* presContext = aFrame->PresContext();
+ RuleNodeCacheConditions dummy;
+ bool dummyBool;
+ nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
+ Matrix4x4 transform =
+ nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform->mHead,
+ aFrame->StyleContext(),
+ presContext,
+ dummy, refBox,
+ presContext->AppUnitsPerCSSPixel(),
+ &dummyBool);
+ Matrix transform2D;
+ if (!transform.Is2D(&transform2D)) {
+ // We don't attempt to handle 3D transforms; just assume the scale changed.
+ aActivity->mPreviousTransformScale = Nothing();
+ IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+ return;
+ }
+
+ gfxSize scale = ThebesMatrix(transform2D).ScaleFactors(true);
+ if (aActivity->mPreviousTransformScale == Some(scale)) {
+ return; // Nothing changed.
+ }
+
+ aActivity->mPreviousTransformScale = Some(scale);
+ IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+ IncrementMutationCount(&mutationCount);
+
+ if (aProperty == eCSSProperty_transform) {
+ IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity);
+ }
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT]);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP]);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT]);
+ IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM]);
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty,
+ const nsAString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl)
+{
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
+ if (mutationCount != 0xFF) {
+ nsAutoString oldValue;
+ aDOMCSSDecl->GetPropertyValue(aProperty, oldValue);
+ if (aNewValue != oldValue) {
+ // We know this is animated, so just hack the mutation count.
+ mutationCount = 0xFF;
+ }
+ }
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ nsIFrame* aScrollFrame)
+{
+ if (aFrame->PresContext() != aScrollFrame->PresContext()) {
+ // Don't allow cross-document dependencies.
+ return;
+ }
+ LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
+ LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
+
+ if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) {
+ // Discard any activity of a different scroll frame. We only track the
+ // most recent scroll handler induced activity.
+ layerActivity->mScrollHandlerInducedActivity.clear();
+ layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame;
+ }
+
+ layerActivity->mScrollHandlerInducedActivity += activityIndex;
+}
+
+static bool
+IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext)
+{
+ if (aPresContext->RefreshDriver()->IsInRefresh()) {
+ return true;
+ }
+ // Treat timeouts/setintervals as scripted animation callbacks for our
+ // purposes.
+ nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow();
+ return win && win->IsRunningTimeout();
+}
+
+/* static */ void
+ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame,
+ nsCSSPropertyID aProperty,
+ const nsAString& aNewValue,
+ nsDOMCSSDeclaration* aDOMCSSDecl)
+{
+ if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
+ NotifyAnimated(aFrame, aProperty, aNewValue, aDOMCSSDecl);
+ }
+ if (gLayerActivityTracker &&
+ gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) {
+ NotifyAnimatedFromScrollHandler(aFrame, aProperty,
+ gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame());
+ }
+}
+
+/* static */ bool
+ActiveLayerTracker::IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+ return IsStyleAnimated(nullptr, aFrame, aProperty);
+}
+
+/* static */ bool
+ActiveLayerTracker::IsBackgroundPositionAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+{
+ return IsStyleAnimated(aBuilder, aFrame, eCSSProperty_background_position_x) ||
+ IsStyleAnimated(aBuilder, aFrame, eCSSProperty_background_position_y);
+}
+
+static bool
+CheckScrollInducedActivity(LayerActivity* aLayerActivity,
+ LayerActivity::ActivityIndex aActivityIndex,
+ nsDisplayListBuilder* aBuilder)
+{
+ if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) ||
+ !aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) {
+ return false;
+ }
+
+ nsIScrollableFrame* scrollFrame =
+ do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame());
+ if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) {
+ return true;
+ }
+
+ // The scroll frame has been destroyed or has become inactive. Clear it from
+ // the layer activity so that it can expire.
+ aLayerActivity->mAnimatingScrollHandlerFrame = nullptr;
+ aLayerActivity->mScrollHandlerInducedActivity.clear();
+ return false;
+}
+
+/* static */ bool
+ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+ // TODO: Add some abuse restrictions
+ if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) &&
+ aProperty == eCSSProperty_transform &&
+ (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
+ return true;
+ }
+ if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) &&
+ aProperty == eCSSProperty_opacity &&
+ (!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
+ return true;
+ }
+
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
+ if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
+ return true;
+ }
+ if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) {
+ return true;
+ }
+ }
+ if (aProperty == eCSSProperty_transform && aFrame->Combines3DTransformWithAncestors()) {
+ return IsStyleAnimated(aBuilder, aFrame->GetParent(), aProperty);
+ }
+ return nsLayoutUtils::HasEffectiveAnimation(aFrame, aProperty);
+}
+
+/* static */ bool
+ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame)
+{
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity) {
+ if (layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_LEFT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_TOP] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_RIGHT] >= 2 ||
+ layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_BOTTOM] >= 2) {
+ return true;
+ }
+ }
+ // We should also check for running CSS animations of these properties once
+ // bug 1009693 is fixed. Until that happens, layerization isn't useful for
+ // animations of these properties because we'll invalidate the layer contents
+ // on every change anyway.
+ // See bug 1151346 for a patch that adds a check for CSS animations.
+ return false;
+}
+
+// A helper function for IsScaleSubjectToAnimation which returns true if the
+// given EffectSet contains a current effect that animates scale.
+static bool
+ContainsAnimatedScale(EffectSet& aEffects, nsIFrame* aFrame)
+{
+ for (dom::KeyframeEffectReadOnly* effect : aEffects) {
+ if (!effect->IsCurrent()) {
+ continue;
+ }
+
+ for (const AnimationProperty& prop : effect->Properties()) {
+ if (prop.mProperty != eCSSProperty_transform) {
+ continue;
+ }
+ for (AnimationPropertySegment segment : prop.mSegments) {
+ gfxSize from = segment.mFromValue.GetScaleValue(aFrame);
+ if (from != gfxSize(1.0f, 1.0f)) {
+ return true;
+ }
+ gfxSize to = segment.mToValue.GetScaleValue(aFrame);
+ if (to != gfxSize(1.0f, 1.0f)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/* static */ bool
+ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame)
+{
+ // Check whether JavaScript is animating this frame's scale.
+ LayerActivity* layerActivity = GetLayerActivity(aFrame);
+ if (layerActivity && layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
+ return true;
+ }
+
+ // Check if any animations, transitions, etc. associated with this frame may
+ // animate its scale.
+ EffectSet* effects = EffectSet::GetEffectSet(aFrame);
+ if (effects && ContainsAnimatedScale(*effects, aFrame)) {
+ return true;
+ }
+
+ 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::SetCurrentScrollHandlerFrame(nsIFrame* aFrame)
+{
+ if (!gLayerActivityTracker) {
+ gLayerActivityTracker = new LayerActivityTracker();
+ }
+ gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame;
+}
+
+/* static */ void
+ActiveLayerTracker::Shutdown()
+{
+ delete gLayerActivityTracker;
+ gLayerActivityTracker = nullptr;
+}
+
+} // namespace mozilla