2015-05-03 19:32:37 +00: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: */
|
2014-08-10 07:06:46 +00:00
|
|
|
/* 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/. */
|
|
|
|
|
2015-04-14 23:48:21 +00:00
|
|
|
#include "mozilla/dom/KeyframeEffect.h"
|
2015-11-20 05:12:00 +00:00
|
|
|
|
2016-03-12 13:14:10 +00:00
|
|
|
#include "mozilla/dom/AnimatableBinding.h"
|
2015-04-14 23:48:21 +00:00
|
|
|
#include "mozilla/dom/KeyframeEffectBinding.h"
|
2015-12-03 23:32:53 +00:00
|
|
|
#include "mozilla/AnimationUtils.h"
|
2014-08-10 07:06:50 +00:00
|
|
|
#include "mozilla/FloatingPoint.h"
|
2015-11-06 01:53:00 +00:00
|
|
|
#include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt
|
2016-03-22 07:20:37 +00:00
|
|
|
#include "mozilla/KeyframeUtils.h"
|
2015-10-22 08:22:38 +00:00
|
|
|
#include "mozilla/StyleAnimationValue.h"
|
2015-11-06 01:53:00 +00:00
|
|
|
#include "Layers.h" // For Layer
|
2016-03-22 07:35:53 +00:00
|
|
|
#include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetStyleContextForElement
|
2014-10-20 04:55:46 +00:00
|
|
|
#include "nsCSSPropertySet.h"
|
2015-09-16 14:05:00 +00:00
|
|
|
#include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
|
2016-03-22 07:20:37 +00:00
|
|
|
#include "nsCSSPseudoElements.h" // For CSSPseudoElementType
|
2016-02-26 21:39:49 +00:00
|
|
|
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
|
2014-08-10 07:06:46 +00:00
|
|
|
|
|
|
|
namespace mozilla {
|
2014-08-10 07:06:50 +00:00
|
|
|
|
2015-11-20 05:12:00 +00:00
|
|
|
// Helper functions for generating a ComputedTimingProperties dictionary
|
2015-10-22 22:48:00 +00:00
|
|
|
static void
|
|
|
|
GetComputedTimingDictionary(const ComputedTiming& aComputedTiming,
|
|
|
|
const Nullable<TimeDuration>& aLocalTime,
|
2016-01-13 17:41:00 +00:00
|
|
|
const TimingParams& aTiming,
|
2015-10-22 22:48:00 +00:00
|
|
|
dom::ComputedTimingProperties& aRetVal)
|
|
|
|
{
|
|
|
|
// AnimationEffectTimingProperties
|
|
|
|
aRetVal.mDelay = aTiming.mDelay.ToMilliseconds();
|
2016-03-08 01:31:10 +00:00
|
|
|
aRetVal.mEndDelay = aTiming.mEndDelay.ToMilliseconds();
|
2016-01-13 17:36:00 +00:00
|
|
|
aRetVal.mFill = aComputedTiming.mFill;
|
2016-01-10 17:41:00 +00:00
|
|
|
aRetVal.mIterations = aComputedTiming.mIterations;
|
2016-03-02 07:23:34 +00:00
|
|
|
aRetVal.mIterationStart = aComputedTiming.mIterationStart;
|
2016-01-13 17:36:00 +00:00
|
|
|
aRetVal.mDuration.SetAsUnrestrictedDouble() =
|
|
|
|
aComputedTiming.mDuration.ToMilliseconds();
|
2015-11-20 05:12:00 +00:00
|
|
|
aRetVal.mDirection = aTiming.mDirection;
|
2015-10-22 22:48:00 +00:00
|
|
|
|
|
|
|
// ComputedTimingProperties
|
|
|
|
aRetVal.mActiveDuration = aComputedTiming.mActiveDuration.ToMilliseconds();
|
2016-02-18 23:37:32 +00:00
|
|
|
aRetVal.mEndTime = aComputedTiming.mEndTime.ToMilliseconds();
|
2015-12-03 23:32:53 +00:00
|
|
|
aRetVal.mLocalTime = AnimationUtils::TimeDurationToDouble(aLocalTime);
|
2015-10-22 22:48:00 +00:00
|
|
|
aRetVal.mProgress = aComputedTiming.mProgress;
|
2016-03-02 07:23:34 +00:00
|
|
|
|
2015-10-22 22:48:00 +00:00
|
|
|
if (!aRetVal.mProgress.IsNull()) {
|
|
|
|
// Convert the returned currentIteration into Infinity if we set
|
|
|
|
// (uint64_t) aComputedTiming.mCurrentIteration to UINT64_MAX
|
|
|
|
double iteration = aComputedTiming.mCurrentIteration == UINT64_MAX
|
|
|
|
? PositiveInfinity<double>()
|
|
|
|
: static_cast<double>(aComputedTiming.mCurrentIteration);
|
|
|
|
aRetVal.mCurrentIteration.SetValue(iteration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-10 07:06:46 +00:00
|
|
|
namespace dom {
|
|
|
|
|
2015-11-26 07:53:54 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,
|
|
|
|
AnimationEffectReadOnly,
|
|
|
|
mTarget,
|
2016-04-13 10:20:46 +00:00
|
|
|
mAnimation,
|
|
|
|
mTiming)
|
2014-08-10 07:06:46 +00:00
|
|
|
|
2015-04-30 13:06:43 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadOnly,
|
|
|
|
AnimationEffectReadOnly)
|
2015-04-14 23:48:21 +00:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
|
2015-04-30 13:06:43 +00:00
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly)
|
|
|
|
NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)
|
2015-04-14 23:48:21 +00:00
|
|
|
|
2015-04-30 13:06:43 +00:00
|
|
|
NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
|
|
|
|
NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
|
2014-08-10 07:06:46 +00:00
|
|
|
|
2015-09-16 14:05:00 +00:00
|
|
|
KeyframeEffectReadOnly::KeyframeEffectReadOnly(
|
|
|
|
nsIDocument* aDocument,
|
2016-04-28 15:22:44 +00:00
|
|
|
const Maybe<OwningAnimationTarget>& aTarget,
|
2016-01-13 17:41:00 +00:00
|
|
|
const TimingParams& aTiming)
|
2016-04-28 15:22:44 +00:00
|
|
|
: KeyframeEffectReadOnly(aDocument, aTarget,
|
2016-04-10 05:51:32 +00:00
|
|
|
new AnimationEffectTimingReadOnly(aDocument,
|
|
|
|
aTiming))
|
2016-02-15 00:34:47 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyframeEffectReadOnly::KeyframeEffectReadOnly(
|
|
|
|
nsIDocument* aDocument,
|
2016-04-28 15:22:44 +00:00
|
|
|
const Maybe<OwningAnimationTarget>& aTarget,
|
2016-02-15 00:34:47 +00:00
|
|
|
AnimationEffectTimingReadOnly* aTiming)
|
2015-09-16 14:05:00 +00:00
|
|
|
: AnimationEffectReadOnly(aDocument)
|
2016-04-28 15:22:44 +00:00
|
|
|
, mTarget(aTarget)
|
2016-04-13 10:20:46 +00:00
|
|
|
, mTiming(aTiming)
|
2016-01-06 02:04:05 +00:00
|
|
|
, mInEffectOnLastAnimationTimingUpdate(false)
|
2016-05-24 03:57:42 +00:00
|
|
|
, mCumulativeChangeHint(nsChangeHint(0))
|
2015-09-16 14:05:00 +00:00
|
|
|
{
|
2016-02-15 00:34:47 +00:00
|
|
|
MOZ_ASSERT(aTiming);
|
2015-09-16 14:05:00 +00:00
|
|
|
}
|
|
|
|
|
2014-08-10 07:06:46 +00:00
|
|
|
JSObject*
|
2015-04-30 13:06:43 +00:00
|
|
|
KeyframeEffectReadOnly::WrapObject(JSContext* aCx,
|
2015-04-14 23:48:21 +00:00
|
|
|
JS::Handle<JSObject*> aGivenProto)
|
2014-08-10 07:06:46 +00:00
|
|
|
{
|
2015-04-30 13:06:43 +00:00
|
|
|
return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
|
2014-08-10 07:06:46 +00:00
|
|
|
}
|
|
|
|
|
2015-11-19 18:48:00 +00:00
|
|
|
IterationCompositeOperation
|
|
|
|
KeyframeEffectReadOnly::IterationComposite() const
|
|
|
|
{
|
|
|
|
return IterationCompositeOperation::Replace;
|
|
|
|
}
|
|
|
|
|
|
|
|
CompositeOperation
|
|
|
|
KeyframeEffectReadOnly::Composite() const
|
|
|
|
{
|
|
|
|
return CompositeOperation::Replace;
|
|
|
|
}
|
|
|
|
|
2016-01-13 17:37:00 +00:00
|
|
|
already_AddRefed<AnimationEffectTimingReadOnly>
|
2016-01-13 17:41:00 +00:00
|
|
|
KeyframeEffectReadOnly::Timing() const
|
2016-01-13 17:37:00 +00:00
|
|
|
{
|
|
|
|
RefPtr<AnimationEffectTimingReadOnly> temp(mTiming);
|
|
|
|
return temp.forget();
|
|
|
|
}
|
|
|
|
|
2015-08-17 18:28:00 +00:00
|
|
|
void
|
2016-01-13 17:41:00 +00:00
|
|
|
KeyframeEffectReadOnly::SetSpecifiedTiming(const TimingParams& aTiming)
|
2015-08-17 18:28:00 +00:00
|
|
|
{
|
2016-01-13 17:41:00 +00:00
|
|
|
if (mTiming->AsTimingParams() == aTiming) {
|
2015-08-17 18:28:00 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-01-13 17:41:00 +00:00
|
|
|
mTiming->SetTimingParams(aTiming);
|
2015-10-07 05:30:27 +00:00
|
|
|
if (mAnimation) {
|
|
|
|
mAnimation->NotifyEffectTimingUpdated();
|
|
|
|
}
|
2015-11-26 07:53:53 +00:00
|
|
|
// NotifyEffectTimingUpdated will eventually cause
|
|
|
|
// NotifyAnimationTimingUpdated to be called on this object which will
|
|
|
|
// update our registration with the target element.
|
2015-08-17 18:28:00 +00:00
|
|
|
}
|
|
|
|
|
2016-01-06 02:04:04 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::NotifyAnimationTimingUpdated()
|
|
|
|
{
|
|
|
|
UpdateTargetRegistration();
|
|
|
|
|
|
|
|
// If the effect is not relevant it will be removed from the target
|
|
|
|
// element's effect set. However, effects not in the effect set
|
|
|
|
// will not be included in the set of candidate effects for running on
|
|
|
|
// the compositor and hence they won't have their compositor status
|
|
|
|
// updated. As a result, we need to make sure we clear their compositor
|
|
|
|
// status here.
|
|
|
|
bool isRelevant = mAnimation && mAnimation->IsRelevant();
|
|
|
|
if (!isRelevant) {
|
|
|
|
ResetIsRunningOnCompositor();
|
|
|
|
}
|
2016-01-06 02:04:05 +00:00
|
|
|
|
|
|
|
// Detect changes to "in effect" status since we need to recalculate the
|
|
|
|
// animation cascade for this element whenever that changes.
|
|
|
|
bool inEffect = IsInEffect();
|
|
|
|
if (inEffect != mInEffectOnLastAnimationTimingUpdate) {
|
|
|
|
if (mTarget) {
|
2016-04-28 15:22:43 +00:00
|
|
|
EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
|
|
|
|
mTarget->mPseudoType);
|
2016-01-06 02:04:05 +00:00
|
|
|
if (effectSet) {
|
|
|
|
effectSet->MarkCascadeNeedsUpdate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mInEffectOnLastAnimationTimingUpdate = inEffect;
|
|
|
|
}
|
2016-01-06 02:04:06 +00:00
|
|
|
|
|
|
|
// Request restyle if necessary.
|
2016-01-12 22:54:54 +00:00
|
|
|
//
|
2016-01-12 22:54:53 +00:00
|
|
|
// Bug 1216843: When we implement iteration composite modes, we need to
|
|
|
|
// also detect if the current iteration has changed.
|
2016-05-09 03:33:52 +00:00
|
|
|
if (mAnimation &&
|
|
|
|
!mProperties.IsEmpty() &&
|
|
|
|
GetComputedTiming().mProgress != mProgressOnLastCompose) {
|
2016-01-12 22:54:53 +00:00
|
|
|
EffectCompositor::RestyleType restyleType =
|
|
|
|
CanThrottle() ?
|
|
|
|
EffectCompositor::RestyleType::Throttled :
|
|
|
|
EffectCompositor::RestyleType::Standard;
|
2016-04-28 15:22:43 +00:00
|
|
|
RequestRestyle(restyleType);
|
2016-04-29 22:08:10 +00:00
|
|
|
}
|
2016-01-15 06:15:47 +00:00
|
|
|
|
2016-04-29 22:08:10 +00:00
|
|
|
// If we're no longer "in effect", our ComposeStyle method will never be
|
|
|
|
// called and we will never have a chance to update mProgressOnLastCompose.
|
|
|
|
// We clear mProgressOnLastCompose here to ensure that if we later become
|
|
|
|
// "in effect" we will request a restyle (above).
|
|
|
|
if (!inEffect) {
|
|
|
|
mProgressOnLastCompose.SetNull();
|
2016-01-06 02:04:06 +00:00
|
|
|
}
|
2016-01-06 02:04:04 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 05:30:28 +00:00
|
|
|
Nullable<TimeDuration>
|
|
|
|
KeyframeEffectReadOnly::GetLocalTime() const
|
|
|
|
{
|
|
|
|
// Since the *animation* start time is currently always zero, the local
|
|
|
|
// time is equal to the parent time.
|
|
|
|
Nullable<TimeDuration> result;
|
|
|
|
if (mAnimation) {
|
|
|
|
result = mAnimation->GetCurrentTime();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-10-22 22:48:00 +00:00
|
|
|
void
|
2016-04-21 05:51:36 +00:00
|
|
|
KeyframeEffectReadOnly::GetComputedTimingAsDict(
|
|
|
|
ComputedTimingProperties& aRetVal) const
|
2015-10-22 22:48:00 +00:00
|
|
|
{
|
|
|
|
const Nullable<TimeDuration> currentTime = GetLocalTime();
|
2016-01-13 17:41:00 +00:00
|
|
|
GetComputedTimingDictionary(GetComputedTimingAt(currentTime,
|
|
|
|
SpecifiedTiming()),
|
2015-10-22 22:48:00 +00:00
|
|
|
currentTime,
|
2016-01-13 17:41:00 +00:00
|
|
|
SpecifiedTiming(),
|
2015-10-22 22:48:00 +00:00
|
|
|
aRetVal);
|
|
|
|
}
|
|
|
|
|
2014-08-10 07:06:50 +00:00
|
|
|
ComputedTiming
|
2015-04-30 13:06:43 +00:00
|
|
|
KeyframeEffectReadOnly::GetComputedTimingAt(
|
2016-04-21 05:51:36 +00:00
|
|
|
const Nullable<TimeDuration>& aLocalTime,
|
|
|
|
const TimingParams& aTiming)
|
2014-08-10 07:06:50 +00:00
|
|
|
{
|
2016-01-13 17:36:00 +00:00
|
|
|
const StickyTimeDuration zeroDuration;
|
2014-08-10 07:06:50 +00:00
|
|
|
|
|
|
|
// Always return the same object to benefit from return-value optimization.
|
|
|
|
ComputedTiming result;
|
|
|
|
|
2016-03-09 05:01:45 +00:00
|
|
|
if (aTiming.mDuration) {
|
|
|
|
MOZ_ASSERT(aTiming.mDuration.ref() >= zeroDuration,
|
|
|
|
"Iteration duration should be positive");
|
2016-03-09 05:14:20 +00:00
|
|
|
result.mDuration = aTiming.mDuration.ref();
|
2016-01-13 17:36:00 +00:00
|
|
|
}
|
2016-03-09 05:14:20 +00:00
|
|
|
|
2016-04-14 10:36:03 +00:00
|
|
|
MOZ_ASSERT(aTiming.mIterations >= 0.0 && !IsNaN(aTiming.mIterations),
|
|
|
|
"mIterations should be nonnegative & finite, as ensured by "
|
|
|
|
"ValidateIterations or CSSParser");
|
|
|
|
result.mIterations = aTiming.mIterations;
|
2016-04-21 05:51:36 +00:00
|
|
|
|
2016-04-14 10:35:59 +00:00
|
|
|
MOZ_ASSERT(aTiming.mIterationStart >= 0.0,
|
|
|
|
"mIterationStart should be nonnegative, as ensured by "
|
|
|
|
"ValidateIterationStart");
|
|
|
|
result.mIterationStart = aTiming.mIterationStart;
|
2016-03-02 07:23:34 +00:00
|
|
|
|
2016-04-14 10:39:39 +00:00
|
|
|
result.mActiveDuration = aTiming.ActiveDuration();
|
2016-04-14 10:39:42 +00:00
|
|
|
result.mEndTime = aTiming.EndTime();
|
2016-01-13 17:36:00 +00:00
|
|
|
result.mFill = aTiming.mFill == dom::FillMode::Auto ?
|
|
|
|
dom::FillMode::None :
|
|
|
|
aTiming.mFill;
|
2014-08-10 07:06:50 +00:00
|
|
|
|
|
|
|
// The default constructor for ComputedTiming sets all other members to
|
|
|
|
// values consistent with an animation that has not been sampled.
|
|
|
|
if (aLocalTime.IsNull()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
const TimeDuration& localTime = aLocalTime.Value();
|
|
|
|
|
2016-04-21 05:51:36 +00:00
|
|
|
// Calculate the time within the active interval.
|
|
|
|
// https://w3c.github.io/web-animations/#active-time
|
2014-09-25 05:25:50 +00:00
|
|
|
StickyTimeDuration activeTime;
|
2016-03-08 01:31:10 +00:00
|
|
|
if (localTime >=
|
|
|
|
std::min(StickyTimeDuration(aTiming.mDelay + result.mActiveDuration),
|
|
|
|
result.mEndTime)) {
|
2015-10-18 22:38:00 +00:00
|
|
|
result.mPhase = ComputedTiming::AnimationPhase::After;
|
2016-01-13 17:36:00 +00:00
|
|
|
if (!result.FillsForwards()) {
|
2014-08-10 07:06:50 +00:00
|
|
|
// The animation isn't active or filling at this time.
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
activeTime = result.mActiveDuration;
|
2016-03-08 01:31:10 +00:00
|
|
|
} else if (localTime <
|
|
|
|
std::min(StickyTimeDuration(aTiming.mDelay), result.mEndTime)) {
|
2015-10-18 22:38:00 +00:00
|
|
|
result.mPhase = ComputedTiming::AnimationPhase::Before;
|
2016-01-13 17:36:00 +00:00
|
|
|
if (!result.FillsBackwards()) {
|
2014-08-10 07:06:50 +00:00
|
|
|
// The animation isn't active or filling at this time.
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
// activeTime is zero
|
|
|
|
} else {
|
|
|
|
MOZ_ASSERT(result.mActiveDuration != zeroDuration,
|
|
|
|
"How can we be in the middle of a zero-duration interval?");
|
2015-10-18 22:38:00 +00:00
|
|
|
result.mPhase = ComputedTiming::AnimationPhase::Active;
|
2014-08-10 07:06:50 +00:00
|
|
|
activeTime = localTime - aTiming.mDelay;
|
|
|
|
}
|
|
|
|
|
2016-04-21 05:51:36 +00:00
|
|
|
// Convert active time to a multiple of iterations.
|
|
|
|
// https://w3c.github.io/web-animations/#overall-progress
|
|
|
|
double overallProgress;
|
|
|
|
if (result.mDuration == zeroDuration) {
|
|
|
|
overallProgress = result.mPhase == ComputedTiming::AnimationPhase::Before
|
|
|
|
? 0.0
|
|
|
|
: result.mIterations;
|
2014-08-10 07:06:50 +00:00
|
|
|
} else {
|
2016-04-21 05:51:36 +00:00
|
|
|
overallProgress = activeTime / result.mDuration;
|
2014-08-10 07:06:50 +00:00
|
|
|
}
|
|
|
|
|
2016-04-21 05:51:36 +00:00
|
|
|
// Factor in iteration start offset.
|
|
|
|
if (IsFinite(overallProgress)) {
|
|
|
|
overallProgress += result.mIterationStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the 0-based index of the current iteration.
|
|
|
|
// https://w3c.github.io/web-animations/#current-iteration
|
|
|
|
result.mCurrentIteration =
|
|
|
|
IsInfinite(result.mIterations) &&
|
|
|
|
result.mPhase == ComputedTiming::AnimationPhase::After
|
|
|
|
? UINT64_MAX // In GetComputedTimingDictionary(),
|
|
|
|
// we will convert this into Infinity
|
|
|
|
: static_cast<uint64_t>(overallProgress);
|
|
|
|
|
|
|
|
// Convert the overall progress to a fraction of a single iteration--the
|
|
|
|
// simply iteration progress.
|
|
|
|
// https://w3c.github.io/web-animations/#simple-iteration-progress
|
|
|
|
double progress = IsFinite(overallProgress)
|
|
|
|
? fmod(overallProgress, 1.0)
|
|
|
|
: fmod(result.mIterationStart, 1.0);
|
|
|
|
|
|
|
|
// When we finish exactly at the end of an iteration we need to report
|
|
|
|
// the end of the final iteration and not the start of the next iteration.
|
|
|
|
if (result.mPhase == ComputedTiming::AnimationPhase::After &&
|
|
|
|
progress == 0.0 &&
|
|
|
|
result.mIterations != 0.0) {
|
|
|
|
progress = 1.0;
|
|
|
|
if (result.mCurrentIteration != UINT64_MAX) {
|
|
|
|
result.mCurrentIteration--;
|
2016-03-02 07:23:34 +00:00
|
|
|
}
|
2014-08-10 07:06:50 +00:00
|
|
|
}
|
|
|
|
|
2016-04-21 05:51:36 +00:00
|
|
|
// Factor in the direction.
|
2014-08-10 07:06:50 +00:00
|
|
|
bool thisIterationReverse = false;
|
|
|
|
switch (aTiming.mDirection) {
|
2015-11-20 05:12:00 +00:00
|
|
|
case PlaybackDirection::Normal:
|
2014-08-10 07:06:50 +00:00
|
|
|
thisIterationReverse = false;
|
|
|
|
break;
|
2015-11-20 05:12:00 +00:00
|
|
|
case PlaybackDirection::Reverse:
|
2014-08-10 07:06:50 +00:00
|
|
|
thisIterationReverse = true;
|
|
|
|
break;
|
2015-11-20 05:12:00 +00:00
|
|
|
case PlaybackDirection::Alternate:
|
2014-08-10 07:06:50 +00:00
|
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 1;
|
|
|
|
break;
|
2015-11-20 05:12:00 +00:00
|
|
|
case PlaybackDirection::Alternate_reverse:
|
2014-08-10 07:06:50 +00:00
|
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 0;
|
|
|
|
break;
|
2015-11-20 05:12:00 +00:00
|
|
|
default:
|
|
|
|
MOZ_ASSERT(true, "Unknown PlaybackDirection type");
|
2014-08-10 07:06:50 +00:00
|
|
|
}
|
|
|
|
if (thisIterationReverse) {
|
2016-04-21 05:51:36 +00:00
|
|
|
progress = 1.0 - progress;
|
2014-08-10 07:06:50 +00:00
|
|
|
}
|
|
|
|
|
2016-04-21 05:51:36 +00:00
|
|
|
// Calculate the 'before flag' which we use when applying step timing
|
|
|
|
// functions.
|
2016-04-01 05:00:57 +00:00
|
|
|
if ((result.mPhase == ComputedTiming::AnimationPhase::After &&
|
|
|
|
thisIterationReverse) ||
|
|
|
|
(result.mPhase == ComputedTiming::AnimationPhase::Before &&
|
|
|
|
!thisIterationReverse)) {
|
|
|
|
result.mBeforeFlag = ComputedTimingFunction::BeforeFlag::Set;
|
|
|
|
}
|
|
|
|
|
2016-04-21 05:51:36 +00:00
|
|
|
// Apply the easing.
|
2016-01-29 13:48:00 +00:00
|
|
|
if (aTiming.mFunction) {
|
2016-04-21 05:51:36 +00:00
|
|
|
progress = aTiming.mFunction->GetValue(progress, result.mBeforeFlag);
|
2016-01-29 13:48:00 +00:00
|
|
|
}
|
|
|
|
|
2016-04-21 05:51:36 +00:00
|
|
|
MOZ_ASSERT(IsFinite(progress), "Progress value should be finite");
|
|
|
|
result.mProgress.SetValue(progress);
|
2014-08-10 07:06:50 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-10-07 05:30:27 +00:00
|
|
|
// https://w3c.github.io/web-animations/#in-play
|
2015-03-27 08:54:39 +00:00
|
|
|
bool
|
2015-10-07 05:30:27 +00:00
|
|
|
KeyframeEffectReadOnly::IsInPlay() const
|
2015-03-27 08:54:39 +00:00
|
|
|
{
|
2015-10-07 05:30:27 +00:00
|
|
|
if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) {
|
2015-03-27 08:54:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-10-18 22:38:00 +00:00
|
|
|
return GetComputedTiming().mPhase == ComputedTiming::AnimationPhase::Active;
|
2015-03-27 08:54:39 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 05:30:27 +00:00
|
|
|
// https://w3c.github.io/web-animations/#current
|
2014-10-02 06:14:14 +00:00
|
|
|
bool
|
2015-10-07 05:30:27 +00:00
|
|
|
KeyframeEffectReadOnly::IsCurrent() const
|
2014-10-02 06:14:14 +00:00
|
|
|
{
|
2015-10-07 05:30:27 +00:00
|
|
|
if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) {
|
2014-10-02 06:14:14 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
2015-10-18 22:38:00 +00:00
|
|
|
return computedTiming.mPhase == ComputedTiming::AnimationPhase::Before ||
|
|
|
|
computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
|
2014-10-02 06:14:14 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 05:30:27 +00:00
|
|
|
// https://w3c.github.io/web-animations/#in-effect
|
2014-10-02 06:14:14 +00:00
|
|
|
bool
|
2015-04-30 13:06:43 +00:00
|
|
|
KeyframeEffectReadOnly::IsInEffect() const
|
2014-10-02 06:14:14 +00:00
|
|
|
{
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
2015-10-18 22:38:00 +00:00
|
|
|
return !computedTiming.mProgress.IsNull();
|
2014-10-02 06:14:14 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 05:30:27 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation)
|
|
|
|
{
|
|
|
|
mAnimation = aAnimation;
|
2015-11-26 07:53:53 +00:00
|
|
|
NotifyAnimationTimingUpdated();
|
2015-10-07 05:30:27 +00:00
|
|
|
}
|
|
|
|
|
2016-03-29 23:59:08 +00:00
|
|
|
static bool
|
|
|
|
KeyframesEqualIgnoringComputedOffsets(const nsTArray<Keyframe>& aLhs,
|
|
|
|
const nsTArray<Keyframe>& aRhs)
|
|
|
|
{
|
|
|
|
if (aLhs.Length() != aRhs.Length()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0, len = aLhs.Length(); i < len; ++i) {
|
|
|
|
const Keyframe& a = aLhs[i];
|
|
|
|
const Keyframe& b = aRhs[i];
|
|
|
|
if (a.mOffset != b.mOffset ||
|
|
|
|
a.mTimingFunction != b.mTimingFunction ||
|
|
|
|
a.mPropertyValues != b.mPropertyValues) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-05-13 00:40:52 +00:00
|
|
|
// https://w3c.github.io/web-animations/#dom-keyframeeffect-setkeyframes
|
2016-04-09 06:33:34 +00:00
|
|
|
void
|
2016-05-13 00:40:52 +00:00
|
|
|
KeyframeEffectReadOnly::SetKeyframes(JSContext* aContext,
|
|
|
|
JS::Handle<JSObject*> aKeyframes,
|
|
|
|
ErrorResult& aRv)
|
2016-04-09 06:33:34 +00:00
|
|
|
{
|
|
|
|
nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aContext);
|
|
|
|
if (!doc) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsTArray<Keyframe> keyframes =
|
2016-05-13 00:40:52 +00:00
|
|
|
KeyframeUtils::GetKeyframesFromObject(aContext, aKeyframes, aRv);
|
2016-04-09 06:33:34 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<nsStyleContext> styleContext;
|
|
|
|
nsIPresShell* shell = doc->GetShell();
|
|
|
|
if (shell && mTarget) {
|
|
|
|
nsIAtom* pseudo =
|
2016-04-28 15:22:43 +00:00
|
|
|
mTarget->mPseudoType < CSSPseudoElementType::Count ?
|
|
|
|
nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) : nullptr;
|
2016-04-09 06:33:34 +00:00
|
|
|
styleContext =
|
2016-04-28 15:22:43 +00:00
|
|
|
nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement,
|
|
|
|
pseudo, shell);
|
2016-04-09 06:33:34 +00:00
|
|
|
}
|
|
|
|
|
2016-05-13 00:40:52 +00:00
|
|
|
SetKeyframes(Move(keyframes), styleContext);
|
2016-04-09 06:33:34 +00:00
|
|
|
}
|
|
|
|
|
2016-03-29 23:59:08 +00:00
|
|
|
void
|
2016-05-13 00:40:52 +00:00
|
|
|
KeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
|
2016-03-29 23:59:08 +00:00
|
|
|
nsStyleContext* aStyleContext)
|
|
|
|
{
|
2016-05-13 00:40:52 +00:00
|
|
|
if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) {
|
2016-03-29 23:59:08 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-13 00:40:52 +00:00
|
|
|
mKeyframes = Move(aKeyframes);
|
|
|
|
KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
|
2016-03-29 23:59:08 +00:00
|
|
|
|
|
|
|
if (mAnimation && mAnimation->IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(mAnimation);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aStyleContext) {
|
|
|
|
UpdateProperties(aStyleContext);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-20 04:10:00 +00:00
|
|
|
const AnimationProperty*
|
2015-04-30 13:06:43 +00:00
|
|
|
KeyframeEffectReadOnly::GetAnimationOfProperty(nsCSSProperty aProperty) const
|
2014-08-10 07:06:49 +00:00
|
|
|
{
|
|
|
|
for (size_t propIdx = 0, propEnd = mProperties.Length();
|
|
|
|
propIdx != propEnd; ++propIdx) {
|
|
|
|
if (aProperty == mProperties[propIdx].mProperty) {
|
2015-03-31 22:05:55 +00:00
|
|
|
const AnimationProperty* result = &mProperties[propIdx];
|
|
|
|
if (!result->mWinsInCascade) {
|
|
|
|
result = nullptr;
|
|
|
|
}
|
|
|
|
return result;
|
2014-08-10 07:06:49 +00:00
|
|
|
}
|
|
|
|
}
|
2015-03-20 04:10:00 +00:00
|
|
|
return nullptr;
|
2014-08-10 07:06:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-06 02:53:51 +00:00
|
|
|
bool
|
2015-04-30 13:06:43 +00:00
|
|
|
KeyframeEffectReadOnly::HasAnimationOfProperties(
|
2015-04-14 23:48:21 +00:00
|
|
|
const nsCSSProperty* aProperties,
|
|
|
|
size_t aPropertyCount) const
|
2015-04-06 02:53:51 +00:00
|
|
|
{
|
|
|
|
for (size_t i = 0; i < aPropertyCount; i++) {
|
|
|
|
if (HasAnimationOfProperty(aProperties[i])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-03-29 23:59:08 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aStyleContext);
|
|
|
|
|
|
|
|
nsTArray<AnimationProperty> properties;
|
|
|
|
if (mTarget) {
|
|
|
|
properties =
|
|
|
|
KeyframeUtils::GetAnimationPropertiesFromKeyframes(aStyleContext,
|
2016-04-28 15:22:43 +00:00
|
|
|
mTarget->mElement,
|
|
|
|
mTarget->mPseudoType,
|
2016-05-13 00:40:52 +00:00
|
|
|
mKeyframes);
|
2016-03-29 23:59:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mProperties == properties) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Preserve the state of mWinsInCascade and mIsRunningOnCompositor flags.
|
|
|
|
nsCSSPropertySet winningInCascadeProperties;
|
|
|
|
nsCSSPropertySet runningOnCompositorProperties;
|
|
|
|
|
|
|
|
for (const AnimationProperty& property : mProperties) {
|
|
|
|
if (property.mWinsInCascade) {
|
|
|
|
winningInCascadeProperties.AddProperty(property.mProperty);
|
|
|
|
}
|
|
|
|
if (property.mIsRunningOnCompositor) {
|
|
|
|
runningOnCompositorProperties.AddProperty(property.mProperty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mProperties = Move(properties);
|
|
|
|
|
|
|
|
for (AnimationProperty& property : mProperties) {
|
|
|
|
property.mWinsInCascade =
|
|
|
|
winningInCascadeProperties.HasProperty(property.mProperty);
|
|
|
|
property.mIsRunningOnCompositor =
|
|
|
|
runningOnCompositorProperties.HasProperty(property.mProperty);
|
|
|
|
}
|
|
|
|
|
2016-05-24 03:57:42 +00:00
|
|
|
CalculateCumulativeChangeHint();
|
|
|
|
|
2016-03-29 23:59:08 +00:00
|
|
|
if (mTarget) {
|
2016-04-28 15:22:43 +00:00
|
|
|
EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
|
|
|
|
mTarget->mPseudoType);
|
2016-03-29 23:59:08 +00:00
|
|
|
if (effectSet) {
|
|
|
|
effectSet->MarkCascadeNeedsUpdate();
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
RequestRestyle(EffectCompositor::RestyleType::Layer);
|
2016-03-29 23:59:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-20 04:55:46 +00:00
|
|
|
void
|
2015-10-18 05:24:48 +00:00
|
|
|
KeyframeEffectReadOnly::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
|
2015-07-29 01:57:39 +00:00
|
|
|
nsCSSPropertySet& aSetProperties)
|
2014-10-20 04:55:46 +00:00
|
|
|
{
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
2016-01-06 02:04:06 +00:00
|
|
|
mProgressOnLastCompose = computedTiming.mProgress;
|
2014-10-20 04:55:46 +00:00
|
|
|
|
2015-05-13 04:57:35 +00:00
|
|
|
// If the progress is null, we don't have fill data for the current
|
2014-10-20 04:55:46 +00:00
|
|
|
// time so we shouldn't animate.
|
2015-10-18 22:38:00 +00:00
|
|
|
if (computedTiming.mProgress.IsNull()) {
|
2014-10-20 04:55:46 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t propIdx = 0, propEnd = mProperties.Length();
|
|
|
|
propIdx != propEnd; ++propIdx)
|
|
|
|
{
|
|
|
|
const AnimationProperty& prop = mProperties[propIdx];
|
|
|
|
|
|
|
|
MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
|
|
|
|
MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
|
|
|
|
"incorrect last to key");
|
|
|
|
|
|
|
|
if (aSetProperties.HasProperty(prop.mProperty)) {
|
2016-01-12 22:54:54 +00:00
|
|
|
// Animations are composed by EffectCompositor by iterating
|
2014-10-20 04:55:46 +00:00
|
|
|
// from the last animation to first. For animations targetting the
|
|
|
|
// same property, the later one wins. So if this property is already set,
|
|
|
|
// we should not override it.
|
2015-03-06 21:35:45 +00:00
|
|
|
continue;
|
2014-10-20 04:55:46 +00:00
|
|
|
}
|
|
|
|
|
2015-03-20 04:10:00 +00:00
|
|
|
if (!prop.mWinsInCascade) {
|
|
|
|
// This isn't the winning declaration, so don't add it to style.
|
|
|
|
// For transitions, this is important, because it's how we
|
|
|
|
// implement the rule that CSS transitions don't run when a CSS
|
|
|
|
// animation is running on the same property and element. For
|
|
|
|
// animations, this is only skipping things that will otherwise be
|
|
|
|
// overridden.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-10-20 04:55:46 +00:00
|
|
|
aSetProperties.AddProperty(prop.mProperty);
|
|
|
|
|
|
|
|
MOZ_ASSERT(prop.mSegments.Length() > 0,
|
|
|
|
"property should not be in animations if it has no segments");
|
|
|
|
|
|
|
|
// FIXME: Maybe cache the current segment?
|
|
|
|
const AnimationPropertySegment *segment = prop.mSegments.Elements(),
|
|
|
|
*segmentEnd = segment + prop.mSegments.Length();
|
2016-03-15 13:13:46 +00:00
|
|
|
while (segment->mToKey <= computedTiming.mProgress.Value()) {
|
|
|
|
MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
|
2016-02-18 13:20:00 +00:00
|
|
|
if ((segment+1) == segmentEnd) {
|
|
|
|
break;
|
2014-10-20 04:55:46 +00:00
|
|
|
}
|
2016-02-18 13:20:00 +00:00
|
|
|
++segment;
|
2014-10-20 04:55:46 +00:00
|
|
|
MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys");
|
|
|
|
}
|
2016-03-15 13:13:46 +00:00
|
|
|
MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
|
2014-10-20 04:55:46 +00:00
|
|
|
MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
|
|
|
|
size_t(segment - prop.mSegments.Elements()) <
|
|
|
|
prop.mSegments.Length(),
|
|
|
|
"out of array bounds");
|
|
|
|
|
|
|
|
if (!aStyleRule) {
|
|
|
|
// Allocate the style rule now that we know we have animation data.
|
2015-07-29 01:57:39 +00:00
|
|
|
aStyleRule = new AnimValuesStyleRule();
|
2014-10-20 04:55:46 +00:00
|
|
|
}
|
|
|
|
|
2016-03-15 13:13:46 +00:00
|
|
|
// Special handling for zero-length segments
|
|
|
|
if (segment->mToKey == segment->mFromKey) {
|
|
|
|
if (computedTiming.mProgress.Value() < 0) {
|
2016-03-29 23:59:01 +00:00
|
|
|
aStyleRule->AddValue(prop.mProperty, segment->mFromValue);
|
2016-03-15 13:13:46 +00:00
|
|
|
} else {
|
2016-03-29 23:59:01 +00:00
|
|
|
aStyleRule->AddValue(prop.mProperty, segment->mToValue);
|
2016-03-15 13:13:46 +00:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-10-20 04:55:46 +00:00
|
|
|
double positionInSegment =
|
2015-10-18 22:38:00 +00:00
|
|
|
(computedTiming.mProgress.Value() - segment->mFromKey) /
|
2014-10-20 04:55:46 +00:00
|
|
|
(segment->mToKey - segment->mFromKey);
|
|
|
|
double valuePosition =
|
2016-01-29 13:44:00 +00:00
|
|
|
ComputedTimingFunction::GetPortion(segment->mTimingFunction,
|
2016-04-01 05:00:57 +00:00
|
|
|
positionInSegment,
|
|
|
|
computedTiming.mBeforeFlag);
|
2014-10-20 04:55:46 +00:00
|
|
|
|
2016-03-29 23:59:01 +00:00
|
|
|
StyleAnimationValue val;
|
Bug 1260572 - Use 50% switch behavior if StyleAnimationValue::Interpolate fails; r=heycam
In KeyframeEffectReadOnly::ComposeStyle we call StyleAnimationValue::Interpolate
but assume that it always passes. That was true when that code was only used for
CSS animations and CSS transitions since they check that their animation values
can be interpolated before setting up segments.
However, when we set up animations using the Web Animations API we don't perform
that check so it is possible for this call to fail.
In that case, we could just bail, but, according to CSS Transitions we should
apply a 50% switch in this case:
https://drafts.csswg.org/css-transitions/#step-types
(In Web Animations, specifying this is an open issue. See:
https://w3c.github.io/web-animations/#specific-animation-behaviors).
Bug 1064937 tracks doing this in general (we'll likely need to mark various
properties as being no longer unanimatable but instead as supporting discrete
animation) but we can start to introduce it now.
Later in bug 1245748, CSS animations and transitions will likely start using
the same code path as the Web Animations API for setting up keyframes.
As a result, unless we take care to add checks that the values we set are
interpolable, the 50% switch behavior will begin to apply to CSS animations and
transitions too at that point.
Some concerns have been raised about possible web compatibility issues around
the 50% switch behavior (see [1] and [2]). For CSS animations, Chrome already
supports this behavior so it should be ok at least for CSS animations.
When we switch CSS transitions over to the same code path, however, we will need
to be careful to add checks that the transition endpoints are interpolable
(we can investigate introducing this behavior to transitions as a separate bug
that can be easily backed out / preffed off).
Regarding the naming of the test added here, going forward we would like to
restructure the tests under web-platform-tests to better match the structure of
the Web Animations since that seems to be the convention there.
However, this doesn't *quite* match the structure of the spec since there are
upcoming changes to the spec in this area (e.g. renaming animation behaviors to
animation types). However, it should be close enough that we don't have to move
it around too much in future.
[1] https://drafts.csswg.org/css-transitions/#step-types
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=1064937#c0
MozReview-Commit-ID: KcxILrckJg9
2016-03-29 23:59:08 +00:00
|
|
|
if (StyleAnimationValue::Interpolate(prop.mProperty,
|
|
|
|
segment->mFromValue,
|
|
|
|
segment->mToValue,
|
|
|
|
valuePosition, val)) {
|
|
|
|
aStyleRule->AddValue(prop.mProperty, Move(val));
|
|
|
|
} else if (valuePosition < 0.5) {
|
|
|
|
aStyleRule->AddValue(prop.mProperty, segment->mFromValue);
|
|
|
|
} else {
|
|
|
|
aStyleRule->AddValue(prop.mProperty, segment->mToValue);
|
|
|
|
}
|
2014-10-20 04:55:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-16 14:05:00 +00:00
|
|
|
bool
|
|
|
|
KeyframeEffectReadOnly::IsRunningOnCompositor() const
|
|
|
|
{
|
|
|
|
// We consider animation is running on compositor if there is at least
|
|
|
|
// one property running on compositor.
|
|
|
|
// Animation.IsRunningOnCompotitor will return more fine grained
|
|
|
|
// information in bug 1196114.
|
2015-12-20 13:16:00 +00:00
|
|
|
for (const AnimationProperty& property : mProperties) {
|
|
|
|
if (property.mIsRunningOnCompositor) {
|
2015-09-16 14:05:00 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSProperty aProperty,
|
|
|
|
bool aIsRunning)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
|
|
|
|
CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
|
|
|
|
"Property being animated on compositor is a recognized "
|
|
|
|
"compositor-animatable property");
|
2015-12-20 13:16:00 +00:00
|
|
|
for (AnimationProperty& property : mProperties) {
|
|
|
|
if (property.mProperty == aProperty) {
|
|
|
|
property.mIsRunningOnCompositor = aIsRunning;
|
2016-03-03 21:36:36 +00:00
|
|
|
// We currently only set a performance warning message when animations
|
|
|
|
// cannot be run on the compositor, so if this animation is running
|
|
|
|
// on the compositor we don't need a message.
|
|
|
|
if (aIsRunning) {
|
|
|
|
property.mPerformanceWarning.reset();
|
|
|
|
}
|
2015-09-16 14:05:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-16 07:25:46 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
|
|
|
|
{
|
|
|
|
for (AnimationProperty& property : mProperties) {
|
|
|
|
property.mIsRunningOnCompositor = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-07 05:30:27 +00:00
|
|
|
KeyframeEffectReadOnly::~KeyframeEffectReadOnly()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
static Maybe<OwningAnimationTarget>
|
|
|
|
ConvertTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget)
|
|
|
|
{
|
|
|
|
// Return value optimization.
|
|
|
|
Maybe<OwningAnimationTarget> result;
|
|
|
|
|
|
|
|
if (aTarget.IsNull()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ElementOrCSSPseudoElement& target = aTarget.Value();
|
|
|
|
MOZ_ASSERT(target.IsElement() || target.IsCSSPseudoElement(),
|
|
|
|
"Uninitialized target");
|
|
|
|
|
|
|
|
if (target.IsElement()) {
|
|
|
|
result.emplace(&target.GetAsElement());
|
|
|
|
} else {
|
|
|
|
RefPtr<Element> elem = target.GetAsCSSPseudoElement().ParentElement();
|
|
|
|
result.emplace(elem, target.GetAsCSSPseudoElement().GetType());
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-03-11 08:27:16 +00:00
|
|
|
template <class KeyframeEffectType, class OptionsType>
|
|
|
|
/* static */ already_AddRefed<KeyframeEffectType>
|
|
|
|
KeyframeEffectReadOnly::ConstructKeyframeEffect(
|
|
|
|
const GlobalObject& aGlobal,
|
|
|
|
const Nullable<ElementOrCSSPseudoElement>& aTarget,
|
2016-05-13 00:40:52 +00:00
|
|
|
JS::Handle<JSObject*> aKeyframes,
|
2016-03-11 08:27:16 +00:00
|
|
|
const OptionsType& aOptions,
|
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
2016-03-11 08:27:34 +00:00
|
|
|
nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
|
|
|
|
if (!doc) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-03-11 08:27:16 +00:00
|
|
|
TimingParams timingParams =
|
2016-03-11 08:27:34 +00:00
|
|
|
TimingParams::FromOptionsUnion(aOptions, doc, aRv);
|
2016-03-11 08:27:16 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
Maybe<OwningAnimationTarget> target = ConvertTarget(aTarget);
|
2016-03-29 23:59:08 +00:00
|
|
|
RefPtr<KeyframeEffectType> effect =
|
2016-04-28 15:22:44 +00:00
|
|
|
new KeyframeEffectType(doc, target, timingParams);
|
2016-04-28 15:22:43 +00:00
|
|
|
|
2016-05-13 00:40:52 +00:00
|
|
|
effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv);
|
2016-04-09 06:33:34 +00:00
|
|
|
if (aRv.Failed()) {
|
|
|
|
return nullptr;
|
2016-03-22 07:35:53 +00:00
|
|
|
}
|
|
|
|
|
2016-02-15 00:34:47 +00:00
|
|
|
return effect.forget();
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::ResetWinsInCascade()
|
|
|
|
{
|
|
|
|
for (AnimationProperty& property : mProperties) {
|
|
|
|
property.mWinsInCascade = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:53:53 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::UpdateTargetRegistration()
|
|
|
|
{
|
|
|
|
if (!mTarget) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isRelevant = mAnimation && mAnimation->IsRelevant();
|
|
|
|
|
|
|
|
// Animation::IsRelevant() returns a cached value. It only updates when
|
|
|
|
// something calls Animation::UpdateRelevance. Whenever our timing changes,
|
|
|
|
// we should be notifying our Animation before calling this, so
|
|
|
|
// Animation::IsRelevant() should be up-to-date by the time we get here.
|
|
|
|
MOZ_ASSERT(isRelevant == IsCurrent() || IsInEffect(),
|
|
|
|
"Out of date Animation::IsRelevant value");
|
|
|
|
|
|
|
|
if (isRelevant) {
|
2016-04-28 15:22:43 +00:00
|
|
|
EffectSet* effectSet =
|
|
|
|
EffectSet::GetOrCreateEffectSet(mTarget->mElement, mTarget->mPseudoType);
|
2015-11-26 07:53:53 +00:00
|
|
|
effectSet->AddEffect(*this);
|
|
|
|
} else {
|
2016-04-28 15:22:43 +00:00
|
|
|
UnregisterTarget();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::UnregisterTarget()
|
|
|
|
{
|
|
|
|
EffectSet* effectSet =
|
|
|
|
EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
|
|
|
|
if (effectSet) {
|
|
|
|
effectSet->RemoveEffect(*this);
|
|
|
|
if (effectSet->IsEmpty()) {
|
|
|
|
EffectSet::DestroyEffectSet(mTarget->mElement, mTarget->mPseudoType);
|
2015-11-26 07:53:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::RequestRestyle(
|
|
|
|
EffectCompositor::RestyleType aRestyleType)
|
|
|
|
{
|
|
|
|
nsPresContext* presContext = GetPresContext();
|
|
|
|
if (presContext && mTarget && mAnimation) {
|
|
|
|
presContext->EffectCompositor()->
|
|
|
|
RequestRestyle(mTarget->mElement, mTarget->mPseudoType,
|
|
|
|
aRestyleType, mAnimation->CascadeLevel());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-22 08:22:38 +00:00
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
|
|
|
DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties)
|
|
|
|
{
|
|
|
|
for (auto& p : aAnimationProperties) {
|
|
|
|
printf("%s\n", nsCSSProps::GetStringValue(p.mProperty).get());
|
|
|
|
for (auto& s : p.mSegments) {
|
|
|
|
nsString fromValue, toValue;
|
|
|
|
StyleAnimationValue::UncomputeValue(p.mProperty,
|
|
|
|
s.mFromValue,
|
|
|
|
fromValue);
|
|
|
|
StyleAnimationValue::UncomputeValue(p.mProperty,
|
|
|
|
s.mToValue,
|
|
|
|
toValue);
|
|
|
|
printf(" %f..%f: %s..%s\n", s.mFromKey, s.mToKey,
|
|
|
|
NS_ConvertUTF16toUTF8(fromValue).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(toValue).get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-03-11 08:27:16 +00:00
|
|
|
/* static */ already_AddRefed<KeyframeEffectReadOnly>
|
|
|
|
KeyframeEffectReadOnly::Constructor(
|
|
|
|
const GlobalObject& aGlobal,
|
|
|
|
const Nullable<ElementOrCSSPseudoElement>& aTarget,
|
2016-05-13 00:40:52 +00:00
|
|
|
JS::Handle<JSObject*> aKeyframes,
|
2016-03-11 08:27:16 +00:00
|
|
|
const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
|
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
return ConstructKeyframeEffect<KeyframeEffectReadOnly>(aGlobal, aTarget,
|
2016-05-13 00:40:52 +00:00
|
|
|
aKeyframes, aOptions,
|
2016-03-11 08:27:16 +00:00
|
|
|
aRv);
|
|
|
|
}
|
|
|
|
|
2016-02-05 18:01:00 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::GetTarget(
|
|
|
|
Nullable<OwningElementOrCSSPseudoElement>& aRv) const
|
|
|
|
{
|
2016-02-01 22:59:00 +00:00
|
|
|
if (!mTarget) {
|
|
|
|
aRv.SetNull();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
switch (mTarget->mPseudoType) {
|
2016-02-16 22:07:00 +00:00
|
|
|
case CSSPseudoElementType::before:
|
|
|
|
case CSSPseudoElementType::after:
|
2016-02-01 22:59:00 +00:00
|
|
|
aRv.SetValue().SetAsCSSPseudoElement() =
|
2016-04-28 15:22:43 +00:00
|
|
|
CSSPseudoElement::GetCSSPseudoElement(mTarget->mElement,
|
|
|
|
mTarget->mPseudoType);
|
2016-02-01 22:59:00 +00:00
|
|
|
break;
|
|
|
|
|
2016-02-16 22:07:00 +00:00
|
|
|
case CSSPseudoElementType::NotPseudo:
|
2016-04-28 15:22:43 +00:00
|
|
|
aRv.SetValue().SetAsElement() = mTarget->mElement;
|
2016-02-01 22:59:00 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
NS_NOTREACHED("Animation of unsupported pseudo-type");
|
|
|
|
aRv.SetNull();
|
|
|
|
}
|
2016-02-05 18:01:00 +00:00
|
|
|
}
|
|
|
|
|
2016-03-15 13:42:14 +00:00
|
|
|
static void
|
|
|
|
CreatePropertyValue(nsCSSProperty aProperty,
|
|
|
|
float aOffset,
|
|
|
|
const Maybe<ComputedTimingFunction>& aTimingFunction,
|
|
|
|
const StyleAnimationValue& aValue,
|
|
|
|
AnimationPropertyValueDetails& aResult)
|
|
|
|
{
|
2016-03-17 02:13:50 +00:00
|
|
|
aResult.mOffset = aOffset;
|
2016-03-15 13:42:14 +00:00
|
|
|
|
|
|
|
nsString stringValue;
|
|
|
|
StyleAnimationValue::UncomputeValue(aProperty, aValue, stringValue);
|
2016-03-17 02:13:50 +00:00
|
|
|
aResult.mValue = stringValue;
|
2016-03-15 13:42:14 +00:00
|
|
|
|
|
|
|
if (aTimingFunction) {
|
|
|
|
aResult.mEasing.Construct();
|
|
|
|
aTimingFunction->AppendToString(aResult.mEasing.Value());
|
|
|
|
} else {
|
|
|
|
aResult.mEasing.Construct(NS_LITERAL_STRING("linear"));
|
|
|
|
}
|
|
|
|
|
2016-03-17 02:13:50 +00:00
|
|
|
aResult.mComposite = CompositeOperation::Replace;
|
2016-03-15 13:42:14 +00:00
|
|
|
}
|
|
|
|
|
2016-03-15 13:18:50 +00:00
|
|
|
void
|
2016-03-13 11:10:10 +00:00
|
|
|
KeyframeEffectReadOnly::GetProperties(
|
2016-03-17 04:47:14 +00:00
|
|
|
nsTArray<AnimationPropertyDetails>& aProperties,
|
|
|
|
ErrorResult& aRv) const
|
2016-03-15 13:18:50 +00:00
|
|
|
{
|
|
|
|
for (const AnimationProperty& property : mProperties) {
|
2016-03-13 11:10:10 +00:00
|
|
|
AnimationPropertyDetails propertyDetails;
|
2016-03-17 02:13:50 +00:00
|
|
|
propertyDetails.mProperty =
|
|
|
|
NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty));
|
|
|
|
propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor;
|
2016-03-15 13:18:50 +00:00
|
|
|
|
|
|
|
nsXPIDLString localizedString;
|
|
|
|
if (property.mPerformanceWarning &&
|
|
|
|
property.mPerformanceWarning->ToLocalizedString(localizedString)) {
|
2016-03-13 11:10:10 +00:00
|
|
|
propertyDetails.mWarning.Construct(localizedString);
|
2016-03-15 13:18:50 +00:00
|
|
|
}
|
|
|
|
|
2016-03-17 04:47:14 +00:00
|
|
|
if (!propertyDetails.mValues.SetCapacity(property.mSegments.Length(),
|
|
|
|
mozilla::fallible)) {
|
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return;
|
2016-03-15 13:42:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length();
|
|
|
|
segmentIdx < segmentLen;
|
|
|
|
segmentIdx++)
|
|
|
|
{
|
|
|
|
const AnimationPropertySegment& segment = property.mSegments[segmentIdx];
|
|
|
|
|
|
|
|
binding_detail::FastAnimationPropertyValueDetails fromValue;
|
|
|
|
CreatePropertyValue(property.mProperty, segment.mFromKey,
|
|
|
|
segment.mTimingFunction, segment.mFromValue,
|
|
|
|
fromValue);
|
|
|
|
// We don't apply timing functions for zero-length segments, so
|
|
|
|
// don't return one here.
|
|
|
|
if (segment.mFromKey == segment.mToKey) {
|
|
|
|
fromValue.mEasing.Reset();
|
|
|
|
}
|
|
|
|
// The following won't fail since we have already allocated the capacity
|
|
|
|
// above.
|
2016-03-17 02:13:50 +00:00
|
|
|
propertyDetails.mValues.AppendElement(fromValue, mozilla::fallible);
|
2016-03-15 13:42:14 +00:00
|
|
|
|
|
|
|
// Normally we can ignore the to-value for this segment since it is
|
|
|
|
// identical to the from-value from the next segment. However, we need
|
|
|
|
// to add it if either:
|
|
|
|
// a) this is the last segment, or
|
|
|
|
// b) the next segment's from-value differs.
|
|
|
|
if (segmentIdx == segmentLen - 1 ||
|
|
|
|
property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
|
|
|
|
binding_detail::FastAnimationPropertyValueDetails toValue;
|
|
|
|
CreatePropertyValue(property.mProperty, segment.mToKey,
|
|
|
|
Nothing(), segment.mToValue, toValue);
|
|
|
|
// It doesn't really make sense to have a timing function on the
|
|
|
|
// last property value or before a sudden jump so we just drop the
|
|
|
|
// easing property altogether.
|
|
|
|
toValue.mEasing.Reset();
|
2016-03-17 02:13:50 +00:00
|
|
|
propertyDetails.mValues.AppendElement(toValue, mozilla::fallible);
|
2016-03-15 13:42:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-13 11:10:10 +00:00
|
|
|
aProperties.AppendElement(propertyDetails);
|
2016-03-15 13:18:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-01 00:29:31 +00:00
|
|
|
void
|
2016-05-13 00:40:52 +00:00
|
|
|
KeyframeEffectReadOnly::GetKeyframes(JSContext*& aCx,
|
|
|
|
nsTArray<JSObject*>& aResult,
|
|
|
|
ErrorResult& aRv)
|
2016-03-29 23:59:08 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(aResult.IsEmpty());
|
|
|
|
MOZ_ASSERT(!aRv.Failed());
|
|
|
|
|
2016-05-13 00:40:52 +00:00
|
|
|
if (!aResult.SetCapacity(mKeyframes.Length(), mozilla::fallible)) {
|
2016-03-29 23:59:08 +00:00
|
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-13 00:40:52 +00:00
|
|
|
for (const Keyframe& keyframe : mKeyframes) {
|
2016-03-29 23:59:08 +00:00
|
|
|
// Set up a dictionary object for the explicit members
|
|
|
|
BaseComputedKeyframe keyframeDict;
|
|
|
|
if (keyframe.mOffset) {
|
|
|
|
keyframeDict.mOffset.SetValue(keyframe.mOffset.value());
|
|
|
|
}
|
|
|
|
keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
|
|
|
|
if (keyframe.mTimingFunction) {
|
|
|
|
keyframeDict.mEasing.Truncate();
|
|
|
|
keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
|
|
|
|
} // else if null, leave easing as its default "linear".
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> keyframeJSValue(aCx);
|
|
|
|
if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
|
|
|
|
for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
|
|
|
|
|
|
|
|
const char* name = nsCSSProps::PropertyIDLName(propertyValue.mProperty);
|
|
|
|
|
|
|
|
// nsCSSValue::AppendToString does not accept shorthands properties but
|
|
|
|
// works with token stream values if we pass eCSSProperty_UNKNOWN as
|
|
|
|
// the property.
|
|
|
|
nsCSSProperty propertyForSerializing =
|
|
|
|
nsCSSProps::IsShorthand(propertyValue.mProperty)
|
|
|
|
? eCSSProperty_UNKNOWN
|
|
|
|
: propertyValue.mProperty;
|
|
|
|
|
|
|
|
nsAutoString stringValue;
|
|
|
|
propertyValue.mValue.AppendToString(
|
|
|
|
propertyForSerializing, stringValue, nsCSSValue::eNormalized);
|
|
|
|
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
|
|
if (!ToJSValue(aCx, stringValue, &value) ||
|
|
|
|
!JS_DefineProperty(aCx, keyframeObject, name, value,
|
|
|
|
JSPROP_ENUMERATE)) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
aResult.AppendElement(keyframeObject);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:55:00 +00:00
|
|
|
/* static */ const TimeDuration
|
|
|
|
KeyframeEffectReadOnly::OverflowRegionRefreshInterval()
|
|
|
|
{
|
|
|
|
// The amount of time we can wait between updating throttled animations
|
|
|
|
// on the main thread that influence the overflow region.
|
|
|
|
static const TimeDuration kOverflowRegionRefreshInterval =
|
|
|
|
TimeDuration::FromMilliseconds(200);
|
|
|
|
|
|
|
|
return kOverflowRegionRefreshInterval;
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:49:00 +00:00
|
|
|
bool
|
|
|
|
KeyframeEffectReadOnly::CanThrottle() const
|
|
|
|
{
|
2016-01-06 02:04:06 +00:00
|
|
|
// Unthrottle if we are not in effect or current. This will be the case when
|
|
|
|
// our owning animation has finished, is idle, or when we are in the delay
|
|
|
|
// phase (but without a backwards fill). In each case the computed progress
|
|
|
|
// value produced on each tick will be the same so we will skip requesting
|
|
|
|
// unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get
|
|
|
|
// here will be because of a change in state (e.g. we are newly finished or
|
|
|
|
// newly no longer in effect) in which case we shouldn't throttle the sample.
|
|
|
|
if (!IsInEffect() || !IsCurrent()) {
|
2015-11-06 01:57:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-11-06 01:53:00 +00:00
|
|
|
|
|
|
|
nsIFrame* frame = GetAnimationFrame();
|
|
|
|
if (!frame) {
|
|
|
|
// There are two possible cases here.
|
|
|
|
// a) No target element
|
|
|
|
// b) The target element has no frame, e.g. because it is in a display:none
|
|
|
|
// subtree.
|
|
|
|
// In either case we can throttle the animation because there is no
|
|
|
|
// need to update on the main thread.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-05-24 03:57:43 +00:00
|
|
|
// We can throttle the animation if the animation is paint only and
|
|
|
|
// the target frame is out of view.
|
|
|
|
if (CanIgnoreIfNotVisible() && frame->IsScrolledOutOfView()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:53:00 +00:00
|
|
|
// First we need to check layer generation and transform overflow
|
2015-12-20 13:16:00 +00:00
|
|
|
// prior to the property.mIsRunningOnCompositor check because we should
|
2015-11-06 01:53:00 +00:00
|
|
|
// occasionally unthrottle these animations even if the animations are
|
|
|
|
// already running on compositor.
|
|
|
|
for (const LayerAnimationInfo::Record& record :
|
|
|
|
LayerAnimationInfo::sRecords) {
|
|
|
|
// Skip properties that are overridden in the cascade.
|
|
|
|
// (GetAnimationOfProperty, as called by HasAnimationOfProperty,
|
|
|
|
// only returns an animation if it currently wins in the cascade.)
|
|
|
|
if (!HasAnimationOfProperty(record.mProperty)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
|
|
|
|
mTarget->mPseudoType);
|
2016-01-06 02:04:05 +00:00
|
|
|
MOZ_ASSERT(effectSet, "CanThrottle should be called on an effect "
|
|
|
|
"associated with a target element");
|
2015-11-06 01:53:00 +00:00
|
|
|
layers::Layer* layer =
|
|
|
|
FrameLayerBuilder::GetDedicatedLayer(frame, record.mLayerType);
|
2016-01-06 02:04:05 +00:00
|
|
|
// Unthrottle if the layer needs to be brought up to date
|
2015-11-06 01:53:00 +00:00
|
|
|
if (!layer ||
|
2016-01-06 02:04:05 +00:00
|
|
|
effectSet->GetAnimationGeneration() !=
|
|
|
|
layer->GetAnimationGeneration()) {
|
2015-11-06 01:53:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a transform animation that affects the overflow region,
|
|
|
|
// we should unthrottle the animation periodically.
|
|
|
|
if (record.mProperty == eCSSProperty_transform &&
|
|
|
|
!CanThrottleTransformChanges(*frame)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:49:00 +00:00
|
|
|
for (const AnimationProperty& property : mProperties) {
|
2015-12-20 13:16:00 +00:00
|
|
|
if (!property.mIsRunningOnCompositor) {
|
2015-11-06 01:49:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2015-11-06 01:53:00 +00:00
|
|
|
|
2015-11-06 01:49:00 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:53:00 +00:00
|
|
|
bool
|
|
|
|
KeyframeEffectReadOnly::CanThrottleTransformChanges(nsIFrame& aFrame) const
|
|
|
|
{
|
|
|
|
// If we know that the animation cannot cause overflow,
|
|
|
|
// we can just disable flushes for this animation.
|
|
|
|
|
|
|
|
// If we don't show scrollbars, we don't care about overflow.
|
|
|
|
if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsPresContext* presContext = GetPresContext();
|
|
|
|
// CanThrottleTransformChanges is only called as part of a refresh driver tick
|
|
|
|
// in which case we expect to has a pres context.
|
|
|
|
MOZ_ASSERT(presContext);
|
|
|
|
|
|
|
|
TimeStamp now =
|
|
|
|
presContext->RefreshDriver()->MostRecentRefresh();
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
|
|
|
|
mTarget->mPseudoType);
|
2016-01-12 22:54:54 +00:00
|
|
|
MOZ_ASSERT(effectSet, "CanThrottleTransformChanges is expected to be called"
|
|
|
|
" on an effect in an effect set");
|
|
|
|
MOZ_ASSERT(mAnimation, "CanThrottleTransformChanges is expected to be called"
|
|
|
|
" on an effect with a parent animation");
|
|
|
|
TimeStamp animationRuleRefreshTime =
|
|
|
|
effectSet->AnimationRuleRefreshTime(mAnimation->CascadeLevel());
|
2015-11-06 01:53:00 +00:00
|
|
|
// If this animation can cause overflow, we can throttle some of the ticks.
|
2016-01-12 22:54:54 +00:00
|
|
|
if (!animationRuleRefreshTime.IsNull() &&
|
|
|
|
(now - animationRuleRefreshTime) < OverflowRegionRefreshInterval()) {
|
2015-11-06 01:53:00 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the nearest scrollable ancestor has overflow:hidden,
|
|
|
|
// we don't care about overflow.
|
|
|
|
nsIScrollableFrame* scrollable =
|
|
|
|
nsLayoutUtils::GetNearestScrollableFrame(&aFrame);
|
|
|
|
if (!scrollable) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ScrollbarStyles ss = scrollable->GetScrollbarStyles();
|
|
|
|
if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
|
|
|
|
ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
|
|
|
|
scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:42:00 +00:00
|
|
|
nsIFrame*
|
|
|
|
KeyframeEffectReadOnly::GetAnimationFrame() const
|
|
|
|
{
|
|
|
|
if (!mTarget) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
nsIFrame* frame = mTarget->mElement->GetPrimaryFrame();
|
2015-11-06 01:42:00 +00:00
|
|
|
if (!frame) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
if (mTarget->mPseudoType == CSSPseudoElementType::before) {
|
2015-11-06 01:42:00 +00:00
|
|
|
frame = nsLayoutUtils::GetBeforeFrame(frame);
|
2016-04-28 15:22:43 +00:00
|
|
|
} else if (mTarget->mPseudoType == CSSPseudoElementType::after) {
|
2015-11-06 01:42:00 +00:00
|
|
|
frame = nsLayoutUtils::GetAfterFrame(frame);
|
|
|
|
} else {
|
2016-04-28 15:22:43 +00:00
|
|
|
MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo,
|
|
|
|
"unknown mTarget->mPseudoType");
|
2015-11-06 01:42:00 +00:00
|
|
|
}
|
|
|
|
if (!frame) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nsLayoutUtils::GetStyleFrame(frame);
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:51:00 +00:00
|
|
|
nsIDocument*
|
|
|
|
KeyframeEffectReadOnly::GetRenderedDocument() const
|
|
|
|
{
|
|
|
|
if (!mTarget) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2016-04-28 15:22:43 +00:00
|
|
|
return mTarget->mElement->GetComposedDoc();
|
2015-11-06 01:51:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nsPresContext*
|
|
|
|
KeyframeEffectReadOnly::GetPresContext() const
|
|
|
|
{
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
|
|
if (!doc) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
nsIPresShell* shell = doc->GetShell();
|
|
|
|
if (!shell) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return shell->GetPresContext();
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:38:00 +00:00
|
|
|
/* static */ bool
|
|
|
|
KeyframeEffectReadOnly::IsGeometricProperty(
|
|
|
|
const nsCSSProperty aProperty)
|
|
|
|
{
|
|
|
|
switch (aProperty) {
|
|
|
|
case eCSSProperty_bottom:
|
|
|
|
case eCSSProperty_height:
|
|
|
|
case eCSSProperty_left:
|
|
|
|
case eCSSProperty_right:
|
|
|
|
case eCSSProperty_top:
|
|
|
|
case eCSSProperty_width:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-06 01:38:00 +00:00
|
|
|
/* static */ bool
|
|
|
|
KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
|
|
|
|
const nsIFrame* aFrame,
|
2016-03-04 08:54:25 +00:00
|
|
|
AnimationPerformanceWarning::Type& aPerformanceWarning)
|
2015-11-06 01:38:00 +00:00
|
|
|
{
|
2016-02-07 10:51:11 +00:00
|
|
|
// Disallow OMTA for preserve-3d transform. Note that we check the style property
|
|
|
|
// rather than Extend3DContext() since that can recurse back into this function
|
2016-03-04 06:48:50 +00:00
|
|
|
// via HasOpacity(). See bug 779598.
|
2015-11-06 01:38:00 +00:00
|
|
|
if (aFrame->Combines3DTransformWithAncestors() ||
|
2016-02-07 10:51:11 +00:00
|
|
|
aFrame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
|
2016-03-04 08:54:25 +00:00
|
|
|
aPerformanceWarning = AnimationPerformanceWarning::Type::TransformPreserve3D;
|
2015-11-06 01:38:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Note that testing BackfaceIsHidden() is not a sufficient test for
|
|
|
|
// what we need for animating backface-visibility correctly if we
|
|
|
|
// remove the above test for Extend3DContext(); that would require
|
2016-03-04 06:48:50 +00:00
|
|
|
// looking at backface-visibility on descendants as well. See bug 1186204.
|
2015-11-06 01:38:00 +00:00
|
|
|
if (aFrame->StyleDisplay()->BackfaceIsHidden()) {
|
2016-03-04 08:54:25 +00:00
|
|
|
aPerformanceWarning =
|
|
|
|
AnimationPerformanceWarning::Type::TransformBackfaceVisibilityHidden;
|
2015-11-06 01:38:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
2016-03-04 06:48:50 +00:00
|
|
|
// Async 'transform' animations of aFrames with SVG transforms is not
|
|
|
|
// supported. See bug 779599.
|
2015-11-06 01:38:00 +00:00
|
|
|
if (aFrame->IsSVGTransformed()) {
|
2016-03-04 08:54:25 +00:00
|
|
|
aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG;
|
2015-11-06 01:38:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-12-03 23:32:53 +00:00
|
|
|
bool
|
2016-03-14 00:07:48 +00:00
|
|
|
KeyframeEffectReadOnly::ShouldBlockAsyncTransformAnimations(
|
2016-03-04 06:07:04 +00:00
|
|
|
const nsIFrame* aFrame,
|
2016-03-04 08:54:25 +00:00
|
|
|
AnimationPerformanceWarning::Type& aPerformanceWarning) const
|
2015-11-06 01:45:00 +00:00
|
|
|
{
|
2015-12-03 23:32:53 +00:00
|
|
|
// We currently only expect this method to be called when this effect
|
|
|
|
// is attached to a playing Animation. If that ever changes we'll need
|
|
|
|
// to update this to only return true when that is the case since paused,
|
|
|
|
// filling, cancelled Animations etc. shouldn't stop other Animations from
|
|
|
|
// running on the compositor.
|
|
|
|
MOZ_ASSERT(mAnimation && mAnimation->IsPlaying());
|
|
|
|
|
|
|
|
for (const AnimationProperty& property : mProperties) {
|
2015-12-24 22:14:02 +00:00
|
|
|
// If a property is overridden in the CSS cascade, it should not block other
|
|
|
|
// animations from running on the compositor.
|
|
|
|
if (!property.mWinsInCascade) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-12-03 23:32:53 +00:00
|
|
|
// Check for geometric properties
|
|
|
|
if (IsGeometricProperty(property.mProperty)) {
|
2016-03-04 08:54:25 +00:00
|
|
|
aPerformanceWarning =
|
2016-03-14 00:07:48 +00:00
|
|
|
AnimationPerformanceWarning::Type::TransformWithGeometricProperties;
|
2015-12-03 23:32:53 +00:00
|
|
|
return true;
|
2015-11-06 01:45:00 +00:00
|
|
|
}
|
2015-12-03 23:32:53 +00:00
|
|
|
|
|
|
|
// Check for unsupported transform animations
|
|
|
|
if (property.mProperty == eCSSProperty_transform) {
|
|
|
|
if (!CanAnimateTransformOnCompositor(aFrame,
|
2016-03-04 06:07:04 +00:00
|
|
|
aPerformanceWarning)) {
|
2015-12-03 23:32:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
2015-11-06 01:45:00 +00:00
|
|
|
}
|
|
|
|
}
|
2015-12-03 23:32:53 +00:00
|
|
|
|
|
|
|
return false;
|
2015-11-06 01:45:00 +00:00
|
|
|
}
|
|
|
|
|
2016-03-03 21:36:36 +00:00
|
|
|
void
|
2016-03-04 08:54:25 +00:00
|
|
|
KeyframeEffectReadOnly::SetPerformanceWarning(
|
|
|
|
nsCSSProperty aProperty,
|
|
|
|
const AnimationPerformanceWarning& aWarning)
|
2016-03-03 21:36:36 +00:00
|
|
|
{
|
|
|
|
for (AnimationProperty& property : mProperties) {
|
2016-03-04 08:54:25 +00:00
|
|
|
if (property.mProperty == aProperty &&
|
|
|
|
(!property.mPerformanceWarning ||
|
|
|
|
*property.mPerformanceWarning != aWarning)) {
|
|
|
|
property.mPerformanceWarning = Some(aWarning);
|
|
|
|
|
|
|
|
nsXPIDLString localizedString;
|
|
|
|
if (nsLayoutUtils::IsAnimationLoggingEnabled() &&
|
|
|
|
property.mPerformanceWarning->ToLocalizedString(localizedString)) {
|
|
|
|
nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString);
|
2016-04-28 15:22:43 +00:00
|
|
|
AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget->mElement);
|
2016-03-04 06:07:04 +00:00
|
|
|
}
|
2016-03-03 21:36:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-24 03:57:42 +00:00
|
|
|
void
|
|
|
|
KeyframeEffectReadOnly::CalculateCumulativeChangeHint()
|
|
|
|
{
|
|
|
|
mCumulativeChangeHint = nsChangeHint(0);
|
|
|
|
|
|
|
|
for (const AnimationProperty& property : mProperties) {
|
|
|
|
for (const AnimationPropertySegment& segment : property.mSegments) {
|
|
|
|
mCumulativeChangeHint |= segment.mChangeHint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-24 03:57:42 +00:00
|
|
|
bool
|
|
|
|
KeyframeEffectReadOnly::CanIgnoreIfNotVisible() const
|
|
|
|
{
|
|
|
|
// FIXME: For further sophisticated optimization we need to check
|
|
|
|
// change hint on the segment corresponding to computedTiming.progress.
|
|
|
|
return NS_IsHintSubset(
|
|
|
|
mCumulativeChangeHint, nsChangeHint_Hints_CanIgnoreIfNotVisible);
|
|
|
|
}
|
|
|
|
|
2016-02-15 00:34:47 +00:00
|
|
|
//---------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// KeyframeEffect
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
|
|
|
|
KeyframeEffect::KeyframeEffect(nsIDocument* aDocument,
|
2016-04-28 15:22:44 +00:00
|
|
|
const Maybe<OwningAnimationTarget>& aTarget,
|
2016-02-15 00:34:47 +00:00
|
|
|
const TimingParams& aTiming)
|
2016-04-28 15:22:44 +00:00
|
|
|
: KeyframeEffectReadOnly(aDocument, aTarget,
|
2016-04-10 05:51:32 +00:00
|
|
|
new AnimationEffectTiming(aDocument, aTiming, this))
|
2016-02-15 00:34:47 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-02-15 00:34:47 +00:00
|
|
|
JSObject*
|
|
|
|
KeyframeEffect::WrapObject(JSContext* aCx,
|
|
|
|
JS::Handle<JSObject*> aGivenProto)
|
|
|
|
{
|
|
|
|
return KeyframeEffectBinding::Wrap(aCx, this, aGivenProto);
|
|
|
|
}
|
|
|
|
|
2016-03-11 08:27:16 +00:00
|
|
|
/* static */ already_AddRefed<KeyframeEffect>
|
|
|
|
KeyframeEffect::Constructor(
|
|
|
|
const GlobalObject& aGlobal,
|
|
|
|
const Nullable<ElementOrCSSPseudoElement>& aTarget,
|
2016-05-13 00:40:52 +00:00
|
|
|
JS::Handle<JSObject*> aKeyframes,
|
2016-03-11 08:27:16 +00:00
|
|
|
const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
|
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
2016-05-13 00:40:52 +00:00
|
|
|
return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aTarget, aKeyframes,
|
2016-03-11 08:27:16 +00:00
|
|
|
aOptions, aRv);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ already_AddRefed<KeyframeEffect>
|
|
|
|
KeyframeEffect::Constructor(
|
|
|
|
const GlobalObject& aGlobal,
|
|
|
|
const Nullable<ElementOrCSSPseudoElement>& aTarget,
|
2016-05-13 00:40:52 +00:00
|
|
|
JS::Handle<JSObject*> aKeyframes,
|
2016-03-12 13:14:10 +00:00
|
|
|
const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
|
2016-03-11 08:27:16 +00:00
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
2016-05-13 00:40:52 +00:00
|
|
|
return ConstructKeyframeEffect<KeyframeEffect>(aGlobal, aTarget, aKeyframes,
|
2016-03-12 13:14:10 +00:00
|
|
|
aOptions, aRv);
|
2016-03-11 08:27:16 +00:00
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
void
|
|
|
|
KeyframeEffect::NotifySpecifiedTimingUpdated()
|
2016-02-26 21:39:49 +00:00
|
|
|
{
|
2016-03-21 08:49:50 +00:00
|
|
|
// Use the same document for a pseudo element and its parent element.
|
2016-04-28 15:22:42 +00:00
|
|
|
// Use nullptr if we don't have mTarget, so disable the mutation batch.
|
2016-04-28 15:22:43 +00:00
|
|
|
nsAutoAnimationMutationBatch mb(mTarget ? mTarget->mElement->OwnerDoc()
|
|
|
|
: nullptr);
|
2016-02-26 21:39:49 +00:00
|
|
|
|
|
|
|
if (mAnimation) {
|
|
|
|
mAnimation->NotifyEffectTimingUpdated();
|
|
|
|
|
|
|
|
if (mAnimation->IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(mAnimation);
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
RequestRestyle(EffectCompositor::RestyleType::Layer);
|
2016-02-26 21:39:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
void
|
|
|
|
KeyframeEffect::SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget)
|
|
|
|
{
|
2016-04-28 15:22:43 +00:00
|
|
|
Maybe<OwningAnimationTarget> newTarget = ConvertTarget(aTarget);
|
|
|
|
if (mTarget == newTarget) {
|
|
|
|
// Assign the same target, skip it.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mTarget) {
|
|
|
|
UnregisterTarget();
|
|
|
|
ResetIsRunningOnCompositor();
|
|
|
|
ResetWinsInCascade();
|
|
|
|
|
|
|
|
RequestRestyle(EffectCompositor::RestyleType::Layer);
|
2016-04-28 15:22:43 +00:00
|
|
|
|
|
|
|
nsAutoAnimationMutationBatch mb(mTarget->mElement->OwnerDoc());
|
|
|
|
if (mAnimation) {
|
|
|
|
nsNodeUtils::AnimationRemoved(mAnimation);
|
|
|
|
}
|
2016-04-28 15:22:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mTarget = newTarget;
|
|
|
|
|
|
|
|
if (mTarget) {
|
|
|
|
UpdateTargetRegistration();
|
|
|
|
MaybeUpdateProperties();
|
|
|
|
|
|
|
|
RequestRestyle(EffectCompositor::RestyleType::Layer);
|
2016-04-28 15:22:43 +00:00
|
|
|
|
|
|
|
nsAutoAnimationMutationBatch mb(mTarget->mElement->OwnerDoc());
|
|
|
|
if (mAnimation) {
|
|
|
|
nsNodeUtils::AnimationAdded(mAnimation);
|
|
|
|
}
|
2016-04-28 15:22:43 +00:00
|
|
|
}
|
2016-04-28 15:22:43 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 21:39:30 +00:00
|
|
|
KeyframeEffect::~KeyframeEffect()
|
|
|
|
{
|
2016-04-13 10:20:46 +00:00
|
|
|
// mTiming is cycle collected, so we have to do null check first even though
|
|
|
|
// mTiming shouldn't be null during the lifetime of KeyframeEffect.
|
|
|
|
if (mTiming) {
|
|
|
|
mTiming->Unlink();
|
|
|
|
}
|
2016-02-26 21:39:30 +00:00
|
|
|
}
|
|
|
|
|
2016-04-28 15:22:43 +00:00
|
|
|
void
|
|
|
|
KeyframeEffect::MaybeUpdateProperties()
|
|
|
|
{
|
|
|
|
if (!mTarget) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIDocument* doc = mTarget->mElement->OwnerDoc();
|
|
|
|
if (!doc) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIAtom* pseudo = mTarget->mPseudoType < CSSPseudoElementType::Count ?
|
|
|
|
nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) :
|
|
|
|
nullptr;
|
|
|
|
RefPtr<nsStyleContext> styleContext =
|
|
|
|
nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement, pseudo,
|
|
|
|
doc->GetShell());
|
|
|
|
if (!styleContext) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateProperties(styleContext);
|
|
|
|
}
|
|
|
|
|
2014-08-10 07:06:46 +00:00
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|