|
1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsAnimationManager.h" |
|
7 #include "nsTransitionManager.h" |
|
8 |
|
9 #include "mozilla/EventDispatcher.h" |
|
10 #include "mozilla/MemoryReporting.h" |
|
11 |
|
12 #include "nsPresContext.h" |
|
13 #include "nsRuleProcessorData.h" |
|
14 #include "nsStyleSet.h" |
|
15 #include "nsStyleChangeList.h" |
|
16 #include "nsCSSRules.h" |
|
17 #include "RestyleManager.h" |
|
18 #include "nsStyleAnimation.h" |
|
19 #include "nsLayoutUtils.h" |
|
20 #include "nsIFrame.h" |
|
21 #include "nsIDocument.h" |
|
22 #include "ActiveLayerTracker.h" |
|
23 #include <math.h> |
|
24 |
|
25 using namespace mozilla; |
|
26 using namespace mozilla::css; |
|
27 |
|
28 ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement, |
|
29 nsIAtom *aElementProperty, |
|
30 nsAnimationManager *aAnimationManager, |
|
31 TimeStamp aNow) |
|
32 : CommonElementAnimationData(aElement, aElementProperty, |
|
33 aAnimationManager, aNow), |
|
34 mNeedsRefreshes(true) |
|
35 { |
|
36 } |
|
37 |
|
38 static void |
|
39 ElementAnimationsPropertyDtor(void *aObject, |
|
40 nsIAtom *aPropertyName, |
|
41 void *aPropertyValue, |
|
42 void *aData) |
|
43 { |
|
44 ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue); |
|
45 #ifdef DEBUG |
|
46 NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice"); |
|
47 ea->mCalledPropertyDtor = true; |
|
48 #endif |
|
49 delete ea; |
|
50 } |
|
51 |
|
52 double |
|
53 ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration, |
|
54 TimeDuration aIterationDuration, |
|
55 double aIterationCount, |
|
56 uint32_t aDirection, |
|
57 StyleAnimation* aAnimation, |
|
58 ElementAnimations* aEa, |
|
59 EventArray* aEventsToDispatch) |
|
60 { |
|
61 MOZ_ASSERT(!aAnimation == !aEa && !aAnimation == !aEventsToDispatch); |
|
62 |
|
63 // Set |currentIterationCount| to the (fractional) number of |
|
64 // iterations we've completed up to the current position. |
|
65 double currentIterationCount = aElapsedDuration / aIterationDuration; |
|
66 bool dispatchStartOrIteration = false; |
|
67 if (currentIterationCount >= aIterationCount) { |
|
68 if (aAnimation) { |
|
69 // Dispatch 'animationend' when needed. |
|
70 if (aAnimation->mLastNotification != |
|
71 StyleAnimation::LAST_NOTIFICATION_END) { |
|
72 aAnimation->mLastNotification = StyleAnimation::LAST_NOTIFICATION_END; |
|
73 AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END, |
|
74 aElapsedDuration, aEa->PseudoElement()); |
|
75 aEventsToDispatch->AppendElement(ei); |
|
76 } |
|
77 |
|
78 if (!aAnimation->FillsForwards()) { |
|
79 // No animation data. |
|
80 return -1; |
|
81 } |
|
82 } else { |
|
83 // If aAnimation is null, that means we're on the compositor |
|
84 // thread. We want to just keep filling forwards until the main |
|
85 // thread gets around to updating the compositor thread (which |
|
86 // might take a little while). So just assume we fill fowards and |
|
87 // move on. |
|
88 } |
|
89 currentIterationCount = aIterationCount; |
|
90 } else { |
|
91 if (aAnimation && !aAnimation->IsPaused()) { |
|
92 aEa->mNeedsRefreshes = true; |
|
93 } |
|
94 if (currentIterationCount < 0.0) { |
|
95 NS_ASSERTION(aAnimation, "Should not run animation that hasn't started yet on the compositor"); |
|
96 if (!aAnimation->FillsBackwards()) { |
|
97 // No animation data. |
|
98 return -1; |
|
99 } |
|
100 currentIterationCount = 0.0; |
|
101 } else { |
|
102 dispatchStartOrIteration = aAnimation && !aAnimation->IsPaused(); |
|
103 } |
|
104 } |
|
105 |
|
106 // Set |positionInIteration| to the position from 0% to 100% along |
|
107 // the keyframes. |
|
108 NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive"); |
|
109 double whichIteration = floor(currentIterationCount); |
|
110 if (whichIteration == aIterationCount && whichIteration != 0.0) { |
|
111 // When the animation's iteration count is an integer (as it |
|
112 // normally is), we need to end at 100% of its last iteration |
|
113 // rather than 0% of the next one (unless it's zero). |
|
114 whichIteration -= 1.0; |
|
115 } |
|
116 double positionInIteration = currentIterationCount - whichIteration; |
|
117 |
|
118 bool thisIterationReverse = false; |
|
119 switch (aDirection) { |
|
120 case NS_STYLE_ANIMATION_DIRECTION_NORMAL: |
|
121 thisIterationReverse = false; |
|
122 break; |
|
123 case NS_STYLE_ANIMATION_DIRECTION_REVERSE: |
|
124 thisIterationReverse = true; |
|
125 break; |
|
126 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE: |
|
127 // uint64_t has more integer precision than double does, so if |
|
128 // whichIteration is that large, we've already lost and we're just |
|
129 // guessing. But the animation is presumably oscillating so fast |
|
130 // it doesn't matter anyway. |
|
131 thisIterationReverse = (uint64_t(whichIteration) & 1) == 1; |
|
132 break; |
|
133 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE: |
|
134 // see as previous case |
|
135 thisIterationReverse = (uint64_t(whichIteration) & 1) == 0; |
|
136 break; |
|
137 } |
|
138 if (thisIterationReverse) { |
|
139 positionInIteration = 1.0 - positionInIteration; |
|
140 } |
|
141 |
|
142 // Dispatch 'animationstart' or 'animationiteration' when needed. |
|
143 if (aAnimation && dispatchStartOrIteration && |
|
144 whichIteration != aAnimation->mLastNotification) { |
|
145 // Notify 'animationstart' even if a negative delay puts us |
|
146 // past the first iteration. |
|
147 // Note that when somebody changes the animation-duration |
|
148 // dynamically, this will fire an extra iteration event |
|
149 // immediately in many cases. It's not clear to me if that's the |
|
150 // right thing to do. |
|
151 uint32_t message = |
|
152 aAnimation->mLastNotification == StyleAnimation::LAST_NOTIFICATION_NONE |
|
153 ? NS_ANIMATION_START : NS_ANIMATION_ITERATION; |
|
154 |
|
155 aAnimation->mLastNotification = whichIteration; |
|
156 AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message, |
|
157 aElapsedDuration, aEa->PseudoElement()); |
|
158 aEventsToDispatch->AppendElement(ei); |
|
159 } |
|
160 |
|
161 return positionInIteration; |
|
162 } |
|
163 |
|
164 void |
|
165 ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime, |
|
166 EventArray& aEventsToDispatch, |
|
167 bool aIsThrottled) |
|
168 { |
|
169 if (!mNeedsRefreshes) { |
|
170 mStyleRuleRefreshTime = aRefreshTime; |
|
171 return; |
|
172 } |
|
173 |
|
174 // If we're performing animations on the compositor thread, then we can skip |
|
175 // most of the work in this method. But even if we are throttled, then we |
|
176 // have to do the work if an animation is ending in order to get correct end |
|
177 // of animation behaviour (the styles of the animation disappear, or the fill |
|
178 // mode behaviour). This loop checks for any finishing animations and forces |
|
179 // the style recalculation if we find any. |
|
180 if (aIsThrottled) { |
|
181 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { |
|
182 StyleAnimation &anim = mAnimations[animIdx]; |
|
183 |
|
184 if (anim.mProperties.Length() == 0 || |
|
185 anim.mIterationDuration.ToMilliseconds() <= 0.0) { |
|
186 continue; |
|
187 } |
|
188 |
|
189 uint32_t oldLastNotification = anim.mLastNotification; |
|
190 |
|
191 // We need to call GetPositionInIteration here to populate |
|
192 // aEventsToDispatch. |
|
193 // The ElapsedDurationAt() call here handles pausing. But: |
|
194 // FIXME: avoid recalculating every time when paused. |
|
195 GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime), |
|
196 anim.mIterationDuration, anim.mIterationCount, |
|
197 anim.mDirection, &anim, this, &aEventsToDispatch); |
|
198 |
|
199 // GetPositionInIteration just adjusted mLastNotification; check |
|
200 // its new value against the value before we called |
|
201 // GetPositionInIteration. |
|
202 // XXX We shouldn't really be using mLastNotification as a general |
|
203 // indicator that the animation has finished, it should be reserved for |
|
204 // events. If we use it differently in the future this use might need |
|
205 // changing. |
|
206 if (!anim.mIsRunningOnCompositor || |
|
207 (anim.mLastNotification != oldLastNotification && |
|
208 anim.mLastNotification == StyleAnimation::LAST_NOTIFICATION_END)) { |
|
209 aIsThrottled = false; |
|
210 break; |
|
211 } |
|
212 } |
|
213 } |
|
214 |
|
215 if (aIsThrottled) { |
|
216 return; |
|
217 } |
|
218 |
|
219 // mStyleRule may be null and valid, if we have no style to apply. |
|
220 if (mStyleRuleRefreshTime.IsNull() || |
|
221 mStyleRuleRefreshTime != aRefreshTime) { |
|
222 mStyleRuleRefreshTime = aRefreshTime; |
|
223 mStyleRule = nullptr; |
|
224 // We'll set mNeedsRefreshes to true below in all cases where we need them. |
|
225 mNeedsRefreshes = false; |
|
226 |
|
227 // FIXME(spec): assume that properties in higher animations override |
|
228 // those in lower ones. |
|
229 // Therefore, we iterate from last animation to first. |
|
230 nsCSSPropertySet properties; |
|
231 |
|
232 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { |
|
233 StyleAnimation &anim = mAnimations[animIdx]; |
|
234 |
|
235 if (anim.mProperties.Length() == 0 || |
|
236 anim.mIterationDuration.ToMilliseconds() <= 0.0) { |
|
237 // No animation data. |
|
238 continue; |
|
239 } |
|
240 |
|
241 // The ElapsedDurationAt() call here handles pausing. But: |
|
242 // FIXME: avoid recalculating every time when paused. |
|
243 double positionInIteration = |
|
244 GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime), |
|
245 anim.mIterationDuration, anim.mIterationCount, |
|
246 anim.mDirection, &anim, this, |
|
247 &aEventsToDispatch); |
|
248 |
|
249 // The position is -1 when we don't have fill data for the current time, |
|
250 // so we shouldn't animate. |
|
251 if (positionInIteration == -1) |
|
252 continue; |
|
253 |
|
254 NS_ABORT_IF_FALSE(0.0 <= positionInIteration && |
|
255 positionInIteration <= 1.0, |
|
256 "position should be in [0-1]"); |
|
257 |
|
258 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length(); |
|
259 propIdx != propEnd; ++propIdx) |
|
260 { |
|
261 const AnimationProperty &prop = anim.mProperties[propIdx]; |
|
262 |
|
263 NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0, |
|
264 "incorrect first from key"); |
|
265 NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey |
|
266 == 1.0, |
|
267 "incorrect last to key"); |
|
268 |
|
269 if (properties.HasProperty(prop.mProperty)) { |
|
270 // A later animation already set this property. |
|
271 continue; |
|
272 } |
|
273 properties.AddProperty(prop.mProperty); |
|
274 |
|
275 NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0, |
|
276 "property should not be in animations if it " |
|
277 "has no segments"); |
|
278 |
|
279 // FIXME: Maybe cache the current segment? |
|
280 const AnimationPropertySegment *segment = prop.mSegments.Elements(), |
|
281 *segmentEnd = segment + prop.mSegments.Length(); |
|
282 while (segment->mToKey < positionInIteration) { |
|
283 NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, |
|
284 "incorrect keys"); |
|
285 ++segment; |
|
286 if (segment == segmentEnd) { |
|
287 NS_ABORT_IF_FALSE(false, "incorrect positionInIteration"); |
|
288 break; // in order to continue in outer loop (just below) |
|
289 } |
|
290 NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey, |
|
291 "incorrect keys"); |
|
292 } |
|
293 if (segment == segmentEnd) { |
|
294 continue; |
|
295 } |
|
296 NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, |
|
297 "incorrect keys"); |
|
298 NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() && |
|
299 size_t(segment - prop.mSegments.Elements()) < |
|
300 prop.mSegments.Length(), |
|
301 "out of array bounds"); |
|
302 |
|
303 if (!mStyleRule) { |
|
304 // Allocate the style rule now that we know we have animation data. |
|
305 mStyleRule = new css::AnimValuesStyleRule(); |
|
306 } |
|
307 |
|
308 double positionInSegment = (positionInIteration - segment->mFromKey) / |
|
309 (segment->mToKey - segment->mFromKey); |
|
310 double valuePosition = |
|
311 segment->mTimingFunction.GetValue(positionInSegment); |
|
312 |
|
313 nsStyleAnimation::Value *val = |
|
314 mStyleRule->AddEmptyValue(prop.mProperty); |
|
315 |
|
316 #ifdef DEBUG |
|
317 bool result = |
|
318 #endif |
|
319 nsStyleAnimation::Interpolate(prop.mProperty, |
|
320 segment->mFromValue, segment->mToValue, |
|
321 valuePosition, *val); |
|
322 NS_ABORT_IF_FALSE(result, "interpolate must succeed now"); |
|
323 } |
|
324 } |
|
325 } |
|
326 } |
|
327 |
|
328 |
|
329 bool |
|
330 ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const |
|
331 { |
|
332 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { |
|
333 const StyleAnimation &anim = mAnimations[animIdx]; |
|
334 if (anim.HasAnimationOfProperty(aProperty)) { |
|
335 return true; |
|
336 } |
|
337 } |
|
338 return false; |
|
339 } |
|
340 |
|
341 bool |
|
342 ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const |
|
343 { |
|
344 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement); |
|
345 if (!frame) { |
|
346 return false; |
|
347 } |
|
348 |
|
349 if (mElementProperty != nsGkAtoms::animationsProperty) { |
|
350 if (nsLayoutUtils::IsAnimationLoggingEnabled()) { |
|
351 nsCString message; |
|
352 message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 ("); |
|
353 message.Append(nsAtomCString(mElementProperty)); |
|
354 message.AppendLiteral(")"); |
|
355 LogAsyncAnimationFailure(message, mElement); |
|
356 } |
|
357 return false; |
|
358 } |
|
359 |
|
360 TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh(); |
|
361 |
|
362 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { |
|
363 const StyleAnimation& anim = mAnimations[animIdx]; |
|
364 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length(); |
|
365 propIdx != propEnd; ++propIdx) { |
|
366 if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) && |
|
367 anim.IsRunningAt(now)) { |
|
368 aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty); |
|
369 break; |
|
370 } |
|
371 } |
|
372 } |
|
373 |
|
374 bool hasOpacity = false; |
|
375 bool hasTransform = false; |
|
376 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { |
|
377 const StyleAnimation& anim = mAnimations[animIdx]; |
|
378 if (!anim.IsRunningAt(now)) { |
|
379 continue; |
|
380 } |
|
381 |
|
382 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length(); |
|
383 propIdx != propEnd; ++propIdx) { |
|
384 const AnimationProperty& prop = anim.mProperties[propIdx]; |
|
385 if (!CanAnimatePropertyOnCompositor(mElement, |
|
386 prop.mProperty, |
|
387 aFlags) || |
|
388 IsCompositorAnimationDisabledForFrame(frame)) { |
|
389 return false; |
|
390 } |
|
391 if (prop.mProperty == eCSSProperty_opacity) { |
|
392 hasOpacity = true; |
|
393 } else if (prop.mProperty == eCSSProperty_transform) { |
|
394 hasTransform = true; |
|
395 } |
|
396 } |
|
397 } |
|
398 // This animation can be done on the compositor. Mark the frame as active, in |
|
399 // case we are able to throttle this animation. |
|
400 if (hasOpacity) { |
|
401 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity); |
|
402 } |
|
403 if (hasTransform) { |
|
404 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform); |
|
405 } |
|
406 return true; |
|
407 } |
|
408 |
|
409 ElementAnimations* |
|
410 nsAnimationManager::GetElementAnimations(dom::Element *aElement, |
|
411 nsCSSPseudoElements::Type aPseudoType, |
|
412 bool aCreateIfNeeded) |
|
413 { |
|
414 if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) { |
|
415 // Early return for the most common case. |
|
416 return nullptr; |
|
417 } |
|
418 |
|
419 nsIAtom *propName; |
|
420 if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) { |
|
421 propName = nsGkAtoms::animationsProperty; |
|
422 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) { |
|
423 propName = nsGkAtoms::animationsOfBeforeProperty; |
|
424 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) { |
|
425 propName = nsGkAtoms::animationsOfAfterProperty; |
|
426 } else { |
|
427 NS_ASSERTION(!aCreateIfNeeded, |
|
428 "should never try to create transitions for pseudo " |
|
429 "other than :before or :after"); |
|
430 return nullptr; |
|
431 } |
|
432 ElementAnimations *ea = static_cast<ElementAnimations*>( |
|
433 aElement->GetProperty(propName)); |
|
434 if (!ea && aCreateIfNeeded) { |
|
435 // FIXME: Consider arena-allocating? |
|
436 ea = new ElementAnimations(aElement, propName, this, |
|
437 mPresContext->RefreshDriver()->MostRecentRefresh()); |
|
438 nsresult rv = aElement->SetProperty(propName, ea, |
|
439 ElementAnimationsPropertyDtor, false); |
|
440 if (NS_FAILED(rv)) { |
|
441 NS_WARNING("SetProperty failed"); |
|
442 delete ea; |
|
443 return nullptr; |
|
444 } |
|
445 if (propName == nsGkAtoms::animationsProperty) { |
|
446 aElement->SetMayHaveAnimations(); |
|
447 } |
|
448 |
|
449 AddElementData(ea); |
|
450 } |
|
451 |
|
452 return ea; |
|
453 } |
|
454 |
|
455 |
|
456 void |
|
457 nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET) |
|
458 { |
|
459 aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(), |
|
460 mPendingEvents, |
|
461 false); |
|
462 CheckNeedsRefresh(); |
|
463 } |
|
464 |
|
465 /* virtual */ void |
|
466 nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData) |
|
467 { |
|
468 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, |
|
469 "pres context mismatch"); |
|
470 nsIStyleRule *rule = |
|
471 GetAnimationRule(aData->mElement, |
|
472 nsCSSPseudoElements::ePseudo_NotPseudoElement); |
|
473 if (rule) { |
|
474 aData->mRuleWalker->Forward(rule); |
|
475 } |
|
476 } |
|
477 |
|
478 /* virtual */ void |
|
479 nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData) |
|
480 { |
|
481 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, |
|
482 "pres context mismatch"); |
|
483 if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before && |
|
484 aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) { |
|
485 return; |
|
486 } |
|
487 |
|
488 // FIXME: Do we really want to be the only thing keeping a |
|
489 // pseudo-element alive? I *think* the non-animation restyle should |
|
490 // handle that, but should add a test. |
|
491 nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType); |
|
492 if (rule) { |
|
493 aData->mRuleWalker->Forward(rule); |
|
494 } |
|
495 } |
|
496 |
|
497 /* virtual */ void |
|
498 nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData) |
|
499 { |
|
500 } |
|
501 |
|
502 #ifdef MOZ_XUL |
|
503 /* virtual */ void |
|
504 nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData) |
|
505 { |
|
506 } |
|
507 #endif |
|
508 |
|
509 /* virtual */ size_t |
|
510 nsAnimationManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
511 { |
|
512 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf); |
|
513 |
|
514 // Measurement of the following members may be added later if DMD finds it is |
|
515 // worthwhile: |
|
516 // - mPendingEvents |
|
517 } |
|
518 |
|
519 /* virtual */ size_t |
|
520 nsAnimationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
521 { |
|
522 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
523 } |
|
524 |
|
525 nsIStyleRule* |
|
526 nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, |
|
527 mozilla::dom::Element* aElement) |
|
528 { |
|
529 if (!mPresContext->IsProcessingAnimationStyleChange()) { |
|
530 if (!mPresContext->IsDynamic()) { |
|
531 // For print or print preview, ignore animations. |
|
532 return nullptr; |
|
533 } |
|
534 |
|
535 // Everything that causes our animation data to change triggers a |
|
536 // style change, which in turn triggers a non-animation restyle. |
|
537 // Likewise, when we initially construct frames, we're not in a |
|
538 // style change, but also not in an animation restyle. |
|
539 |
|
540 const nsStyleDisplay *disp = aStyleContext->StyleDisplay(); |
|
541 ElementAnimations *ea = |
|
542 GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false); |
|
543 if (!ea && |
|
544 disp->mAnimationNameCount == 1 && |
|
545 disp->mAnimations[0].GetName().IsEmpty()) { |
|
546 return nullptr; |
|
547 } |
|
548 |
|
549 // build the animations list |
|
550 InfallibleTArray<StyleAnimation> newAnimations; |
|
551 BuildAnimations(aStyleContext, newAnimations); |
|
552 |
|
553 if (newAnimations.IsEmpty()) { |
|
554 if (ea) { |
|
555 ea->Destroy(); |
|
556 } |
|
557 return nullptr; |
|
558 } |
|
559 |
|
560 TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); |
|
561 |
|
562 if (ea) { |
|
563 ea->mStyleRule = nullptr; |
|
564 ea->mStyleRuleRefreshTime = TimeStamp(); |
|
565 ea->UpdateAnimationGeneration(mPresContext); |
|
566 |
|
567 // Copy over the start times and (if still paused) pause starts |
|
568 // for each animation (matching on name only) that was also in the |
|
569 // old list of animations. |
|
570 // This means that we honor dynamic changes, which isn't what the |
|
571 // spec says to do, but WebKit seems to honor at least some of |
|
572 // them. See |
|
573 // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html |
|
574 // In order to honor what the spec said, we'd copy more data over |
|
575 // (or potentially optimize BuildAnimations to avoid rebuilding it |
|
576 // in the first place). |
|
577 if (!ea->mAnimations.IsEmpty()) { |
|
578 for (uint32_t newIdx = 0, newEnd = newAnimations.Length(); |
|
579 newIdx != newEnd; ++newIdx) { |
|
580 StyleAnimation *newAnim = &newAnimations[newIdx]; |
|
581 |
|
582 // Find the matching animation with this name in the old list |
|
583 // of animations. Because of this code, they must all have |
|
584 // the same start time, though they might differ in pause |
|
585 // state. So if a page uses multiple copies of the same |
|
586 // animation in one element's animation list, and gives them |
|
587 // different pause states, they, well, get what they deserve. |
|
588 // We'll use the last one since it's more likely to be the one |
|
589 // doing something. |
|
590 const StyleAnimation *oldAnim = nullptr; |
|
591 for (uint32_t oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) { |
|
592 const StyleAnimation *a = &ea->mAnimations[oldIdx]; |
|
593 if (a->mName == newAnim->mName) { |
|
594 oldAnim = a; |
|
595 break; |
|
596 } |
|
597 } |
|
598 if (!oldAnim) { |
|
599 continue; |
|
600 } |
|
601 |
|
602 newAnim->mStartTime = oldAnim->mStartTime; |
|
603 newAnim->mLastNotification = oldAnim->mLastNotification; |
|
604 |
|
605 if (oldAnim->IsPaused()) { |
|
606 if (newAnim->IsPaused()) { |
|
607 // Copy pause start just like start time. |
|
608 newAnim->mPauseStart = oldAnim->mPauseStart; |
|
609 } else { |
|
610 // Handle change in pause state by adjusting start |
|
611 // time to unpause. |
|
612 newAnim->mStartTime += refreshTime - oldAnim->mPauseStart; |
|
613 } |
|
614 } |
|
615 } |
|
616 } |
|
617 } else { |
|
618 ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(), |
|
619 true); |
|
620 } |
|
621 ea->mAnimations.SwapElements(newAnimations); |
|
622 ea->mNeedsRefreshes = true; |
|
623 |
|
624 ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false); |
|
625 CheckNeedsRefresh(); |
|
626 // We don't actually dispatch the mPendingEvents now. We'll either |
|
627 // dispatch them the next time we get a refresh driver notification |
|
628 // or the next time somebody calls |
|
629 // nsPresShell::FlushPendingNotifications. |
|
630 if (!mPendingEvents.IsEmpty()) { |
|
631 mPresContext->Document()->SetNeedStyleFlush(); |
|
632 } |
|
633 } |
|
634 |
|
635 return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); |
|
636 } |
|
637 |
|
638 class PercentageHashKey : public PLDHashEntryHdr |
|
639 { |
|
640 public: |
|
641 typedef const float& KeyType; |
|
642 typedef const float* KeyTypePointer; |
|
643 |
|
644 PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { } |
|
645 PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { } |
|
646 ~PercentageHashKey() { } |
|
647 |
|
648 KeyType GetKey() const { return mValue; } |
|
649 bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } |
|
650 |
|
651 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } |
|
652 static PLDHashNumber HashKey(KeyTypePointer aKey) { |
|
653 static_assert(sizeof(PLDHashNumber) == sizeof(uint32_t), |
|
654 "this hash function assumes PLDHashNumber is uint32_t"); |
|
655 static_assert(PLDHashNumber(-1) > PLDHashNumber(0), |
|
656 "this hash function assumes PLDHashNumber is uint32_t"); |
|
657 float key = *aKey; |
|
658 NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range"); |
|
659 return PLDHashNumber(key * UINT32_MAX); |
|
660 } |
|
661 enum { ALLOW_MEMMOVE = true }; |
|
662 |
|
663 private: |
|
664 const float mValue; |
|
665 }; |
|
666 |
|
667 struct KeyframeData { |
|
668 float mKey; |
|
669 uint32_t mIndex; // store original order since sort algorithm is not stable |
|
670 nsCSSKeyframeRule *mRule; |
|
671 }; |
|
672 |
|
673 struct KeyframeDataComparator { |
|
674 bool Equals(const KeyframeData& A, const KeyframeData& B) const { |
|
675 return A.mKey == B.mKey && A.mIndex == B.mIndex; |
|
676 } |
|
677 bool LessThan(const KeyframeData& A, const KeyframeData& B) const { |
|
678 return A.mKey < B.mKey || (A.mKey == B.mKey && A.mIndex < B.mIndex); |
|
679 } |
|
680 }; |
|
681 |
|
682 class ResolvedStyleCache { |
|
683 public: |
|
684 ResolvedStyleCache() : mCache(16) {} |
|
685 nsStyleContext* Get(nsPresContext *aPresContext, |
|
686 nsStyleContext *aParentStyleContext, |
|
687 nsCSSKeyframeRule *aKeyframe); |
|
688 |
|
689 private: |
|
690 nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache; |
|
691 }; |
|
692 |
|
693 nsStyleContext* |
|
694 ResolvedStyleCache::Get(nsPresContext *aPresContext, |
|
695 nsStyleContext *aParentStyleContext, |
|
696 nsCSSKeyframeRule *aKeyframe) |
|
697 { |
|
698 // FIXME (spec): The css3-animations spec isn't very clear about how |
|
699 // properties are resolved when they have values that depend on other |
|
700 // properties (e.g., values in 'em'). I presume that they're resolved |
|
701 // relative to the other styles of the element. The question is |
|
702 // whether they are resolved relative to other animations: I assume |
|
703 // that they're not, since that would prevent us from caching a lot of |
|
704 // data that we'd really like to cache (in particular, the |
|
705 // nsStyleAnimation::Value values in AnimationPropertySegment). |
|
706 nsStyleContext *result = mCache.GetWeak(aKeyframe); |
|
707 if (!result) { |
|
708 nsCOMArray<nsIStyleRule> rules; |
|
709 rules.AppendObject(aKeyframe); |
|
710 nsRefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()-> |
|
711 ResolveStyleByAddingRules(aParentStyleContext, rules); |
|
712 mCache.Put(aKeyframe, resultStrong); |
|
713 result = resultStrong; |
|
714 } |
|
715 return result; |
|
716 } |
|
717 |
|
718 void |
|
719 nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext, |
|
720 InfallibleTArray<StyleAnimation>& |
|
721 aAnimations) |
|
722 { |
|
723 NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array"); |
|
724 |
|
725 ResolvedStyleCache resolvedStyles; |
|
726 |
|
727 const nsStyleDisplay *disp = aStyleContext->StyleDisplay(); |
|
728 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); |
|
729 for (uint32_t animIdx = 0, animEnd = disp->mAnimationNameCount; |
|
730 animIdx != animEnd; ++animIdx) { |
|
731 const nsAnimation& aSrc = disp->mAnimations[animIdx]; |
|
732 StyleAnimation& aDest = *aAnimations.AppendElement(); |
|
733 |
|
734 aDest.mName = aSrc.GetName(); |
|
735 aDest.mIterationCount = aSrc.GetIterationCount(); |
|
736 aDest.mDirection = aSrc.GetDirection(); |
|
737 aDest.mFillMode = aSrc.GetFillMode(); |
|
738 aDest.mPlayState = aSrc.GetPlayState(); |
|
739 |
|
740 aDest.mDelay = TimeDuration::FromMilliseconds(aSrc.GetDelay()); |
|
741 aDest.mStartTime = now; |
|
742 if (aDest.IsPaused()) { |
|
743 aDest.mPauseStart = now; |
|
744 } else { |
|
745 aDest.mPauseStart = TimeStamp(); |
|
746 } |
|
747 |
|
748 aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration()); |
|
749 |
|
750 nsCSSKeyframesRule* rule = |
|
751 mPresContext->StyleSet()->KeyframesRuleForName(mPresContext, aDest.mName); |
|
752 if (!rule) { |
|
753 // no segments |
|
754 continue; |
|
755 } |
|
756 |
|
757 // While current drafts of css3-animations say that later keyframes |
|
758 // with the same key entirely replace earlier ones (no cascading), |
|
759 // this is a bad idea and contradictory to the rest of CSS. So |
|
760 // we're going to keep all the keyframes for each key and then do |
|
761 // the replacement on a per-property basis rather than a per-rule |
|
762 // basis, just like everything else in CSS. |
|
763 |
|
764 AutoInfallibleTArray<KeyframeData, 16> sortedKeyframes; |
|
765 |
|
766 for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount(); |
|
767 ruleIdx != ruleEnd; ++ruleIdx) { |
|
768 css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx); |
|
769 NS_ABORT_IF_FALSE(cssRule, "must have rule"); |
|
770 NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE, |
|
771 "must be keyframe rule"); |
|
772 nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule); |
|
773 |
|
774 const nsTArray<float> &keys = kfRule->GetKeys(); |
|
775 for (uint32_t keyIdx = 0, keyEnd = keys.Length(); |
|
776 keyIdx != keyEnd; ++keyIdx) { |
|
777 float key = keys[keyIdx]; |
|
778 // FIXME (spec): The spec doesn't say what to do with |
|
779 // out-of-range keyframes. We'll ignore them. |
|
780 // (And PercentageHashKey currently assumes we either ignore or |
|
781 // clamp them.) |
|
782 if (0.0f <= key && key <= 1.0f) { |
|
783 KeyframeData *data = sortedKeyframes.AppendElement(); |
|
784 data->mKey = key; |
|
785 data->mIndex = ruleIdx; |
|
786 data->mRule = kfRule; |
|
787 } |
|
788 } |
|
789 } |
|
790 |
|
791 sortedKeyframes.Sort(KeyframeDataComparator()); |
|
792 |
|
793 if (sortedKeyframes.Length() == 0) { |
|
794 // no segments |
|
795 continue; |
|
796 } |
|
797 |
|
798 // Record the properties that are present in any keyframe rules we |
|
799 // are using. |
|
800 nsCSSPropertySet properties; |
|
801 |
|
802 for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); |
|
803 kfIdx != kfEnd; ++kfIdx) { |
|
804 css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration(); |
|
805 for (uint32_t propIdx = 0, propEnd = decl->Count(); |
|
806 propIdx != propEnd; ++propIdx) { |
|
807 nsCSSProperty prop = decl->GetPropertyAt(propIdx); |
|
808 if (prop != eCSSPropertyExtra_variable) { |
|
809 // CSS Variables are not animatable |
|
810 properties.AddProperty(prop); |
|
811 } |
|
812 } |
|
813 } |
|
814 |
|
815 for (nsCSSProperty prop = nsCSSProperty(0); |
|
816 prop < eCSSProperty_COUNT_no_shorthands; |
|
817 prop = nsCSSProperty(prop + 1)) { |
|
818 if (!properties.HasProperty(prop) || |
|
819 nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) { |
|
820 continue; |
|
821 } |
|
822 |
|
823 // Build a list of the keyframes to use for this property. This |
|
824 // means we need every keyframe with the property in it, except |
|
825 // for those keyframes where a later keyframe with the *same key* |
|
826 // also has the property. |
|
827 AutoInfallibleTArray<uint32_t, 16> keyframesWithProperty; |
|
828 float lastKey = 100.0f; // an invalid key |
|
829 for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); |
|
830 kfIdx != kfEnd; ++kfIdx) { |
|
831 KeyframeData &kf = sortedKeyframes[kfIdx]; |
|
832 if (!kf.mRule->Declaration()->HasProperty(prop)) { |
|
833 continue; |
|
834 } |
|
835 if (kf.mKey == lastKey) { |
|
836 // Replace previous occurrence of same key. |
|
837 keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx; |
|
838 } else { |
|
839 keyframesWithProperty.AppendElement(kfIdx); |
|
840 } |
|
841 lastKey = kf.mKey; |
|
842 } |
|
843 |
|
844 AnimationProperty &propData = *aDest.mProperties.AppendElement(); |
|
845 propData.mProperty = prop; |
|
846 |
|
847 KeyframeData *fromKeyframe = nullptr; |
|
848 nsRefPtr<nsStyleContext> fromContext; |
|
849 bool interpolated = true; |
|
850 for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length(); |
|
851 wpIdx != wpEnd; ++wpIdx) { |
|
852 uint32_t kfIdx = keyframesWithProperty[wpIdx]; |
|
853 KeyframeData &toKeyframe = sortedKeyframes[kfIdx]; |
|
854 |
|
855 nsRefPtr<nsStyleContext> toContext = |
|
856 resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule); |
|
857 |
|
858 if (fromKeyframe) { |
|
859 interpolated = interpolated && |
|
860 BuildSegment(propData.mSegments, prop, aSrc, |
|
861 fromKeyframe->mKey, fromContext, |
|
862 fromKeyframe->mRule->Declaration(), |
|
863 toKeyframe.mKey, toContext); |
|
864 } else { |
|
865 if (toKeyframe.mKey != 0.0f) { |
|
866 // There's no data for this property at 0%, so use the |
|
867 // cascaded value above us. |
|
868 interpolated = interpolated && |
|
869 BuildSegment(propData.mSegments, prop, aSrc, |
|
870 0.0f, aStyleContext, nullptr, |
|
871 toKeyframe.mKey, toContext); |
|
872 } |
|
873 } |
|
874 |
|
875 fromContext = toContext; |
|
876 fromKeyframe = &toKeyframe; |
|
877 } |
|
878 |
|
879 if (fromKeyframe->mKey != 1.0f) { |
|
880 // There's no data for this property at 100%, so use the |
|
881 // cascaded value above us. |
|
882 interpolated = interpolated && |
|
883 BuildSegment(propData.mSegments, prop, aSrc, |
|
884 fromKeyframe->mKey, fromContext, |
|
885 fromKeyframe->mRule->Declaration(), |
|
886 1.0f, aStyleContext); |
|
887 } |
|
888 |
|
889 // If we failed to build any segments due to inability to |
|
890 // interpolate, remove the property from the animation. (It's not |
|
891 // clear if this is the right thing to do -- we could run some of |
|
892 // the segments, but it's really not clear whether we should skip |
|
893 // values (which?) or skip segments, so best to skip the whole |
|
894 // thing for now.) |
|
895 if (!interpolated) { |
|
896 aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1); |
|
897 } |
|
898 } |
|
899 } |
|
900 } |
|
901 |
|
902 bool |
|
903 nsAnimationManager::BuildSegment(InfallibleTArray<AnimationPropertySegment>& |
|
904 aSegments, |
|
905 nsCSSProperty aProperty, |
|
906 const nsAnimation& aAnimation, |
|
907 float aFromKey, nsStyleContext* aFromContext, |
|
908 mozilla::css::Declaration* aFromDeclaration, |
|
909 float aToKey, nsStyleContext* aToContext) |
|
910 { |
|
911 nsStyleAnimation::Value fromValue, toValue, dummyValue; |
|
912 if (!ExtractComputedValueForTransition(aProperty, aFromContext, fromValue) || |
|
913 !ExtractComputedValueForTransition(aProperty, aToContext, toValue) || |
|
914 // Check that we can interpolate between these values |
|
915 // (If this is ever a performance problem, we could add a |
|
916 // CanInterpolate method, but it seems fine for now.) |
|
917 !nsStyleAnimation::Interpolate(aProperty, fromValue, toValue, |
|
918 0.5, dummyValue)) { |
|
919 return false; |
|
920 } |
|
921 |
|
922 AnimationPropertySegment &segment = *aSegments.AppendElement(); |
|
923 |
|
924 segment.mFromValue = fromValue; |
|
925 segment.mToValue = toValue; |
|
926 segment.mFromKey = aFromKey; |
|
927 segment.mToKey = aToKey; |
|
928 const nsTimingFunction *tf; |
|
929 if (aFromDeclaration && |
|
930 aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) { |
|
931 tf = &aFromContext->StyleDisplay()->mAnimations[0].GetTimingFunction(); |
|
932 } else { |
|
933 tf = &aAnimation.GetTimingFunction(); |
|
934 } |
|
935 segment.mTimingFunction.Init(*tf); |
|
936 |
|
937 return true; |
|
938 } |
|
939 |
|
940 nsIStyleRule* |
|
941 nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, |
|
942 nsCSSPseudoElements::Type aPseudoType) |
|
943 { |
|
944 NS_ABORT_IF_FALSE( |
|
945 aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement || |
|
946 aPseudoType == nsCSSPseudoElements::ePseudo_before || |
|
947 aPseudoType == nsCSSPseudoElements::ePseudo_after, |
|
948 "forbidden pseudo type"); |
|
949 |
|
950 if (!mPresContext->IsDynamic()) { |
|
951 // For print or print preview, ignore animations. |
|
952 return nullptr; |
|
953 } |
|
954 |
|
955 ElementAnimations *ea = |
|
956 GetElementAnimations(aElement, aPseudoType, false); |
|
957 if (!ea) { |
|
958 return nullptr; |
|
959 } |
|
960 |
|
961 if (mPresContext->IsProcessingRestyles() && |
|
962 !mPresContext->IsProcessingAnimationStyleChange()) { |
|
963 // During the non-animation part of processing restyles, we don't |
|
964 // add the animation rule. |
|
965 |
|
966 if (ea->mStyleRule) { |
|
967 ea->PostRestyleForAnimation(mPresContext); |
|
968 } |
|
969 |
|
970 return nullptr; |
|
971 } |
|
972 |
|
973 NS_WARN_IF_FALSE(!ea->mNeedsRefreshes || |
|
974 ea->mStyleRuleRefreshTime == |
|
975 mPresContext->RefreshDriver()->MostRecentRefresh(), |
|
976 "should already have refreshed style rule"); |
|
977 |
|
978 return ea->mStyleRule; |
|
979 } |
|
980 |
|
981 /* virtual */ void |
|
982 nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime) |
|
983 { |
|
984 NS_ABORT_IF_FALSE(mPresContext, |
|
985 "refresh driver should not notify additional observers " |
|
986 "after pres context has been destroyed"); |
|
987 if (!mPresContext->GetPresShell()) { |
|
988 // Someone might be keeping mPresContext alive past the point |
|
989 // where it has been torn down; don't bother doing anything in |
|
990 // this case. But do get rid of all our transitions so we stop |
|
991 // triggering refreshes. |
|
992 RemoveAllElementData(); |
|
993 return; |
|
994 } |
|
995 |
|
996 FlushAnimations(Can_Throttle); |
|
997 } |
|
998 |
|
999 void |
|
1000 nsAnimationManager::AddElementData(CommonElementAnimationData* aData) |
|
1001 { |
|
1002 if (!mObservingRefreshDriver) { |
|
1003 NS_ASSERTION(static_cast<ElementAnimations*>(aData)->mNeedsRefreshes, |
|
1004 "Added data which doesn't need refreshing?"); |
|
1005 // We need to observe the refresh driver. |
|
1006 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style); |
|
1007 mObservingRefreshDriver = true; |
|
1008 } |
|
1009 |
|
1010 PR_INSERT_BEFORE(aData, &mElementData); |
|
1011 } |
|
1012 |
|
1013 void |
|
1014 nsAnimationManager::CheckNeedsRefresh() |
|
1015 { |
|
1016 for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; |
|
1017 l = PR_NEXT_LINK(l)) { |
|
1018 if (static_cast<ElementAnimations*>(l)->mNeedsRefreshes) { |
|
1019 if (!mObservingRefreshDriver) { |
|
1020 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style); |
|
1021 mObservingRefreshDriver = true; |
|
1022 } |
|
1023 return; |
|
1024 } |
|
1025 } |
|
1026 if (mObservingRefreshDriver) { |
|
1027 mObservingRefreshDriver = false; |
|
1028 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style); |
|
1029 } |
|
1030 } |
|
1031 |
|
1032 void |
|
1033 nsAnimationManager::FlushAnimations(FlushFlags aFlags) |
|
1034 { |
|
1035 // FIXME: check that there's at least one style rule that's not |
|
1036 // in its "done" state, and if there isn't, remove ourselves from |
|
1037 // the refresh driver (but leave the animations!). |
|
1038 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); |
|
1039 bool didThrottle = false; |
|
1040 for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; |
|
1041 l = PR_NEXT_LINK(l)) { |
|
1042 ElementAnimations *ea = static_cast<ElementAnimations*>(l); |
|
1043 bool canThrottleTick = aFlags == Can_Throttle && |
|
1044 ea->CanPerformOnCompositorThread( |
|
1045 CommonElementAnimationData::CanAnimateFlags(0)) && |
|
1046 ea->CanThrottleAnimation(now); |
|
1047 |
|
1048 nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule; |
|
1049 ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick); |
|
1050 CheckNeedsRefresh(); |
|
1051 if (oldStyleRule != ea->mStyleRule) { |
|
1052 ea->PostRestyleForAnimation(mPresContext); |
|
1053 } else { |
|
1054 didThrottle = true; |
|
1055 } |
|
1056 } |
|
1057 |
|
1058 if (didThrottle) { |
|
1059 mPresContext->Document()->SetNeedStyleFlush(); |
|
1060 } |
|
1061 |
|
1062 DispatchEvents(); // may destroy us |
|
1063 } |
|
1064 |
|
1065 void |
|
1066 nsAnimationManager::DoDispatchEvents() |
|
1067 { |
|
1068 EventArray events; |
|
1069 mPendingEvents.SwapElements(events); |
|
1070 for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) { |
|
1071 AnimationEventInfo &info = events[i]; |
|
1072 EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent); |
|
1073 |
|
1074 if (!mPresContext) { |
|
1075 break; |
|
1076 } |
|
1077 } |
|
1078 } |
|
1079 |
|
1080 void |
|
1081 nsAnimationManager::UpdateThrottledStylesForSubtree(nsIContent* aContent, |
|
1082 nsStyleContext* aParentStyle, |
|
1083 nsStyleChangeList& aChangeList) |
|
1084 { |
|
1085 dom::Element* element; |
|
1086 if (aContent->IsElement()) { |
|
1087 element = aContent->AsElement(); |
|
1088 } else { |
|
1089 element = nullptr; |
|
1090 } |
|
1091 |
|
1092 nsRefPtr<nsStyleContext> newStyle; |
|
1093 |
|
1094 ElementAnimations* ea; |
|
1095 if (element && |
|
1096 (ea = GetElementAnimations(element, |
|
1097 nsCSSPseudoElements::ePseudo_NotPseudoElement, |
|
1098 false))) { |
|
1099 // re-resolve our style |
|
1100 newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList); |
|
1101 // remove the current transition from the working set |
|
1102 ea->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh(); |
|
1103 } else { |
|
1104 newStyle = ReparentContent(aContent, aParentStyle); |
|
1105 } |
|
1106 |
|
1107 // walk the children |
|
1108 if (newStyle) { |
|
1109 for (nsIContent *child = aContent->GetFirstChild(); child; |
|
1110 child = child->GetNextSibling()) { |
|
1111 UpdateThrottledStylesForSubtree(child, newStyle, aChangeList); |
|
1112 } |
|
1113 } |
|
1114 } |
|
1115 |
|
1116 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsAnimationManager, |
|
1117 GetElementAnimations) |
|
1118 |
|
1119 void |
|
1120 nsAnimationManager::UpdateAllThrottledStyles() |
|
1121 { |
|
1122 if (PR_CLIST_IS_EMPTY(&mElementData)) { |
|
1123 // no throttled animations, leave early |
|
1124 mPresContext->TickLastUpdateThrottledAnimationStyle(); |
|
1125 return; |
|
1126 } |
|
1127 |
|
1128 if (mPresContext->ThrottledAnimationStyleIsUpToDate()) { |
|
1129 // throttled transitions are up to date, leave early |
|
1130 return; |
|
1131 } |
|
1132 |
|
1133 mPresContext->TickLastUpdateThrottledAnimationStyle(); |
|
1134 |
|
1135 UpdateAllThrottledStylesInternal(); |
|
1136 } |
|
1137 |