1 : /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is nsAnimationManager, an implementation of part
16 : * of css3-animations.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2011
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsAnimationManager.h"
40 : #include "nsPresContext.h"
41 : #include "nsRuleProcessorData.h"
42 : #include "nsStyleSet.h"
43 : #include "nsCSSRules.h"
44 : #include "nsStyleAnimation.h"
45 : #include "nsSMILKeySpline.h"
46 : #include "nsEventDispatcher.h"
47 :
48 : using namespace mozilla;
49 :
50 : struct AnimationPropertySegment
51 0 : {
52 : float mFromKey, mToKey;
53 : nsStyleAnimation::Value mFromValue, mToValue;
54 : css::ComputedTimingFunction mTimingFunction;
55 : };
56 :
57 : struct AnimationProperty
58 0 : {
59 : nsCSSProperty mProperty;
60 : InfallibleTArray<AnimationPropertySegment> mSegments;
61 : };
62 :
63 : /**
64 : * Data about one animation (i.e., one of the values of
65 : * 'animation-name') running on an element.
66 : */
67 : struct ElementAnimation
68 0 : {
69 0 : ElementAnimation()
70 0 : : mLastNotification(LAST_NOTIFICATION_NONE)
71 : {
72 0 : }
73 :
74 : nsString mName; // empty string for 'none'
75 : float mIterationCount; // NS_IEEEPositiveInfinity() means infinite
76 : PRUint8 mDirection;
77 : PRUint8 mFillMode;
78 : PRUint8 mPlayState;
79 :
80 0 : bool FillsForwards() const {
81 : return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH ||
82 0 : mFillMode == NS_STYLE_ANIMATION_FILL_MODE_FORWARDS;
83 : }
84 0 : bool FillsBackwards() const {
85 : return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH ||
86 0 : mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
87 : }
88 :
89 0 : bool IsPaused() const {
90 0 : return mPlayState == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
91 : }
92 :
93 : TimeStamp mStartTime; // with delay taken into account
94 : TimeStamp mPauseStart;
95 : TimeDuration mIterationDuration;
96 :
97 : enum {
98 : LAST_NOTIFICATION_NONE = PRUint32(-1),
99 : LAST_NOTIFICATION_END = PRUint32(-2)
100 : };
101 : // One of the above constants, or an integer for the iteration
102 : // whose start we last notified on.
103 : PRUint32 mLastNotification;
104 :
105 : InfallibleTArray<AnimationProperty> mProperties;
106 : };
107 :
108 : typedef nsAnimationManager::EventArray EventArray;
109 : typedef nsAnimationManager::AnimationEventInfo AnimationEventInfo;
110 :
111 : /**
112 : * Data about all of the animations running on an element.
113 : */
114 : struct ElementAnimations : public mozilla::css::CommonElementAnimationData
115 0 : {
116 0 : ElementAnimations(dom::Element *aElement, nsIAtom *aElementProperty,
117 : nsAnimationManager *aAnimationManager)
118 : : CommonElementAnimationData(aElement, aElementProperty,
119 : aAnimationManager),
120 0 : mNeedsRefreshes(true)
121 : {
122 0 : }
123 :
124 : void EnsureStyleRuleFor(TimeStamp aRefreshTime,
125 : EventArray &aEventsToDispatch);
126 :
127 0 : bool IsForElement() const { // rather than for a pseudo-element
128 0 : return mElementProperty == nsGkAtoms::animationsProperty;
129 : }
130 :
131 0 : void PostRestyleForAnimation(nsPresContext *aPresContext) {
132 0 : nsRestyleHint hint = IsForElement() ? eRestyle_Self : eRestyle_Subtree;
133 0 : aPresContext->PresShell()->RestyleForAnimation(mElement, hint);
134 0 : }
135 :
136 : // This style rule contains the style data for currently animating
137 : // values. It only matches when styling with animation. When we
138 : // style without animation, we need to not use it so that we can
139 : // detect any new changes; if necessary we restyle immediately
140 : // afterwards with animation.
141 : // NOTE: If we don't need to apply any styles, mStyleRule will be
142 : // null, but mStyleRuleRefreshTime will still be valid.
143 : nsRefPtr<css::AnimValuesStyleRule> mStyleRule;
144 : // The refresh time associated with mStyleRule.
145 : TimeStamp mStyleRuleRefreshTime;
146 :
147 : // False when we know that our current style rule is valid
148 : // indefinitely into the future (because all of our animations are
149 : // either completed or paused). May be invalidated by a style change.
150 : bool mNeedsRefreshes;
151 :
152 : InfallibleTArray<ElementAnimation> mAnimations;
153 : };
154 :
155 : static void
156 0 : ElementAnimationsPropertyDtor(void *aObject,
157 : nsIAtom *aPropertyName,
158 : void *aPropertyValue,
159 : void *aData)
160 : {
161 0 : ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
162 : #ifdef DEBUG
163 0 : NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice");
164 0 : ea->mCalledPropertyDtor = true;
165 : #endif
166 0 : delete ea;
167 0 : }
168 :
169 : void
170 0 : ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
171 : EventArray& aEventsToDispatch)
172 : {
173 0 : if (!mNeedsRefreshes) {
174 : // All of our animations are paused or completed.
175 0 : mStyleRuleRefreshTime = aRefreshTime;
176 0 : return;
177 : }
178 :
179 : // mStyleRule may be null and valid, if we have no style to apply.
180 0 : if (mStyleRuleRefreshTime.IsNull() ||
181 0 : mStyleRuleRefreshTime != aRefreshTime) {
182 0 : mStyleRuleRefreshTime = aRefreshTime;
183 0 : mStyleRule = nsnull;
184 : // We'll set mNeedsRefreshes to true below in all cases where we need them.
185 0 : mNeedsRefreshes = false;
186 :
187 : // FIXME(spec): assume that properties in higher animations override
188 : // those in lower ones.
189 : // Therefore, we iterate from last animation to first.
190 0 : nsCSSPropertySet properties;
191 :
192 0 : for (PRUint32 animIdx = mAnimations.Length(); animIdx-- != 0; ) {
193 0 : ElementAnimation &anim = mAnimations[animIdx];
194 :
195 0 : if (anim.mProperties.Length() == 0 ||
196 0 : anim.mIterationDuration.ToMilliseconds() <= 0.0) {
197 : // No animation data.
198 0 : continue;
199 : }
200 :
201 0 : TimeDuration currentTimeDuration;
202 0 : if (anim.IsPaused()) {
203 : // FIXME: avoid recalculating every time
204 0 : currentTimeDuration = anim.mPauseStart - anim.mStartTime;
205 : } else {
206 0 : currentTimeDuration = aRefreshTime - anim.mStartTime;
207 : }
208 :
209 : // Set |currentIterationCount| to the (fractional) number of
210 : // iterations we've completed up to the current position.
211 : double currentIterationCount =
212 0 : currentTimeDuration / anim.mIterationDuration;
213 0 : bool dispatchStartOrIteration = false;
214 0 : if (currentIterationCount >= double(anim.mIterationCount)) {
215 : // Dispatch 'animationend' when needed.
216 0 : if (IsForElement() &&
217 : anim.mLastNotification !=
218 : ElementAnimation::LAST_NOTIFICATION_END) {
219 0 : anim.mLastNotification = ElementAnimation::LAST_NOTIFICATION_END;
220 : AnimationEventInfo ei(mElement, anim.mName, NS_ANIMATION_END,
221 0 : currentTimeDuration);
222 0 : aEventsToDispatch.AppendElement(ei);
223 : }
224 :
225 0 : if (!anim.FillsForwards()) {
226 : // No animation data.
227 0 : continue;
228 : }
229 0 : currentIterationCount = double(anim.mIterationCount);
230 : } else {
231 0 : if (!anim.IsPaused()) {
232 0 : mNeedsRefreshes = true;
233 : }
234 0 : if (currentIterationCount < 0.0) {
235 0 : if (!anim.FillsBackwards()) {
236 : // No animation data.
237 0 : continue;
238 : }
239 0 : currentIterationCount = 0.0;
240 : } else {
241 0 : dispatchStartOrIteration = !anim.IsPaused();
242 : }
243 : }
244 :
245 : // Set |positionInIteration| to the position from 0% to 100% along
246 : // the keyframes.
247 0 : NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
248 0 : PRUint32 whichIteration = int(currentIterationCount);
249 0 : if (whichIteration == anim.mIterationCount) {
250 : // When the animation's iteration count is an integer (as it
251 : // normally is), we need to end at 100% of its last iteration
252 : // rather than 0% of the next one.
253 0 : --whichIteration;
254 : }
255 : double positionInIteration =
256 0 : currentIterationCount - double(whichIteration);
257 0 : if (anim.mDirection == NS_STYLE_ANIMATION_DIRECTION_ALTERNATE &&
258 : (whichIteration & 1) == 1) {
259 0 : positionInIteration = 1.0 - positionInIteration;
260 : }
261 :
262 : // Dispatch 'animationstart' or 'animationiteration' when needed.
263 0 : if (IsForElement() && dispatchStartOrIteration &&
264 : whichIteration != anim.mLastNotification) {
265 : // Notify 'animationstart' even if a negative delay puts us
266 : // past the first iteration.
267 : // Note that when somebody changes the animation-duration
268 : // dynamically, this will fire an extra iteration event
269 : // immediately in many cases. It's not clear to me if that's the
270 : // right thing to do.
271 : PRUint32 message =
272 : anim.mLastNotification == ElementAnimation::LAST_NOTIFICATION_NONE
273 0 : ? NS_ANIMATION_START : NS_ANIMATION_ITERATION;
274 0 : anim.mLastNotification = whichIteration;
275 : AnimationEventInfo ei(mElement, anim.mName, message,
276 0 : currentTimeDuration);
277 0 : aEventsToDispatch.AppendElement(ei);
278 : }
279 :
280 0 : NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
281 : positionInIteration <= 1.0,
282 : "position should be in [0-1]");
283 :
284 0 : for (PRUint32 propIdx = 0, propEnd = anim.mProperties.Length();
285 : propIdx != propEnd; ++propIdx)
286 : {
287 0 : const AnimationProperty &prop = anim.mProperties[propIdx];
288 :
289 0 : NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
290 : "incorrect first from key");
291 0 : NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
292 : == 1.0,
293 : "incorrect last to key");
294 :
295 0 : if (properties.HasProperty(prop.mProperty)) {
296 : // A later animation already set this property.
297 0 : continue;
298 : }
299 0 : properties.AddProperty(prop.mProperty);
300 :
301 0 : NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
302 : "property should not be in animations if it "
303 : "has no segments");
304 :
305 : // FIXME: Maybe cache the current segment?
306 0 : const AnimationPropertySegment *segment = prop.mSegments.Elements();
307 0 : while (segment->mToKey < positionInIteration) {
308 0 : NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
309 : "incorrect keys");
310 0 : ++segment;
311 0 : NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
312 : "incorrect keys");
313 : }
314 0 : NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
315 : "incorrect keys");
316 0 : NS_ABORT_IF_FALSE(segment - prop.mSegments.Elements() <
317 : prop.mSegments.Length(),
318 : "ran off end");
319 :
320 0 : if (!mStyleRule) {
321 : // Allocate the style rule now that we know we have animation data.
322 0 : mStyleRule = new css::AnimValuesStyleRule();
323 : }
324 :
325 : double positionInSegment = (positionInIteration - segment->mFromKey) /
326 0 : (segment->mToKey - segment->mFromKey);
327 : double valuePosition =
328 0 : segment->mTimingFunction.GetValue(positionInSegment);
329 :
330 : nsStyleAnimation::Value *val =
331 0 : mStyleRule->AddEmptyValue(prop.mProperty);
332 :
333 : #ifdef DEBUG
334 : bool result =
335 : #endif
336 : nsStyleAnimation::Interpolate(prop.mProperty,
337 : segment->mFromValue, segment->mToValue,
338 0 : valuePosition, *val);
339 0 : NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
340 : }
341 : }
342 : }
343 : }
344 :
345 : ElementAnimations*
346 0 : nsAnimationManager::GetElementAnimations(dom::Element *aElement,
347 : nsCSSPseudoElements::Type aPseudoType,
348 : bool aCreateIfNeeded)
349 : {
350 0 : if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
351 : // Early return for the most common case.
352 0 : return nsnull;
353 : }
354 :
355 : nsIAtom *propName;
356 0 : if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
357 0 : propName = nsGkAtoms::animationsProperty;
358 0 : } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
359 0 : propName = nsGkAtoms::animationsOfBeforeProperty;
360 0 : } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
361 0 : propName = nsGkAtoms::animationsOfAfterProperty;
362 : } else {
363 0 : NS_ASSERTION(!aCreateIfNeeded,
364 : "should never try to create transitions for pseudo "
365 : "other than :before or :after");
366 0 : return nsnull;
367 : }
368 : ElementAnimations *ea = static_cast<ElementAnimations*>(
369 0 : aElement->GetProperty(propName));
370 0 : if (!ea && aCreateIfNeeded) {
371 : // FIXME: Consider arena-allocating?
372 0 : ea = new ElementAnimations(aElement, propName, this);
373 0 : if (!ea) {
374 0 : NS_WARNING("out of memory");
375 0 : return nsnull;
376 : }
377 : nsresult rv = aElement->SetProperty(propName, ea,
378 0 : ElementAnimationsPropertyDtor, nsnull);
379 0 : if (NS_FAILED(rv)) {
380 0 : NS_WARNING("SetProperty failed");
381 0 : delete ea;
382 0 : return nsnull;
383 : }
384 :
385 0 : AddElementData(ea);
386 : }
387 :
388 0 : return ea;
389 : }
390 :
391 : /* virtual */ void
392 0 : nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
393 : {
394 0 : NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
395 : "pres context mismatch");
396 : nsIStyleRule *rule =
397 : GetAnimationRule(aData->mElement,
398 0 : nsCSSPseudoElements::ePseudo_NotPseudoElement);
399 0 : if (rule) {
400 0 : aData->mRuleWalker->Forward(rule);
401 : }
402 0 : }
403 :
404 : /* virtual */ void
405 0 : nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData)
406 : {
407 0 : NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
408 : "pres context mismatch");
409 0 : if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before &&
410 : aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) {
411 0 : return;
412 : }
413 :
414 : // FIXME: Do we really want to be the only thing keeping a
415 : // pseudo-element alive? I *think* the non-animation restyle should
416 : // handle that, but should add a test.
417 0 : nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType);
418 0 : if (rule) {
419 0 : aData->mRuleWalker->Forward(rule);
420 : }
421 : }
422 :
423 : /* virtual */ void
424 0 : nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
425 : {
426 0 : }
427 :
428 : #ifdef MOZ_XUL
429 : /* virtual */ void
430 0 : nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
431 : {
432 0 : }
433 : #endif
434 :
435 : /* virtual */ size_t
436 0 : nsAnimationManager::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const
437 : {
438 0 : return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
439 :
440 : // Measurement of the following members may be added later if DMD finds it is
441 : // worthwhile:
442 : // - mKeyframesRules
443 : // - mPendingEvents
444 : }
445 :
446 : /* virtual */ size_t
447 0 : nsAnimationManager::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
448 : {
449 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
450 : }
451 :
452 : nsIStyleRule*
453 0 : nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
454 : mozilla::dom::Element* aElement)
455 : {
456 0 : if (!mPresContext->IsProcessingAnimationStyleChange()) {
457 : // Everything that causes our animation data to change triggers a
458 : // style change, which in turn triggers a non-animation restyle.
459 : // Likewise, when we initially construct frames, we're not in a
460 : // style change, but also not in an animation restyle.
461 :
462 0 : const nsStyleDisplay *disp = aStyleContext->GetStyleDisplay();
463 : ElementAnimations *ea =
464 0 : GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false);
465 0 : if (!ea &&
466 0 : disp->mAnimations.Length() == 1 &&
467 0 : disp->mAnimations[0].GetName().IsEmpty()) {
468 0 : return nsnull;
469 : }
470 :
471 : // build the animations list
472 0 : InfallibleTArray<ElementAnimation> newAnimations;
473 0 : BuildAnimations(aStyleContext, newAnimations);
474 :
475 0 : if (newAnimations.IsEmpty()) {
476 0 : if (ea) {
477 0 : ea->Destroy();
478 : }
479 0 : return nsnull;
480 : }
481 :
482 0 : TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
483 :
484 0 : if (ea) {
485 : // The cached style rule is invalid.
486 0 : ea->mStyleRule = nsnull;
487 0 : ea->mStyleRuleRefreshTime = TimeStamp();
488 :
489 : // Copy over the start times and (if still paused) pause starts
490 : // for each animation (matching on name only) that was also in the
491 : // old list of animations.
492 : // This means that we honor dynamic changes, which isn't what the
493 : // spec says to do, but WebKit seems to honor at least some of
494 : // them. See
495 : // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
496 : // In order to honor what the spec said, we'd copy more data over
497 : // (or potentially optimize BuildAnimations to avoid rebuilding it
498 : // in the first place).
499 0 : if (!ea->mAnimations.IsEmpty()) {
500 0 : for (PRUint32 newIdx = 0, newEnd = newAnimations.Length();
501 : newIdx != newEnd; ++newIdx) {
502 0 : ElementAnimation *newAnim = &newAnimations[newIdx];
503 :
504 : // Find the matching animation with this name in the old list
505 : // of animations. Because of this code, they must all have
506 : // the same start time, though they might differ in pause
507 : // state. So if a page uses multiple copies of the same
508 : // animation in one element's animation list, and gives them
509 : // different pause states, they, well, get what they deserve.
510 : // We'll use the last one since it's more likely to be the one
511 : // doing something.
512 0 : const ElementAnimation *oldAnim = nsnull;
513 0 : for (PRUint32 oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) {
514 0 : const ElementAnimation *a = &ea->mAnimations[oldIdx];
515 0 : if (a->mName == newAnim->mName) {
516 0 : oldAnim = a;
517 0 : break;
518 : }
519 : }
520 0 : if (!oldAnim) {
521 0 : continue;
522 : }
523 :
524 0 : newAnim->mStartTime = oldAnim->mStartTime;
525 0 : newAnim->mLastNotification = oldAnim->mLastNotification;
526 :
527 0 : if (oldAnim->IsPaused()) {
528 0 : if (newAnim->IsPaused()) {
529 : // Copy pause start just like start time.
530 0 : newAnim->mPauseStart = oldAnim->mPauseStart;
531 : } else {
532 : // Handle change in pause state by adjusting start
533 : // time to unpause.
534 0 : newAnim->mStartTime += refreshTime - oldAnim->mPauseStart;
535 : }
536 : }
537 : }
538 : }
539 : } else {
540 : ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(),
541 0 : true);
542 : }
543 0 : ea->mAnimations.SwapElements(newAnimations);
544 0 : ea->mNeedsRefreshes = true;
545 :
546 0 : ea->EnsureStyleRuleFor(refreshTime, mPendingEvents);
547 : // We don't actually dispatch the mPendingEvents now. We'll either
548 : // dispatch them the next time we get a refresh driver notification
549 : // or the next time somebody calls
550 : // nsPresShell::FlushPendingNotifications.
551 0 : if (!mPendingEvents.IsEmpty()) {
552 0 : mPresContext->Document()->SetNeedStyleFlush();
553 : }
554 : }
555 :
556 0 : return GetAnimationRule(aElement, aStyleContext->GetPseudoType());
557 : }
558 :
559 : class PercentageHashKey : public PLDHashEntryHdr
560 : {
561 : public:
562 : typedef const float& KeyType;
563 : typedef const float* KeyTypePointer;
564 :
565 0 : PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { }
566 : PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { }
567 0 : ~PercentageHashKey() { }
568 :
569 0 : KeyType GetKey() const { return mValue; }
570 0 : bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
571 :
572 0 : static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
573 0 : static PLDHashNumber HashKey(KeyTypePointer aKey) {
574 : MOZ_STATIC_ASSERT(sizeof(PLDHashNumber) == sizeof(PRUint32),
575 : "this hash function assumes PLDHashNumber is PRUint32");
576 : MOZ_STATIC_ASSERT(PLDHashNumber(-1) > PLDHashNumber(0),
577 : "this hash function assumes PLDHashNumber is PRUint32");
578 0 : float key = *aKey;
579 0 : NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range");
580 0 : return PLDHashNumber(key * PR_UINT32_MAX);
581 : }
582 : enum { ALLOW_MEMMOVE = true };
583 :
584 : private:
585 : const float mValue;
586 : };
587 :
588 0 : struct KeyframeData {
589 : float mKey;
590 : nsCSSKeyframeRule *mRule;
591 : };
592 :
593 : typedef InfallibleTArray<KeyframeData> KeyframeDataArray;
594 :
595 : static PLDHashOperator
596 0 : AppendKeyframeData(const float &aKey, nsCSSKeyframeRule *aRule, void *aData)
597 : {
598 0 : KeyframeDataArray *array = static_cast<KeyframeDataArray*>(aData);
599 0 : KeyframeData *data = array->AppendElement();
600 0 : data->mKey = aKey;
601 0 : data->mRule = aRule;
602 0 : return PL_DHASH_NEXT;
603 : }
604 :
605 : struct KeyframeDataComparator {
606 0 : bool Equals(const KeyframeData& A, const KeyframeData& B) const {
607 0 : return A.mKey == B.mKey;
608 : }
609 0 : bool LessThan(const KeyframeData& A, const KeyframeData& B) const {
610 0 : return A.mKey < B.mKey;
611 : }
612 : };
613 :
614 0 : class ResolvedStyleCache {
615 : public:
616 0 : ResolvedStyleCache() {
617 0 : mCache.Init(16); // FIXME: make infallible!
618 0 : }
619 : nsStyleContext* Get(nsPresContext *aPresContext,
620 : nsStyleContext *aParentStyleContext,
621 : nsCSSKeyframeRule *aKeyframe);
622 :
623 : private:
624 : nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache;
625 : };
626 :
627 : nsStyleContext*
628 0 : ResolvedStyleCache::Get(nsPresContext *aPresContext,
629 : nsStyleContext *aParentStyleContext,
630 : nsCSSKeyframeRule *aKeyframe)
631 : {
632 : // FIXME (spec): The css3-animations spec isn't very clear about how
633 : // properties are resolved when they have values that depend on other
634 : // properties (e.g., values in 'em'). I presume that they're resolved
635 : // relative to the other styles of the element. The question is
636 : // whether they are resolved relative to other animations: I assume
637 : // that they're not, since that would prevent us from caching a lot of
638 : // data that we'd really like to cache (in particular, the
639 : // nsStyleAnimation::Value values in AnimationPropertySegment).
640 0 : nsStyleContext *result = mCache.GetWeak(aKeyframe);
641 0 : if (!result) {
642 0 : nsCOMArray<nsIStyleRule> rules;
643 0 : rules.AppendObject(aKeyframe);
644 : nsRefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->
645 0 : ResolveStyleByAddingRules(aParentStyleContext, rules);
646 0 : mCache.Put(aKeyframe, resultStrong);
647 0 : result = resultStrong;
648 : }
649 0 : return result;
650 : }
651 :
652 : void
653 0 : nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext,
654 : InfallibleTArray<ElementAnimation>& aAnimations)
655 : {
656 0 : NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array");
657 :
658 0 : ResolvedStyleCache resolvedStyles;
659 :
660 0 : const nsStyleDisplay *disp = aStyleContext->GetStyleDisplay();
661 0 : TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
662 0 : for (PRUint32 animIdx = 0, animEnd = disp->mAnimations.Length();
663 : animIdx != animEnd; ++animIdx) {
664 0 : const nsAnimation& aSrc = disp->mAnimations[animIdx];
665 0 : ElementAnimation& aDest = *aAnimations.AppendElement();
666 :
667 0 : aDest.mName = aSrc.GetName();
668 0 : aDest.mIterationCount = aSrc.GetIterationCount();
669 0 : aDest.mDirection = aSrc.GetDirection();
670 0 : aDest.mFillMode = aSrc.GetFillMode();
671 0 : aDest.mPlayState = aSrc.GetPlayState();
672 :
673 0 : aDest.mStartTime = now + TimeDuration::FromMilliseconds(aSrc.GetDelay());
674 0 : if (aDest.IsPaused()) {
675 0 : aDest.mPauseStart = now;
676 : } else {
677 0 : aDest.mPauseStart = TimeStamp();
678 : }
679 :
680 0 : aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration());
681 :
682 0 : nsCSSKeyframesRule *rule = KeyframesRuleFor(aDest.mName);
683 0 : if (!rule) {
684 : // no segments
685 0 : continue;
686 : }
687 :
688 : // Build the set of unique keyframes in the @keyframes rule. Per
689 : // css3-animations, later keyframes with the same key replace
690 : // earlier ones (no cascading).
691 0 : nsDataHashtable<PercentageHashKey, nsCSSKeyframeRule*> keyframes;
692 0 : keyframes.Init(16); // FIXME: make infallible!
693 0 : for (PRUint32 ruleIdx = 0, ruleEnd = rule->StyleRuleCount();
694 : ruleIdx != ruleEnd; ++ruleIdx) {
695 0 : css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx);
696 0 : NS_ABORT_IF_FALSE(cssRule, "must have rule");
697 0 : NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
698 : "must be keyframe rule");
699 0 : nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule);
700 :
701 0 : const nsTArray<float> &keys = kfRule->GetKeys();
702 0 : for (PRUint32 keyIdx = 0, keyEnd = keys.Length();
703 : keyIdx != keyEnd; ++keyIdx) {
704 0 : float key = keys[keyIdx];
705 : // FIXME (spec): The spec doesn't say what to do with
706 : // out-of-range keyframes. We'll ignore them.
707 : // (And PercentageHashKey currently assumes we either ignore or
708 : // clamp them.)
709 0 : if (0.0f <= key && key <= 1.0f) {
710 0 : keyframes.Put(key, kfRule);
711 : }
712 : }
713 : }
714 :
715 0 : KeyframeDataArray sortedKeyframes;
716 0 : keyframes.EnumerateRead(AppendKeyframeData, &sortedKeyframes);
717 0 : sortedKeyframes.Sort(KeyframeDataComparator());
718 :
719 0 : if (sortedKeyframes.Length() == 0) {
720 : // no segments
721 0 : continue;
722 : }
723 :
724 : // Record the properties that are present in any keyframe rules we
725 : // are using.
726 0 : nsCSSPropertySet properties;
727 :
728 0 : for (PRUint32 kfIdx = 0, kfEnd = sortedKeyframes.Length();
729 : kfIdx != kfEnd; ++kfIdx) {
730 0 : css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration();
731 0 : for (PRUint32 propIdx = 0, propEnd = decl->Count();
732 : propIdx != propEnd; ++propIdx) {
733 0 : properties.AddProperty(decl->OrderValueAt(propIdx));
734 : }
735 : }
736 :
737 0 : for (nsCSSProperty prop = nsCSSProperty(0);
738 : prop < eCSSProperty_COUNT_no_shorthands;
739 : prop = nsCSSProperty(prop + 1)) {
740 0 : if (!properties.HasProperty(prop) ||
741 0 : nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
742 0 : continue;
743 : }
744 :
745 0 : AnimationProperty &propData = *aDest.mProperties.AppendElement();
746 0 : propData.mProperty = prop;
747 :
748 0 : KeyframeData *fromKeyframe = nsnull;
749 0 : nsRefPtr<nsStyleContext> fromContext;
750 0 : bool interpolated = true;
751 0 : for (PRUint32 kfIdx = 0, kfEnd = sortedKeyframes.Length();
752 : kfIdx != kfEnd; ++kfIdx) {
753 0 : KeyframeData &toKeyframe = sortedKeyframes[kfIdx];
754 0 : if (!toKeyframe.mRule->Declaration()->HasProperty(prop)) {
755 0 : continue;
756 : }
757 :
758 : nsRefPtr<nsStyleContext> toContext =
759 0 : resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule);
760 :
761 0 : if (fromKeyframe) {
762 : interpolated = interpolated &&
763 : BuildSegment(propData.mSegments, prop, aSrc,
764 : fromKeyframe->mKey, fromContext,
765 : fromKeyframe->mRule->Declaration(),
766 0 : toKeyframe.mKey, toContext);
767 : } else {
768 0 : if (toKeyframe.mKey != 0.0f) {
769 : // There's no data for this property at 0%, so use the
770 : // cascaded value above us.
771 : interpolated = interpolated &&
772 : BuildSegment(propData.mSegments, prop, aSrc,
773 : 0.0f, aStyleContext, nsnull,
774 0 : toKeyframe.mKey, toContext);
775 : }
776 : }
777 :
778 0 : fromContext = toContext;
779 0 : fromKeyframe = &toKeyframe;
780 : }
781 :
782 0 : if (fromKeyframe->mKey != 1.0f) {
783 : // There's no data for this property at 100%, so use the
784 : // cascaded value above us.
785 : interpolated = interpolated &&
786 : BuildSegment(propData.mSegments, prop, aSrc,
787 : fromKeyframe->mKey, fromContext,
788 : fromKeyframe->mRule->Declaration(),
789 0 : 1.0f, aStyleContext);
790 : }
791 :
792 : // If we failed to build any segments due to inability to
793 : // interpolate, remove the property from the animation. (It's not
794 : // clear if this is the right thing to do -- we could run some of
795 : // the segments, but it's really not clear whether we should skip
796 : // values (which?) or skip segments, so best to skip the whole
797 : // thing for now.)
798 0 : if (!interpolated) {
799 0 : aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1);
800 : }
801 : }
802 : }
803 0 : }
804 :
805 : bool
806 0 : nsAnimationManager::BuildSegment(InfallibleTArray<AnimationPropertySegment>&
807 : aSegments,
808 : nsCSSProperty aProperty,
809 : const nsAnimation& aAnimation,
810 : float aFromKey, nsStyleContext* aFromContext,
811 : mozilla::css::Declaration* aFromDeclaration,
812 : float aToKey, nsStyleContext* aToContext)
813 : {
814 0 : nsStyleAnimation::Value fromValue, toValue, dummyValue;
815 0 : if (!ExtractComputedValueForTransition(aProperty, aFromContext, fromValue) ||
816 0 : !ExtractComputedValueForTransition(aProperty, aToContext, toValue) ||
817 : // Check that we can interpolate between these values
818 : // (If this is ever a performance problem, we could add a
819 : // CanInterpolate method, but it seems fine for now.)
820 : !nsStyleAnimation::Interpolate(aProperty, fromValue, toValue,
821 0 : 0.5, dummyValue)) {
822 0 : return false;
823 : }
824 :
825 0 : AnimationPropertySegment &segment = *aSegments.AppendElement();
826 :
827 0 : segment.mFromValue = fromValue;
828 0 : segment.mToValue = toValue;
829 0 : segment.mFromKey = aFromKey;
830 0 : segment.mToKey = aToKey;
831 : const nsTimingFunction *tf;
832 0 : if (aFromDeclaration &&
833 0 : aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) {
834 0 : tf = &aFromContext->GetStyleDisplay()->mAnimations[0].GetTimingFunction();
835 : } else {
836 0 : tf = &aAnimation.GetTimingFunction();
837 : }
838 0 : segment.mTimingFunction.Init(*tf);
839 :
840 0 : return true;
841 : }
842 :
843 : nsIStyleRule*
844 0 : nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
845 : nsCSSPseudoElements::Type aPseudoType)
846 : {
847 0 : NS_ABORT_IF_FALSE(
848 : aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
849 : aPseudoType == nsCSSPseudoElements::ePseudo_before ||
850 : aPseudoType == nsCSSPseudoElements::ePseudo_after,
851 : "forbidden pseudo type");
852 :
853 : ElementAnimations *ea =
854 0 : GetElementAnimations(aElement, aPseudoType, false);
855 0 : if (!ea) {
856 0 : return nsnull;
857 : }
858 :
859 0 : NS_WARN_IF_FALSE(ea->mStyleRuleRefreshTime ==
860 : mPresContext->RefreshDriver()->MostRecentRefresh(),
861 : "should already have refreshed style rule");
862 :
863 0 : if (mPresContext->IsProcessingRestyles() &&
864 0 : !mPresContext->IsProcessingAnimationStyleChange()) {
865 : // During the non-animation part of processing restyles, we don't
866 : // add the animation rule.
867 :
868 0 : if (ea->mStyleRule) {
869 0 : ea->PostRestyleForAnimation(mPresContext);
870 : }
871 :
872 0 : return nsnull;
873 : }
874 :
875 0 : return ea->mStyleRule;
876 : }
877 :
878 : /* virtual */ void
879 0 : nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime)
880 : {
881 0 : NS_ABORT_IF_FALSE(mPresContext,
882 : "refresh driver should not notify additional observers "
883 : "after pres context has been destroyed");
884 0 : if (!mPresContext->GetPresShell()) {
885 : // Someone might be keeping mPresContext alive past the point
886 : // where it has been torn down; don't bother doing anything in
887 : // this case. But do get rid of all our transitions so we stop
888 : // triggering refreshes.
889 0 : RemoveAllElementData();
890 0 : return;
891 : }
892 :
893 : // FIXME: check that there's at least one style rule that's not
894 : // in its "done" state, and if there isn't, remove ourselves from
895 : // the refresh driver (but leave the animations!).
896 0 : for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
897 : l = PR_NEXT_LINK(l)) {
898 0 : ElementAnimations *ea = static_cast<ElementAnimations*>(l);
899 0 : nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
900 0 : ea->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(),
901 0 : mPendingEvents);
902 0 : if (oldStyleRule != ea->mStyleRule) {
903 0 : ea->PostRestyleForAnimation(mPresContext);
904 : }
905 : }
906 :
907 0 : DispatchEvents(); // may destroy us
908 : }
909 :
910 : void
911 0 : nsAnimationManager::DoDispatchEvents()
912 : {
913 0 : EventArray events;
914 0 : mPendingEvents.SwapElements(events);
915 0 : for (PRUint32 i = 0, i_end = events.Length(); i < i_end; ++i) {
916 0 : AnimationEventInfo &info = events[i];
917 0 : nsEventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
918 :
919 0 : if (!mPresContext) {
920 0 : break;
921 : }
922 : }
923 0 : }
924 :
925 : nsCSSKeyframesRule*
926 0 : nsAnimationManager::KeyframesRuleFor(const nsSubstring& aName)
927 : {
928 0 : if (mKeyframesListIsDirty) {
929 0 : mKeyframesListIsDirty = false;
930 :
931 0 : nsTArray<nsCSSKeyframesRule*> rules;
932 0 : mPresContext->StyleSet()->AppendKeyframesRules(mPresContext, rules);
933 :
934 : // Per css3-animations, the last @keyframes rule specified wins.
935 0 : mKeyframesRules.Clear();
936 0 : for (PRUint32 i = 0, i_end = rules.Length(); i != i_end; ++i) {
937 0 : nsCSSKeyframesRule *rule = rules[i];
938 0 : mKeyframesRules.Put(rule->GetName(), rule);
939 : }
940 : }
941 :
942 0 : return mKeyframesRules.Get(aName);
943 : }
944 :
|