|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* Code to start and animate CSS transitions. */ |
|
8 |
|
9 #include "nsTransitionManager.h" |
|
10 #include "nsAnimationManager.h" |
|
11 #include "nsIContent.h" |
|
12 #include "nsStyleContext.h" |
|
13 #include "nsCSSProps.h" |
|
14 #include "mozilla/MemoryReporting.h" |
|
15 #include "mozilla/TimeStamp.h" |
|
16 #include "nsRefreshDriver.h" |
|
17 #include "nsRuleProcessorData.h" |
|
18 #include "nsRuleWalker.h" |
|
19 #include "nsCSSPropertySet.h" |
|
20 #include "nsStyleAnimation.h" |
|
21 #include "mozilla/EventDispatcher.h" |
|
22 #include "mozilla/ContentEvents.h" |
|
23 #include "mozilla/dom/Element.h" |
|
24 #include "nsIFrame.h" |
|
25 #include "Layers.h" |
|
26 #include "FrameLayerBuilder.h" |
|
27 #include "nsDisplayList.h" |
|
28 #include "nsStyleChangeList.h" |
|
29 #include "nsStyleSet.h" |
|
30 #include "RestyleManager.h" |
|
31 #include "ActiveLayerTracker.h" |
|
32 |
|
33 using mozilla::TimeStamp; |
|
34 using mozilla::TimeDuration; |
|
35 |
|
36 using namespace mozilla; |
|
37 using namespace mozilla::layers; |
|
38 using namespace mozilla::css; |
|
39 |
|
40 ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement, |
|
41 nsIAtom *aElementProperty, |
|
42 nsTransitionManager *aTransitionManager, |
|
43 TimeStamp aNow) |
|
44 : CommonElementAnimationData(aElement, aElementProperty, |
|
45 aTransitionManager, aNow) |
|
46 { |
|
47 } |
|
48 |
|
49 double |
|
50 ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const |
|
51 { |
|
52 // Set |timePortion| to the portion of the way we are through the time |
|
53 // input to the transition's timing function (always within the range |
|
54 // 0-1). |
|
55 double duration = mIterationDuration.ToSeconds(); |
|
56 NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden"); |
|
57 double timePortion; |
|
58 if (IsRemovedSentinel()) { |
|
59 // The transition is being removed, but we still want an update so that any |
|
60 // new transitions start in the right place. |
|
61 timePortion = 1.0; |
|
62 } else if (duration == 0.0) { |
|
63 // When duration is zero, we can still have a transition when delay |
|
64 // is nonzero. |
|
65 if (aRefreshTime >= mStartTime + mDelay) { |
|
66 timePortion = 1.0; |
|
67 } else { |
|
68 timePortion = 0.0; |
|
69 } |
|
70 } else { |
|
71 timePortion = (aRefreshTime - (mStartTime + mDelay)).ToSeconds() / duration; |
|
72 if (timePortion < 0.0) |
|
73 timePortion = 0.0; // use start value during transition-delay |
|
74 if (timePortion > 1.0) |
|
75 timePortion = 1.0; // we might be behind on flushing |
|
76 } |
|
77 MOZ_ASSERT(mProperties.Length() == 1, |
|
78 "Should have one animation property for a transition"); |
|
79 MOZ_ASSERT(mProperties[0].mSegments.Length() == 1, |
|
80 "Animation property should have one segment for a transition"); |
|
81 |
|
82 return mProperties[0].mSegments[0].mTimingFunction.GetValue(timePortion); |
|
83 } |
|
84 |
|
85 static void |
|
86 ElementTransitionsPropertyDtor(void *aObject, |
|
87 nsIAtom *aPropertyName, |
|
88 void *aPropertyValue, |
|
89 void *aData) |
|
90 { |
|
91 ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue); |
|
92 #ifdef DEBUG |
|
93 NS_ABORT_IF_FALSE(!et->mCalledPropertyDtor, "can't call dtor twice"); |
|
94 et->mCalledPropertyDtor = true; |
|
95 #endif |
|
96 delete et; |
|
97 } |
|
98 |
|
99 void |
|
100 ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime) |
|
101 { |
|
102 if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) { |
|
103 mStyleRule = new css::AnimValuesStyleRule(); |
|
104 mStyleRuleRefreshTime = aRefreshTime; |
|
105 |
|
106 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) |
|
107 { |
|
108 ElementPropertyTransition &pt = mPropertyTransitions[i]; |
|
109 if (pt.IsRemovedSentinel()) { |
|
110 continue; |
|
111 } |
|
112 |
|
113 MOZ_ASSERT(pt.mProperties.Length() == 1, |
|
114 "Should have one animation property for a transition"); |
|
115 const AnimationProperty &prop = pt.mProperties[0]; |
|
116 |
|
117 nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty); |
|
118 |
|
119 double valuePortion = pt.ValuePortionFor(aRefreshTime); |
|
120 |
|
121 MOZ_ASSERT(prop.mSegments.Length() == 1, |
|
122 "Animation property should have one segment for a transition"); |
|
123 #ifdef DEBUG |
|
124 bool ok = |
|
125 #endif |
|
126 nsStyleAnimation::Interpolate(prop.mProperty, |
|
127 prop.mSegments[0].mFromValue, |
|
128 prop.mSegments[0].mToValue, |
|
129 valuePortion, *val); |
|
130 NS_ABORT_IF_FALSE(ok, "could not interpolate values"); |
|
131 } |
|
132 } |
|
133 } |
|
134 |
|
135 bool |
|
136 ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const |
|
137 { |
|
138 for (uint32_t tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) { |
|
139 const ElementPropertyTransition& pt = mPropertyTransitions[tranIdx]; |
|
140 if (pt.HasAnimationOfProperty(aProperty) && !pt.IsRemovedSentinel()) { |
|
141 return true; |
|
142 } |
|
143 } |
|
144 return false; |
|
145 } |
|
146 |
|
147 bool |
|
148 ElementTransitions::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const |
|
149 { |
|
150 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement); |
|
151 if (!frame) { |
|
152 return false; |
|
153 } |
|
154 |
|
155 if (mElementProperty != nsGkAtoms::transitionsProperty) { |
|
156 if (nsLayoutUtils::IsAnimationLoggingEnabled()) { |
|
157 nsCString message; |
|
158 message.AppendLiteral("Gecko bug: Async transition of pseudoelements not supported. See bug 771367"); |
|
159 LogAsyncAnimationFailure(message, mElement); |
|
160 } |
|
161 return false; |
|
162 } |
|
163 |
|
164 TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh(); |
|
165 |
|
166 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) { |
|
167 const ElementPropertyTransition& pt = mPropertyTransitions[i]; |
|
168 MOZ_ASSERT(pt.mProperties.Length() == 1, |
|
169 "Should have one animation property for a transition"); |
|
170 if (css::IsGeometricProperty(pt.mProperties[0].mProperty) && |
|
171 pt.IsRunningAt(now)) { |
|
172 aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty); |
|
173 break; |
|
174 } |
|
175 } |
|
176 |
|
177 bool hasOpacity = false; |
|
178 bool hasTransform = false; |
|
179 bool existsProperty = false; |
|
180 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) { |
|
181 const ElementPropertyTransition& pt = mPropertyTransitions[i]; |
|
182 if (!pt.IsRunningAt(now)) { |
|
183 continue; |
|
184 } |
|
185 |
|
186 existsProperty = true; |
|
187 |
|
188 MOZ_ASSERT(pt.mProperties.Length() == 1, |
|
189 "Should have one animation property for a transition"); |
|
190 const AnimationProperty& prop = pt.mProperties[0]; |
|
191 |
|
192 if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor( |
|
193 mElement, prop.mProperty, aFlags) || |
|
194 css::CommonElementAnimationData::IsCompositorAnimationDisabledForFrame(frame)) { |
|
195 return false; |
|
196 } |
|
197 if (prop.mProperty == eCSSProperty_opacity) { |
|
198 hasOpacity = true; |
|
199 } else if (prop.mProperty == eCSSProperty_transform) { |
|
200 hasTransform = true; |
|
201 } |
|
202 } |
|
203 |
|
204 // No properties to animate |
|
205 if (!existsProperty) { |
|
206 return false; |
|
207 } |
|
208 |
|
209 // This transition can be done on the compositor. Mark the frame as active, in |
|
210 // case we are able to throttle this transition. |
|
211 if (hasOpacity) { |
|
212 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity); |
|
213 } |
|
214 if (hasTransform) { |
|
215 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform); |
|
216 } |
|
217 return true; |
|
218 } |
|
219 |
|
220 /***************************************************************************** |
|
221 * nsTransitionManager * |
|
222 *****************************************************************************/ |
|
223 |
|
224 void |
|
225 nsTransitionManager::UpdateThrottledStylesForSubtree(nsIContent* aContent, |
|
226 nsStyleContext* aParentStyle, |
|
227 nsStyleChangeList& aChangeList) |
|
228 { |
|
229 dom::Element* element; |
|
230 if (aContent->IsElement()) { |
|
231 element = aContent->AsElement(); |
|
232 } else { |
|
233 element = nullptr; |
|
234 } |
|
235 |
|
236 nsRefPtr<nsStyleContext> newStyle; |
|
237 |
|
238 ElementTransitions* et; |
|
239 if (element && |
|
240 (et = GetElementTransitions(element, |
|
241 nsCSSPseudoElements::ePseudo_NotPseudoElement, |
|
242 false))) { |
|
243 // re-resolve our style |
|
244 newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList); |
|
245 // remove the current transition from the working set |
|
246 et->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh(); |
|
247 } else { |
|
248 newStyle = ReparentContent(aContent, aParentStyle); |
|
249 } |
|
250 |
|
251 // walk the children |
|
252 if (newStyle) { |
|
253 for (nsIContent *child = aContent->GetFirstChild(); child; |
|
254 child = child->GetNextSibling()) { |
|
255 UpdateThrottledStylesForSubtree(child, newStyle, aChangeList); |
|
256 } |
|
257 } |
|
258 } |
|
259 |
|
260 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsTransitionManager, |
|
261 GetElementTransitions) |
|
262 |
|
263 void |
|
264 nsTransitionManager::UpdateAllThrottledStyles() |
|
265 { |
|
266 if (PR_CLIST_IS_EMPTY(&mElementData)) { |
|
267 // no throttled transitions, leave early |
|
268 mPresContext->TickLastUpdateThrottledTransitionStyle(); |
|
269 return; |
|
270 } |
|
271 |
|
272 if (mPresContext->ThrottledTransitionStyleIsUpToDate()) { |
|
273 // throttled transitions are up to date, leave early |
|
274 return; |
|
275 } |
|
276 |
|
277 mPresContext->TickLastUpdateThrottledTransitionStyle(); |
|
278 UpdateAllThrottledStylesInternal(); |
|
279 } |
|
280 |
|
281 void |
|
282 nsTransitionManager::ElementDataRemoved() |
|
283 { |
|
284 // If we have no transitions or animations left, remove ourselves from |
|
285 // the refresh driver. |
|
286 if (PR_CLIST_IS_EMPTY(&mElementData)) { |
|
287 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style); |
|
288 } |
|
289 } |
|
290 |
|
291 void |
|
292 nsTransitionManager::AddElementData(CommonElementAnimationData* aData) |
|
293 { |
|
294 if (PR_CLIST_IS_EMPTY(&mElementData)) { |
|
295 // We need to observe the refresh driver. |
|
296 nsRefreshDriver *rd = mPresContext->RefreshDriver(); |
|
297 rd->AddRefreshObserver(this, Flush_Style); |
|
298 } |
|
299 |
|
300 PR_INSERT_BEFORE(aData, &mElementData); |
|
301 } |
|
302 |
|
303 already_AddRefed<nsIStyleRule> |
|
304 nsTransitionManager::StyleContextChanged(dom::Element *aElement, |
|
305 nsStyleContext *aOldStyleContext, |
|
306 nsStyleContext *aNewStyleContext) |
|
307 { |
|
308 NS_PRECONDITION(aOldStyleContext->GetPseudo() == |
|
309 aNewStyleContext->GetPseudo(), |
|
310 "pseudo type mismatch"); |
|
311 // If we were called from ReparentStyleContext, this assertion would |
|
312 // actually fire. If we need to be called from there, we can probably |
|
313 // just remove it; the condition probably isn't critical, although |
|
314 // it's worth thinking about some more. |
|
315 NS_PRECONDITION(aOldStyleContext->HasPseudoElementData() == |
|
316 aNewStyleContext->HasPseudoElementData(), |
|
317 "pseudo type mismatch"); |
|
318 |
|
319 if (!mPresContext->IsDynamic()) { |
|
320 // For print or print preview, ignore transitions. |
|
321 return nullptr; |
|
322 } |
|
323 |
|
324 // NOTE: Things in this function (and ConsiderStartingTransition) |
|
325 // should never call PeekStyleData because we don't preserve gotten |
|
326 // structs across reframes. |
|
327 |
|
328 // Return sooner (before the startedAny check below) for the most |
|
329 // common case: no transitions specified or running. |
|
330 const nsStyleDisplay *disp = aNewStyleContext->StyleDisplay(); |
|
331 nsCSSPseudoElements::Type pseudoType = aNewStyleContext->GetPseudoType(); |
|
332 if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) { |
|
333 if (pseudoType != nsCSSPseudoElements::ePseudo_before && |
|
334 pseudoType != nsCSSPseudoElements::ePseudo_after) { |
|
335 return nullptr; |
|
336 } |
|
337 |
|
338 NS_ASSERTION((pseudoType == nsCSSPseudoElements::ePseudo_before && |
|
339 aElement->Tag() == nsGkAtoms::mozgeneratedcontentbefore) || |
|
340 (pseudoType == nsCSSPseudoElements::ePseudo_after && |
|
341 aElement->Tag() == nsGkAtoms::mozgeneratedcontentafter), |
|
342 "Unexpected aElement coming through"); |
|
343 |
|
344 // Else the element we want to use from now on is the element the |
|
345 // :before or :after is attached to. |
|
346 aElement = aElement->GetParent()->AsElement(); |
|
347 } |
|
348 |
|
349 ElementTransitions *et = |
|
350 GetElementTransitions(aElement, pseudoType, false); |
|
351 if (!et && |
|
352 disp->mTransitionPropertyCount == 1 && |
|
353 disp->mTransitions[0].GetDelay() == 0.0f && |
|
354 disp->mTransitions[0].GetDuration() == 0.0f) { |
|
355 return nullptr; |
|
356 } |
|
357 |
|
358 |
|
359 if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) { |
|
360 return nullptr; |
|
361 } |
|
362 |
|
363 if (aNewStyleContext->GetParent() && |
|
364 aNewStyleContext->GetParent()->HasPseudoElementData()) { |
|
365 // Ignore transitions on things that inherit properties from |
|
366 // pseudo-elements. |
|
367 // FIXME (Bug 522599): Add tests for this. |
|
368 return nullptr; |
|
369 } |
|
370 |
|
371 NS_WARN_IF_FALSE(!nsLayoutUtils::AreAsyncAnimationsEnabled() || |
|
372 mPresContext->ThrottledTransitionStyleIsUpToDate(), |
|
373 "throttled animations not up to date"); |
|
374 |
|
375 // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html |
|
376 // I'll consider only the transitions from the number of items in |
|
377 // 'transition-property' on down, and later ones will override earlier |
|
378 // ones (tracked using |whichStarted|). |
|
379 bool startedAny = false; |
|
380 nsCSSPropertySet whichStarted; |
|
381 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) { |
|
382 const nsTransition& t = disp->mTransitions[i]; |
|
383 // Check delay and duration first, since they default to zero, and |
|
384 // when they're both zero, we can ignore the transition. |
|
385 if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) { |
|
386 // We might have something to transition. See if any of the |
|
387 // properties in question changed and are animatable. |
|
388 // FIXME: Would be good to find a way to share code between this |
|
389 // interpretation of transition-property and the one below. |
|
390 nsCSSProperty property = t.GetProperty(); |
|
391 if (property == eCSSPropertyExtra_no_properties || |
|
392 property == eCSSPropertyExtra_variable || |
|
393 property == eCSSProperty_UNKNOWN) { |
|
394 // Nothing to do, but need to exclude this from cases below. |
|
395 } else if (property == eCSSPropertyExtra_all_properties) { |
|
396 for (nsCSSProperty p = nsCSSProperty(0); |
|
397 p < eCSSProperty_COUNT_no_shorthands; |
|
398 p = nsCSSProperty(p + 1)) { |
|
399 ConsiderStartingTransition(p, t, aElement, et, |
|
400 aOldStyleContext, aNewStyleContext, |
|
401 &startedAny, &whichStarted); |
|
402 } |
|
403 } else if (nsCSSProps::IsShorthand(property)) { |
|
404 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) { |
|
405 ConsiderStartingTransition(*subprop, t, aElement, et, |
|
406 aOldStyleContext, aNewStyleContext, |
|
407 &startedAny, &whichStarted); |
|
408 } |
|
409 } else { |
|
410 ConsiderStartingTransition(property, t, aElement, et, |
|
411 aOldStyleContext, aNewStyleContext, |
|
412 &startedAny, &whichStarted); |
|
413 } |
|
414 } |
|
415 } |
|
416 |
|
417 // Stop any transitions for properties that are no longer in |
|
418 // 'transition-property'. |
|
419 // Also stop any transitions for properties that just changed (and are |
|
420 // still in the set of properties to transition), but we didn't just |
|
421 // start the transition because delay and duration are both zero. |
|
422 if (et) { |
|
423 bool checkProperties = |
|
424 disp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties; |
|
425 nsCSSPropertySet allTransitionProperties; |
|
426 if (checkProperties) { |
|
427 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) { |
|
428 const nsTransition& t = disp->mTransitions[i]; |
|
429 // FIXME: Would be good to find a way to share code between this |
|
430 // interpretation of transition-property and the one above. |
|
431 nsCSSProperty property = t.GetProperty(); |
|
432 if (property == eCSSPropertyExtra_no_properties || |
|
433 property == eCSSPropertyExtra_variable || |
|
434 property == eCSSProperty_UNKNOWN) { |
|
435 // Nothing to do, but need to exclude this from cases below. |
|
436 } else if (property == eCSSPropertyExtra_all_properties) { |
|
437 for (nsCSSProperty p = nsCSSProperty(0); |
|
438 p < eCSSProperty_COUNT_no_shorthands; |
|
439 p = nsCSSProperty(p + 1)) { |
|
440 allTransitionProperties.AddProperty(p); |
|
441 } |
|
442 } else if (nsCSSProps::IsShorthand(property)) { |
|
443 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) { |
|
444 allTransitionProperties.AddProperty(*subprop); |
|
445 } |
|
446 } else { |
|
447 allTransitionProperties.AddProperty(property); |
|
448 } |
|
449 } |
|
450 } |
|
451 |
|
452 nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions; |
|
453 uint32_t i = pts.Length(); |
|
454 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?"); |
|
455 nsStyleAnimation::Value currentValue; |
|
456 do { |
|
457 --i; |
|
458 ElementPropertyTransition &pt = pts[i]; |
|
459 MOZ_ASSERT(pt.mProperties.Length() == 1, |
|
460 "Should have one animation property for a transition"); |
|
461 MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1, |
|
462 "Animation property should have one segment for a transition"); |
|
463 const AnimationProperty& prop = pt.mProperties[0]; |
|
464 const AnimationPropertySegment& segment = prop.mSegments[0]; |
|
465 // properties no longer in 'transition-property' |
|
466 if ((checkProperties && |
|
467 !allTransitionProperties.HasProperty(prop.mProperty)) || |
|
468 // properties whose computed values changed but delay and |
|
469 // duration are both zero |
|
470 !ExtractComputedValueForTransition(prop.mProperty, aNewStyleContext, |
|
471 currentValue) || |
|
472 currentValue != segment.mToValue) { |
|
473 // stop the transition |
|
474 pts.RemoveElementAt(i); |
|
475 et->UpdateAnimationGeneration(mPresContext); |
|
476 } |
|
477 } while (i != 0); |
|
478 |
|
479 if (pts.IsEmpty()) { |
|
480 et->Destroy(); |
|
481 et = nullptr; |
|
482 } |
|
483 } |
|
484 |
|
485 if (!startedAny) { |
|
486 return nullptr; |
|
487 } |
|
488 |
|
489 NS_ABORT_IF_FALSE(et, "must have element transitions if we started " |
|
490 "any transitions"); |
|
491 |
|
492 // In the CSS working group discussion (2009 Jul 15 telecon, |
|
493 // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of |
|
494 // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html , |
|
495 // the working group decided that a transition property on an |
|
496 // element should not cause any transitions if the property change |
|
497 // is itself inheriting a value that is transitioning on an |
|
498 // ancestor. So, to get the correct behavior, we continue the |
|
499 // restyle that caused this transition using a "covering" rule that |
|
500 // covers up any changes on which we started transitions, so that |
|
501 // descendants don't start their own transitions. (In the case of |
|
502 // negative transition delay, this covering rule produces different |
|
503 // results than applying the transition rule immediately would). |
|
504 // Our caller is responsible for restyling again using this covering |
|
505 // rule. |
|
506 |
|
507 nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule; |
|
508 |
|
509 nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions; |
|
510 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) { |
|
511 ElementPropertyTransition &pt = pts[i]; |
|
512 MOZ_ASSERT(pt.mProperties.Length() == 1, |
|
513 "Should have one animation property for a transition"); |
|
514 MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1, |
|
515 "Animation property should have one segment for a transition"); |
|
516 AnimationProperty& prop = pt.mProperties[0]; |
|
517 AnimationPropertySegment& segment = prop.mSegments[0]; |
|
518 if (whichStarted.HasProperty(prop.mProperty)) { |
|
519 coverRule->AddValue(prop.mProperty, segment.mFromValue); |
|
520 } |
|
521 } |
|
522 |
|
523 et->mStyleRule = nullptr; |
|
524 |
|
525 return coverRule.forget(); |
|
526 } |
|
527 |
|
528 void |
|
529 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty, |
|
530 const nsTransition& aTransition, |
|
531 dom::Element* aElement, |
|
532 ElementTransitions*& aElementTransitions, |
|
533 nsStyleContext* aOldStyleContext, |
|
534 nsStyleContext* aNewStyleContext, |
|
535 bool* aStartedAny, |
|
536 nsCSSPropertySet* aWhichStarted) |
|
537 { |
|
538 // IsShorthand itself will assert if aProperty is not a property. |
|
539 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty), |
|
540 "property out of range"); |
|
541 NS_ASSERTION(!aElementTransitions || |
|
542 aElementTransitions->mElement == aElement, "Element mismatch"); |
|
543 |
|
544 if (aWhichStarted->HasProperty(aProperty)) { |
|
545 // A later item in transition-property already started a |
|
546 // transition for this property, so we ignore this one. |
|
547 // See comment above and |
|
548 // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html . |
|
549 return; |
|
550 } |
|
551 |
|
552 if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) { |
|
553 return; |
|
554 } |
|
555 |
|
556 ElementPropertyTransition pt; |
|
557 |
|
558 nsStyleAnimation::Value startValue, endValue, dummyValue; |
|
559 bool haveValues = |
|
560 ExtractComputedValueForTransition(aProperty, aOldStyleContext, |
|
561 startValue) && |
|
562 ExtractComputedValueForTransition(aProperty, aNewStyleContext, |
|
563 endValue); |
|
564 |
|
565 bool haveChange = startValue != endValue; |
|
566 |
|
567 bool shouldAnimate = |
|
568 haveValues && |
|
569 haveChange && |
|
570 // Check that we can interpolate between these values |
|
571 // (If this is ever a performance problem, we could add a |
|
572 // CanInterpolate method, but it seems fine for now.) |
|
573 nsStyleAnimation::Interpolate(aProperty, startValue, endValue, |
|
574 0.5, dummyValue); |
|
575 |
|
576 bool haveCurrentTransition = false; |
|
577 uint32_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex; |
|
578 const ElementPropertyTransition *oldPT = nullptr; |
|
579 if (aElementTransitions) { |
|
580 nsTArray<ElementPropertyTransition> &pts = |
|
581 aElementTransitions->mPropertyTransitions; |
|
582 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) { |
|
583 MOZ_ASSERT(pts[i].mProperties.Length() == 1, |
|
584 "Should have one animation property for a transition"); |
|
585 if (pts[i].mProperties[0].mProperty == aProperty) { |
|
586 haveCurrentTransition = true; |
|
587 currentIndex = i; |
|
588 oldPT = &aElementTransitions->mPropertyTransitions[currentIndex]; |
|
589 break; |
|
590 } |
|
591 } |
|
592 } |
|
593 |
|
594 // If we got a style change that changed the value to the endpoint |
|
595 // of the currently running transition, we don't want to interrupt |
|
596 // its timing function. |
|
597 // This needs to be before the !shouldAnimate && haveCurrentTransition |
|
598 // case below because we might be close enough to the end of the |
|
599 // transition that the current value rounds to the final value. In |
|
600 // this case, we'll end up with shouldAnimate as false (because |
|
601 // there's no value change), but we need to return early here rather |
|
602 // than cancel the running transition because shouldAnimate is false! |
|
603 MOZ_ASSERT(!oldPT || oldPT->mProperties[0].mSegments.Length() == 1, |
|
604 "Should have one animation property segment for a transition"); |
|
605 if (haveCurrentTransition && haveValues && |
|
606 oldPT->mProperties[0].mSegments[0].mToValue == endValue) { |
|
607 // WalkTransitionRule already called RestyleForAnimation. |
|
608 return; |
|
609 } |
|
610 |
|
611 nsPresContext *presContext = aNewStyleContext->PresContext(); |
|
612 |
|
613 if (!shouldAnimate) { |
|
614 if (haveCurrentTransition) { |
|
615 // We're in the middle of a transition, and just got a non-transition |
|
616 // style change to something that we can't animate. This might happen |
|
617 // because we got a non-transition style change changing to the current |
|
618 // in-progress value (which is particularly easy to cause when we're |
|
619 // currently in the 'transition-delay'). It also might happen because we |
|
620 // just got a style change to a value that can't be interpolated. |
|
621 nsTArray<ElementPropertyTransition> &pts = |
|
622 aElementTransitions->mPropertyTransitions; |
|
623 pts.RemoveElementAt(currentIndex); |
|
624 aElementTransitions->UpdateAnimationGeneration(mPresContext); |
|
625 |
|
626 if (pts.IsEmpty()) { |
|
627 aElementTransitions->Destroy(); |
|
628 // |aElementTransitions| is now a dangling pointer! |
|
629 aElementTransitions = nullptr; |
|
630 } |
|
631 // WalkTransitionRule already called RestyleForAnimation. |
|
632 } |
|
633 return; |
|
634 } |
|
635 |
|
636 TimeStamp mostRecentRefresh = |
|
637 presContext->RefreshDriver()->MostRecentRefresh(); |
|
638 |
|
639 const nsTimingFunction &tf = aTransition.GetTimingFunction(); |
|
640 float delay = aTransition.GetDelay(); |
|
641 float duration = aTransition.GetDuration(); |
|
642 if (duration < 0.0) { |
|
643 // The spec says a negative duration is treated as zero. |
|
644 duration = 0.0; |
|
645 } |
|
646 pt.mStartForReversingTest = startValue; |
|
647 pt.mReversePortion = 1.0; |
|
648 |
|
649 // If the new transition reverses an existing one, we'll need to |
|
650 // handle the timing differently. |
|
651 if (haveCurrentTransition && |
|
652 !oldPT->IsRemovedSentinel() && |
|
653 oldPT->mStartForReversingTest == endValue) { |
|
654 // Compute the appropriate negative transition-delay such that right |
|
655 // now we'd end up at the current position. |
|
656 double valuePortion = |
|
657 oldPT->ValuePortionFor(mostRecentRefresh) * oldPT->mReversePortion + |
|
658 (1.0 - oldPT->mReversePortion); |
|
659 // A timing function with negative y1 (or y2!) might make |
|
660 // valuePortion negative. In this case, we still want to apply our |
|
661 // reversing logic based on relative distances, not make duration |
|
662 // negative. |
|
663 if (valuePortion < 0.0) { |
|
664 valuePortion = -valuePortion; |
|
665 } |
|
666 // A timing function with y2 (or y1!) greater than one might |
|
667 // advance past its terminal value. It's probably a good idea to |
|
668 // clamp valuePortion to be at most one to preserve the invariant |
|
669 // that a transition will complete within at most its specified |
|
670 // time. |
|
671 if (valuePortion > 1.0) { |
|
672 valuePortion = 1.0; |
|
673 } |
|
674 |
|
675 // Negative delays are essentially part of the transition |
|
676 // function, so reduce them along with the duration, but don't |
|
677 // reduce positive delays. |
|
678 if (delay < 0.0f) { |
|
679 delay *= valuePortion; |
|
680 } |
|
681 |
|
682 duration *= valuePortion; |
|
683 |
|
684 pt.mStartForReversingTest = oldPT->mProperties[0].mSegments[0].mToValue; |
|
685 pt.mReversePortion = valuePortion; |
|
686 } |
|
687 |
|
688 AnimationProperty& prop = *pt.mProperties.AppendElement(); |
|
689 prop.mProperty = aProperty; |
|
690 |
|
691 AnimationPropertySegment& segment = *prop.mSegments.AppendElement(); |
|
692 segment.mFromValue = startValue; |
|
693 segment.mToValue = endValue; |
|
694 segment.mFromKey = 0; |
|
695 segment.mToKey = 1; |
|
696 segment.mTimingFunction.Init(tf); |
|
697 |
|
698 pt.mStartTime = mostRecentRefresh; |
|
699 pt.mDelay = TimeDuration::FromMilliseconds(delay); |
|
700 pt.mIterationDuration = TimeDuration::FromMilliseconds(duration); |
|
701 pt.mIterationCount = 1; |
|
702 pt.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL; |
|
703 pt.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS; |
|
704 pt.mPlayState = NS_STYLE_ANIMATION_PLAY_STATE_RUNNING; |
|
705 pt.mPauseStart = TimeStamp(); |
|
706 |
|
707 if (!aElementTransitions) { |
|
708 aElementTransitions = |
|
709 GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(), |
|
710 true); |
|
711 if (!aElementTransitions) { |
|
712 NS_WARNING("allocating ElementTransitions failed"); |
|
713 return; |
|
714 } |
|
715 } |
|
716 |
|
717 nsTArray<ElementPropertyTransition> &pts = |
|
718 aElementTransitions->mPropertyTransitions; |
|
719 #ifdef DEBUG |
|
720 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) { |
|
721 NS_ABORT_IF_FALSE(pts[i].mProperties.Length() == 1, |
|
722 "Should have one animation property for a transition"); |
|
723 NS_ABORT_IF_FALSE(i == currentIndex || |
|
724 pts[i].mProperties[0].mProperty != aProperty, |
|
725 "duplicate transitions for property"); |
|
726 } |
|
727 #endif |
|
728 if (haveCurrentTransition) { |
|
729 pts[currentIndex] = pt; |
|
730 } else { |
|
731 if (!pts.AppendElement(pt)) { |
|
732 NS_WARNING("out of memory"); |
|
733 return; |
|
734 } |
|
735 } |
|
736 aElementTransitions->UpdateAnimationGeneration(mPresContext); |
|
737 |
|
738 nsRestyleHint hint = |
|
739 aNewStyleContext->GetPseudoType() == |
|
740 nsCSSPseudoElements::ePseudo_NotPseudoElement ? |
|
741 eRestyle_Self : eRestyle_Subtree; |
|
742 presContext->PresShell()->RestyleForAnimation(aElement, hint); |
|
743 |
|
744 *aStartedAny = true; |
|
745 aWhichStarted->AddProperty(aProperty); |
|
746 } |
|
747 |
|
748 ElementTransitions* |
|
749 nsTransitionManager::GetElementTransitions(dom::Element *aElement, |
|
750 nsCSSPseudoElements::Type aPseudoType, |
|
751 bool aCreateIfNeeded) |
|
752 { |
|
753 if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) { |
|
754 // Early return for the most common case. |
|
755 return nullptr; |
|
756 } |
|
757 |
|
758 nsIAtom *propName; |
|
759 if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) { |
|
760 propName = nsGkAtoms::transitionsProperty; |
|
761 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) { |
|
762 propName = nsGkAtoms::transitionsOfBeforeProperty; |
|
763 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) { |
|
764 propName = nsGkAtoms::transitionsOfAfterProperty; |
|
765 } else { |
|
766 NS_ASSERTION(!aCreateIfNeeded, |
|
767 "should never try to create transitions for pseudo " |
|
768 "other than :before or :after"); |
|
769 return nullptr; |
|
770 } |
|
771 ElementTransitions *et = static_cast<ElementTransitions*>( |
|
772 aElement->GetProperty(propName)); |
|
773 if (!et && aCreateIfNeeded) { |
|
774 // FIXME: Consider arena-allocating? |
|
775 et = new ElementTransitions(aElement, propName, this, |
|
776 mPresContext->RefreshDriver()->MostRecentRefresh()); |
|
777 nsresult rv = aElement->SetProperty(propName, et, |
|
778 ElementTransitionsPropertyDtor, false); |
|
779 if (NS_FAILED(rv)) { |
|
780 NS_WARNING("SetProperty failed"); |
|
781 delete et; |
|
782 return nullptr; |
|
783 } |
|
784 if (propName == nsGkAtoms::transitionsProperty) { |
|
785 aElement->SetMayHaveAnimations(); |
|
786 } |
|
787 |
|
788 AddElementData(et); |
|
789 } |
|
790 |
|
791 return et; |
|
792 } |
|
793 |
|
794 /* |
|
795 * nsIStyleRuleProcessor implementation |
|
796 */ |
|
797 |
|
798 void |
|
799 nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData, |
|
800 nsCSSPseudoElements::Type aPseudoType) |
|
801 { |
|
802 ElementTransitions *et = |
|
803 GetElementTransitions(aData->mElement, aPseudoType, false); |
|
804 if (!et) { |
|
805 return; |
|
806 } |
|
807 |
|
808 if (!mPresContext->IsDynamic()) { |
|
809 // For print or print preview, ignore animations. |
|
810 return; |
|
811 } |
|
812 |
|
813 if (aData->mPresContext->IsProcessingRestyles() && |
|
814 !aData->mPresContext->IsProcessingAnimationStyleChange()) { |
|
815 // If we're processing a normal style change rather than one from |
|
816 // animation, don't add the transition rule. This allows us to |
|
817 // compute the new style value rather than having the transition |
|
818 // override it, so that we can start transitioning differently. |
|
819 |
|
820 // We need to immediately restyle with animation |
|
821 // after doing this. |
|
822 nsRestyleHint hint = |
|
823 aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ? |
|
824 eRestyle_Self : eRestyle_Subtree; |
|
825 mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint); |
|
826 return; |
|
827 } |
|
828 |
|
829 et->EnsureStyleRuleFor( |
|
830 aData->mPresContext->RefreshDriver()->MostRecentRefresh()); |
|
831 |
|
832 aData->mRuleWalker->Forward(et->mStyleRule); |
|
833 } |
|
834 |
|
835 /* virtual */ void |
|
836 nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData) |
|
837 { |
|
838 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, |
|
839 "pres context mismatch"); |
|
840 WalkTransitionRule(aData, |
|
841 nsCSSPseudoElements::ePseudo_NotPseudoElement); |
|
842 } |
|
843 |
|
844 /* virtual */ void |
|
845 nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData* aData) |
|
846 { |
|
847 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, |
|
848 "pres context mismatch"); |
|
849 |
|
850 // Note: If we're the only thing keeping a pseudo-element frame alive |
|
851 // (per ProbePseudoStyleContext), we still want to keep it alive, so |
|
852 // this is ok. |
|
853 WalkTransitionRule(aData, aData->mPseudoType); |
|
854 } |
|
855 |
|
856 /* virtual */ void |
|
857 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData) |
|
858 { |
|
859 } |
|
860 |
|
861 #ifdef MOZ_XUL |
|
862 /* virtual */ void |
|
863 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData) |
|
864 { |
|
865 } |
|
866 #endif |
|
867 |
|
868 /* virtual */ size_t |
|
869 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
870 { |
|
871 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf); |
|
872 } |
|
873 |
|
874 /* virtual */ size_t |
|
875 nsTransitionManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
876 { |
|
877 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
|
878 } |
|
879 |
|
880 struct TransitionEventInfo { |
|
881 nsCOMPtr<nsIContent> mElement; |
|
882 InternalTransitionEvent mEvent; |
|
883 |
|
884 TransitionEventInfo(nsIContent *aElement, nsCSSProperty aProperty, |
|
885 TimeDuration aDuration, const nsAString& aPseudoElement) |
|
886 : mElement(aElement) |
|
887 , mEvent(true, NS_TRANSITION_END) |
|
888 { |
|
889 // XXX Looks like nobody initialize WidgetEvent::time |
|
890 mEvent.propertyName = |
|
891 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty)); |
|
892 mEvent.elapsedTime = aDuration.ToSeconds(); |
|
893 mEvent.pseudoElement = aPseudoElement; |
|
894 } |
|
895 |
|
896 // InternalTransitionEvent doesn't support copy-construction, so we need |
|
897 // to ourselves in order to work with nsTArray |
|
898 TransitionEventInfo(const TransitionEventInfo &aOther) |
|
899 : mElement(aOther.mElement) |
|
900 , mEvent(true, NS_TRANSITION_END) |
|
901 { |
|
902 mEvent.AssignTransitionEventData(aOther.mEvent, false); |
|
903 } |
|
904 }; |
|
905 |
|
906 /* virtual */ void |
|
907 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime) |
|
908 { |
|
909 NS_ABORT_IF_FALSE(mPresContext, |
|
910 "refresh driver should not notify additional observers " |
|
911 "after pres context has been destroyed"); |
|
912 if (!mPresContext->GetPresShell()) { |
|
913 // Someone might be keeping mPresContext alive past the point |
|
914 // where it has been torn down; don't bother doing anything in |
|
915 // this case. But do get rid of all our transitions so we stop |
|
916 // triggering refreshes. |
|
917 RemoveAllElementData(); |
|
918 return; |
|
919 } |
|
920 |
|
921 FlushTransitions(Can_Throttle); |
|
922 } |
|
923 |
|
924 void |
|
925 nsTransitionManager::FlushTransitions(FlushFlags aFlags) |
|
926 { |
|
927 if (PR_CLIST_IS_EMPTY(&mElementData)) { |
|
928 // no transitions, leave early |
|
929 return; |
|
930 } |
|
931 |
|
932 nsTArray<TransitionEventInfo> events; |
|
933 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); |
|
934 bool didThrottle = false; |
|
935 // Trim transitions that have completed, post restyle events for frames that |
|
936 // are still transitioning, and start transitions with delays. |
|
937 { |
|
938 PRCList *next = PR_LIST_HEAD(&mElementData); |
|
939 while (next != &mElementData) { |
|
940 ElementTransitions *et = static_cast<ElementTransitions*>(next); |
|
941 next = PR_NEXT_LINK(next); |
|
942 |
|
943 bool canThrottleTick = aFlags == Can_Throttle && |
|
944 et->CanPerformOnCompositorThread( |
|
945 CommonElementAnimationData::CanAnimateFlags(0)) && |
|
946 et->CanThrottleAnimation(now); |
|
947 |
|
948 NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() == |
|
949 mPresContext->Document(), |
|
950 "Element::UnbindFromTree should have " |
|
951 "destroyed the element transitions object"); |
|
952 |
|
953 uint32_t i = et->mPropertyTransitions.Length(); |
|
954 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?"); |
|
955 bool transitionStartedOrEnded = false; |
|
956 do { |
|
957 --i; |
|
958 ElementPropertyTransition &pt = et->mPropertyTransitions[i]; |
|
959 if (pt.IsRemovedSentinel()) { |
|
960 // Actually remove transitions one throttle-able cycle after their |
|
961 // completion. We only clear on a throttle-able cycle because that |
|
962 // means it is a regular restyle tick and thus it is safe to discard |
|
963 // the transition. If the flush is not throttle-able, we might still |
|
964 // have new transitions left to process. See comment below. |
|
965 if (aFlags == Can_Throttle) { |
|
966 et->mPropertyTransitions.RemoveElementAt(i); |
|
967 } |
|
968 } else if (pt.mStartTime + pt.mDelay + pt.mIterationDuration <= now) { |
|
969 MOZ_ASSERT(pt.mProperties.Length() == 1, |
|
970 "Should have one animation property for a transition"); |
|
971 nsCSSProperty prop = pt.mProperties[0].mProperty; |
|
972 if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME)) |
|
973 { |
|
974 prop = nsCSSProps::OtherNameFor(prop); |
|
975 } |
|
976 nsIAtom* ep = et->mElementProperty; |
|
977 NS_NAMED_LITERAL_STRING(before, "::before"); |
|
978 NS_NAMED_LITERAL_STRING(after, "::after"); |
|
979 events.AppendElement( |
|
980 TransitionEventInfo(et->mElement, prop, pt.mIterationDuration, |
|
981 ep == nsGkAtoms::transitionsProperty ? |
|
982 EmptyString() : |
|
983 ep == nsGkAtoms::transitionsOfBeforeProperty ? |
|
984 before : |
|
985 after)); |
|
986 |
|
987 // Leave this transition in the list for one more refresh |
|
988 // cycle, since we haven't yet processed its style change, and |
|
989 // if we also have (already, or will have from processing |
|
990 // transitionend events or other refresh driver notifications) |
|
991 // a non-animation style change that would affect it, we need |
|
992 // to know not to start a new transition for the transition |
|
993 // from the almost-completed value to the final value. |
|
994 pt.SetRemovedSentinel(); |
|
995 et->UpdateAnimationGeneration(mPresContext); |
|
996 transitionStartedOrEnded = true; |
|
997 } else if (pt.mStartTime + pt.mDelay <= now && canThrottleTick && |
|
998 !pt.mIsRunningOnCompositor) { |
|
999 // Start a transition with a delay where we should start the |
|
1000 // transition proper. |
|
1001 et->UpdateAnimationGeneration(mPresContext); |
|
1002 transitionStartedOrEnded = true; |
|
1003 } |
|
1004 } while (i != 0); |
|
1005 |
|
1006 // We need to restyle even if the transition rule no longer |
|
1007 // applies (in which case we just made it not apply). |
|
1008 NS_ASSERTION(et->mElementProperty == nsGkAtoms::transitionsProperty || |
|
1009 et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty || |
|
1010 et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty, |
|
1011 "Unexpected element property; might restyle too much"); |
|
1012 if (!canThrottleTick || transitionStartedOrEnded) { |
|
1013 nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ? |
|
1014 eRestyle_Self : eRestyle_Subtree; |
|
1015 mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint); |
|
1016 } else { |
|
1017 didThrottle = true; |
|
1018 } |
|
1019 |
|
1020 if (et->mPropertyTransitions.IsEmpty()) { |
|
1021 et->Destroy(); |
|
1022 // |et| is now a dangling pointer! |
|
1023 et = nullptr; |
|
1024 } |
|
1025 } |
|
1026 } |
|
1027 |
|
1028 if (didThrottle) { |
|
1029 mPresContext->Document()->SetNeedStyleFlush(); |
|
1030 } |
|
1031 |
|
1032 for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) { |
|
1033 TransitionEventInfo &info = events[i]; |
|
1034 EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent); |
|
1035 |
|
1036 if (!mPresContext) { |
|
1037 break; |
|
1038 } |
|
1039 } |
|
1040 } |