gecko-dev/dom/animation/EffectSet.h
Boris Chiou bf583442cc Bug 1888317 - Use AnimatedPropertyIDSet for EffectSet::mPropertiesForAnimationsLevel. r=layout-reviewers,zrhoffman,emilio
We keep a set of properties which are running on the
`CascadeLevel::Animations`, to make sure we compose the correct properties
for the rule on Animations/Transitions cascade level.

In other words, without this patch, we may accidentally compose an
animation rule for `CascadeLevel::Animations` (from the KeyframeEffect of
the existing transition), even if we don't have any running animations,
if this transition property is a custom property.

The resolution is to use `AnimatedPropertyIDSet`, for
`mPropertiesForAnimationsLevel`, so we can skip the custom properties which
shouldn't belong to Animations cascade level when composing them.

Note that we don't change the logic, and we are still using
`nsCSSPropertyIDSet` for `EffectSet::mPropertiesWithImportantRules`
because compositor animations don't allow custom properties now.
(This could be a future work I guess, if we are using custom property
values for opacity, translate, rotate, scale, etc.)

Also, I noticed the subtest, "No transition when removing @property rule",
is similar to the testcase in this bug, and so it got passed as well.

Differential Revision: https://phabricator.services.mozilla.com/D210589
2024-05-21 16:55:00 +00:00

