gecko-dev/dom/animation/EffectCompositor.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

952 lines
34 KiB
C++
Raw Normal View History

Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
/* -*- 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/. */
#include "EffectCompositor.h"
#include "mozilla/dom/Animation.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/KeyframeEffect.h"
#include "mozilla/AnimationComparator.h"
#include "mozilla/AnimationPerformanceWarning.h"
#include "mozilla/AnimationTarget.h"
#include "mozilla/AnimationUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/ComputedStyleInlines.h"
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
#include "mozilla/EffectSet.h"
#include "mozilla/LayerAnimationInfo.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoBindings.h" // Servo_GetProperties_Overriding_Animation
#include "mozilla/ServoStyleSet.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/StyleAnimationValue.h"
#include "nsContentUtils.h"
#include "nsCSSPropertyIDSet.h"
#include "nsCSSProps.h"
#include "nsDisplayItemTypes.h"
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
#include "nsLayoutUtils.h"
#include "nsTArray.h"
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
using mozilla::dom::Animation;
using mozilla::dom::Element;
using mozilla::dom::KeyframeEffect;
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
namespace mozilla {
NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor)
for (auto& elementSet : tmp->mElementsToRestyle) {
elementSet.Clear();
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EffectCompositor)
for (const auto& elementSet : tmp->mElementsToRestyle) {
for (const auto& key : elementSet.Keys()) {
CycleCollectionNoteChild(cb, key.mElement,
"EffectCompositor::mElementsToRestyle[]",
cb.Flags());
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
/* static */
bool EffectCompositor::AllowCompositorAnimationsOnFrame(
const nsIFrame* aFrame,
AnimationPerformanceWarning::Type& aWarning /* out */) {
if (aFrame->RefusedAsyncAnimation()) {
return false;
}
if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
if (StaticPrefs::layers_offmainthreadcomposition_log_animations()) {
nsCString message;
message.AppendLiteral(
"Performance warning: Async animations are "
"disabled");
AnimationUtils::LogAsyncAnimationFailure(message);
}
return false;
}
// Disable async animations if we have a rendering observer that
// depends on our content (svg masking, -moz-element etc) so that
// it gets updated correctly.
nsIContent* content = aFrame->GetContent();
while (content) {
if (content->HasRenderingObservers()) {
aWarning = AnimationPerformanceWarning::Type::HasRenderingObserver;
return false;
}
content = content->GetParent();
}
return true;
}
// Helper function to factor out the common logic from
// GetAnimationsForCompositor and HasAnimationsForCompositor.
//
// Takes an optional array to fill with eligible animations.
//
// Returns true if there are eligible animations, false otherwise.
bool FindAnimationsForCompositor(
const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
nsTArray<RefPtr<dom::Animation>>* aMatches /*out*/) {
// Do not process any animations on the compositor when in print or print
// preview.
if (aFrame->PresContext()->IsPrintingOrPrintPreview()) {
return false;
}
MOZ_ASSERT(
aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
DisplayItemType::TYPE_TRANSFORM)) ||
aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
DisplayItemType::TYPE_OPACITY)) ||
aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
DisplayItemType::TYPE_BACKGROUND_COLOR)),
"Should be the subset of transform-like properties, or opacity, "
"or background color");
MOZ_ASSERT(!aMatches || aMatches->IsEmpty(),
"Matches array, if provided, should be empty");
EffectSet* effects = EffectSet::GetForFrame(aFrame, aPropertySet);
if (!effects || effects->IsEmpty()) {
return false;
}
AnimationPerformanceWarning::Type warning =
AnimationPerformanceWarning::Type::None;
if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aFrame, warning)) {
if (warning != AnimationPerformanceWarning::Type::None) {
EffectCompositor::SetPerformanceWarning(
aFrame, aPropertySet, AnimationPerformanceWarning(warning));
}
return false;
}
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
// The animation cascade will almost always be up-to-date by this point
// but there are some cases such as when we are restoring the refresh driver
// from test control after seeking where it might not be the case.
//
// Those cases are probably not important but just to be safe, let's make
// sure the cascade is up to date since if it *is* up to date, this is
// basically a no-op.
Maybe<NonOwningAnimationTarget> pseudoElement =
EffectCompositor::GetAnimationElementAndPseudoForFrame(
nsLayoutUtils::GetStyleFrame(aFrame));
MOZ_ASSERT(pseudoElement,
"We have a valid element for the frame, if we don't we should "
"have bailed out at above the call to EffectSet::Get");
EffectCompositor::MaybeUpdateCascadeResults(pseudoElement->mElement,
pseudoElement->mPseudoType);
bool foundRunningAnimations = false;
for (KeyframeEffect* effect : *effects) {
auto effectWarning = AnimationPerformanceWarning::Type::None;
KeyframeEffect::MatchForCompositor matchResult =
effect->IsMatchForCompositor(aPropertySet, aFrame, *effects,
effectWarning);
if (effectWarning != AnimationPerformanceWarning::Type::None) {
EffectCompositor::SetPerformanceWarning(
aFrame, aPropertySet, AnimationPerformanceWarning(effectWarning));
}
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
if (matchResult ==
KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty) {
// For a given |aFrame|, we don't want some animations of |aPropertySet|
// to run on the compositor and others to run on the main thread, so if
// any need to be synchronized with the main thread, run them all there.
if (aMatches) {
aMatches->Clear();
}
return false;
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
}
if (matchResult == KeyframeEffect::MatchForCompositor::No) {
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
continue;
}
if (aMatches) {
aMatches->AppendElement(effect->GetAnimation());
}
if (matchResult == KeyframeEffect::MatchForCompositor::Yes) {
foundRunningAnimations = true;
}
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
}
// If all animations we added were not currently playing animations, don't
// send them to the compositor.
if (aMatches && !foundRunningAnimations) {
aMatches->Clear();
}
MOZ_ASSERT(!foundRunningAnimations || !aMatches || !aMatches->IsEmpty(),
"If return value is true, matches array should be non-empty");
if (aMatches && foundRunningAnimations) {
aMatches->Sort(AnimationPtrComparator<RefPtr<dom::Animation>>());
}
return foundRunningAnimations;
}
void EffectCompositor::RequestRestyle(dom::Element* aElement,
PseudoStyleType aPseudoType,
RestyleType aRestyleType,
CascadeLevel aCascadeLevel) {
if (!mPresContext) {
// Pres context will be null after the effect compositor is disconnected.
return;
}
// Ignore animations on orphaned elements and elements in documents without
// a pres shell (e.g. XMLHttpRequest responseXML documents).
if (!nsContentUtils::GetPresShellForContent(aElement)) {
return;
}
auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel];
PseudoElementHashEntry::KeyType key = {aElement, aPseudoType};
bool& restyleEntry = elementsToRestyle.LookupOrInsert(key, false);
if (aRestyleType == RestyleType::Throttled) {
mPresContext->PresShell()->SetNeedThrottledAnimationFlush();
} else {
// Update hashtable first in case PostRestyleForAnimation mutates it
// and invalidates the restyleEntry reference.
// (It shouldn't, but just to be sure.)
bool skipRestyle = std::exchange(restyleEntry, true);
if (!skipRestyle) {
PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel);
}
}
if (aRestyleType == RestyleType::Layer) {
mPresContext->RestyleManager()->IncrementAnimationGeneration();
if (auto* effectSet = EffectSet::Get(aElement, aPseudoType)) {
effectSet->UpdateAnimationGeneration(mPresContext);
}
}
}
void EffectCompositor::PostRestyleForAnimation(dom::Element* aElement,
PseudoStyleType aPseudoType,
CascadeLevel aCascadeLevel) {
if (!mPresContext) {
return;
}
// FIXME: Bug 1615083 KeyframeEffect::SetTarget() and
// KeyframeEffect::SetPseudoElement() may set a non-existing pseudo element,
// and we still have to update its style, based on the wpt. However, we don't
// have the generated element here, so we failed the wpt.
//
// See wpt for more info: web-animations/interfaces/KeyframeEffect/target.html
Element* element =
AnimationUtils::GetElementForRestyle(aElement, aPseudoType);
if (!element) {
return;
}
RestyleHint hint = aCascadeLevel == CascadeLevel::Transitions
? RestyleHint::RESTYLE_CSS_TRANSITIONS
: RestyleHint::RESTYLE_CSS_ANIMATIONS;
MOZ_ASSERT(NS_IsMainThread(),
"Restyle request during restyling should be requested only on "
"the main-thread. e.g. after the parallel traversal");
if (ServoStyleSet::IsInServoTraversal() || mIsInPreTraverse) {
MOZ_ASSERT(hint == RestyleHint::RESTYLE_CSS_ANIMATIONS ||
hint == RestyleHint::RESTYLE_CSS_TRANSITIONS);
// We can't call Servo_NoteExplicitHints here since AtomicRefCell does not
// allow us mutate ElementData of the |aElement| in SequentialTask.
// Instead we call Servo_NoteExplicitHints for the element in PreTraverse()
// which will be called right before the second traversal that we do for
// updating CSS animations.
// In that case PreTraverse() will return true so that we know to do the
// second traversal so we don't need to post any restyle requests to the
// PresShell.
return;
}
MOZ_ASSERT(!mPresContext->RestyleManager()->IsInStyleRefresh());
mPresContext->PresShell()->RestyleForAnimation(element, hint);
}
void EffectCompositor::PostRestyleForThrottledAnimations() {
for (size_t i = 0; i < kCascadeLevelCount; i++) {
CascadeLevel cascadeLevel = CascadeLevel(i);
auto& elementSet = mElementsToRestyle[cascadeLevel];
for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
bool& postedRestyle = iter.Data();
if (postedRestyle) {
continue;
}
PostRestyleForAnimation(iter.Key().mElement, iter.Key().mPseudoType,
cascadeLevel);
postedRestyle = true;
}
}
}
void EffectCompositor::ClearRestyleRequestsFor(Element* aElement) {
MOZ_ASSERT(aElement);
auto& elementsToRestyle = mElementsToRestyle[CascadeLevel::Animations];
PseudoStyleType pseudoType = aElement->GetPseudoElementType();
if (pseudoType == PseudoStyleType::NotPseudo) {
PseudoElementHashEntry::KeyType notPseudoKey = {aElement,
PseudoStyleType::NotPseudo};
PseudoElementHashEntry::KeyType beforePseudoKey = {aElement,
PseudoStyleType::before};
PseudoElementHashEntry::KeyType afterPseudoKey = {aElement,
PseudoStyleType::after};
PseudoElementHashEntry::KeyType markerPseudoKey = {aElement,
PseudoStyleType::marker};
elementsToRestyle.Remove(notPseudoKey);
elementsToRestyle.Remove(beforePseudoKey);
elementsToRestyle.Remove(afterPseudoKey);
elementsToRestyle.Remove(markerPseudoKey);
} else if (AnimationUtils::IsSupportedPseudoForAnimations(pseudoType)) {
Element* parentElement = aElement->GetParentElement();
MOZ_ASSERT(parentElement);
PseudoElementHashEntry::KeyType key = {parentElement, pseudoType};
elementsToRestyle.Remove(key);
}
}
void EffectCompositor::UpdateEffectProperties(const ComputedStyle* aStyle,
Element* aElement,
PseudoStyleType aPseudoType) {
EffectSet* effectSet = EffectSet::Get(aElement, aPseudoType);
if (!effectSet) {
return;
}
// Style context (Gecko) or computed values (Stylo) change might cause CSS
// cascade level, e.g removing !important, so we should update the cascading
// result.
effectSet->MarkCascadeNeedsUpdate();
for (KeyframeEffect* effect : *effectSet) {
effect->UpdateProperties(aStyle);
}
}
namespace {
class EffectCompositeOrderComparator {
public:
bool Equals(const KeyframeEffect* a, const KeyframeEffect* b) const {
return a == b;
}
bool LessThan(const KeyframeEffect* a, const KeyframeEffect* b) const {
MOZ_ASSERT(a->GetAnimation() && b->GetAnimation());
MOZ_ASSERT(
Equals(a, b) ||
a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) !=
b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation()));
return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation());
}
};
} // namespace
static void ComposeSortedEffects(
const nsTArray<KeyframeEffect*>& aSortedEffects,
const EffectSet* aEffectSet, EffectCompositor::CascadeLevel aCascadeLevel,
StyleAnimationValueMap* aAnimationValues) {
Bug 1605610 - Ensure to not create transition rules for elements that don't have any transition effect. r=hiro There are multiple places where bogus non-empty transition rules can be created before this patch when EffectSet's cascade information isn't up-to-date. That can happen as described in bug 1606176. Anyhow, in this particular call site, this is only used to filter from transition rules effects that are from running animations, to implement: https://drafts.csswg.org/css-transitions/#application: > Implementations must add this value to the cascade if and only if that > property is not currently undergoing a CSS Animation ([CSS3-ANIMATIONS]) > on the same element. In the test-case, the EffectSet cascade info is empty, so we hit the "skip everything" for animations (wrong), and "skip nothing for transitions" (also wrong). This creates a transition rule node which then we never remove (understandably, as the element never had a transition!). This fixes the observables of this test-case, by checking the cascade level (so that we don't create transition rule nodes with declarations coming from animations). This is strictly more correct than what we were doing. If we hit the proposed assertion after this change, this code may still create transition rules that incorrectly override animations, but will never mint one out of the blue which we'd then fail to remove (which is the problem the test-case is hitting). Differential Revision: https://phabricator.services.mozilla.com/D58333 --HG-- extra : moz-landing-system : lando
2020-01-07 16:21:08 +00:00
const bool isTransition =
aCascadeLevel == EffectCompositor::CascadeLevel::Transitions;
nsCSSPropertyIDSet propertiesToSkip;
Bug 1605610 - Ensure to not create transition rules for elements that don't have any transition effect. r=hiro There are multiple places where bogus non-empty transition rules can be created before this patch when EffectSet's cascade information isn't up-to-date. That can happen as described in bug 1606176. Anyhow, in this particular call site, this is only used to filter from transition rules effects that are from running animations, to implement: https://drafts.csswg.org/css-transitions/#application: > Implementations must add this value to the cascade if and only if that > property is not currently undergoing a CSS Animation ([CSS3-ANIMATIONS]) > on the same element. In the test-case, the EffectSet cascade info is empty, so we hit the "skip everything" for animations (wrong), and "skip nothing for transitions" (also wrong). This creates a transition rule node which then we never remove (understandably, as the element never had a transition!). This fixes the observables of this test-case, by checking the cascade level (so that we don't create transition rule nodes with declarations coming from animations). This is strictly more correct than what we were doing. If we hit the proposed assertion after this change, this code may still create transition rules that incorrectly override animations, but will never mint one out of the blue which we'd then fail to remove (which is the problem the test-case is hitting). Differential Revision: https://phabricator.services.mozilla.com/D58333 --HG-- extra : moz-landing-system : lando
2020-01-07 16:21:08 +00:00
// Transitions should be overridden by running animations of the same
// property per https://drafts.csswg.org/css-transitions/#application:
//
// > Implementations must add this value to the cascade if and only if that
// > property is not currently undergoing a CSS Animation on the same element.
//
// FIXME(emilio, bug 1606176): This should assert that
// aEffectSet->PropertiesForAnimationsLevel() is up-to-date, and it may not
// follow the spec in those cases. There are various places where we get style
// without flushing that would trigger the below assertion.
//
// MOZ_ASSERT_IF(aEffectSet, !aEffectSet->CascadeNeedsUpdate());
if (aEffectSet) {
propertiesToSkip =
Bug 1605610 - Ensure to not create transition rules for elements that don't have any transition effect. r=hiro There are multiple places where bogus non-empty transition rules can be created before this patch when EffectSet's cascade information isn't up-to-date. That can happen as described in bug 1606176. Anyhow, in this particular call site, this is only used to filter from transition rules effects that are from running animations, to implement: https://drafts.csswg.org/css-transitions/#application: > Implementations must add this value to the cascade if and only if that > property is not currently undergoing a CSS Animation ([CSS3-ANIMATIONS]) > on the same element. In the test-case, the EffectSet cascade info is empty, so we hit the "skip everything" for animations (wrong), and "skip nothing for transitions" (also wrong). This creates a transition rule node which then we never remove (understandably, as the element never had a transition!). This fixes the observables of this test-case, by checking the cascade level (so that we don't create transition rule nodes with declarations coming from animations). This is strictly more correct than what we were doing. If we hit the proposed assertion after this change, this code may still create transition rules that incorrectly override animations, but will never mint one out of the blue which we'd then fail to remove (which is the problem the test-case is hitting). Differential Revision: https://phabricator.services.mozilla.com/D58333 --HG-- extra : moz-landing-system : lando
2020-01-07 16:21:08 +00:00
isTransition ? aEffectSet->PropertiesForAnimationsLevel()
: aEffectSet->PropertiesForAnimationsLevel().Inverse();
}
for (KeyframeEffect* effect : aSortedEffects) {
Bug 1605610 - Ensure to not create transition rules for elements that don't have any transition effect. r=hiro There are multiple places where bogus non-empty transition rules can be created before this patch when EffectSet's cascade information isn't up-to-date. That can happen as described in bug 1606176. Anyhow, in this particular call site, this is only used to filter from transition rules effects that are from running animations, to implement: https://drafts.csswg.org/css-transitions/#application: > Implementations must add this value to the cascade if and only if that > property is not currently undergoing a CSS Animation ([CSS3-ANIMATIONS]) > on the same element. In the test-case, the EffectSet cascade info is empty, so we hit the "skip everything" for animations (wrong), and "skip nothing for transitions" (also wrong). This creates a transition rule node which then we never remove (understandably, as the element never had a transition!). This fixes the observables of this test-case, by checking the cascade level (so that we don't create transition rule nodes with declarations coming from animations). This is strictly more correct than what we were doing. If we hit the proposed assertion after this change, this code may still create transition rules that incorrectly override animations, but will never mint one out of the blue which we'd then fail to remove (which is the problem the test-case is hitting). Differential Revision: https://phabricator.services.mozilla.com/D58333 --HG-- extra : moz-landing-system : lando
2020-01-07 16:21:08 +00:00
auto* animation = effect->GetAnimation();
MOZ_ASSERT(!isTransition || animation->CascadeLevel() == aCascadeLevel);
animation->ComposeStyle(*aAnimationValues, propertiesToSkip);
}
}
bool EffectCompositor::GetServoAnimationRule(
const dom::Element* aElement, PseudoStyleType aPseudoType,
CascadeLevel aCascadeLevel, StyleAnimationValueMap* aAnimationValues) {
MOZ_ASSERT(aAnimationValues);
// Gecko_GetAnimationRule should have already checked this
MOZ_ASSERT(nsContentUtils::GetPresShellForContent(aElement),
"Should not be trying to run animations on elements in documents"
" without a pres shell (e.g. XMLHttpRequest documents)");
EffectSet* effectSet = EffectSet::Get(aElement, aPseudoType);
if (!effectSet) {
return false;
}
Bug 1605610 - Ensure to not create transition rules for elements that don't have any transition effect. r=hiro There are multiple places where bogus non-empty transition rules can be created before this patch when EffectSet's cascade information isn't up-to-date. That can happen as described in bug 1606176. Anyhow, in this particular call site, this is only used to filter from transition rules effects that are from running animations, to implement: https://drafts.csswg.org/css-transitions/#application: > Implementations must add this value to the cascade if and only if that > property is not currently undergoing a CSS Animation ([CSS3-ANIMATIONS]) > on the same element. In the test-case, the EffectSet cascade info is empty, so we hit the "skip everything" for animations (wrong), and "skip nothing for transitions" (also wrong). This creates a transition rule node which then we never remove (understandably, as the element never had a transition!). This fixes the observables of this test-case, by checking the cascade level (so that we don't create transition rule nodes with declarations coming from animations). This is strictly more correct than what we were doing. If we hit the proposed assertion after this change, this code may still create transition rules that incorrectly override animations, but will never mint one out of the blue which we'd then fail to remove (which is the problem the test-case is hitting). Differential Revision: https://phabricator.services.mozilla.com/D58333 --HG-- extra : moz-landing-system : lando
2020-01-07 16:21:08 +00:00
const bool isTransition = aCascadeLevel == CascadeLevel::Transitions;
// Get a list of effects sorted by composite order.
nsTArray<KeyframeEffect*> sortedEffectList(effectSet->Count());
for (KeyframeEffect* effect : *effectSet) {
Bug 1605610 - Ensure to not create transition rules for elements that don't have any transition effect. r=hiro There are multiple places where bogus non-empty transition rules can be created before this patch when EffectSet's cascade information isn't up-to-date. That can happen as described in bug 1606176. Anyhow, in this particular call site, this is only used to filter from transition rules effects that are from running animations, to implement: https://drafts.csswg.org/css-transitions/#application: > Implementations must add this value to the cascade if and only if that > property is not currently undergoing a CSS Animation ([CSS3-ANIMATIONS]) > on the same element. In the test-case, the EffectSet cascade info is empty, so we hit the "skip everything" for animations (wrong), and "skip nothing for transitions" (also wrong). This creates a transition rule node which then we never remove (understandably, as the element never had a transition!). This fixes the observables of this test-case, by checking the cascade level (so that we don't create transition rule nodes with declarations coming from animations). This is strictly more correct than what we were doing. If we hit the proposed assertion after this change, this code may still create transition rules that incorrectly override animations, but will never mint one out of the blue which we'd then fail to remove (which is the problem the test-case is hitting). Differential Revision: https://phabricator.services.mozilla.com/D58333 --HG-- extra : moz-landing-system : lando
2020-01-07 16:21:08 +00:00
if (isTransition &&
effect->GetAnimation()->CascadeLevel() != aCascadeLevel) {
// We may need to use transition rules for the animations level for the
// case of missing keyframes in animations, but we don't ever need to look
// at non-transition levels to build a transition rule. When the effect
// set information is out of date (see above), this avoids creating bogus
// transition rules, see bug 1605610.
continue;
}
sortedEffectList.AppendElement(effect);
}
Bug 1605610 - Ensure to not create transition rules for elements that don't have any transition effect. r=hiro There are multiple places where bogus non-empty transition rules can be created before this patch when EffectSet's cascade information isn't up-to-date. That can happen as described in bug 1606176. Anyhow, in this particular call site, this is only used to filter from transition rules effects that are from running animations, to implement: https://drafts.csswg.org/css-transitions/#application: > Implementations must add this value to the cascade if and only if that > property is not currently undergoing a CSS Animation ([CSS3-ANIMATIONS]) > on the same element. In the test-case, the EffectSet cascade info is empty, so we hit the "skip everything" for animations (wrong), and "skip nothing for transitions" (also wrong). This creates a transition rule node which then we never remove (understandably, as the element never had a transition!). This fixes the observables of this test-case, by checking the cascade level (so that we don't create transition rule nodes with declarations coming from animations). This is strictly more correct than what we were doing. If we hit the proposed assertion after this change, this code may still create transition rules that incorrectly override animations, but will never mint one out of the blue which we'd then fail to remove (which is the problem the test-case is hitting). Differential Revision: https://phabricator.services.mozilla.com/D58333 --HG-- extra : moz-landing-system : lando
2020-01-07 16:21:08 +00:00
if (sortedEffectList.IsEmpty()) {
return false;
}
sortedEffectList.Sort(EffectCompositeOrderComparator());
ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel,
aAnimationValues);
MOZ_ASSERT(effectSet == EffectSet::Get(aElement, aPseudoType),
"EffectSet should not change while composing style");
return true;
}
bool EffectCompositor::ComposeServoAnimationRuleForEffect(
KeyframeEffect& aEffect, CascadeLevel aCascadeLevel,
StyleAnimationValueMap* aAnimationValues) {
MOZ_ASSERT(aAnimationValues);
MOZ_ASSERT(mPresContext && mPresContext->IsDynamic(),
"Should not be in print preview");
NonOwningAnimationTarget target = aEffect.GetAnimationTarget();
if (!target) {
return false;
}
// Don't try to compose animations for elements in documents without a pres
// shell (e.g. XMLHttpRequest documents).
if (!nsContentUtils::GetPresShellForContent(target.mElement)) {
return false;
}
// GetServoAnimationRule is called as part of the regular style resolution
// where the cascade results are updated in the pre-traversal as needed.
// This function, however, is only called when committing styles so we
// need to ensure the cascade results are up-to-date manually.
MaybeUpdateCascadeResults(target.mElement, target.mPseudoType);
EffectSet* effectSet = EffectSet::Get(target.mElement, target.mPseudoType);
// Get a list of effects sorted by composite order up to and including
// |aEffect|, even if it is not in the EffectSet.
auto comparator = EffectCompositeOrderComparator();
nsTArray<KeyframeEffect*> sortedEffectList(effectSet ? effectSet->Count() + 1
: 1);
if (effectSet) {
for (KeyframeEffect* effect : *effectSet) {
if (comparator.LessThan(effect, &aEffect)) {
sortedEffectList.AppendElement(effect);
}
}
sortedEffectList.Sort(comparator);
}
sortedEffectList.AppendElement(&aEffect);
ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel,
aAnimationValues);
MOZ_ASSERT(effectSet == EffectSet::Get(target.mElement, target.mPseudoType),
"EffectSet should not change while composing style");
return true;
}
2016-01-13 07:54:55 +09:00
bool EffectCompositor::HasPendingStyleUpdates() const {
for (auto& elementSet : mElementsToRestyle) {
if (elementSet.Count()) {
return true;
}
}
return false;
}
/* static */
bool EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame,
DisplayItemType aType) {
return FindAnimationsForCompositor(
aFrame, LayerAnimationInfo::GetCSSPropertiesFor(aType), nullptr);
}
/* static */
nsTArray<RefPtr<dom::Animation>> EffectCompositor::GetAnimationsForCompositor(
const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
nsTArray<RefPtr<dom::Animation>> result;
#ifdef DEBUG
bool foundSome =
#endif
FindAnimationsForCompositor(aFrame, aPropertySet, &result);
MOZ_ASSERT(!foundSome || !result.IsEmpty(),
"If return value is true, matches array should be non-empty");
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
return result;
}
/* static */
void EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame* aFrame,
DisplayItemType aType) {
EffectSet* effects = EffectSet::GetForFrame(aFrame, aType);
if (!effects) {
return;
}
const nsCSSPropertyIDSet& propertySet =
LayerAnimationInfo::GetCSSPropertiesFor(aType);
for (KeyframeEffect* effect : *effects) {
effect->SetIsRunningOnCompositor(propertySet, false);
}
}
/* static */
void EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
PseudoStyleType aPseudoType) {
EffectSet* effects = EffectSet::Get(aElement, aPseudoType);
if (!effects || !effects->CascadeNeedsUpdate()) {
return;
}
UpdateCascadeResults(*effects, aElement, aPseudoType);
MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state");
}
/* static */
Maybe<NonOwningAnimationTarget>
EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame) {
// Always return the same object to benefit from return-value optimization.
Maybe<NonOwningAnimationTarget> result;
PseudoStyleType pseudoType = aFrame->Style()->GetPseudoType();
if (pseudoType != PseudoStyleType::NotPseudo &&
!AnimationUtils::IsSupportedPseudoForAnimations(pseudoType)) {
return result;
}
nsIContent* content = aFrame->GetContent();
if (!content) {
return result;
}
if (AnimationUtils::IsSupportedPseudoForAnimations(pseudoType)) {
content = content->GetParent();
if (!content) {
return result;
}
}
if (!content->IsElement()) {
return result;
}
result.emplace(content->AsElement(), pseudoType);
return result;
}
/* static */
nsCSSPropertyIDSet EffectCompositor::GetOverriddenProperties(
EffectSet& aEffectSet, Element* aElement, PseudoStyleType aPseudoType) {
MOZ_ASSERT(aElement, "Should have an element to get style data from");
nsCSSPropertyIDSet result;
Element* elementForRestyle =
AnimationUtils::GetElementForRestyle(aElement, aPseudoType);
if (!elementForRestyle) {
return result;
}
static constexpr size_t compositorAnimatableCount =
nsCSSPropertyIDSet::CompositorAnimatableCount();
AutoTArray<nsCSSPropertyID, compositorAnimatableCount> propertiesToTrack;
{
nsCSSPropertyIDSet propertiesToTrackAsSet;
for (KeyframeEffect* effect : aEffectSet) {
for (const AnimationProperty& property : effect->Properties()) {
if (property.mProperty.IsCustom()) {
continue;
}
if (nsCSSProps::PropHasFlags(property.mProperty.mID,
CSSPropFlags::CanAnimateOnCompositor) &&
!propertiesToTrackAsSet.HasProperty(property.mProperty.mID)) {
propertiesToTrackAsSet.AddProperty(property.mProperty.mID);
propertiesToTrack.AppendElement(property.mProperty.mID);
}
}
// Skip iterating over the rest of the effects if we've already
// found all the compositor-animatable properties.
if (propertiesToTrack.Length() == compositorAnimatableCount) {
break;
}
}
}
if (propertiesToTrack.IsEmpty()) {
return result;
}
Servo_GetProperties_Overriding_Animation(elementForRestyle,
&propertiesToTrack, &result);
return result;
}
/* static */
void EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet,
Element* aElement,
PseudoStyleType aPseudoType) {
MOZ_ASSERT(EffectSet::Get(aElement, aPseudoType) == &aEffectSet,
"Effect set should correspond to the specified (pseudo-)element");
if (aEffectSet.IsEmpty()) {
aEffectSet.MarkCascadeUpdated();
return;
}
// Get a list of effects sorted by composite order.
nsTArray<KeyframeEffect*> sortedEffectList(aEffectSet.Count());
for (KeyframeEffect* effect : aEffectSet) {
sortedEffectList.AppendElement(effect);
}
sortedEffectList.Sort(EffectCompositeOrderComparator());
// Get properties that override the *animations* level of the cascade.
//
// We only do this for properties that we can animate on the compositor
// since we will apply other properties on the main thread where the usual
// cascade applies.
nsCSSPropertyIDSet overriddenProperties =
GetOverriddenProperties(aEffectSet, aElement, aPseudoType);
nsCSSPropertyIDSet& propertiesWithImportantRules =
aEffectSet.PropertiesWithImportantRules();
nsCSSPropertyIDSet& propertiesForAnimationsLevel =
aEffectSet.PropertiesForAnimationsLevel();
static constexpr nsCSSPropertyIDSet compositorAnimatables =
nsCSSPropertyIDSet::CompositorAnimatables();
// Record which compositor-animatable properties were originally set so we can
// compare for changes later.
nsCSSPropertyIDSet prevCompositorPropertiesWithImportantRules =
propertiesWithImportantRules.Intersect(compositorAnimatables);
nsCSSPropertyIDSet prevPropertiesForAnimationsLevel =
propertiesForAnimationsLevel;
propertiesWithImportantRules.Empty();
propertiesForAnimationsLevel.Empty();
nsCSSPropertyIDSet propertiesForTransitionsLevel;
for (const KeyframeEffect* effect : sortedEffectList) {
MOZ_ASSERT(effect->GetAnimation(),
"Effects on a target element should have an Animation");
CascadeLevel cascadeLevel = effect->GetAnimation()->CascadeLevel();
for (const AnimationProperty& prop : effect->Properties()) {
if (prop.mProperty.IsCustom()) {
continue;
}
if (overriddenProperties.HasProperty(prop.mProperty.mID)) {
propertiesWithImportantRules.AddProperty(prop.mProperty.mID);
}
switch (cascadeLevel) {
case EffectCompositor::CascadeLevel::Animations:
propertiesForAnimationsLevel.AddProperty(prop.mProperty.mID);
break;
case EffectCompositor::CascadeLevel::Transitions:
propertiesForTransitionsLevel.AddProperty(prop.mProperty.mID);
break;
}
}
}
aEffectSet.MarkCascadeUpdated();
nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
if (!presContext) {
return;
}
// If properties for compositor are newly overridden by !important rules, or
// released from being overridden by !important rules, we need to update
// layers for animations level because it's a trigger to send animations to
// the compositor or pull animations back from the compositor.
if (!prevCompositorPropertiesWithImportantRules.Equals(
propertiesWithImportantRules.Intersect(compositorAnimatables))) {
presContext->EffectCompositor()->RequestRestyle(
aElement, aPseudoType, EffectCompositor::RestyleType::Layer,
EffectCompositor::CascadeLevel::Animations);
}
// If we have transition properties and if the same propery for animations
// level is newly added or removed, we need to update the transition level
// rule since the it will be added/removed from the rule tree.
nsCSSPropertyIDSet changedPropertiesForAnimationLevel =
prevPropertiesForAnimationsLevel.Xor(propertiesForAnimationsLevel);
nsCSSPropertyIDSet commonProperties = propertiesForTransitionsLevel.Intersect(
changedPropertiesForAnimationLevel);
if (!commonProperties.IsEmpty()) {
EffectCompositor::RestyleType restyleType =
changedPropertiesForAnimationLevel.Intersects(compositorAnimatables)
? EffectCompositor::RestyleType::Standard
: EffectCompositor::RestyleType::Layer;
presContext->EffectCompositor()->RequestRestyle(
aElement, aPseudoType, restyleType,
EffectCompositor::CascadeLevel::Transitions);
}
}
/* static */
void EffectCompositor::SetPerformanceWarning(
const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
const AnimationPerformanceWarning& aWarning) {
EffectSet* effects = EffectSet::GetForFrame(aFrame, aPropertySet);
if (!effects) {
return;
}
for (KeyframeEffect* effect : *effects) {
effect->SetPerformanceWarning(aPropertySet, aWarning);
}
}
bool EffectCompositor::PreTraverse(ServoTraversalFlags aFlags) {
return PreTraverseInSubtree(aFlags, nullptr);
}
bool EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags,
Element* aRoot) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!aRoot || nsContentUtils::GetPresShellForContent(aRoot),
"Traversal root, if provided, should be bound to a display "
"document");
// Convert the root element to the parent element if the root element is
// pseudo since we check each element in mElementsToRestyle is in the subtree
// of the root element later in this function, but for pseudo elements the
// element in mElementsToRestyle is the parent of the pseudo.
if (aRoot && (aRoot->IsGeneratedContentContainerForBefore() ||
aRoot->IsGeneratedContentContainerForAfter() ||
aRoot->IsGeneratedContentContainerForMarker())) {
aRoot = aRoot->GetParentElement();
}
AutoRestore<bool> guard(mIsInPreTraverse);
mIsInPreTraverse = true;
// We need to force flush all throttled animations if we also have
// non-animation restyles (since we'll want the up-to-date animation style
// when we go to process them so we can trigger transitions correctly), and
// if we are currently flushing all throttled animation restyles.
bool flushThrottledRestyles =
(aRoot && aRoot->HasDirtyDescendantsForServo()) ||
(aFlags & ServoTraversalFlags::FlushThrottledAnimations);
using ElementsToRestyleIterType =
nsTHashMap<PseudoElementHashEntry, bool>::ConstIterator;
auto getNeededRestyleTarget =
[&](const ElementsToRestyleIterType& aIter) -> NonOwningAnimationTarget {
NonOwningAnimationTarget returnTarget;
// If aIter.Data() is false, the element only requested a throttled
// (skippable) restyle, so we can skip it if flushThrottledRestyles is not
// true.
if (!flushThrottledRestyles && !aIter.Data()) {
return returnTarget;
}
const NonOwningAnimationTarget& target = aIter.Key();
// Skip elements in documents without a pres shell. Normally we filter out
// such elements in RequestRestyle but it can happen that, after adding
// them to mElementsToRestyle, they are transferred to a different document.
//
// We will drop them from mElementsToRestyle at the end of the next full
// document restyle (at the end of this function) but for consistency with
// how we treat such elements in RequestRestyle, we just ignore them here.
if (!nsContentUtils::GetPresShellForContent(target.mElement)) {
return returnTarget;
}
// Ignore restyles that aren't in the flattened tree subtree rooted at
// aRoot.
if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
target.mElement, aRoot)) {
return returnTarget;
}
returnTarget = target;
return returnTarget;
};
bool foundElementsNeedingRestyle = false;
nsTArray<NonOwningAnimationTarget> elementsWithCascadeUpdates;
for (size_t i = 0; i < kCascadeLevelCount; ++i) {
CascadeLevel cascadeLevel = CascadeLevel(i);
auto& elementSet = mElementsToRestyle[cascadeLevel];
for (auto iter = elementSet.ConstIter(); !iter.Done(); iter.Next()) {
const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter);
if (!target.mElement) {
continue;
}
EffectSet* effects = EffectSet::Get(target.mElement, target.mPseudoType);
if (!effects || !effects->CascadeNeedsUpdate()) {
continue;
}
elementsWithCascadeUpdates.AppendElement(target);
}
}
for (const NonOwningAnimationTarget& target : elementsWithCascadeUpdates) {
MaybeUpdateCascadeResults(target.mElement, target.mPseudoType);
}
elementsWithCascadeUpdates.Clear();
for (size_t i = 0; i < kCascadeLevelCount; ++i) {
CascadeLevel cascadeLevel = CascadeLevel(i);
auto& elementSet = mElementsToRestyle[cascadeLevel];
for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter);
if (!target.mElement) {
continue;
}
if (target.mElement->GetComposedDoc() != mPresContext->Document()) {
iter.Remove();
continue;
}
// We need to post restyle hints even if the target is not in EffectSet to
// ensure the final restyling for removed animations.
// We can't call PostRestyleEvent directly here since we are still in the
// middle of the servo traversal.
mPresContext->RestyleManager()->PostRestyleEventForAnimations(
target.mElement, target.mPseudoType,
cascadeLevel == CascadeLevel::Transitions
? RestyleHint::RESTYLE_CSS_TRANSITIONS
: RestyleHint::RESTYLE_CSS_ANIMATIONS);
foundElementsNeedingRestyle = true;
auto* effects = EffectSet::Get(target.mElement, target.mPseudoType);
if (!effects) {
// Drop EffectSets that have been destroyed.
iter.Remove();
continue;
}
for (KeyframeEffect* effect : *effects) {
effect->GetAnimation()->WillComposeStyle();
}
// Remove the element from the list of elements to restyle since we are
// about to restyle it.
iter.Remove();
}
// If this is a full document restyle, then unconditionally clear
// elementSet in case there are any elements that didn't match above
// because they were moved to a document without a pres shell after
// posting an animation restyle.
if (!aRoot && flushThrottledRestyles) {
elementSet.Clear();
}
}
return foundElementsNeedingRestyle;
}
void EffectCompositor::NoteElementForReducing(
const NonOwningAnimationTarget& aTarget) {
Unused << mElementsToReduce.put(
OwningAnimationTarget{aTarget.mElement, aTarget.mPseudoType});
}
static void ReduceEffectSet(EffectSet& aEffectSet) {
// Get a list of effects sorted by composite order.
nsTArray<KeyframeEffect*> sortedEffectList(aEffectSet.Count());
for (KeyframeEffect* effect : aEffectSet) {
sortedEffectList.AppendElement(effect);
}
sortedEffectList.Sort(EffectCompositeOrderComparator());
AnimatedPropertyIDSet setProperties;
// Iterate in reverse
for (auto iter = sortedEffectList.rbegin(); iter != sortedEffectList.rend();
++iter) {
MOZ_ASSERT(*iter && (*iter)->GetAnimation(),
"Effect in an EffectSet should have an animation");
KeyframeEffect& effect = **iter;
Animation& animation = *effect.GetAnimation();
if (animation.IsRemovable() &&
effect.GetPropertySet().IsSubsetOf(setProperties)) {
animation.Remove();
} else if (animation.IsReplaceable()) {
setProperties.AddProperties(effect.GetPropertySet());
}
}
}
void EffectCompositor::ReduceAnimations() {
for (auto iter = mElementsToReduce.iter(); !iter.done(); iter.next()) {
const OwningAnimationTarget& target = iter.get();
auto* effectSet = EffectSet::Get(target.mElement, target.mPseudoType);
if (effectSet) {
ReduceEffectSet(*effectSet);
}
}
mElementsToReduce.clear();
}
Bug 1226118 part 8 - Add EffectCompositor::GetAnimationsForCompositor that uses the EffectSet rather than AnimationCollection; r=dholbert This added method should behave in an equivalent manner to the existing CommonAnimationManager::GetAnimationsForCompositor except for the following differences: * It uses the EffectSet attached to a target element rather than one of the AnimationCollection object on the owning element. * It returns an array of Animation objects consisting of only those Animations that actually have the specified property as opposed to the AnimationCollection consisting of *all* CSS animations or *all* CSS transitions for the element regardless of whether they run on the compositor or not. It may not be obvious why these two methods otherwise behave in an equivalent fashion so the following explains how the existing code is mirrored in the new method. The existing code is as follows: > AnimationCollection* > CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame, > nsCSSProperty aProperty) > { > AnimationCollection* collection = GetAnimationCollection(aFrame); > if (!collection || > !collection->HasCurrentAnimationOfProperty(aProperty) || > !collection->CanPerformOnCompositorThread(aFrame)) { > return nullptr; > } > > // This animation can be done on the compositor. > return collection; > } The new EffectCompositor::GetAnimationsForCompositor begins with two checks performed at the beginning of CanPerformOnCompositorThread: the checks for whether async animations are enabled or not and whether the frame has refused async animations since these are cheap and it makes sense to check them first. The next part of EffectCompositor::GetAnimationsForCompositor checks if there is an EffectSet associated with the frame. This is equivalent to the check whether |collection| is null or not above. Following, we iterate through the effects in the EffectSet. We first check if each effect is playing or not. In the above code, HasCurrentAnimationOfProperty only checks if the effect is *current* or not. However, CanPerformOnCompositorThread will only return true if it finds an animation that can run on the compositor that is *playing*. Since playing is a strict subset of current we only need to perform the more restrictive test. Next we check if the effect should block running other animations on the compositor. This is equivalent to the remainder of CanPerformOnCompositorThread. Note that the order is important here. Only playing animations should block other animations from running on the compositor. Furthermore, this needs to happen before the following step since animations of property other than |aProperty| can still block animations from running on the compositor. Finally, we check if the effect has an animation of |aProperty|. This is equivalent to the remainder of HasCurrentAnimationOfProperty. If all these checks succeed, we add the effect's animation to the result to return.
2015-12-04 08:34:12 +09:00
} // namespace mozilla