| |
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 |