mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
811 lines
28 KiB
C++
811 lines
28 KiB
C++
|
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
||
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
|
*
|
||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
|
* the License. You may obtain a copy of the License at
|
||
|
* http://www.mozilla.org/MPL/
|
||
|
*
|
||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
|
* for the specific language governing rights and limitations under the
|
||
|
* License.
|
||
|
*
|
||
|
* The Original Code is nsAnimationManager, an implementation of part
|
||
|
* of css3-animations.
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
||
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
||
|
* the Initial Developer. All Rights Reserved.
|
||
|
*
|
||
|
* Contributor(s):
|
||
|
* L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
|
||
|
*
|
||
|
* Alternatively, the contents of this file may be used under the terms of
|
||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||
|
* of those above. If you wish to allow use of your version of this file only
|
||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||
|
* use your version of this file under the terms of the MPL, indicate your
|
||
|
* decision by deleting the provisions above and replace them with the notice
|
||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||
|
* the provisions above, a recipient may use your version of this file under
|
||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
#include "nsAnimationManager.h"
|
||
|
#include "nsPresContext.h"
|
||
|
#include "nsRuleProcessorData.h"
|
||
|
#include "nsStyleSet.h"
|
||
|
#include "nsCSSRules.h"
|
||
|
#include "mozilla/TimeStamp.h"
|
||
|
#include "nsStyleAnimation.h"
|
||
|
#include "nsSMILKeySpline.h"
|
||
|
|
||
|
using namespace mozilla;
|
||
|
|
||
|
struct AnimationSegmentProperty
|
||
|
{
|
||
|
nsCSSProperty mProperty;
|
||
|
nsStyleAnimation::Value mFromValue, mToValue;
|
||
|
};
|
||
|
|
||
|
struct AnimationSegment
|
||
|
{
|
||
|
float mFromKey, mToKey;
|
||
|
css::ComputedTimingFunction mTimingFunction;
|
||
|
InfallibleTArray<AnimationSegmentProperty> mProperties;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Data about one animation (i.e., one of the values of
|
||
|
* 'animation-name') running on an element.
|
||
|
*/
|
||
|
struct ElementAnimation
|
||
|
{
|
||
|
nsString mName; // empty string for 'none'
|
||
|
float mIterationCount; // NS_IEEEPositiveInfinity() means infinite
|
||
|
PRUint8 mDirection;
|
||
|
PRUint8 mFillMode;
|
||
|
PRUint8 mPlayState;
|
||
|
|
||
|
bool FillsForwards() const {
|
||
|
return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH ||
|
||
|
mFillMode == NS_STYLE_ANIMATION_FILL_MODE_FORWARDS;
|
||
|
}
|
||
|
bool FillsBackwards() const {
|
||
|
return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH ||
|
||
|
mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
|
||
|
}
|
||
|
|
||
|
bool IsPaused() const {
|
||
|
return mPlayState == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
|
||
|
}
|
||
|
|
||
|
TimeStamp mStartTime; // with delay taken into account
|
||
|
TimeStamp mPauseStart;
|
||
|
TimeDuration mIterationDuration;
|
||
|
|
||
|
InfallibleTArray<AnimationSegment> mSegments;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Data about all of the animations running on an element.
|
||
|
*/
|
||
|
struct ElementAnimations : public mozilla::css::CommonElementAnimationData
|
||
|
{
|
||
|
ElementAnimations(dom::Element *aElement, nsIAtom *aElementProperty,
|
||
|
nsAnimationManager *aAnimationManager)
|
||
|
: CommonElementAnimationData(aElement, aElementProperty,
|
||
|
aAnimationManager),
|
||
|
mNeedsRefreshes(true)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void EnsureStyleRuleFor(TimeStamp aRefreshTime);
|
||
|
|
||
|
void PostRestyleForAnimation(nsPresContext *aPresContext) {
|
||
|
nsRestyleHint hint =
|
||
|
(mElementProperty == nsGkAtoms::animationsProperty)
|
||
|
? eRestyle_Self : eRestyle_Subtree;
|
||
|
aPresContext->PresShell()->RestyleForAnimation(mElement, hint);
|
||
|
}
|
||
|
|
||
|
// This style rule contains the style data for currently animating
|
||
|
// values. It only matches when styling with animation. When we
|
||
|
// style without animation, we need to not use it so that we can
|
||
|
// detect any new changes; if necessary we restyle immediately
|
||
|
// afterwards with animation.
|
||
|
// NOTE: If we don't need to apply any styles, mStyleRule will be
|
||
|
// null, but mStyleRuleRefreshTime will still be valid.
|
||
|
nsRefPtr<css::AnimValuesStyleRule> mStyleRule;
|
||
|
// The refresh time associated with mStyleRule.
|
||
|
TimeStamp mStyleRuleRefreshTime;
|
||
|
|
||
|
// False when we know that our current style rule is valid
|
||
|
// indefinitely into the future (because all of our animations are
|
||
|
// either completed or paused). May be invalidated by a style change.
|
||
|
bool mNeedsRefreshes;
|
||
|
|
||
|
InfallibleTArray<ElementAnimation> mAnimations;
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
ElementAnimationsPropertyDtor(void *aObject,
|
||
|
nsIAtom *aPropertyName,
|
||
|
void *aPropertyValue,
|
||
|
void *aData)
|
||
|
{
|
||
|
ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
|
||
|
delete ea;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime)
|
||
|
{
|
||
|
if (!mNeedsRefreshes) {
|
||
|
// All of our animations are paused or completed.
|
||
|
mStyleRuleRefreshTime = aRefreshTime;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mNeedsRefreshes = false;
|
||
|
|
||
|
// mStyleRule may be null and valid, if we have no style to apply.
|
||
|
if (mStyleRuleRefreshTime.IsNull() ||
|
||
|
mStyleRuleRefreshTime != aRefreshTime) {
|
||
|
mStyleRuleRefreshTime = aRefreshTime;
|
||
|
mStyleRule = nsnull;
|
||
|
|
||
|
// FIXME(spec): assume that properties in higher animations override
|
||
|
// those in lower ones (and that our |HasProperty| check in
|
||
|
// |BuildSegment| matches the definition of when they should do so.
|
||
|
// Therefore, we iterate from last animation to first.
|
||
|
nsCSSPropertySet properties;
|
||
|
|
||
|
for (PRUint32 i = mAnimations.Length(); i-- != 0; ) {
|
||
|
const ElementAnimation &anim = mAnimations[i];
|
||
|
|
||
|
if (anim.mSegments.Length() == 0 ||
|
||
|
anim.mIterationDuration.ToMilliseconds() <= 0.0) {
|
||
|
// No animation data.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
TimeDuration currentTimeDuration;
|
||
|
if (anim.IsPaused()) {
|
||
|
// FIXME: avoid recalculating every time
|
||
|
currentTimeDuration = anim.mPauseStart - anim.mStartTime;
|
||
|
} else {
|
||
|
currentTimeDuration = aRefreshTime - anim.mStartTime;
|
||
|
}
|
||
|
|
||
|
// Set |currentIterationCount| to the (fractional) number of
|
||
|
// iterations we've completed up to the current position.
|
||
|
double currentIterationCount =
|
||
|
currentTimeDuration / anim.mIterationDuration;
|
||
|
if (currentIterationCount >= double(anim.mIterationCount)) {
|
||
|
if (!anim.FillsForwards()) {
|
||
|
// No animation data.
|
||
|
continue;
|
||
|
}
|
||
|
currentIterationCount = double(anim.mIterationCount);
|
||
|
} else {
|
||
|
if (!anim.IsPaused()) {
|
||
|
mNeedsRefreshes = true;
|
||
|
}
|
||
|
if (currentIterationCount < 0.0) {
|
||
|
if (!anim.FillsBackwards()) {
|
||
|
// No animation data.
|
||
|
continue;
|
||
|
}
|
||
|
currentIterationCount = 0.0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set |positionInIteration| to the position from 0% to 100% along
|
||
|
// the keyframes.
|
||
|
NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
|
||
|
PRUint32 whichIteration = int(currentIterationCount);
|
||
|
if (whichIteration == anim.mIterationCount) {
|
||
|
// When the animation's iteration count is an integer (as it
|
||
|
// normally is), we need to end at 100% of its last iteration
|
||
|
// rather than 0% of the next one.
|
||
|
--whichIteration;
|
||
|
}
|
||
|
double positionInIteration =
|
||
|
currentIterationCount - double(whichIteration);
|
||
|
if (anim.mDirection == NS_STYLE_ANIMATION_DIRECTION_ALTERNATE &&
|
||
|
(whichIteration & 1) == 1) {
|
||
|
positionInIteration = 1.0 - positionInIteration;
|
||
|
}
|
||
|
|
||
|
NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
|
||
|
positionInIteration <= 1.0,
|
||
|
"position should be in [0-1]");
|
||
|
|
||
|
NS_ABORT_IF_FALSE(anim.mSegments[0].mFromKey == 0.0,
|
||
|
"incorrect first from key");
|
||
|
NS_ABORT_IF_FALSE(anim.mSegments[anim.mSegments.Length() - 1].mToKey
|
||
|
== 1.0,
|
||
|
"incorrect last to key");
|
||
|
|
||
|
// FIXME: Maybe cache the current segment?
|
||
|
const AnimationSegment *segment = anim.mSegments.Elements();
|
||
|
while (segment->mToKey < positionInIteration) {
|
||
|
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
|
||
|
"incorrect keys");
|
||
|
++segment;
|
||
|
NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
|
||
|
"incorrect keys");
|
||
|
}
|
||
|
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
|
||
|
"incorrect keys");
|
||
|
NS_ABORT_IF_FALSE(segment - anim.mSegments.Elements() <
|
||
|
anim.mSegments.Length(),
|
||
|
"ran off end");
|
||
|
|
||
|
if (segment->mProperties.IsEmpty()) {
|
||
|
// No animation data.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!mStyleRule) {
|
||
|
// Allocate the style rule now that we know we have animation data.
|
||
|
mStyleRule = new css::AnimValuesStyleRule();
|
||
|
}
|
||
|
|
||
|
double positionInSegment = (positionInIteration - segment->mFromKey) /
|
||
|
(segment->mToKey - segment->mFromKey);
|
||
|
double valuePosition =
|
||
|
segment->mTimingFunction.GetValue(positionInSegment);
|
||
|
|
||
|
for (PRUint32 j = 0, j_end = segment->mProperties.Length();
|
||
|
j != j_end; ++j) {
|
||
|
const AnimationSegmentProperty &prop = segment->mProperties[j];
|
||
|
if (properties.HasProperty(prop.mProperty)) {
|
||
|
// A later animation already set this property.
|
||
|
continue;
|
||
|
}
|
||
|
properties.AddProperty(prop.mProperty);
|
||
|
|
||
|
nsStyleAnimation::Value *val =
|
||
|
mStyleRule->AddEmptyValue(prop.mProperty);
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
PRBool result =
|
||
|
#endif
|
||
|
nsStyleAnimation::Interpolate(prop.mProperty,
|
||
|
prop.mFromValue, prop.mToValue,
|
||
|
valuePosition, *val);
|
||
|
NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ElementAnimations*
|
||
|
nsAnimationManager::GetElementAnimations(dom::Element *aElement,
|
||
|
nsCSSPseudoElements::Type aPseudoType,
|
||
|
PRBool aCreateIfNeeded)
|
||
|
{
|
||
|
if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
|
||
|
// Early return for the most common case.
|
||
|
return nsnull;
|
||
|
}
|
||
|
|
||
|
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");
|
||
|
return nsnull;
|
||
|
}
|
||
|
ElementAnimations *ea = static_cast<ElementAnimations*>(
|
||
|
aElement->GetProperty(propName));
|
||
|
if (!ea && aCreateIfNeeded) {
|
||
|
// FIXME: Consider arena-allocating?
|
||
|
ea = new ElementAnimations(aElement, propName, this);
|
||
|
if (!ea) {
|
||
|
NS_WARNING("out of memory");
|
||
|
return nsnull;
|
||
|
}
|
||
|
nsresult rv = aElement->SetProperty(propName, ea,
|
||
|
ElementAnimationsPropertyDtor, nsnull);
|
||
|
if (NS_FAILED(rv)) {
|
||
|
NS_WARNING("SetProperty failed");
|
||
|
delete ea;
|
||
|
return nsnull;
|
||
|
}
|
||
|
|
||
|
AddElementData(ea);
|
||
|
}
|
||
|
|
||
|
return ea;
|
||
|
}
|
||
|
|
||
|
/* 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
|
||
|
|
||
|
nsIStyleRule*
|
||
|
nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
|
||
|
mozilla::dom::Element* aElement)
|
||
|
{
|
||
|
if (!mPresContext->IsProcessingAnimationStyleChange()) {
|
||
|
// 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.
|
||
|
|
||
|
const nsStyleDisplay *disp = aStyleContext->GetStyleDisplay();
|
||
|
ElementAnimations *ea =
|
||
|
GetElementAnimations(aElement, aStyleContext->GetPseudoType(), PR_FALSE);
|
||
|
if (!ea &&
|
||
|
disp->mAnimations.Length() == 1 &&
|
||
|
disp->mAnimations[0].GetName().IsEmpty()) {
|
||
|
return nsnull;
|
||
|
}
|
||
|
|
||
|
// build the animations list
|
||
|
InfallibleTArray<ElementAnimation> newAnimations;
|
||
|
BuildAnimations(aStyleContext, newAnimations);
|
||
|
|
||
|
if (newAnimations.IsEmpty()) {
|
||
|
if (ea) {
|
||
|
ea->Destroy();
|
||
|
}
|
||
|
return nsnull;
|
||
|
}
|
||
|
|
||
|
TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
|
||
|
|
||
|
if (ea) {
|
||
|
// The cached style rule is invalid.
|
||
|
ea->mStyleRule = nsnull;
|
||
|
ea->mStyleRuleRefreshTime = TimeStamp();
|
||
|
|
||
|
// 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()) {
|
||
|
for (PRUint32 i = 0, i_end = newAnimations.Length();
|
||
|
i != i_end; ++i) {
|
||
|
ElementAnimation *newAnim = &newAnimations[i];
|
||
|
|
||
|
// 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.
|
||
|
const ElementAnimation *oldAnim = nsnull;
|
||
|
for (PRUint32 j = ea->mAnimations.Length(); j-- != 0; ) {
|
||
|
const ElementAnimation *a = &ea->mAnimations[j];
|
||
|
if (a->mName == newAnim->mName) {
|
||
|
oldAnim = a;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!oldAnim) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
newAnim->mStartTime = oldAnim->mStartTime;
|
||
|
|
||
|
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(),
|
||
|
PR_TRUE);
|
||
|
}
|
||
|
ea->mAnimations.SwapElements(newAnimations);
|
||
|
ea->mNeedsRefreshes = true;
|
||
|
|
||
|
ea->EnsureStyleRuleFor(refreshTime);
|
||
|
}
|
||
|
|
||
|
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; }
|
||
|
PRBool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
|
||
|
|
||
|
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
|
||
|
static PLDHashNumber HashKey(KeyTypePointer aKey) {
|
||
|
PR_STATIC_ASSERT(sizeof(PLDHashNumber) == sizeof(PRUint32));
|
||
|
PR_STATIC_ASSERT(PLDHashNumber(-1) > PLDHashNumber(0));
|
||
|
float key = *aKey;
|
||
|
NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range");
|
||
|
return PLDHashNumber(key * PR_UINT32_MAX);
|
||
|
}
|
||
|
enum { ALLOW_MEMMOVE = PR_TRUE };
|
||
|
|
||
|
private:
|
||
|
const float mValue;
|
||
|
};
|
||
|
|
||
|
struct KeyframeData {
|
||
|
float mKey;
|
||
|
nsCSSKeyframeRule *mRule;
|
||
|
};
|
||
|
|
||
|
typedef InfallibleTArray<KeyframeData> KeyframeDataArray;
|
||
|
|
||
|
static PLDHashOperator
|
||
|
AppendKeyframeData(const float &aKey, nsCSSKeyframeRule *aRule, void *aData)
|
||
|
{
|
||
|
KeyframeDataArray *array = static_cast<KeyframeDataArray*>(aData);
|
||
|
KeyframeData *data = array->AppendElement();
|
||
|
data->mKey = aKey;
|
||
|
data->mRule = aRule;
|
||
|
return PL_DHASH_NEXT;
|
||
|
}
|
||
|
|
||
|
struct KeyframeDataComparator {
|
||
|
PRBool Equals(const KeyframeData& A, const KeyframeData& B) const {
|
||
|
return A.mKey == B.mKey;
|
||
|
}
|
||
|
PRBool LessThan(const KeyframeData& A, const KeyframeData& B) const {
|
||
|
return A.mKey < B.mKey;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class ResolvedStyleCache {
|
||
|
public:
|
||
|
ResolvedStyleCache() {
|
||
|
mCache.Init(16); // FIXME: make infallible!
|
||
|
}
|
||
|
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
|
||
|
// nsStyleAnimation::Value values in AnimationSegmentProperty).
|
||
|
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,
|
||
|
InfallibleTArray<ElementAnimation>& aAnimations)
|
||
|
{
|
||
|
NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array");
|
||
|
|
||
|
ResolvedStyleCache resolvedStyles;
|
||
|
|
||
|
const nsStyleDisplay *disp = aStyleContext->GetStyleDisplay();
|
||
|
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
|
||
|
for (PRUint32 i = 0, i_end = disp->mAnimations.Length(); i != i_end; ++i) {
|
||
|
const nsAnimation& aSrc = disp->mAnimations[i];
|
||
|
ElementAnimation& aDest = *aAnimations.AppendElement();
|
||
|
|
||
|
aDest.mName = aSrc.GetName();
|
||
|
aDest.mIterationCount = aSrc.GetIterationCount();
|
||
|
aDest.mDirection = aSrc.GetDirection();
|
||
|
aDest.mFillMode = aSrc.GetFillMode();
|
||
|
aDest.mPlayState = aSrc.GetPlayState();
|
||
|
|
||
|
aDest.mStartTime = now + TimeDuration::FromMilliseconds(aSrc.GetDelay());
|
||
|
if (aDest.IsPaused()) {
|
||
|
aDest.mPauseStart = now;
|
||
|
} else {
|
||
|
aDest.mPauseStart = TimeStamp();
|
||
|
}
|
||
|
|
||
|
aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration());
|
||
|
|
||
|
nsCSSKeyframesRule *rule = KeyframesRuleFor(aDest.mName);
|
||
|
if (!rule) {
|
||
|
// no segments
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Build the set of unique keyframes in the @keyframes rule. Per
|
||
|
// css3-animations, later keyframes with the same key replace
|
||
|
// earlier ones (no cascading).
|
||
|
nsDataHashtable<PercentageHashKey, nsCSSKeyframeRule*> keyframes;
|
||
|
keyframes.Init(16); // FIXME: make infallible!
|
||
|
for (PRUint32 j = 0, j_end = rule->StyleRuleCount(); j != j_end; ++j) {
|
||
|
nsICSSRule* cssRule = rule->GetStyleRuleAt(j);
|
||
|
NS_ABORT_IF_FALSE(cssRule, "must have rule");
|
||
|
NS_ABORT_IF_FALSE(cssRule->GetType() == nsICSSRule::KEYFRAME_RULE,
|
||
|
"must be keyframe rule");
|
||
|
nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule);
|
||
|
|
||
|
const nsTArray<float> &keys = kfRule->GetKeys();
|
||
|
for (PRUint32 k = 0, k_end = keys.Length(); k != k_end; ++k) {
|
||
|
float key = keys[k];
|
||
|
// 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) {
|
||
|
keyframes.Put(key, kfRule);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KeyframeDataArray sortedKeyframes;
|
||
|
keyframes.EnumerateRead(AppendKeyframeData, &sortedKeyframes);
|
||
|
sortedKeyframes.Sort(KeyframeDataComparator());
|
||
|
|
||
|
if (sortedKeyframes.Length() == 0) {
|
||
|
// no segments
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
KeyframeData fromKeyframe = sortedKeyframes[0];
|
||
|
nsRefPtr<nsStyleContext> fromContext =
|
||
|
resolvedStyles.Get(mPresContext, aStyleContext,
|
||
|
fromKeyframe.mRule);
|
||
|
|
||
|
// If there's no rule for 0%, there's implicitly an empty rule.
|
||
|
if (fromKeyframe.mKey != 0.0f) {
|
||
|
BuildSegment(aDest.mSegments, aSrc,
|
||
|
0.0f, aStyleContext, nsnull,
|
||
|
fromKeyframe.mKey, fromContext,
|
||
|
fromKeyframe.mRule->Declaration());
|
||
|
}
|
||
|
|
||
|
for (PRUint32 j = 1, j_end = sortedKeyframes.Length(); j != j_end; ++j) {
|
||
|
KeyframeData toKeyframe = sortedKeyframes[j];
|
||
|
nsRefPtr<nsStyleContext> toContext =
|
||
|
resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule);
|
||
|
|
||
|
BuildSegment(aDest.mSegments, aSrc,
|
||
|
fromKeyframe.mKey, fromContext,
|
||
|
fromKeyframe.mRule->Declaration(),
|
||
|
toKeyframe.mKey, toContext,
|
||
|
toKeyframe.mRule->Declaration());
|
||
|
|
||
|
fromContext = toContext;
|
||
|
fromKeyframe = toKeyframe;
|
||
|
}
|
||
|
|
||
|
// If there's no rule for 100%, there's implicitly an empty rule.
|
||
|
if (fromKeyframe.mKey != 1.0f) {
|
||
|
BuildSegment(aDest.mSegments, aSrc,
|
||
|
fromKeyframe.mKey, fromContext,
|
||
|
fromKeyframe.mRule->Declaration(),
|
||
|
1.0f, aStyleContext, nsnull);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nsAnimationManager::BuildSegment(InfallibleTArray<AnimationSegment>& aSegments,
|
||
|
const nsAnimation& aAnimation,
|
||
|
float aFromKey, nsStyleContext* aFromContext,
|
||
|
mozilla::css::Declaration* aFromDeclaration,
|
||
|
float aToKey, nsStyleContext* aToContext,
|
||
|
mozilla::css::Declaration* aToDeclaration)
|
||
|
{
|
||
|
AnimationSegment &segment = *aSegments.AppendElement();
|
||
|
|
||
|
segment.mFromKey = aFromKey;
|
||
|
segment.mToKey = aToKey;
|
||
|
const nsTimingFunction *tf;
|
||
|
if (aFromDeclaration &&
|
||
|
aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) {
|
||
|
tf = &aFromContext->GetStyleDisplay()->mAnimations[0].GetTimingFunction();
|
||
|
} else {
|
||
|
tf = &aAnimation.GetTimingFunction();
|
||
|
}
|
||
|
segment.mTimingFunction.Init(*tf);
|
||
|
|
||
|
for (nsCSSProperty prop = nsCSSProperty(0);
|
||
|
prop < eCSSProperty_COUNT_no_shorthands;
|
||
|
prop = nsCSSProperty(prop + 1)) {
|
||
|
if (nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!(aFromDeclaration && aFromDeclaration->HasProperty(prop)) &&
|
||
|
!(aToDeclaration && aToDeclaration->HasProperty(prop))) {
|
||
|
// Don't store an animation if neither declaration has the property.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
nsStyleAnimation::Value fromValue, toValue, dummyValue;
|
||
|
if (ExtractComputedValueForTransition(prop, aFromContext, fromValue) &&
|
||
|
ExtractComputedValueForTransition(prop, 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(prop, fromValue, toValue,
|
||
|
0.5, dummyValue)) {
|
||
|
AnimationSegmentProperty &p = *segment.mProperties.AppendElement();
|
||
|
p.mProperty = prop;
|
||
|
p.mFromValue = fromValue;
|
||
|
p.mToValue = toValue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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");
|
||
|
|
||
|
ElementAnimations *ea =
|
||
|
GetElementAnimations(aElement, aPseudoType, PR_FALSE);
|
||
|
if (!ea) {
|
||
|
return nsnull;
|
||
|
}
|
||
|
|
||
|
NS_ABORT_IF_FALSE(ea->mStyleRuleRefreshTime ==
|
||
|
mPresContext->RefreshDriver()->MostRecentRefresh(),
|
||
|
"should already have refreshed style rule");
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
return nsnull;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// 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!).
|
||
|
for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
|
||
|
l = PR_NEXT_LINK(l)) {
|
||
|
ElementAnimations *ea = static_cast<ElementAnimations*>(l);
|
||
|
nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
|
||
|
ea->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh());
|
||
|
if (oldStyleRule != ea->mStyleRule) {
|
||
|
ea->PostRestyleForAnimation(mPresContext);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
nsCSSKeyframesRule*
|
||
|
nsAnimationManager::KeyframesRuleFor(const nsSubstring& aName)
|
||
|
{
|
||
|
if (mKeyframesListIsDirty) {
|
||
|
mKeyframesListIsDirty = false;
|
||
|
|
||
|
nsTArray<nsCSSKeyframesRule*> rules;
|
||
|
mPresContext->StyleSet()->AppendKeyframesRules(mPresContext, rules);
|
||
|
|
||
|
// Per css3-animations, the last @keyframes rule specified wins.
|
||
|
mKeyframesRules.Clear();
|
||
|
for (PRUint32 i = 0, i_end = rules.Length(); i != i_end; ++i) {
|
||
|
nsCSSKeyframesRule *rule = rules[i];
|
||
|
mKeyframesRules.Put(rule->GetName(), rule);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return mKeyframesRules.Get(aName);
|
||
|
}
|
||
|
|