247 lines
8.4 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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_EffectSet_h
#define mozilla_EffectSet_h
#include "mozilla/DebugOnly.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EnumeratedArray.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/KeyframeEffect.h"
#include "nsHashKeys.h" // For nsPtrHashKey
#include "nsTHashSet.h"
class nsPresContext;
enum class DisplayItemType : uint8_t;
namespace mozilla {
namespace dom {
class Element;
} // namespace dom
enum class PseudoStyleType : uint8_t;
// A wrapper around a hashset of AnimationEffect objects to handle
// storing the set as a property of an element.
class EffectSet {
public:
EffectSet()
: mCascadeNeedsUpdate(false),
mMayHaveOpacityAnim(false),
mMayHaveTransformAnim(false) {
MOZ_COUNT_CTOR(EffectSet);
}
~EffectSet() {
MOZ_ASSERT(!IsBeingEnumerated(),
"Effect set should not be destroyed while it is being "
"enumerated");
MOZ_COUNT_DTOR(EffectSet);
}
// Methods for supporting cycle-collection
void Traverse(nsCycleCollectionTraversalCallback& aCallback);
static EffectSet* Get(const dom::Element* aElement,
PseudoStyleType aPseudoType);
static EffectSet* GetOrCreate(dom::Element* aElement,
PseudoStyleType aPseudoType);
static EffectSet* GetForFrame(const nsIFrame* aFrame,
const nsCSSPropertyIDSet& aProperties);
static EffectSet* GetForFrame(const nsIFrame* aFrame,
DisplayItemType aDisplayItemType);
// Gets the EffectSet associated with the specified frame's content.
//
// Typically the specified frame should be a "style frame".
//
// That is because display:table content:
//
// - makes a distinction between the primary frame and style frame,
// - associates the EffectSet with the style frame's content,
// - applies transform animations to the primary frame.
//
// In such a situation, passing in the primary frame here will return nullptr
// despite the fact that it has a transform animation applied to it.
//
// GetForFrame, above, handles this by automatically looking up the
// EffectSet on the corresponding style frame when querying transform
// properties. Unless you are sure you know what you are doing, you should
// try using GetForFrame first.
//
// If you decide to use this, consider documenting why you are sure it is ok
// to use this.
static EffectSet* GetForStyleFrame(const nsIFrame* aStyleFrame);
static EffectSet* GetForEffect(const dom::KeyframeEffect* aEffect);
static void DestroyEffectSet(dom::Element* aElement,
PseudoStyleType aPseudoType);
void AddEffect(dom::KeyframeEffect& aEffect);
void RemoveEffect(dom::KeyframeEffect& aEffect);
void SetMayHaveOpacityAnimation() { mMayHaveOpacityAnim = true; }
bool MayHaveOpacityAnimation() const { return mMayHaveOpacityAnim; }
void SetMayHaveTransformAnimation() { mMayHaveTransformAnim = true; }
bool MayHaveTransformAnimation() const { return mMayHaveTransformAnim; }
private:
using OwningEffectSet = nsTHashSet<nsRefPtrHashKey<dom::KeyframeEffect>>;
public:
// A simple iterator to support iterating over the effects in this object in
// range-based for loops.
//
// This allows us to avoid exposing mEffects directly and saves the
// caller from having to dereference hashtable iterators using
// the rather complicated: iter.Get()->GetKey().
//
// XXX Except for the active iterator checks, this could be replaced by the
// STL-style iterators of nsTHashSet directly now.
class Iterator {
public:
explicit Iterator(EffectSet& aEffectSet)
: Iterator(aEffectSet, aEffectSet.mEffects.begin()) {}
Iterator() = delete;
Iterator(const Iterator&) = delete;
Iterator(Iterator&&) = delete;
Iterator& operator=(const Iterator&) = delete;
Iterator& operator=(Iterator&&) = delete;
static Iterator EndIterator(EffectSet& aEffectSet) {
return {aEffectSet, aEffectSet.mEffects.end()};
}
#ifdef DEBUG
~Iterator() {
MOZ_ASSERT(mEffectSet.mActiveIterators > 0);
mEffectSet.mActiveIterators--;
}
#endif
bool operator!=(const Iterator& aOther) const {
return mHashIterator != aOther.mHashIterator;
}
Iterator& operator++() {
++mHashIterator;
return *this;
}
dom::KeyframeEffect* operator*() { return *mHashIterator; }
private:
Iterator(EffectSet& aEffectSet,
OwningEffectSet::const_iterator aHashIterator)
:
#ifdef DEBUG
mEffectSet(aEffectSet),
#endif
mHashIterator(std::move(aHashIterator)) {
#ifdef DEBUG
mEffectSet.mActiveIterators++;
#endif
}
#ifdef DEBUG
EffectSet& mEffectSet;
#endif
OwningEffectSet::const_iterator mHashIterator;
};
friend class Iterator;
Iterator begin() { return Iterator(*this); }
Iterator end() { return Iterator::EndIterator(*this); }
#ifdef DEBUG
bool IsBeingEnumerated() const { return mActiveIterators != 0; }
#endif
bool IsEmpty() const { return mEffects.IsEmpty(); }
size_t Count() const { return mEffects.Count(); }
const TimeStamp& LastOverflowAnimationSyncTime() const {
return mLastOverflowAnimationSyncTime;
}
void UpdateLastOverflowAnimationSyncTime(const TimeStamp& aRefreshTime) {
mLastOverflowAnimationSyncTime = aRefreshTime;
}
bool CascadeNeedsUpdate() const { return mCascadeNeedsUpdate; }
void MarkCascadeNeedsUpdate() { mCascadeNeedsUpdate = true; }
void MarkCascadeUpdated() { mCascadeNeedsUpdate = false; }
void UpdateAnimationGeneration(nsPresContext* aPresContext);
uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
const nsCSSPropertyIDSet& PropertiesWithImportantRules() const {
return mPropertiesWithImportantRules;
}
nsCSSPropertyIDSet& PropertiesWithImportantRules() {
return mPropertiesWithImportantRules;
}
const AnimatedPropertyIDSet& PropertiesForAnimationsLevel() const {
return mPropertiesForAnimationsLevel;
}
AnimatedPropertyIDSet& PropertiesForAnimationsLevel() {
return mPropertiesForAnimationsLevel;
}
private:
OwningEffectSet mEffects;
// Refresh driver timestamp from the moment when the animations which produce
// overflow change hints in this effect set were last updated.
// This is used for animations whose main-thread restyling is throttled either
// because they are running on the compositor or because they are not visible.
// We still need to update them on the main thread periodically, however (e.g.
// so scrollbars can be updated), so this tracks the last time we did that.
TimeStamp mLastOverflowAnimationSyncTime;
// RestyleManager keeps track of the number of animation restyles.
// 'mini-flushes' (see nsTransitionManager::UpdateAllThrottledStyles()).
// mAnimationGeneration is the sequence number of the last flush where a
// transition/animation changed. We keep a similar count on the
// corresponding layer so we can check that the layer is up to date with
// the animation manager.
uint64_t mAnimationGeneration = 0;
// Specifies the compositor-animatable properties that are overridden by
// !important rules.
nsCSSPropertyIDSet mPropertiesWithImportantRules;
// Specifies the properties for which the result will be added to the
// animations level of the cascade and hence should be skipped when we are
// composing the animation style for the transitions level of the cascede.
AnimatedPropertyIDSet mPropertiesForAnimationsLevel;
#ifdef DEBUG
// Track how many iterators are referencing this effect set when we are
// destroyed, we can assert that nothing is still pointing to us.
uint64_t mActiveIterators = 0;
#endif
// Dirty flag to represent when the mPropertiesWithImportantRules and
// mPropertiesForAnimationsLevel on effects in this set might need to be
// updated.
//
// Set to true any time the set of effects is changed or when
// one the effects goes in or out of the "in effect" state.
bool mCascadeNeedsUpdate = false;
bool mMayHaveOpacityAnim = false;
bool mMayHaveTransformAnim = false;
};
} // namespace mozilla
#endif // mozilla_EffectSet_h