mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 18:08:58 +00:00
5d0121968c
So we can run scroll animation with `animation-duration:auto`. Note that the active duration is still 0s (when calling `AnimationEffect::getComputedTiming()`) because we haven't changed our time type from double [1] to CSSNumberish [2] (Bug 1762238). [1] https://drafts.csswg.org/web-animations-1/#the-computedeffecttiming-dictionary [2] https://drafts.csswg.org/web-animations-2/#the-computedeffecttiming-dictionary Differential Revision: https://phabricator.services.mozilla.com/D217872
539 lines
22 KiB
C++
539 lines
22 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/. */
|
|
|
|
/* Code to start and animate CSS transitions. */
|
|
|
|
#include "nsTransitionManager.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsAnimationManager.h"
|
|
|
|
#include "nsIContent.h"
|
|
#include "AnimatedPropertyID.h"
|
|
#include "AnimatedPropertyIDSet.h"
|
|
#include "mozilla/ComputedStyle.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "nsCSSPropertyIDSet.h"
|
|
#include "mozilla/EffectSet.h"
|
|
#include "mozilla/ElementAnimationData.h"
|
|
#include "mozilla/EventDispatcher.h"
|
|
#include "mozilla/ServoBindings.h"
|
|
#include "mozilla/StyleAnimationValue.h"
|
|
#include "mozilla/dom/DocumentTimeline.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsCSSProps.h"
|
|
#include "nsCSSPseudoElements.h"
|
|
#include "nsDisplayList.h"
|
|
#include "nsRFPService.h"
|
|
#include "nsStyleChangeList.h"
|
|
#include "mozilla/RestyleManager.h"
|
|
|
|
using mozilla::dom::CSSTransition;
|
|
using mozilla::dom::DocumentTimeline;
|
|
using mozilla::dom::KeyframeEffect;
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::css;
|
|
|
|
bool nsTransitionManager::UpdateTransitions(dom::Element* aElement,
|
|
PseudoStyleType aPseudoType,
|
|
const ComputedStyle& aOldStyle,
|
|
const ComputedStyle& aNewStyle) {
|
|
if (mPresContext->Medium() == nsGkAtoms::print) {
|
|
// For print or print preview, ignore transitions.
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(mPresContext->IsDynamic());
|
|
if (aNewStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
|
|
StopAnimationsForElement(aElement, aPseudoType);
|
|
return false;
|
|
}
|
|
|
|
auto* collection = CSSTransitionCollection::Get(aElement, aPseudoType);
|
|
return DoUpdateTransitions(*aNewStyle.StyleUIReset(), aElement, aPseudoType,
|
|
collection, aOldStyle, aNewStyle);
|
|
}
|
|
|
|
// This function expands the shorthands and "all" keyword specified in
|
|
// transition-property, and then execute |aHandler| on the expanded longhand.
|
|
// |aHandler| should be a lamda function which accepts nsCSSPropertyID.
|
|
template <typename T>
|
|
static void ExpandTransitionProperty(const StyleTransitionProperty& aProperty,
|
|
T aHandler) {
|
|
switch (aProperty.tag) {
|
|
case StyleTransitionProperty::Tag::Unsupported:
|
|
break;
|
|
case StyleTransitionProperty::Tag::Custom: {
|
|
AnimatedPropertyID property(aProperty.AsCustom().AsAtom());
|
|
aHandler(property);
|
|
break;
|
|
}
|
|
case StyleTransitionProperty::Tag::NonCustom: {
|
|
nsCSSPropertyID id = nsCSSPropertyID(aProperty.AsNonCustom()._0);
|
|
if (nsCSSProps::IsShorthand(id)) {
|
|
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, id,
|
|
CSSEnabledState::ForAllContent) {
|
|
AnimatedPropertyID property(*subprop);
|
|
aHandler(property);
|
|
}
|
|
} else {
|
|
AnimatedPropertyID property(id);
|
|
aHandler(property);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool nsTransitionManager::DoUpdateTransitions(
|
|
const nsStyleUIReset& aStyle, dom::Element* aElement,
|
|
PseudoStyleType aPseudoType, CSSTransitionCollection*& aElementTransitions,
|
|
const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle) {
|
|
MOZ_ASSERT(!aElementTransitions || &aElementTransitions->mElement == aElement,
|
|
"Element mismatch");
|
|
|
|
// Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
|
|
// I'll consider only the transitions from the number of items in
|
|
// 'transition-property' on down, and later ones will override earlier
|
|
// ones (tracked using |propertiesChecked|).
|
|
bool startedAny = false;
|
|
AnimatedPropertyIDSet propertiesChecked;
|
|
for (uint32_t i = aStyle.mTransitionPropertyCount; i--;) {
|
|
const float delay = aStyle.GetTransitionDelay(i).ToMilliseconds();
|
|
|
|
// The spec says a negative duration is treated as zero.
|
|
const float duration =
|
|
std::max(aStyle.GetTransitionDuration(i).ToMilliseconds(), 0.0f);
|
|
|
|
// If the combined duration of this transition is 0 or less we won't start a
|
|
// transition, we can avoid even looking at transition-property if we're the
|
|
// last one.
|
|
if (i == 0 && delay + duration <= 0.0f) {
|
|
continue;
|
|
}
|
|
|
|
const auto behavior = aStyle.GetTransitionBehavior(i);
|
|
ExpandTransitionProperty(aStyle.GetTransitionProperty(i),
|
|
[&](const AnimatedPropertyID& aProperty) {
|
|
// We might have something to transition. See if
|
|
// any of the properties in question changed and
|
|
// are animatable.
|
|
startedAny |= ConsiderInitiatingTransition(
|
|
aProperty, aStyle, i, delay, duration,
|
|
behavior, aElement, aPseudoType,
|
|
aElementTransitions, aOldStyle, aNewStyle,
|
|
propertiesChecked);
|
|
});
|
|
}
|
|
|
|
// Stop any transitions for properties that are no longer in
|
|
// 'transition-property', including finished transitions.
|
|
// Also stop any transitions (and remove any finished transitions)
|
|
// for properties that just changed (and are still in the set of
|
|
// properties to transition), but for which we didn't just start the
|
|
// transition. This can happen delay and duration are both zero, or
|
|
// because the new value is not interpolable.
|
|
if (aElementTransitions) {
|
|
const bool checkProperties = !aStyle.GetTransitionProperty(0).IsAll();
|
|
AnimatedPropertyIDSet allTransitionProperties;
|
|
if (checkProperties) {
|
|
for (uint32_t i = aStyle.mTransitionPropertyCount; i-- != 0;) {
|
|
ExpandTransitionProperty(aStyle.GetTransitionProperty(i),
|
|
[&](const AnimatedPropertyID& aProperty) {
|
|
allTransitionProperties.AddProperty(
|
|
aProperty.ToPhysical(aNewStyle));
|
|
});
|
|
}
|
|
}
|
|
|
|
OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
|
|
size_t i = animations.Length();
|
|
MOZ_ASSERT(i != 0, "empty transitions list?");
|
|
AnimationValue currentValue;
|
|
do {
|
|
--i;
|
|
CSSTransition* anim = animations[i];
|
|
const AnimatedPropertyID& property = anim->TransitionProperty();
|
|
if (
|
|
// Properties no longer in `transition-property`.
|
|
(checkProperties && !allTransitionProperties.HasProperty(property)) ||
|
|
// Properties whose computed values changed but for which we did not
|
|
// start a new transition (because delay and duration are both zero,
|
|
// or because the new value is not interpolable); a new transition
|
|
// would have anim->ToValue() matching currentValue.
|
|
!Servo_ComputedValues_TransitionValueMatches(
|
|
&aNewStyle, &property, anim->ToValue().mServo.get())) {
|
|
// Stop the transition.
|
|
DoCancelTransition(aElement, aPseudoType, aElementTransitions, i);
|
|
}
|
|
} while (i != 0);
|
|
}
|
|
|
|
return startedAny;
|
|
}
|
|
|
|
static Keyframe& AppendKeyframe(double aOffset,
|
|
const AnimatedPropertyID& aProperty,
|
|
AnimationValue&& aValue,
|
|
nsTArray<Keyframe>& aKeyframes) {
|
|
Keyframe& frame = *aKeyframes.AppendElement();
|
|
frame.mOffset.emplace(aOffset);
|
|
MOZ_ASSERT(aValue.mServo);
|
|
RefPtr<StyleLockedDeclarationBlock> decl =
|
|
Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
|
|
frame.mPropertyValues.AppendElement(
|
|
PropertyValuePair(aProperty, std::move(decl)));
|
|
return frame;
|
|
}
|
|
|
|
static nsTArray<Keyframe> GetTransitionKeyframes(
|
|
const AnimatedPropertyID& aProperty, AnimationValue&& aStartValue,
|
|
AnimationValue&& aEndValue) {
|
|
nsTArray<Keyframe> keyframes(2);
|
|
|
|
AppendKeyframe(0.0, aProperty, std::move(aStartValue), keyframes);
|
|
AppendKeyframe(1.0, aProperty, std::move(aEndValue), keyframes);
|
|
|
|
return keyframes;
|
|
}
|
|
|
|
using ReplacedTransitionProperties =
|
|
CSSTransition::ReplacedTransitionProperties;
|
|
static Maybe<ReplacedTransitionProperties> GetReplacedTransitionProperties(
|
|
const CSSTransition* aTransition,
|
|
const DocumentTimeline* aTimelineToMatch) {
|
|
Maybe<ReplacedTransitionProperties> result;
|
|
|
|
// Transition needs to be currently running on the compositor to be
|
|
// replaceable.
|
|
if (!aTransition || !aTransition->HasCurrentEffect() ||
|
|
!aTransition->IsRunningOnCompositor() ||
|
|
aTransition->GetStartTime().IsNull()) {
|
|
return result;
|
|
}
|
|
|
|
// Transition needs to be running on the same timeline.
|
|
if (aTransition->GetTimeline() != aTimelineToMatch) {
|
|
return result;
|
|
}
|
|
|
|
// The transition needs to have a keyframe effect.
|
|
const KeyframeEffect* keyframeEffect =
|
|
aTransition->GetEffect() ? aTransition->GetEffect()->AsKeyframeEffect()
|
|
: nullptr;
|
|
if (!keyframeEffect) {
|
|
return result;
|
|
}
|
|
|
|
// The keyframe effect needs to be a simple transition of the original
|
|
// transition property (i.e. not replaced with something else).
|
|
if (keyframeEffect->Properties().Length() != 1 ||
|
|
keyframeEffect->Properties()[0].mSegments.Length() != 1 ||
|
|
keyframeEffect->Properties()[0].mProperty !=
|
|
aTransition->TransitionProperty()) {
|
|
return result;
|
|
}
|
|
|
|
const AnimationPropertySegment& segment =
|
|
keyframeEffect->Properties()[0].mSegments[0];
|
|
|
|
result.emplace(ReplacedTransitionProperties(
|
|
{aTransition->GetStartTime().Value(), aTransition->PlaybackRate(),
|
|
keyframeEffect->SpecifiedTiming(), segment.mTimingFunction,
|
|
segment.mFromValue, segment.mToValue}));
|
|
|
|
return result;
|
|
}
|
|
|
|
bool nsTransitionManager::ConsiderInitiatingTransition(
|
|
const AnimatedPropertyID& aProperty, const nsStyleUIReset& aStyle,
|
|
uint32_t aTransitionIndex, float aDelay, float aDuration,
|
|
mozilla::StyleTransitionBehavior aBehavior, dom::Element* aElement,
|
|
PseudoStyleType aPseudoType, CSSTransitionCollection*& aElementTransitions,
|
|
const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle,
|
|
AnimatedPropertyIDSet& aPropertiesChecked) {
|
|
// IsShorthand itself will assert if aProperty is not a property.
|
|
MOZ_ASSERT(aProperty.IsCustom() || !nsCSSProps::IsShorthand(aProperty.mID),
|
|
"property out of range");
|
|
NS_ASSERTION(
|
|
!aElementTransitions || &aElementTransitions->mElement == aElement,
|
|
"Element mismatch");
|
|
|
|
AnimatedPropertyID property = aProperty.ToPhysical(aNewStyle);
|
|
|
|
// A later item in transition-property already specified a transition for
|
|
// this property, so we ignore this one.
|
|
//
|
|
// See http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
|
|
if (aPropertiesChecked.HasProperty(property)) {
|
|
return false;
|
|
}
|
|
|
|
aPropertiesChecked.AddProperty(property);
|
|
|
|
if (aDuration + aDelay <= 0.0f) {
|
|
return false;
|
|
}
|
|
|
|
size_t currentIndex = nsTArray<KeyframeEffect>::NoIndex;
|
|
const auto* oldTransition = [&]() -> const CSSTransition* {
|
|
if (!aElementTransitions) {
|
|
return nullptr;
|
|
}
|
|
const OwningCSSTransitionPtrArray& animations =
|
|
aElementTransitions->mAnimations;
|
|
for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
|
|
if (animations[i]->TransitionProperty() == property) {
|
|
currentIndex = i;
|
|
return animations[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}();
|
|
|
|
// For compositor animations, |aOldStyle| may have out-of-date transition
|
|
// rules, and it may be equal to the |endValue| of a reversing transition by
|
|
// accidentally. This causes Servo_ComputedValues_ShouldTransition() returns
|
|
// an incorrect result. Therefore, we have to recompute the current value if
|
|
// this transition is running on the compositor, to make sure we create the
|
|
// transition properly. Here, we precompute the progress and collect the
|
|
// necessary info, so Servo_ComputedValues_ShouldTransition() could compute
|
|
// the current value if needed.
|
|
// FIXME: Bug 1634945. We should use the last value from the compositor as the
|
|
// current value.
|
|
Maybe<ReplacedTransitionProperties> replacedTransitionProperties;
|
|
Maybe<double> progress;
|
|
if (oldTransition) {
|
|
// If this new transition is replacing an existing transition that is
|
|
// running on the compositor, we store select parameters from the replaced
|
|
// transition so that later, once all scripts have run, we can update the
|
|
// start value of the transition using TimeStamp::Now(). This allows us to
|
|
// avoid a large jump when starting a new transition when the main thread
|
|
// lags behind the compositor.
|
|
//
|
|
// Note: We compute this before calling
|
|
// Servo_ComputedValues_ShouldTransition() so we can reuse it for computing
|
|
// the current value and setting the replaced transition properties later in
|
|
// this function. Also, |replacedTransitionProperties| is Nothing() if the
|
|
// running transition is not on the compositor.
|
|
const dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
|
|
replacedTransitionProperties =
|
|
GetReplacedTransitionProperties(oldTransition, timeline);
|
|
progress = replacedTransitionProperties.andThen(
|
|
[&](const ReplacedTransitionProperties& aProperties) {
|
|
const dom::AnimationTimeline* timeline = oldTransition->GetTimeline();
|
|
MOZ_ASSERT(timeline);
|
|
return CSSTransition::ComputeTransformedProgress(*timeline,
|
|
aProperties);
|
|
});
|
|
}
|
|
|
|
AnimationValue startValue, endValue;
|
|
const StyleShouldTransitionResult result =
|
|
Servo_ComputedValues_ShouldTransition(
|
|
&aOldStyle, &aNewStyle, &property, aBehavior,
|
|
oldTransition ? oldTransition->ToValue().mServo.get() : nullptr,
|
|
replacedTransitionProperties
|
|
? replacedTransitionProperties->mFromValue.mServo.get()
|
|
: nullptr,
|
|
// Note: It's possible to replace the keyframes by Web Animations API,
|
|
// so we have to pass the mToValue from the keyframe segment, to make
|
|
// sure this value is aligned with mFromValue.
|
|
replacedTransitionProperties
|
|
? replacedTransitionProperties->mToValue.mServo.get()
|
|
: nullptr,
|
|
progress.ptrOr(nullptr), &startValue.mServo, &endValue.mServo);
|
|
|
|
// If we got a style change that changed the value to the endpoint
|
|
// of the currently running transition, we don't want to interrupt
|
|
// its timing function.
|
|
// This needs to be before the !shouldAnimate && haveCurrentTransition
|
|
// case below because we might be close enough to the end of the
|
|
// transition that the current value rounds to the final value. In
|
|
// this case, we'll end up with shouldAnimate as false (because
|
|
// there's no value change), but we need to return early here rather
|
|
// than cancel the running transition because shouldAnimate is false!
|
|
//
|
|
// Likewise, if we got a style change that changed the value to the
|
|
// endpoint of our finished transition, we also don't want to start
|
|
// a new transition for the reasons described in
|
|
// https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
|
|
if (result.old_transition_value_matches) {
|
|
// GetAnimationRule already called RestyleForAnimation.
|
|
return false;
|
|
}
|
|
|
|
if (!result.should_animate) {
|
|
if (oldTransition) {
|
|
// We're in the middle of a transition, and just got a non-transition
|
|
// style change to something that we can't animate. This might happen
|
|
// because we got a non-transition style change changing to the current
|
|
// in-progress value (which is particularly easy to cause when we're
|
|
// currently in the 'transition-delay'). It also might happen because we
|
|
// just got a style change to a value that can't be interpolated.
|
|
DoCancelTransition(aElement, aPseudoType, aElementTransitions,
|
|
currentIndex);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
AnimationValue startForReversingTest = startValue;
|
|
double reversePortion = 1.0;
|
|
|
|
// If the new transition reverses an existing one, we'll need to
|
|
// handle the timing differently.
|
|
if (oldTransition && oldTransition->HasCurrentEffect() &&
|
|
oldTransition->StartForReversingTest() == endValue) {
|
|
// Compute the appropriate negative transition-delay such that right
|
|
// now we'd end up at the current position.
|
|
double valuePortion =
|
|
oldTransition->CurrentValuePortion() * oldTransition->ReversePortion() +
|
|
(1.0 - oldTransition->ReversePortion());
|
|
// A timing function with negative y1 (or y2!) might make
|
|
// valuePortion negative. In this case, we still want to apply our
|
|
// reversing logic based on relative distances, not make duration
|
|
// negative.
|
|
if (valuePortion < 0.0) {
|
|
valuePortion = -valuePortion;
|
|
}
|
|
// A timing function with y2 (or y1!) greater than one might
|
|
// advance past its terminal value. It's probably a good idea to
|
|
// clamp valuePortion to be at most one to preserve the invariant
|
|
// that a transition will complete within at most its specified
|
|
// time.
|
|
if (valuePortion > 1.0) {
|
|
valuePortion = 1.0;
|
|
}
|
|
|
|
// Negative delays are essentially part of the transition
|
|
// function, so reduce them along with the duration, but don't
|
|
// reduce positive delays.
|
|
if (aDelay < 0.0f && std::isfinite(aDelay)) {
|
|
aDelay *= valuePortion;
|
|
}
|
|
|
|
if (std::isfinite(aDuration)) {
|
|
aDuration *= valuePortion;
|
|
}
|
|
|
|
startForReversingTest = oldTransition->ToValue();
|
|
reversePortion = valuePortion;
|
|
}
|
|
|
|
TimingParams timing = TimingParamsFromCSSParams(
|
|
Some(aDuration), aDelay, 1.0 /* iteration count */,
|
|
StyleAnimationDirection::Normal, StyleAnimationFillMode::Backwards);
|
|
|
|
const StyleComputedTimingFunction& tf =
|
|
aStyle.GetTransitionTimingFunction(aTransitionIndex);
|
|
if (!tf.IsLinearKeyword()) {
|
|
timing.SetTimingFunction(Some(tf));
|
|
}
|
|
|
|
RefPtr<CSSTransition> transition = DoCreateTransition(
|
|
property, aElement, aPseudoType, aNewStyle, aElementTransitions,
|
|
std::move(timing), std::move(startValue), std::move(endValue),
|
|
std::move(startForReversingTest), reversePortion);
|
|
if (!transition) {
|
|
return false;
|
|
}
|
|
|
|
OwningCSSTransitionPtrArray& transitions = aElementTransitions->mAnimations;
|
|
#ifdef DEBUG
|
|
for (size_t i = 0, i_end = transitions.Length(); i < i_end; ++i) {
|
|
MOZ_ASSERT(
|
|
i == currentIndex || transitions[i]->TransitionProperty() != property,
|
|
"duplicate transitions for property");
|
|
}
|
|
#endif
|
|
if (oldTransition) {
|
|
if (replacedTransitionProperties) {
|
|
transition->SetReplacedTransition(
|
|
std::move(replacedTransitionProperties.ref()));
|
|
}
|
|
|
|
transitions[currentIndex]->CancelFromStyle(PostRestyleMode::IfNeeded);
|
|
oldTransition = nullptr; // Clear pointer so it doesn't dangle
|
|
transitions[currentIndex] = transition;
|
|
} else {
|
|
// XXX(Bug 1631371) Check if this should use a fallible operation as it
|
|
// pretended earlier.
|
|
transitions.AppendElement(transition);
|
|
}
|
|
|
|
if (auto* effectSet = EffectSet::Get(aElement, aPseudoType)) {
|
|
effectSet->UpdateAnimationGeneration(mPresContext);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
already_AddRefed<CSSTransition> nsTransitionManager::DoCreateTransition(
|
|
const AnimatedPropertyID& aProperty, dom::Element* aElement,
|
|
PseudoStyleType aPseudoType, const mozilla::ComputedStyle& aNewStyle,
|
|
CSSTransitionCollection*& aElementTransitions, TimingParams&& aTiming,
|
|
AnimationValue&& aStartValue, AnimationValue&& aEndValue,
|
|
AnimationValue&& aStartForReversingTest, double aReversePortion) {
|
|
dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
|
|
KeyframeEffectParams effectOptions;
|
|
auto keyframeEffect = MakeRefPtr<KeyframeEffect>(
|
|
aElement->OwnerDoc(), OwningAnimationTarget(aElement, aPseudoType),
|
|
std::move(aTiming), effectOptions);
|
|
|
|
keyframeEffect->SetKeyframes(
|
|
GetTransitionKeyframes(aProperty, std::move(aStartValue),
|
|
std::move(aEndValue)),
|
|
&aNewStyle, timeline);
|
|
|
|
if (NS_WARN_IF(MOZ_UNLIKELY(!keyframeEffect->IsValidTransition()))) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto animation = MakeRefPtr<CSSTransition>(
|
|
mPresContext->Document()->GetScopeObject(), aProperty);
|
|
animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
|
|
animation->SetTimelineNoUpdate(timeline);
|
|
animation->SetCreationSequence(
|
|
mPresContext->RestyleManager()->GetAnimationGeneration());
|
|
animation->SetEffectFromStyle(keyframeEffect);
|
|
animation->SetReverseParameters(std::move(aStartForReversingTest),
|
|
aReversePortion);
|
|
animation->PlayFromStyle();
|
|
|
|
if (!aElementTransitions) {
|
|
aElementTransitions =
|
|
&aElement->EnsureAnimationData().EnsureTransitionCollection(
|
|
*aElement, aPseudoType);
|
|
if (!aElementTransitions->isInList()) {
|
|
AddElementCollection(aElementTransitions);
|
|
}
|
|
}
|
|
return animation.forget();
|
|
}
|
|
|
|
void nsTransitionManager::DoCancelTransition(
|
|
dom::Element* aElement, PseudoStyleType aPseudoType,
|
|
CSSTransitionCollection*& aElementTransitions, size_t aIndex) {
|
|
MOZ_ASSERT(aElementTransitions);
|
|
OwningCSSTransitionPtrArray& transitions = aElementTransitions->mAnimations;
|
|
CSSTransition* transition = transitions[aIndex];
|
|
|
|
if (transition->HasCurrentEffect()) {
|
|
if (auto* effectSet = EffectSet::Get(aElement, aPseudoType)) {
|
|
effectSet->UpdateAnimationGeneration(mPresContext);
|
|
}
|
|
}
|
|
transition->CancelFromStyle(PostRestyleMode::IfNeeded);
|
|
transitions.RemoveElementAt(aIndex);
|
|
|
|
if (transitions.IsEmpty()) {
|
|
aElementTransitions->Destroy();
|
|
// |aElementTransitions| is now a dangling pointer!
|
|
aElementTransitions = nullptr;
|
|
}
|
|
}
|