2011-04-11 23:18:44 -07:00
|
|
|
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
2012-05-21 04:12:37 -07: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/. */
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
#include "nsAnimationManager.h"
|
2013-10-22 05:14:41 -07:00
|
|
|
#include "nsTransitionManager.h"
|
2013-06-30 09:26:39 -07:00
|
|
|
|
2014-03-17 21:48:21 -07:00
|
|
|
#include "mozilla/EventDispatcher.h"
|
2013-06-30 09:26:39 -07:00
|
|
|
#include "mozilla/MemoryReporting.h"
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
#include "nsPresContext.h"
|
|
|
|
#include "nsRuleProcessorData.h"
|
|
|
|
#include "nsStyleSet.h"
|
2013-10-22 05:14:41 -07:00
|
|
|
#include "nsStyleChangeList.h"
|
2011-04-11 23:18:44 -07:00
|
|
|
#include "nsCSSRules.h"
|
2013-10-22 05:14:41 -07:00
|
|
|
#include "RestyleManager.h"
|
2011-04-11 23:18:44 -07:00
|
|
|
#include "nsStyleAnimation.h"
|
2013-06-18 04:41:30 -07:00
|
|
|
#include "nsLayoutUtils.h"
|
2013-09-30 14:26:04 -07:00
|
|
|
#include "nsIFrame.h"
|
2013-10-02 13:09:18 -07:00
|
|
|
#include "nsIDocument.h"
|
2013-09-04 04:30:57 -07:00
|
|
|
#include "ActiveLayerTracker.h"
|
2013-01-10 21:14:51 -08:00
|
|
|
#include <math.h>
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
using namespace mozilla;
|
2012-08-21 18:48:47 -07:00
|
|
|
using namespace mozilla::css;
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2013-10-22 05:14:41 -07:00
|
|
|
ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement,
|
|
|
|
nsIAtom *aElementProperty,
|
|
|
|
nsAnimationManager *aAnimationManager,
|
|
|
|
TimeStamp aNow)
|
2012-07-31 10:28:21 -07:00
|
|
|
: CommonElementAnimationData(aElement, aElementProperty,
|
2013-10-22 05:14:41 -07:00
|
|
|
aAnimationManager, aNow),
|
2012-07-31 10:28:21 -07:00
|
|
|
mNeedsRefreshes(true)
|
2011-04-11 23:18:44 -07:00
|
|
|
{
|
2012-07-31 10:28:21 -07:00
|
|
|
}
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
static void
|
|
|
|
ElementAnimationsPropertyDtor(void *aObject,
|
|
|
|
nsIAtom *aPropertyName,
|
|
|
|
void *aPropertyValue,
|
|
|
|
void *aData)
|
|
|
|
{
|
|
|
|
ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
|
2011-04-30 15:16:19 -07:00
|
|
|
#ifdef DEBUG
|
|
|
|
NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice");
|
|
|
|
ea->mCalledPropertyDtor = true;
|
|
|
|
#endif
|
2011-04-11 23:18:44 -07:00
|
|
|
delete ea;
|
|
|
|
}
|
|
|
|
|
2014-05-28 00:51:49 -07:00
|
|
|
ComputedTiming
|
2013-01-10 21:14:51 -08:00
|
|
|
ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration,
|
2014-05-28 00:51:49 -07:00
|
|
|
const AnimationTiming& aTiming)
|
2012-07-31 10:28:21 -07:00
|
|
|
{
|
2014-05-28 00:51:49 -07:00
|
|
|
// Always return the same object to benefit from return-value optimization.
|
|
|
|
ComputedTiming result;
|
|
|
|
|
2012-07-31 10:28:21 -07:00
|
|
|
// Set |currentIterationCount| to the (fractional) number of
|
|
|
|
// iterations we've completed up to the current position.
|
2014-05-28 00:51:49 -07:00
|
|
|
double currentIterationCount = aElapsedDuration / aTiming.mIterationDuration;
|
|
|
|
if (currentIterationCount >= aTiming.mIterationCount) {
|
2014-05-28 00:51:49 -07:00
|
|
|
result.mPhase = ComputedTiming::AnimationPhase_After;
|
2014-05-28 00:51:49 -07:00
|
|
|
if (!aTiming.FillsForwards()) {
|
2014-05-28 00:51:48 -07:00
|
|
|
// The animation isn't active or filling at this time.
|
2014-05-28 00:51:49 -07:00
|
|
|
result.mTimeFraction = ComputedTiming::kNullTimeFraction;
|
|
|
|
return result;
|
2012-07-31 10:28:21 -07:00
|
|
|
}
|
2014-05-28 00:51:49 -07:00
|
|
|
currentIterationCount = aTiming.mIterationCount;
|
2014-05-28 00:51:48 -07:00
|
|
|
} else if (currentIterationCount < 0.0) {
|
2014-05-28 00:51:49 -07:00
|
|
|
result.mPhase = ComputedTiming::AnimationPhase_Before;
|
2014-05-28 00:51:49 -07:00
|
|
|
if (!aTiming.FillsBackwards()) {
|
2014-05-28 00:51:48 -07:00
|
|
|
// The animation isn't active or filling at this time.
|
2014-05-28 00:51:49 -07:00
|
|
|
result.mTimeFraction = ComputedTiming::kNullTimeFraction;
|
|
|
|
return result;
|
2012-07-31 10:28:21 -07:00
|
|
|
}
|
2014-05-28 00:51:48 -07:00
|
|
|
currentIterationCount = 0.0;
|
|
|
|
} else {
|
2014-05-28 00:51:49 -07:00
|
|
|
result.mPhase = ComputedTiming::AnimationPhase_Active;
|
2012-07-31 10:28:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set |positionInIteration| to the position from 0% to 100% along
|
|
|
|
// the keyframes.
|
|
|
|
NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
|
2014-05-28 00:51:49 -07:00
|
|
|
double positionInIteration = fmod(currentIterationCount, 1);
|
|
|
|
|
|
|
|
// Set |whichIteration| to the integral index of the current iteration.
|
|
|
|
// Casting to an integer here gives us floor(currentIterationCount).
|
|
|
|
// We don't check for overflow here since the range of an unsigned 64-bit
|
|
|
|
// integer is more than enough (i.e. we could handle an animation that
|
|
|
|
// iterates every *microsecond* for about 580,000 years).
|
|
|
|
uint64_t whichIteration = static_cast<uint64_t>(currentIterationCount);
|
|
|
|
|
|
|
|
// Check for the end of the final iteration.
|
|
|
|
if (whichIteration != 0 &&
|
|
|
|
result.mPhase == ComputedTiming::AnimationPhase_After &&
|
|
|
|
aTiming.mIterationCount == floor(aTiming.mIterationCount)) {
|
2012-07-31 10:28:21 -07:00
|
|
|
// When the animation's iteration count is an integer (as it
|
2014-05-28 00:51:49 -07:00
|
|
|
// normally is), we need to end at 100% of its final iteration
|
2012-07-31 10:28:21 -07:00
|
|
|
// rather than 0% of the next one (unless it's zero).
|
2014-05-28 00:51:49 -07:00
|
|
|
whichIteration -= 1;
|
|
|
|
positionInIteration = 1.0;
|
2012-07-31 10:28:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
bool thisIterationReverse = false;
|
2014-05-28 00:51:49 -07:00
|
|
|
switch (aTiming.mDirection) {
|
2012-07-31 10:28:21 -07:00
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
|
|
|
|
thisIterationReverse = false;
|
|
|
|
break;
|
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
|
|
|
|
thisIterationReverse = true;
|
|
|
|
break;
|
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
|
2013-01-10 21:14:51 -08:00
|
|
|
// uint64_t has more integer precision than double does, so if
|
|
|
|
// whichIteration is that large, we've already lost and we're just
|
|
|
|
// guessing. But the animation is presumably oscillating so fast
|
|
|
|
// it doesn't matter anyway.
|
2014-05-28 00:51:49 -07:00
|
|
|
thisIterationReverse = (whichIteration & 1) == 1;
|
2012-07-31 10:28:21 -07:00
|
|
|
break;
|
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
|
2013-01-10 21:14:51 -08:00
|
|
|
// see as previous case
|
2014-05-28 00:51:49 -07:00
|
|
|
thisIterationReverse = (whichIteration & 1) == 0;
|
2012-07-31 10:28:21 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (thisIterationReverse) {
|
|
|
|
positionInIteration = 1.0 - positionInIteration;
|
|
|
|
}
|
|
|
|
|
2014-05-28 00:51:49 -07:00
|
|
|
result.mTimeFraction = positionInIteration;
|
|
|
|
result.mCurrentIteration = whichIteration;
|
|
|
|
return result;
|
2012-07-31 10:28:21 -07:00
|
|
|
}
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
void
|
2011-04-11 23:18:44 -07:00
|
|
|
ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
|
2012-12-11 13:12:43 -08:00
|
|
|
bool aIsThrottled)
|
2011-04-11 23:18:44 -07:00
|
|
|
{
|
2012-12-21 13:58:14 -08:00
|
|
|
if (!mNeedsRefreshes) {
|
|
|
|
mStyleRuleRefreshTime = aRefreshTime;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're performing animations on the compositor thread, then we can skip
|
|
|
|
// most of the work in this method. But even if we are throttled, then we
|
|
|
|
// have to do the work if an animation is ending in order to get correct end
|
|
|
|
// of animation behaviour (the styles of the animation disappear, or the fill
|
|
|
|
// mode behaviour). This loop checks for any finishing animations and forces
|
|
|
|
// the style recalculation if we find any.
|
|
|
|
if (aIsThrottled) {
|
|
|
|
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
|
2014-05-14 16:38:37 -07:00
|
|
|
ElementAnimation* anim = mAnimations[animIdx];
|
2012-12-21 13:58:14 -08:00
|
|
|
|
2014-05-28 00:51:48 -07:00
|
|
|
if (anim->mProperties.IsEmpty() ||
|
2014-05-28 00:51:49 -07:00
|
|
|
anim->mTiming.mIterationDuration.ToMilliseconds() <= 0.0) {
|
2012-12-21 13:58:14 -08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-01-11 09:53:14 -08:00
|
|
|
// The ElapsedDurationAt() call here handles pausing. But:
|
|
|
|
// FIXME: avoid recalculating every time when paused.
|
2014-05-28 00:51:49 -07:00
|
|
|
AnimationTiming timing = anim->mTiming;
|
|
|
|
timing.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BOTH;
|
2014-05-28 00:51:49 -07:00
|
|
|
ComputedTiming computedTiming =
|
2014-05-28 00:51:49 -07:00
|
|
|
GetPositionInIteration(anim->ElapsedDurationAt(aRefreshTime), timing);
|
2013-01-11 09:53:14 -08:00
|
|
|
|
2012-12-21 13:58:14 -08:00
|
|
|
// XXX We shouldn't really be using mLastNotification as a general
|
|
|
|
// indicator that the animation has finished, it should be reserved for
|
|
|
|
// events. If we use it differently in the future this use might need
|
|
|
|
// changing.
|
2014-05-14 16:38:37 -07:00
|
|
|
if (!anim->mIsRunningOnCompositor ||
|
2014-05-28 00:51:49 -07:00
|
|
|
(computedTiming.mPhase == ComputedTiming::AnimationPhase_After &&
|
2014-05-28 00:51:48 -07:00
|
|
|
anim->mLastNotification != ElementAnimation::LAST_NOTIFICATION_END))
|
|
|
|
{
|
2012-12-21 13:58:14 -08:00
|
|
|
aIsThrottled = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aIsThrottled) {
|
2011-04-11 23:18:44 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// mStyleRule may be null and valid, if we have no style to apply.
|
|
|
|
if (mStyleRuleRefreshTime.IsNull() ||
|
|
|
|
mStyleRuleRefreshTime != aRefreshTime) {
|
|
|
|
mStyleRuleRefreshTime = aRefreshTime;
|
2012-07-30 07:20:58 -07:00
|
|
|
mStyleRule = nullptr;
|
2011-04-28 10:21:36 -07:00
|
|
|
// We'll set mNeedsRefreshes to true below in all cases where we need them.
|
|
|
|
mNeedsRefreshes = false;
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
// FIXME(spec): assume that properties in higher animations override
|
2011-04-22 18:36:23 -07:00
|
|
|
// those in lower ones.
|
2011-04-11 23:18:44 -07:00
|
|
|
// Therefore, we iterate from last animation to first.
|
|
|
|
nsCSSPropertySet properties;
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
|
2014-05-14 16:38:37 -07:00
|
|
|
ElementAnimation* anim = mAnimations[animIdx];
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2014-05-28 00:51:48 -07:00
|
|
|
if (anim->mProperties.IsEmpty() ||
|
2014-05-28 00:51:49 -07:00
|
|
|
anim->mTiming.mIterationDuration.ToMilliseconds() <= 0.0) {
|
2014-05-28 00:51:48 -07:00
|
|
|
// The animation isn't active or filling at this time.
|
2011-04-11 23:18:44 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-01-10 21:14:51 -08:00
|
|
|
// The ElapsedDurationAt() call here handles pausing. But:
|
|
|
|
// FIXME: avoid recalculating every time when paused.
|
2014-05-28 00:51:49 -07:00
|
|
|
ComputedTiming computedTiming =
|
2014-05-14 16:38:37 -07:00
|
|
|
GetPositionInIteration(anim->ElapsedDurationAt(aRefreshTime),
|
2014-05-28 00:51:49 -07:00
|
|
|
anim->mTiming);
|
2014-05-28 00:51:49 -07:00
|
|
|
|
|
|
|
if ((computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
|
|
|
|
computedTiming.mPhase == ComputedTiming::AnimationPhase_Active) &&
|
|
|
|
!anim->IsPaused()) {
|
2014-05-28 00:51:48 -07:00
|
|
|
mNeedsRefreshes = true;
|
|
|
|
}
|
2012-07-31 10:28:21 -07:00
|
|
|
|
2014-05-28 00:51:49 -07:00
|
|
|
// If the time fraction is null, we don't have fill data for the current
|
|
|
|
// time so we shouldn't animate.
|
|
|
|
if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction)
|
2012-07-31 10:28:21 -07:00
|
|
|
continue;
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2014-05-28 00:51:49 -07:00
|
|
|
NS_ABORT_IF_FALSE(0.0 <= computedTiming.mTimeFraction &&
|
|
|
|
computedTiming.mTimeFraction <= 1.0,
|
|
|
|
"timing fraction should be in [0-1]");
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2014-05-14 16:38:37 -07:00
|
|
|
for (uint32_t propIdx = 0, propEnd = anim->mProperties.Length();
|
2011-04-22 18:36:23 -07:00
|
|
|
propIdx != propEnd; ++propIdx)
|
|
|
|
{
|
2014-05-14 16:38:37 -07:00
|
|
|
const AnimationProperty &prop = anim->mProperties[propIdx];
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2011-04-22 18:36:23 -07:00
|
|
|
NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
|
|
|
|
"incorrect first from key");
|
|
|
|
NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
|
|
|
|
== 1.0,
|
|
|
|
"incorrect last to key");
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
if (properties.HasProperty(prop.mProperty)) {
|
|
|
|
// A later animation already set this property.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
properties.AddProperty(prop.mProperty);
|
|
|
|
|
2011-04-22 18:36:23 -07:00
|
|
|
NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
|
|
|
|
"property should not be in animations if it "
|
|
|
|
"has no segments");
|
|
|
|
|
|
|
|
// FIXME: Maybe cache the current segment?
|
2013-01-10 21:14:51 -08:00
|
|
|
const AnimationPropertySegment *segment = prop.mSegments.Elements(),
|
|
|
|
*segmentEnd = segment + prop.mSegments.Length();
|
2014-05-28 00:51:49 -07:00
|
|
|
while (segment->mToKey < computedTiming.mTimeFraction) {
|
2011-04-22 18:36:23 -07:00
|
|
|
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
|
|
|
|
"incorrect keys");
|
|
|
|
++segment;
|
2013-01-10 21:14:51 -08:00
|
|
|
if (segment == segmentEnd) {
|
2014-05-28 00:51:49 -07:00
|
|
|
NS_ABORT_IF_FALSE(false, "incorrect time fraction");
|
2013-01-10 21:14:51 -08:00
|
|
|
break; // in order to continue in outer loop (just below)
|
|
|
|
}
|
2011-04-22 18:36:23 -07:00
|
|
|
NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
|
|
|
|
"incorrect keys");
|
|
|
|
}
|
2013-01-10 21:14:51 -08:00
|
|
|
if (segment == segmentEnd) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-04-22 18:36:23 -07:00
|
|
|
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
|
|
|
|
"incorrect keys");
|
2013-01-12 01:56:15 -08:00
|
|
|
NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() &&
|
|
|
|
size_t(segment - prop.mSegments.Elements()) <
|
2011-04-22 18:36:23 -07:00
|
|
|
prop.mSegments.Length(),
|
2013-01-12 01:56:15 -08:00
|
|
|
"out of array bounds");
|
2011-04-22 18:36:23 -07:00
|
|
|
|
|
|
|
if (!mStyleRule) {
|
|
|
|
// Allocate the style rule now that we know we have animation data.
|
|
|
|
mStyleRule = new css::AnimValuesStyleRule();
|
|
|
|
}
|
|
|
|
|
2014-05-28 00:51:49 -07:00
|
|
|
double positionInSegment =
|
|
|
|
(computedTiming.mTimeFraction - segment->mFromKey) /
|
|
|
|
(segment->mToKey - segment->mFromKey);
|
2011-04-22 18:36:23 -07:00
|
|
|
double valuePosition =
|
|
|
|
segment->mTimingFunction.GetValue(positionInSegment);
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
nsStyleAnimation::Value *val =
|
|
|
|
mStyleRule->AddEmptyValue(prop.mProperty);
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
2011-09-28 23:19:26 -07:00
|
|
|
bool result =
|
2011-04-11 23:18:44 -07:00
|
|
|
#endif
|
|
|
|
nsStyleAnimation::Interpolate(prop.mProperty,
|
2011-04-22 18:36:23 -07:00
|
|
|
segment->mFromValue, segment->mToValue,
|
2011-04-11 23:18:44 -07:00
|
|
|
valuePosition, *val);
|
|
|
|
NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-28 00:51:48 -07:00
|
|
|
void
|
|
|
|
ElementAnimations::GetEventsAt(TimeStamp aRefreshTime,
|
|
|
|
EventArray& aEventsToDispatch)
|
|
|
|
{
|
|
|
|
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
|
|
|
|
ElementAnimation* anim = mAnimations[animIdx];
|
|
|
|
|
|
|
|
// We should *not* skip animations with zero duration (bug 1004365) or
|
|
|
|
// those with no keyframes (bug 1004377).
|
|
|
|
// We will fix this separately but for now this is necessary since
|
|
|
|
// GetPositionInIteration does not yet handle zero-duration iterations.
|
|
|
|
if (anim->mProperties.IsEmpty() ||
|
2014-05-28 00:51:49 -07:00
|
|
|
anim->mTiming.mIterationDuration.ToMilliseconds() <= 0.0) {
|
2014-05-28 00:51:48 -07:00
|
|
|
// The animation isn't active or filling at this time.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-05-28 00:51:49 -07:00
|
|
|
TimeDuration elapsedDuration = anim->ElapsedDurationAt(aRefreshTime);
|
|
|
|
ComputedTiming computedTiming =
|
|
|
|
GetPositionInIteration(elapsedDuration, anim->mTiming);
|
|
|
|
|
2014-05-28 00:51:49 -07:00
|
|
|
// FIXME: Bug 1004361: If our active duration is sufficiently short and our
|
|
|
|
// samples are sufficiently infrequent we will end up skipping the start
|
|
|
|
// event and jumping straight to the end event.
|
|
|
|
switch (computedTiming.mPhase) {
|
|
|
|
case ComputedTiming::AnimationPhase_Before:
|
|
|
|
// Do nothing
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ComputedTiming::AnimationPhase_Active:
|
2014-05-28 00:51:49 -07:00
|
|
|
// Dispatch 'animationstart' or 'animationiteration' when needed.
|
|
|
|
if (computedTiming.mCurrentIteration != anim->mLastNotification) {
|
|
|
|
// Notify 'animationstart' even if a negative delay puts us
|
|
|
|
// past the first iteration.
|
|
|
|
// Note that when somebody changes the animation-duration
|
|
|
|
// dynamically, this will fire an extra iteration event
|
|
|
|
// immediately in many cases. It's not clear to me if that's the
|
|
|
|
// right thing to do.
|
|
|
|
uint32_t message =
|
|
|
|
anim->mLastNotification == ElementAnimation::LAST_NOTIFICATION_NONE
|
|
|
|
? NS_ANIMATION_START : NS_ANIMATION_ITERATION;
|
|
|
|
|
|
|
|
anim->mLastNotification = computedTiming.mCurrentIteration;
|
|
|
|
AnimationEventInfo ei(mElement, anim->mName, message,
|
|
|
|
elapsedDuration, PseudoElement());
|
|
|
|
aEventsToDispatch.AppendElement(ei);
|
|
|
|
}
|
2014-05-28 00:51:49 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ComputedTiming::AnimationPhase_After:
|
|
|
|
// Dispatch 'animationend' when needed.
|
|
|
|
if (anim->mLastNotification !=
|
|
|
|
ElementAnimation::LAST_NOTIFICATION_END) {
|
|
|
|
anim->mLastNotification = ElementAnimation::LAST_NOTIFICATION_END;
|
|
|
|
AnimationEventInfo ei(mElement, anim->mName, NS_ANIMATION_END,
|
|
|
|
elapsedDuration, PseudoElement());
|
|
|
|
aEventsToDispatch.AppendElement(ei);
|
|
|
|
}
|
|
|
|
break;
|
2014-05-28 00:51:49 -07:00
|
|
|
}
|
2014-05-28 00:51:48 -07:00
|
|
|
}
|
|
|
|
}
|
2012-08-20 21:06:47 -07:00
|
|
|
|
2012-07-31 10:28:21 -07:00
|
|
|
bool
|
|
|
|
ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const
|
|
|
|
{
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
|
2014-05-14 16:38:37 -07:00
|
|
|
const ElementAnimation* anim = mAnimations[animIdx];
|
2014-05-14 16:38:37 -07:00
|
|
|
if (anim->HasAnimationOfProperty(aProperty)) {
|
2012-08-20 21:06:47 -07:00
|
|
|
return true;
|
2012-07-31 10:28:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2012-12-11 13:12:43 -08:00
|
|
|
ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
|
2012-07-31 10:28:21 -07:00
|
|
|
{
|
2013-06-24 22:32:10 -07:00
|
|
|
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
|
2012-08-25 18:27:28 -07:00
|
|
|
if (!frame) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-08-21 18:48:47 -07:00
|
|
|
if (mElementProperty != nsGkAtoms::animationsProperty) {
|
2012-08-24 12:08:18 -07:00
|
|
|
if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
|
2012-08-25 18:27:28 -07:00
|
|
|
nsCString message;
|
2012-12-11 13:12:43 -08:00
|
|
|
message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 (");
|
|
|
|
message.Append(nsAtomCString(mElementProperty));
|
2014-05-21 20:48:51 -07:00
|
|
|
message.Append(')');
|
2012-08-25 18:27:28 -07:00
|
|
|
LogAsyncAnimationFailure(message, mElement);
|
2012-08-24 12:08:18 -07:00
|
|
|
}
|
2012-07-31 10:28:21 -07:00
|
|
|
return false;
|
2012-08-21 18:48:47 -07:00
|
|
|
}
|
2012-08-25 18:27:28 -07:00
|
|
|
|
2012-08-21 18:48:47 -07:00
|
|
|
TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
|
2014-05-14 16:38:37 -07:00
|
|
|
const ElementAnimation* anim = mAnimations[animIdx];
|
2014-05-14 16:38:37 -07:00
|
|
|
for (uint32_t propIdx = 0, propEnd = anim->mProperties.Length();
|
2012-08-21 18:48:47 -07:00
|
|
|
propIdx != propEnd; ++propIdx) {
|
2014-05-14 16:38:37 -07:00
|
|
|
if (IsGeometricProperty(anim->mProperties[propIdx].mProperty) &&
|
|
|
|
anim->IsRunningAt(now)) {
|
2012-12-11 13:12:43 -08:00
|
|
|
aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
|
2012-08-21 18:48:47 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-25 07:52:30 -07:00
|
|
|
bool hasOpacity = false;
|
|
|
|
bool hasTransform = false;
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
|
2014-05-14 16:38:37 -07:00
|
|
|
const ElementAnimation* anim = mAnimations[animIdx];
|
2014-05-14 16:38:37 -07:00
|
|
|
if (!anim->IsRunningAt(now)) {
|
2012-07-31 10:28:21 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-05-14 16:38:37 -07:00
|
|
|
for (uint32_t propIdx = 0, propEnd = anim->mProperties.Length();
|
2012-08-21 18:48:47 -07:00
|
|
|
propIdx != propEnd; ++propIdx) {
|
2014-05-14 16:38:37 -07:00
|
|
|
const AnimationProperty& prop = anim->mProperties[propIdx];
|
2012-08-21 18:48:47 -07:00
|
|
|
if (!CanAnimatePropertyOnCompositor(mElement,
|
|
|
|
prop.mProperty,
|
2013-10-22 03:30:45 -07:00
|
|
|
aFlags) ||
|
|
|
|
IsCompositorAnimationDisabledForFrame(frame)) {
|
2012-08-21 18:48:47 -07:00
|
|
|
return false;
|
|
|
|
}
|
2012-08-25 07:52:30 -07:00
|
|
|
if (prop.mProperty == eCSSProperty_opacity) {
|
|
|
|
hasOpacity = true;
|
|
|
|
} else if (prop.mProperty == eCSSProperty_transform) {
|
|
|
|
hasTransform = true;
|
|
|
|
}
|
2012-08-21 18:48:47 -07:00
|
|
|
}
|
|
|
|
}
|
2012-08-25 07:52:30 -07:00
|
|
|
// This animation can be done on the compositor. Mark the frame as active, in
|
|
|
|
// case we are able to throttle this animation.
|
|
|
|
if (hasOpacity) {
|
2013-09-04 04:30:57 -07:00
|
|
|
ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
|
2012-08-25 07:52:30 -07:00
|
|
|
}
|
|
|
|
if (hasTransform) {
|
2013-09-04 04:30:57 -07:00
|
|
|
ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
|
2012-08-25 07:52:30 -07:00
|
|
|
}
|
2012-07-31 10:28:21 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
ElementAnimations*
|
|
|
|
nsAnimationManager::GetElementAnimations(dom::Element *aElement,
|
|
|
|
nsCSSPseudoElements::Type aPseudoType,
|
2011-09-28 23:19:26 -07:00
|
|
|
bool aCreateIfNeeded)
|
2011-04-11 23:18:44 -07:00
|
|
|
{
|
|
|
|
if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
|
|
|
|
// Early return for the most common case.
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsIAtom *propName;
|
|
|
|
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
|
|
|
|
propName = nsGkAtoms::animationsProperty;
|
|
|
|
} else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
|
|
|
|
propName = nsGkAtoms::animationsOfBeforeProperty;
|
|
|
|
} else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
|
|
|
|
propName = nsGkAtoms::animationsOfAfterProperty;
|
|
|
|
} else {
|
|
|
|
NS_ASSERTION(!aCreateIfNeeded,
|
|
|
|
"should never try to create transitions for pseudo "
|
|
|
|
"other than :before or :after");
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
ElementAnimations *ea = static_cast<ElementAnimations*>(
|
|
|
|
aElement->GetProperty(propName));
|
|
|
|
if (!ea && aCreateIfNeeded) {
|
|
|
|
// FIXME: Consider arena-allocating?
|
2013-10-22 05:14:41 -07:00
|
|
|
ea = new ElementAnimations(aElement, propName, this,
|
|
|
|
mPresContext->RefreshDriver()->MostRecentRefresh());
|
2011-04-11 23:18:44 -07:00
|
|
|
nsresult rv = aElement->SetProperty(propName, ea,
|
2012-10-09 16:14:42 -07:00
|
|
|
ElementAnimationsPropertyDtor, false);
|
2011-04-11 23:18:44 -07:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_WARNING("SetProperty failed");
|
|
|
|
delete ea;
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
2012-07-31 10:28:21 -07:00
|
|
|
if (propName == nsGkAtoms::animationsProperty) {
|
|
|
|
aElement->SetMayHaveAnimations();
|
|
|
|
}
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
AddElementData(ea);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ea;
|
|
|
|
}
|
|
|
|
|
2012-12-11 13:12:43 -08:00
|
|
|
|
|
|
|
void
|
2014-05-28 00:51:48 -07:00
|
|
|
nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aEA)
|
2012-12-11 13:12:43 -08:00
|
|
|
{
|
2014-05-28 00:51:48 -07:00
|
|
|
TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
|
|
|
|
aEA->EnsureStyleRuleFor(refreshTime, false);
|
|
|
|
aEA->GetEventsAt(refreshTime, mPendingEvents);
|
2013-06-25 01:58:46 -07:00
|
|
|
CheckNeedsRefresh();
|
2012-12-11 13:12:43 -08:00
|
|
|
}
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
/* virtual */ void
|
|
|
|
nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
|
|
|
|
"pres context mismatch");
|
|
|
|
nsIStyleRule *rule =
|
|
|
|
GetAnimationRule(aData->mElement,
|
|
|
|
nsCSSPseudoElements::ePseudo_NotPseudoElement);
|
|
|
|
if (rule) {
|
|
|
|
aData->mRuleWalker->Forward(rule);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* virtual */ void
|
|
|
|
nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
|
|
|
|
"pres context mismatch");
|
|
|
|
if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before &&
|
|
|
|
aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Do we really want to be the only thing keeping a
|
|
|
|
// pseudo-element alive? I *think* the non-animation restyle should
|
|
|
|
// handle that, but should add a test.
|
|
|
|
nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType);
|
|
|
|
if (rule) {
|
|
|
|
aData->mRuleWalker->Forward(rule);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* virtual */ void
|
|
|
|
nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef MOZ_XUL
|
|
|
|
/* virtual */ void
|
|
|
|
nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2011-12-08 21:01:52 -08:00
|
|
|
/* virtual */ size_t
|
2013-06-23 05:03:39 -07:00
|
|
|
nsAnimationManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
2011-12-08 21:01:52 -08:00
|
|
|
{
|
|
|
|
return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
|
2012-01-02 18:19:14 -08:00
|
|
|
|
|
|
|
// Measurement of the following members may be added later if DMD finds it is
|
|
|
|
// worthwhile:
|
|
|
|
// - mPendingEvents
|
2011-12-08 21:01:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* virtual */ size_t
|
2013-06-23 05:03:39 -07:00
|
|
|
nsAnimationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
2011-12-08 21:01:52 -08:00
|
|
|
{
|
2012-01-25 00:52:51 -08:00
|
|
|
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
2011-12-08 21:01:52 -08:00
|
|
|
}
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
nsIStyleRule*
|
|
|
|
nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
|
|
|
|
mozilla::dom::Element* aElement)
|
|
|
|
{
|
|
|
|
if (!mPresContext->IsProcessingAnimationStyleChange()) {
|
2013-05-28 23:36:39 -07:00
|
|
|
if (!mPresContext->IsDynamic()) {
|
|
|
|
// For print or print preview, ignore animations.
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
// Everything that causes our animation data to change triggers a
|
|
|
|
// style change, which in turn triggers a non-animation restyle.
|
|
|
|
// Likewise, when we initially construct frames, we're not in a
|
|
|
|
// style change, but also not in an animation restyle.
|
|
|
|
|
2013-02-16 13:51:02 -08:00
|
|
|
const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
|
2011-04-11 23:18:44 -07:00
|
|
|
ElementAnimations *ea =
|
2011-10-17 07:59:28 -07:00
|
|
|
GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false);
|
2011-04-11 23:18:44 -07:00
|
|
|
if (!ea &&
|
2013-01-10 21:14:51 -08:00
|
|
|
disp->mAnimationNameCount == 1 &&
|
2011-04-11 23:18:44 -07:00
|
|
|
disp->mAnimations[0].GetName().IsEmpty()) {
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// build the animations list
|
2014-05-14 16:38:37 -07:00
|
|
|
ElementAnimationPtrArray newAnimations;
|
2011-04-11 23:18:44 -07:00
|
|
|
BuildAnimations(aStyleContext, newAnimations);
|
|
|
|
|
|
|
|
if (newAnimations.IsEmpty()) {
|
|
|
|
if (ea) {
|
|
|
|
ea->Destroy();
|
|
|
|
}
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
|
|
|
|
|
|
|
|
if (ea) {
|
2012-07-30 07:20:58 -07:00
|
|
|
ea->mStyleRule = nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
ea->mStyleRuleRefreshTime = TimeStamp();
|
2012-12-11 13:12:43 -08:00
|
|
|
ea->UpdateAnimationGeneration(mPresContext);
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
// Copy over the start times and (if still paused) pause starts
|
|
|
|
// for each animation (matching on name only) that was also in the
|
|
|
|
// old list of animations.
|
|
|
|
// This means that we honor dynamic changes, which isn't what the
|
|
|
|
// spec says to do, but WebKit seems to honor at least some of
|
|
|
|
// them. See
|
|
|
|
// http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
|
|
|
|
// In order to honor what the spec said, we'd copy more data over
|
|
|
|
// (or potentially optimize BuildAnimations to avoid rebuilding it
|
|
|
|
// in the first place).
|
|
|
|
if (!ea->mAnimations.IsEmpty()) {
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t newIdx = 0, newEnd = newAnimations.Length();
|
2011-04-22 18:36:23 -07:00
|
|
|
newIdx != newEnd; ++newIdx) {
|
2014-05-14 16:38:37 -07:00
|
|
|
nsRefPtr<ElementAnimation> newAnim = newAnimations[newIdx];
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
// Find the matching animation with this name in the old list
|
|
|
|
// of animations. Because of this code, they must all have
|
|
|
|
// the same start time, though they might differ in pause
|
|
|
|
// state. So if a page uses multiple copies of the same
|
|
|
|
// animation in one element's animation list, and gives them
|
|
|
|
// different pause states, they, well, get what they deserve.
|
|
|
|
// We'll use the last one since it's more likely to be the one
|
|
|
|
// doing something.
|
2014-05-14 16:38:37 -07:00
|
|
|
const ElementAnimation* oldAnim = nullptr;
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) {
|
2014-05-14 16:38:37 -07:00
|
|
|
const ElementAnimation* a = ea->mAnimations[oldIdx];
|
2011-04-11 23:18:44 -07:00
|
|
|
if (a->mName == newAnim->mName) {
|
|
|
|
oldAnim = a;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!oldAnim) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
newAnim->mStartTime = oldAnim->mStartTime;
|
2011-04-11 23:18:44 -07:00
|
|
|
newAnim->mLastNotification = oldAnim->mLastNotification;
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
if (oldAnim->IsPaused()) {
|
|
|
|
if (newAnim->IsPaused()) {
|
|
|
|
// Copy pause start just like start time.
|
|
|
|
newAnim->mPauseStart = oldAnim->mPauseStart;
|
|
|
|
} else {
|
|
|
|
// Handle change in pause state by adjusting start
|
|
|
|
// time to unpause.
|
|
|
|
newAnim->mStartTime += refreshTime - oldAnim->mPauseStart;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(),
|
2011-10-17 07:59:28 -07:00
|
|
|
true);
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
ea->mAnimations.SwapElements(newAnimations);
|
|
|
|
ea->mNeedsRefreshes = true;
|
|
|
|
|
2014-05-28 00:51:48 -07:00
|
|
|
ea->EnsureStyleRuleFor(refreshTime, false);
|
|
|
|
ea->GetEventsAt(refreshTime, mPendingEvents);
|
2013-06-25 01:58:46 -07:00
|
|
|
CheckNeedsRefresh();
|
2011-04-11 23:18:44 -07:00
|
|
|
// We don't actually dispatch the mPendingEvents now. We'll either
|
|
|
|
// dispatch them the next time we get a refresh driver notification
|
|
|
|
// or the next time somebody calls
|
|
|
|
// nsPresShell::FlushPendingNotifications.
|
2011-12-14 20:42:15 -08:00
|
|
|
if (!mPendingEvents.IsEmpty()) {
|
|
|
|
mPresContext->Document()->SetNeedStyleFlush();
|
|
|
|
}
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return GetAnimationRule(aElement, aStyleContext->GetPseudoType());
|
|
|
|
}
|
|
|
|
|
|
|
|
class PercentageHashKey : public PLDHashEntryHdr
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
typedef const float& KeyType;
|
|
|
|
typedef const float* KeyTypePointer;
|
|
|
|
|
|
|
|
PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { }
|
|
|
|
PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { }
|
|
|
|
~PercentageHashKey() { }
|
|
|
|
|
|
|
|
KeyType GetKey() const { return mValue; }
|
2011-09-28 23:19:26 -07:00
|
|
|
bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
|
|
|
|
static PLDHashNumber HashKey(KeyTypePointer aKey) {
|
2013-07-18 10:59:53 -07:00
|
|
|
static_assert(sizeof(PLDHashNumber) == sizeof(uint32_t),
|
|
|
|
"this hash function assumes PLDHashNumber is uint32_t");
|
|
|
|
static_assert(PLDHashNumber(-1) > PLDHashNumber(0),
|
|
|
|
"this hash function assumes PLDHashNumber is uint32_t");
|
2011-04-11 23:18:44 -07:00
|
|
|
float key = *aKey;
|
|
|
|
NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range");
|
2012-09-27 23:57:33 -07:00
|
|
|
return PLDHashNumber(key * UINT32_MAX);
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
2011-10-17 07:59:28 -07:00
|
|
|
enum { ALLOW_MEMMOVE = true };
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
private:
|
|
|
|
const float mValue;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct KeyframeData {
|
|
|
|
float mKey;
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t mIndex; // store original order since sort algorithm is not stable
|
2011-04-11 23:18:44 -07:00
|
|
|
nsCSSKeyframeRule *mRule;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct KeyframeDataComparator {
|
2011-09-28 23:19:26 -07:00
|
|
|
bool Equals(const KeyframeData& A, const KeyframeData& B) const {
|
2012-03-21 22:10:02 -07:00
|
|
|
return A.mKey == B.mKey && A.mIndex == B.mIndex;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
2011-09-28 23:19:26 -07:00
|
|
|
bool LessThan(const KeyframeData& A, const KeyframeData& B) const {
|
2012-03-21 22:10:02 -07:00
|
|
|
return A.mKey < B.mKey || (A.mKey == B.mKey && A.mIndex < B.mIndex);
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class ResolvedStyleCache {
|
|
|
|
public:
|
2013-09-02 01:41:57 -07:00
|
|
|
ResolvedStyleCache() : mCache(16) {}
|
2011-04-11 23:18:44 -07:00
|
|
|
nsStyleContext* Get(nsPresContext *aPresContext,
|
|
|
|
nsStyleContext *aParentStyleContext,
|
|
|
|
nsCSSKeyframeRule *aKeyframe);
|
|
|
|
|
|
|
|
private:
|
|
|
|
nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache;
|
|
|
|
};
|
|
|
|
|
|
|
|
nsStyleContext*
|
|
|
|
ResolvedStyleCache::Get(nsPresContext *aPresContext,
|
|
|
|
nsStyleContext *aParentStyleContext,
|
|
|
|
nsCSSKeyframeRule *aKeyframe)
|
|
|
|
{
|
|
|
|
// FIXME (spec): The css3-animations spec isn't very clear about how
|
|
|
|
// properties are resolved when they have values that depend on other
|
|
|
|
// properties (e.g., values in 'em'). I presume that they're resolved
|
|
|
|
// relative to the other styles of the element. The question is
|
|
|
|
// whether they are resolved relative to other animations: I assume
|
|
|
|
// that they're not, since that would prevent us from caching a lot of
|
|
|
|
// data that we'd really like to cache (in particular, the
|
2011-04-22 18:36:23 -07:00
|
|
|
// nsStyleAnimation::Value values in AnimationPropertySegment).
|
2011-04-11 23:18:44 -07:00
|
|
|
nsStyleContext *result = mCache.GetWeak(aKeyframe);
|
|
|
|
if (!result) {
|
|
|
|
nsCOMArray<nsIStyleRule> rules;
|
|
|
|
rules.AppendObject(aKeyframe);
|
|
|
|
nsRefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->
|
|
|
|
ResolveStyleByAddingRules(aParentStyleContext, rules);
|
|
|
|
mCache.Put(aKeyframe, resultStrong);
|
|
|
|
result = resultStrong;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext,
|
2014-05-14 16:38:37 -07:00
|
|
|
ElementAnimationPtrArray& aAnimations)
|
2011-04-11 23:18:44 -07:00
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array");
|
|
|
|
|
|
|
|
ResolvedStyleCache resolvedStyles;
|
|
|
|
|
2013-02-16 13:51:02 -08:00
|
|
|
const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
|
2011-04-11 23:18:44 -07:00
|
|
|
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
|
2013-01-10 21:14:51 -08:00
|
|
|
for (uint32_t animIdx = 0, animEnd = disp->mAnimationNameCount;
|
2011-04-22 18:36:23 -07:00
|
|
|
animIdx != animEnd; ++animIdx) {
|
2014-05-14 16:38:37 -07:00
|
|
|
const nsAnimation& src = disp->mAnimations[animIdx];
|
2014-05-14 16:38:37 -07:00
|
|
|
nsRefPtr<ElementAnimation> dest =
|
|
|
|
*aAnimations.AppendElement(new ElementAnimation());
|
2014-05-14 16:38:37 -07:00
|
|
|
|
|
|
|
dest->mName = src.GetName();
|
2014-05-28 00:51:49 -07:00
|
|
|
|
|
|
|
dest->mTiming.mIterationDuration =
|
|
|
|
TimeDuration::FromMilliseconds(src.GetDuration());
|
|
|
|
dest->mTiming.mIterationCount = src.GetIterationCount();
|
|
|
|
dest->mTiming.mDirection = src.GetDirection();
|
|
|
|
dest->mTiming.mFillMode = src.GetFillMode();
|
2014-05-14 16:38:37 -07:00
|
|
|
dest->mPlayState = src.GetPlayState();
|
|
|
|
|
|
|
|
dest->mDelay = TimeDuration::FromMilliseconds(src.GetDelay());
|
|
|
|
dest->mStartTime = now;
|
|
|
|
if (dest->IsPaused()) {
|
|
|
|
dest->mPauseStart = now;
|
2011-04-11 23:18:44 -07:00
|
|
|
} else {
|
2014-05-14 16:38:37 -07:00
|
|
|
dest->mPauseStart = TimeStamp();
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
2013-08-14 21:58:37 -07:00
|
|
|
nsCSSKeyframesRule* rule =
|
2014-05-14 16:38:37 -07:00
|
|
|
mPresContext->StyleSet()->KeyframesRuleForName(mPresContext,
|
|
|
|
dest->mName);
|
2011-04-11 23:18:44 -07:00
|
|
|
if (!rule) {
|
|
|
|
// no segments
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-03-21 22:10:02 -07:00
|
|
|
// While current drafts of css3-animations say that later keyframes
|
|
|
|
// with the same key entirely replace earlier ones (no cascading),
|
|
|
|
// this is a bad idea and contradictory to the rest of CSS. So
|
|
|
|
// we're going to keep all the keyframes for each key and then do
|
|
|
|
// the replacement on a per-property basis rather than a per-rule
|
|
|
|
// basis, just like everything else in CSS.
|
|
|
|
|
|
|
|
AutoInfallibleTArray<KeyframeData, 16> sortedKeyframes;
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount();
|
2011-04-22 18:36:23 -07:00
|
|
|
ruleIdx != ruleEnd; ++ruleIdx) {
|
2011-04-07 18:23:46 -07:00
|
|
|
css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx);
|
2011-04-11 23:18:44 -07:00
|
|
|
NS_ABORT_IF_FALSE(cssRule, "must have rule");
|
2011-04-07 18:23:46 -07:00
|
|
|
NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
|
2011-04-11 23:18:44 -07:00
|
|
|
"must be keyframe rule");
|
|
|
|
nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule);
|
|
|
|
|
|
|
|
const nsTArray<float> &keys = kfRule->GetKeys();
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t keyIdx = 0, keyEnd = keys.Length();
|
2011-04-22 18:36:23 -07:00
|
|
|
keyIdx != keyEnd; ++keyIdx) {
|
|
|
|
float key = keys[keyIdx];
|
2011-04-11 23:18:44 -07:00
|
|
|
// FIXME (spec): The spec doesn't say what to do with
|
|
|
|
// out-of-range keyframes. We'll ignore them.
|
|
|
|
// (And PercentageHashKey currently assumes we either ignore or
|
|
|
|
// clamp them.)
|
|
|
|
if (0.0f <= key && key <= 1.0f) {
|
2012-03-21 22:10:02 -07:00
|
|
|
KeyframeData *data = sortedKeyframes.AppendElement();
|
|
|
|
data->mKey = key;
|
|
|
|
data->mIndex = ruleIdx;
|
|
|
|
data->mRule = kfRule;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sortedKeyframes.Sort(KeyframeDataComparator());
|
|
|
|
|
|
|
|
if (sortedKeyframes.Length() == 0) {
|
|
|
|
// no segments
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-04-22 18:36:23 -07:00
|
|
|
// Record the properties that are present in any keyframe rules we
|
|
|
|
// are using.
|
|
|
|
nsCSSPropertySet properties;
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
|
2011-04-22 18:36:23 -07:00
|
|
|
kfIdx != kfEnd; ++kfIdx) {
|
2011-04-22 18:36:23 -07:00
|
|
|
css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration();
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t propIdx = 0, propEnd = decl->Count();
|
2011-04-22 18:36:23 -07:00
|
|
|
propIdx != propEnd; ++propIdx) {
|
Bug 773296 - Part 2: Parse CSS variable declarations and store them on Declaration objects. p=ebassi,heycam r=dbaron
Patch co-authored by Emmanuele Bassi <ebassi@gmail.com>
This defines a CSSVariableDeclarations class that holds a set of
variable declarations. This is at the specified value stage, so values
can either be 'initial', 'inherit' or a token stream (which is what you
normally have). The variables are stored in a hash table. Although
it's a bit of a hack, we store 'initial' and 'inherit' using special
string values that can't be valid token streams (we use "!" and ";").
Declaration objects now can have two CSSVariableDeclarations objects
on them, to store normal and !important variable declarations. So that
we keep preserving the order of declarations on the object, we inflate
mOrder to store uint32_ts, where values from eCSSProperty_COUNT onwards
represent custom properties. mVariableOrder stores the names of the
variables corresponding to those entries in mOrder.
We also add a new nsCSSProperty value, eCSSPropertyExtra_variable, which
is used to represent any custom property name.
nsCSSProps::LookupProperty can return this value.
The changes to nsCSSParser are straightforward. Custom properties
are parsed and checked for syntactic validity (e.g. "var(a,)" being
invalid) and stored on the Declaration. We use nsCSSScanner's
recording ability to grab the unparsed CSS string corresponding to
the variable's value.
2013-12-11 18:09:40 -08:00
|
|
|
nsCSSProperty prop = decl->GetPropertyAt(propIdx);
|
|
|
|
if (prop != eCSSPropertyExtra_variable) {
|
|
|
|
// CSS Variables are not animatable
|
|
|
|
properties.AddProperty(prop);
|
|
|
|
}
|
2011-04-22 18:36:23 -07:00
|
|
|
}
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
2011-04-22 18:36:23 -07:00
|
|
|
for (nsCSSProperty prop = nsCSSProperty(0);
|
|
|
|
prop < eCSSProperty_COUNT_no_shorthands;
|
|
|
|
prop = nsCSSProperty(prop + 1)) {
|
|
|
|
if (!properties.HasProperty(prop) ||
|
|
|
|
nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-03-21 22:10:02 -07:00
|
|
|
// Build a list of the keyframes to use for this property. This
|
|
|
|
// means we need every keyframe with the property in it, except
|
|
|
|
// for those keyframes where a later keyframe with the *same key*
|
|
|
|
// also has the property.
|
2012-08-22 08:56:38 -07:00
|
|
|
AutoInfallibleTArray<uint32_t, 16> keyframesWithProperty;
|
2012-03-21 22:10:02 -07:00
|
|
|
float lastKey = 100.0f; // an invalid key
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
|
2012-03-21 22:10:02 -07:00
|
|
|
kfIdx != kfEnd; ++kfIdx) {
|
|
|
|
KeyframeData &kf = sortedKeyframes[kfIdx];
|
|
|
|
if (!kf.mRule->Declaration()->HasProperty(prop)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (kf.mKey == lastKey) {
|
|
|
|
// Replace previous occurrence of same key.
|
|
|
|
keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx;
|
|
|
|
} else {
|
|
|
|
keyframesWithProperty.AppendElement(kfIdx);
|
|
|
|
}
|
|
|
|
lastKey = kf.mKey;
|
|
|
|
}
|
|
|
|
|
2014-05-14 16:38:37 -07:00
|
|
|
AnimationProperty &propData = *dest->mProperties.AppendElement();
|
2011-04-22 18:36:23 -07:00
|
|
|
propData.mProperty = prop;
|
|
|
|
|
2012-07-30 07:20:58 -07:00
|
|
|
KeyframeData *fromKeyframe = nullptr;
|
2011-04-22 18:36:23 -07:00
|
|
|
nsRefPtr<nsStyleContext> fromContext;
|
|
|
|
bool interpolated = true;
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length();
|
2012-03-21 22:10:02 -07:00
|
|
|
wpIdx != wpEnd; ++wpIdx) {
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t kfIdx = keyframesWithProperty[wpIdx];
|
2011-04-22 18:36:23 -07:00
|
|
|
KeyframeData &toKeyframe = sortedKeyframes[kfIdx];
|
|
|
|
|
|
|
|
nsRefPtr<nsStyleContext> toContext =
|
|
|
|
resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule);
|
|
|
|
|
|
|
|
if (fromKeyframe) {
|
|
|
|
interpolated = interpolated &&
|
2014-05-14 16:38:37 -07:00
|
|
|
BuildSegment(propData.mSegments, prop, src,
|
2011-04-22 18:36:23 -07:00
|
|
|
fromKeyframe->mKey, fromContext,
|
|
|
|
fromKeyframe->mRule->Declaration(),
|
|
|
|
toKeyframe.mKey, toContext);
|
|
|
|
} else {
|
|
|
|
if (toKeyframe.mKey != 0.0f) {
|
|
|
|
// There's no data for this property at 0%, so use the
|
|
|
|
// cascaded value above us.
|
|
|
|
interpolated = interpolated &&
|
2014-05-14 16:38:37 -07:00
|
|
|
BuildSegment(propData.mSegments, prop, src,
|
2012-07-30 07:20:58 -07:00
|
|
|
0.0f, aStyleContext, nullptr,
|
2011-04-22 18:36:23 -07:00
|
|
|
toKeyframe.mKey, toContext);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fromContext = toContext;
|
|
|
|
fromKeyframe = &toKeyframe;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fromKeyframe->mKey != 1.0f) {
|
|
|
|
// There's no data for this property at 100%, so use the
|
|
|
|
// cascaded value above us.
|
|
|
|
interpolated = interpolated &&
|
2014-05-14 16:38:37 -07:00
|
|
|
BuildSegment(propData.mSegments, prop, src,
|
2011-04-22 18:36:23 -07:00
|
|
|
fromKeyframe->mKey, fromContext,
|
|
|
|
fromKeyframe->mRule->Declaration(),
|
|
|
|
1.0f, aStyleContext);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we failed to build any segments due to inability to
|
|
|
|
// interpolate, remove the property from the animation. (It's not
|
|
|
|
// clear if this is the right thing to do -- we could run some of
|
|
|
|
// the segments, but it's really not clear whether we should skip
|
|
|
|
// values (which?) or skip segments, so best to skip the whole
|
|
|
|
// thing for now.)
|
|
|
|
if (!interpolated) {
|
2014-05-14 16:38:37 -07:00
|
|
|
dest->mProperties.RemoveElementAt(dest->mProperties.Length() - 1);
|
2011-04-22 18:36:23 -07:00
|
|
|
}
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
2014-05-14 16:38:37 -07:00
|
|
|
|
|
|
|
aAnimations.AppendElement(dest);
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-22 18:36:23 -07:00
|
|
|
bool
|
|
|
|
nsAnimationManager::BuildSegment(InfallibleTArray<AnimationPropertySegment>&
|
|
|
|
aSegments,
|
|
|
|
nsCSSProperty aProperty,
|
2011-04-11 23:18:44 -07:00
|
|
|
const nsAnimation& aAnimation,
|
|
|
|
float aFromKey, nsStyleContext* aFromContext,
|
|
|
|
mozilla::css::Declaration* aFromDeclaration,
|
2011-04-22 18:36:23 -07:00
|
|
|
float aToKey, nsStyleContext* aToContext)
|
2011-04-11 23:18:44 -07:00
|
|
|
{
|
2011-04-22 18:36:23 -07:00
|
|
|
nsStyleAnimation::Value fromValue, toValue, dummyValue;
|
|
|
|
if (!ExtractComputedValueForTransition(aProperty, aFromContext, fromValue) ||
|
|
|
|
!ExtractComputedValueForTransition(aProperty, aToContext, toValue) ||
|
|
|
|
// Check that we can interpolate between these values
|
|
|
|
// (If this is ever a performance problem, we could add a
|
|
|
|
// CanInterpolate method, but it seems fine for now.)
|
|
|
|
!nsStyleAnimation::Interpolate(aProperty, fromValue, toValue,
|
|
|
|
0.5, dummyValue)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
AnimationPropertySegment &segment = *aSegments.AppendElement();
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2011-04-22 18:36:23 -07:00
|
|
|
segment.mFromValue = fromValue;
|
|
|
|
segment.mToValue = toValue;
|
2011-04-11 23:18:44 -07:00
|
|
|
segment.mFromKey = aFromKey;
|
|
|
|
segment.mToKey = aToKey;
|
|
|
|
const nsTimingFunction *tf;
|
|
|
|
if (aFromDeclaration &&
|
|
|
|
aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) {
|
2013-02-16 13:51:02 -08:00
|
|
|
tf = &aFromContext->StyleDisplay()->mAnimations[0].GetTimingFunction();
|
2011-04-11 23:18:44 -07:00
|
|
|
} else {
|
|
|
|
tf = &aAnimation.GetTimingFunction();
|
|
|
|
}
|
|
|
|
segment.mTimingFunction.Init(*tf);
|
|
|
|
|
2011-04-22 18:36:23 -07:00
|
|
|
return true;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsIStyleRule*
|
|
|
|
nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
|
|
|
|
nsCSSPseudoElements::Type aPseudoType)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(
|
|
|
|
aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
|
|
|
|
aPseudoType == nsCSSPseudoElements::ePseudo_before ||
|
|
|
|
aPseudoType == nsCSSPseudoElements::ePseudo_after,
|
|
|
|
"forbidden pseudo type");
|
|
|
|
|
2013-05-28 23:36:39 -07:00
|
|
|
if (!mPresContext->IsDynamic()) {
|
|
|
|
// For print or print preview, ignore animations.
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
ElementAnimations *ea =
|
2011-10-17 07:59:28 -07:00
|
|
|
GetElementAnimations(aElement, aPseudoType, false);
|
2011-04-11 23:18:44 -07:00
|
|
|
if (!ea) {
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mPresContext->IsProcessingRestyles() &&
|
|
|
|
!mPresContext->IsProcessingAnimationStyleChange()) {
|
|
|
|
// During the non-animation part of processing restyles, we don't
|
|
|
|
// add the animation rule.
|
|
|
|
|
|
|
|
if (ea->mStyleRule) {
|
|
|
|
ea->PostRestyleForAnimation(mPresContext);
|
|
|
|
}
|
|
|
|
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
|
2013-09-11 15:34:27 -07:00
|
|
|
NS_WARN_IF_FALSE(!ea->mNeedsRefreshes ||
|
|
|
|
ea->mStyleRuleRefreshTime ==
|
2012-12-11 13:12:43 -08:00
|
|
|
mPresContext->RefreshDriver()->MostRecentRefresh(),
|
|
|
|
"should already have refreshed style rule");
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
return ea->mStyleRule;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* virtual */ void
|
|
|
|
nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(mPresContext,
|
|
|
|
"refresh driver should not notify additional observers "
|
|
|
|
"after pres context has been destroyed");
|
|
|
|
if (!mPresContext->GetPresShell()) {
|
|
|
|
// Someone might be keeping mPresContext alive past the point
|
|
|
|
// where it has been torn down; don't bother doing anything in
|
|
|
|
// this case. But do get rid of all our transitions so we stop
|
|
|
|
// triggering refreshes.
|
|
|
|
RemoveAllElementData();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-11 13:12:43 -08:00
|
|
|
FlushAnimations(Can_Throttle);
|
|
|
|
}
|
|
|
|
|
2013-06-25 01:58:46 -07:00
|
|
|
void
|
|
|
|
nsAnimationManager::AddElementData(CommonElementAnimationData* aData)
|
|
|
|
{
|
|
|
|
if (!mObservingRefreshDriver) {
|
|
|
|
NS_ASSERTION(static_cast<ElementAnimations*>(aData)->mNeedsRefreshes,
|
|
|
|
"Added data which doesn't need refreshing?");
|
|
|
|
// We need to observe the refresh driver.
|
|
|
|
mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
|
|
|
|
mObservingRefreshDriver = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
PR_INSERT_BEFORE(aData, &mElementData);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsAnimationManager::CheckNeedsRefresh()
|
|
|
|
{
|
|
|
|
for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
|
|
|
|
l = PR_NEXT_LINK(l)) {
|
|
|
|
if (static_cast<ElementAnimations*>(l)->mNeedsRefreshes) {
|
|
|
|
if (!mObservingRefreshDriver) {
|
|
|
|
mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
|
|
|
|
mObservingRefreshDriver = true;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mObservingRefreshDriver) {
|
|
|
|
mObservingRefreshDriver = false;
|
|
|
|
mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-11 13:12:43 -08:00
|
|
|
void
|
|
|
|
nsAnimationManager::FlushAnimations(FlushFlags aFlags)
|
|
|
|
{
|
2011-04-11 23:18:44 -07:00
|
|
|
// FIXME: check that there's at least one style rule that's not
|
|
|
|
// in its "done" state, and if there isn't, remove ourselves from
|
|
|
|
// the refresh driver (but leave the animations!).
|
2012-12-11 13:12:43 -08:00
|
|
|
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
|
|
|
|
bool didThrottle = false;
|
2011-04-11 23:18:44 -07:00
|
|
|
for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
|
|
|
|
l = PR_NEXT_LINK(l)) {
|
|
|
|
ElementAnimations *ea = static_cast<ElementAnimations*>(l);
|
2012-12-11 13:12:43 -08:00
|
|
|
bool canThrottleTick = aFlags == Can_Throttle &&
|
|
|
|
ea->CanPerformOnCompositorThread(
|
|
|
|
CommonElementAnimationData::CanAnimateFlags(0)) &&
|
|
|
|
ea->CanThrottleAnimation(now);
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
|
2014-05-28 00:51:48 -07:00
|
|
|
ea->EnsureStyleRuleFor(now, canThrottleTick);
|
|
|
|
ea->GetEventsAt(now, mPendingEvents);
|
2013-06-25 01:58:46 -07:00
|
|
|
CheckNeedsRefresh();
|
2011-04-11 23:18:44 -07:00
|
|
|
if (oldStyleRule != ea->mStyleRule) {
|
|
|
|
ea->PostRestyleForAnimation(mPresContext);
|
2012-12-11 13:12:43 -08:00
|
|
|
} else {
|
|
|
|
didThrottle = true;
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
|
|
|
}
|
2011-04-11 23:18:44 -07:00
|
|
|
|
2012-12-11 13:12:43 -08:00
|
|
|
if (didThrottle) {
|
|
|
|
mPresContext->Document()->SetNeedStyleFlush();
|
|
|
|
}
|
|
|
|
|
2011-04-11 23:18:44 -07:00
|
|
|
DispatchEvents(); // may destroy us
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-12-14 20:42:15 -08:00
|
|
|
nsAnimationManager::DoDispatchEvents()
|
2011-04-11 23:18:44 -07:00
|
|
|
{
|
|
|
|
EventArray events;
|
|
|
|
mPendingEvents.SwapElements(events);
|
2012-08-22 08:56:38 -07:00
|
|
|
for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
|
2011-04-11 23:18:44 -07:00
|
|
|
AnimationEventInfo &info = events[i];
|
2014-03-17 21:48:21 -07:00
|
|
|
EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
|
2011-04-11 23:18:44 -07:00
|
|
|
|
|
|
|
if (!mPresContext) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-04-11 23:18:44 -07:00
|
|
|
}
|
2013-10-22 05:14:41 -07:00
|
|
|
|
|
|
|
void
|
|
|
|
nsAnimationManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
|
|
|
|
nsStyleContext* aParentStyle,
|
|
|
|
nsStyleChangeList& aChangeList)
|
|
|
|
{
|
|
|
|
dom::Element* element;
|
|
|
|
if (aContent->IsElement()) {
|
|
|
|
element = aContent->AsElement();
|
|
|
|
} else {
|
|
|
|
element = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRefPtr<nsStyleContext> newStyle;
|
|
|
|
|
|
|
|
ElementAnimations* ea;
|
|
|
|
if (element &&
|
|
|
|
(ea = GetElementAnimations(element,
|
|
|
|
nsCSSPseudoElements::ePseudo_NotPseudoElement,
|
|
|
|
false))) {
|
|
|
|
// re-resolve our style
|
|
|
|
newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList);
|
|
|
|
// remove the current transition from the working set
|
|
|
|
ea->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
|
|
|
|
} else {
|
|
|
|
newStyle = ReparentContent(aContent, aParentStyle);
|
|
|
|
}
|
|
|
|
|
|
|
|
// walk the children
|
|
|
|
if (newStyle) {
|
|
|
|
for (nsIContent *child = aContent->GetFirstChild(); child;
|
|
|
|
child = child->GetNextSibling()) {
|
|
|
|
UpdateThrottledStylesForSubtree(child, newStyle, aChangeList);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsAnimationManager,
|
|
|
|
GetElementAnimations)
|
|
|
|
|
|
|
|
void
|
|
|
|
nsAnimationManager::UpdateAllThrottledStyles()
|
|
|
|
{
|
|
|
|
if (PR_CLIST_IS_EMPTY(&mElementData)) {
|
|
|
|
// no throttled animations, leave early
|
|
|
|
mPresContext->TickLastUpdateThrottledAnimationStyle();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPresContext->ThrottledAnimationStyleIsUpToDate()) {
|
|
|
|
// throttled transitions are up to date, leave early
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mPresContext->TickLastUpdateThrottledAnimationStyle();
|
|
|
|
|
|
|
|
UpdateAllThrottledStylesInternal();
|
|
|
|
}
|
|
|
|
|